Primary APIs:
AvaloniaProperty.Register<TOwner, TValue>(...)AvaloniaProperty.RegisterAttached<...>(...)AvaloniaProperty.RegisterDirect<...>(...)AvaloniaProperty.Name, PropertyType, OwnerTypeAvaloniaProperty.GetMetadata<T>() / GetMetadata(Type) / GetMetadata(AvaloniaObject)AvaloniaProperty.IsValidValue(object?)AvaloniaProperty.UnsetValue and UnsetValueTypeStyledProperty<T>AttachedProperty<T>DirectProperty<TOwner, TValue>AvaloniaObject.GetValue/SetValue/ClearValue/SetCurrentValueAvaloniaObject.Bind(...)AvaloniaPropertyChangedEventArgs / AvaloniaPropertyChangedEventArgs<T>AvaloniaPropertyChangedExtensionsAvaloniaPropertyRegistrySetter, Style, Styles, SelectorsContainerQuery, Container.Name, Container.SizingInteractive, routed events, InteractiveExtensionsReference source files:
src/Avalonia.Base/AvaloniaProperty.cssrc/Avalonia.Base/AvaloniaObject.cssrc/Avalonia.Base/AttachedProperty.cssrc/Avalonia.Base/DirectProperty.cssrc/Avalonia.Base/StyledProperty.cssrc/Avalonia.Base/AvaloniaPropertyMetadata.cssrc/Avalonia.Base/AvaloniaPropertyRegistry.cssrc/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cssrc/Avalonia.Base/AvaloniaPropertyChangedExtensions.cssrc/Avalonia.Base/Styling/Setter.cssrc/Avalonia.Base/Styling/Style.cssrc/Avalonia.Base/Styling/ContainerQuery.cssrc/Avalonia.Base/Styling/Container.csProperty flavors:
StyledProperty<T>: styleable, bindable, inheritable if configured.AttachedProperty<T>: property attached to other types.DirectProperty<TOwner, TValue>: field-backed, optionally read-only.Read/write APIs:
GetValue(...)SetValue(...)SetCurrentValue(...)ClearValue(...)Bind(...)Choose registration based on need:
Register for normal styleable control properties.RegisterAttached for layout/behavior metadata shared by arbitrary hosts.RegisterDirect for high-performance field-backed values.Every AvaloniaProperty exposes identity and capability flags:
NamePropertyTypeOwnerTypeInheritsIsAttachedIsDirectIsReadOnlyMetadata and validation query APIs:
GetMetadata<T>()GetMetadata(Type)GetMetadata(AvaloniaObject)IsValidValue(object?)Indexer helper operators:
operator !(AvaloniaProperty) for property-indexer binding descriptors.operator ~(AvaloniaProperty) for descriptor inversion workflows.Unset marker semantics:
AvaloniaProperty.UnsetValue is a singleton UnsetValueType.UnsetValueType means “no value provided by this source”, which is different from null.Administrative cleanup API:
Unregister(Type) exists for specialized cleanup/testing paths, not normal app runtime flows.Example:
AvaloniaProperty property = TextBox.TextProperty;
string name = property.Name;
Type propertyType = property.PropertyType;
Type ownerType = property.OwnerType;
AvaloniaPropertyMetadata metadataByType = property.GetMetadata(typeof(TextBox));
AvaloniaPropertyMetadata metadataByInstance = property.GetMetadata(textBox);
object? candidate = "hello";
if (!property.IsValidValue(candidate))
candidate = AvaloniaProperty.UnsetValue; // UnsetValueType marker
var descriptor = ~property;
AvaloniaPropertyMetadata controls binding and validation behavior at registration/owner level.
Key points:
DefaultBindingMode resolves BindingMode.Default to a concrete mode (commonly OneWay).EnableDataValidation opt-in controls whether a property participates in data-validation messages.AddOwner(...) + optional metadata overrides for owner-specific behavior.StyledProperty<T> advanced APIs:
AddOwner<TOwner>(...)OverrideMetadata<T>(...)OverrideDefaultValue<T>(...)GetDefaultValue(Type) / GetDefaultValue(AvaloniaObject)CoerceValue(AvaloniaObject, TValue)ValidateValueDirectProperty<TOwner, TValue> advanced owner extension:
AddOwner<TNewOwner>(getter, setter, unsetValue, defaultBindingMode, enableDataValidation)GetterMetadata lifecycle APIs (mainly for control/framework authors):
AvaloniaPropertyMetadata.Merge(...)AvaloniaPropertyMetadata.Freeze()AvaloniaPropertyMetadata.IsReadOnlyAvaloniaPropertyMetadata.GenerateTypeSafeMetadata()Use these APIs to tune property behavior per control family, not per instance.
Notes:
StyledProperty<T>.ValidateValue is the runtime predicate used to validate typed values before they become effective.DirectProperty<TOwner, TValue>.Getter exposes the field-backed read delegate used by direct property access paths.Important distinction:
Useful APIs:
GetBaseValue<T>(StyledProperty<T>)IsAnimating(AvaloniaProperty)IsSet(AvaloniaProperty)SetCurrentValue(...) (preserve existing binding/style source)CoerceValue(AvaloniaProperty)AvaloniaProperty.UnsetValueUnsetValue versus null:
null can be a valid effective value for many reference-type properties.AvaloniaProperty.UnsetValue (UnsetValueType) means the current source did not produce a value, so priority fallback continues.Example:
if (control.IsSet(MyControl.ValueProperty))
{
var baseValue = control.GetBaseValue(MyControl.ValueProperty).GetValueOrDefault();
// Use baseValue for diagnostics or non-animated logic paths.
}
control.SetCurrentValue(MyControl.ValueProperty, 42);
control.CoerceValue(MyControl.ValueProperty);
When updating control-owned properties at runtime, prefer SetCurrentValue over SetValue to avoid accidentally breaking app-declared bindings and style precedence.
Change event APIs:
AvaloniaObject.PropertyChangedAvaloniaProperty.ChangedAvaloniaPropertyChangedEventArgs(AvaloniaObject sender, BindingPriority priority)AvaloniaPropertyChangedEventArgs.PropertyAvaloniaPropertyChangedEventArgs.SenderAvaloniaPropertyChangedEventArgs.PriorityAvaloniaPropertyChangedEventArgs.OldValue / AvaloniaPropertyChangedEventArgs.NewValueAvaloniaPropertyChangedEventArgs<T>AvaloniaPropertyChangedEventArgs<T>.OldValueAvaloniaPropertyChangedEventArgs<T>.NewValueAvaloniaPropertyChangedExtensions.GetOldValue<T>()AvaloniaPropertyChangedExtensions.GetNewValue<T>()Typed constructor path for manual advanced scenarios:
AvaloniaPropertyChangedEventArgs<T>(AvaloniaObject sender, AvaloniaProperty<T> property, Optional<T> oldValue, BindingValue<T> newValue, BindingPriority priority)Registry APIs (diagnostics/tooling oriented):
AvaloniaPropertyRegistry.InstanceGetRegistered(Type) / GetRegisteredAttached(Type) / GetRegisteredDirect(Type) / GetRegisteredInherited(Type)FindRegistered(Type, string) / FindRegistered(AvaloniaObject, string)FindRegisteredDirect<T>(AvaloniaObject, DirectPropertyBase<T>)IsRegistered(Type, AvaloniaProperty) / IsRegistered(object, AvaloniaProperty)UnregisterByModule(IEnumerable<Type>)Practical uses:
Example:
control.PropertyChanged += (_, e) =>
{
if (e.Property == TextBox.TextProperty)
{
string? oldText = e.GetOldValue<string?>();
string? newText = e.GetNewValue<string?>();
}
};
var registry = AvaloniaPropertyRegistry.Instance;
bool isRegistered = registry.IsRegistered(typeof(TextBox), TextBox.TextProperty);
Canonical pattern:
public static class FocusAssist
{
public static readonly AttachedProperty<bool> HighlightWhenFocusedProperty =
AvaloniaProperty.RegisterAttached<FocusAssist, Control, bool>("HighlightWhenFocused");
static FocusAssist()
{
HighlightWhenFocusedProperty.Changed.AddClassHandler<Control>((control, e) =>
{
if (e.GetNewValue<bool>())
control.Classes.Add("focus-highlight");
else
control.Classes.Remove("focus-highlight");
});
}
public static bool GetHighlightWhenFocused(Control control) =>
control.GetValue(HighlightWhenFocusedProperty);
public static void SetHighlightWhenFocused(Control control, bool value) =>
control.SetValue(HighlightWhenFocusedProperty, value);
}
XAML usage:
<TextBox local:FocusAssist.HighlightWhenFocused="True" />
Avalonia core does not provide a built-in Behavior<T> base in this repository.
Preferred behavior patterns in core apps:
Interactive.AddHandler or InteractiveExtensions.GetObservable.Use cases:
In styles/themes:
Setter(Property, Value).Typed C# selector example:
var style = new Style(x => x.OfType<Button>().Class("primary"));
style.Setters.Add(new Setter(Button.FontWeightProperty, FontWeight.SemiBold));
Avalonia equivalent of media-query-like behavior uses ContainerQuery.
Key APIs:
ContainerQuery.QueryContainerQuery.NameContainer.Name attached propertyContainer.Sizing attached propertyExample from sample style pattern:
<ContainerQuery Name="Host" Query="min-width:800">
<Style Selector="UniformGrid#ContentGrid">
<Setter Property="Columns" Value="3" />
</Style>
</ContainerQuery>
<Border Container.Name="Host" Container.Sizing="Width" />
SetCurrentValue when preserving existing binding/priority semantics.Container.Name or Container.Sizing on expected container.Default mode:
XAML-first complete example:
<StackPanel xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyApp.Behaviors"
Spacing="8">
<TextBox local:FocusAssist.HighlightWhenFocused="True" />
<Button Classes="primary" Content="Submit" />
</StackPanel>
Code-only alternative (on request):
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Styling;
var text = new TextBox();
FocusAssist.SetHighlightWhenFocused(text, true);
var style = new Style(x => x.OfType<Button>().Class("primary"));
style.Setters.Add(new Setter(Button.FontWeightProperty, FontWeight.SemiBold));
Application.Current!.Styles.Add(style);