OutWit.Common.MVVM.Avalonia
3.1.0
dotnet add package OutWit.Common.MVVM.Avalonia --version 3.1.0
NuGet\Install-Package OutWit.Common.MVVM.Avalonia -Version 3.1.0
<PackageReference Include="OutWit.Common.MVVM.Avalonia" Version="3.1.0" />
<PackageVersion Include="OutWit.Common.MVVM.Avalonia" Version="3.1.0" />
<PackageReference Include="OutWit.Common.MVVM.Avalonia" />
paket add OutWit.Common.MVVM.Avalonia --version 3.1.0
#r "nuget: OutWit.Common.MVVM.Avalonia, 3.1.0"
#:package OutWit.Common.MVVM.Avalonia@3.1.0
#addin nuget:?package=OutWit.Common.MVVM.Avalonia&version=3.1.0
#tool nuget:?package=OutWit.Common.MVVM.Avalonia&version=3.1.0
OutWit.Common.MVVM.Avalonia
Avalonia-specific MVVM components and utilities, including source generator for automatic StyledProperty, DirectProperty, and AttachedProperty generation.
Features
- Source Generator for Properties: Automatically generate StyledProperty, DirectProperty, and AttachedProperty from attributes
- AvaloniaDispatcher:
IDispatcherimplementation for UI thread invocation - Binding Utilities: Helper methods for property registration
- Visual Tree Traversal: Extension methods for navigating Avalonia visual tree
- BindingProxy: Binding proxy for DataContext access
- DataTemplate Utilities: Helper methods for DataTemplate creation
Installation
dotnet add package OutWit.Common.MVVM.Avalonia
This automatically includes:
OutWit.Common.MVVM(base cross-platform package)OutWit.Common.MVVM.Avalonia.Generator(source generator)OutWit.Common.Logging
Quick Start
Source Generator for StyledProperty
The simplest way to create StyledProperties:
using Avalonia.Controls;
using OutWit.Common.MVVM.Avalonia.Attributes;
namespace MyApp.Controls
{
public partial class CustomButton : Button
{
[StyledProperty(DefaultValue = "Click Me")]
public string Label { get; set; }
[StyledProperty(BindsTwoWayByDefault = true)]
public bool IsPressed { get; set; }
}
}
Important: Mark your class as partial to allow source generator to add code.
The generator automatically creates:
// Generated code (you don't write this):
public static readonly StyledProperty<string> LabelProperty =
AvaloniaProperty.Register<CustomButton, string>(nameof(Label), "Click Me");
public static readonly StyledProperty<bool> IsPressedProperty =
AvaloniaProperty.Register<CustomButton, bool>(nameof(IsPressed), defaultBindingMode: BindingMode.TwoWay);
DirectProperty for Performance
Use DirectProperty for frequently changing values (better performance, no style system participation):
public partial class CounterControl : Control
{
[DirectProperty(DefaultValue = 0)]
public int Counter { get; set; }
[DirectProperty(BindsTwoWayByDefault = true)]
public string Text { get; set; }
}
Generated code:
// Backing field is generated automatically
private int m_counter = 0;
public static readonly DirectProperty<CounterControl, int> CounterProperty =
AvaloniaProperty.RegisterDirect<CounterControl, int>(
nameof(Counter),
o => o.m_counter,
(o, v) => o.m_counter = v,
unsetValue: 0);
Attached Properties
using OutWit.Common.MVVM.Avalonia.Attributes;
public static partial class MyAttachedProperties
{
[AttachedProperty(DefaultValue = false)]
public static bool IsHighlighted { get; set; }
[AttachedProperty(DefaultValue = 1.0, Inherits = true)]
public static double Opacity { get; set; }
}
// Usage in AXAML:
// <Button local:MyAttachedProperties.IsHighlighted="True" />
Generated code includes Get/Set methods:
public static bool GetIsHighlighted(AvaloniaObject obj) => obj.GetValue(IsHighlightedProperty);
public static void SetIsHighlighted(AvaloniaObject obj, bool value) => obj.SetValue(IsHighlightedProperty, value);
Convention-Based Callbacks
The generator discovers callback methods by naming convention and subscribes them for you.
Avalonia, unlike WPF, has no changed-callback parameter on AvaloniaProperty.Register — the
notification arrives through the property's Changed observable — so the generated code
registers the property and subscribes in one step. Nothing is needed in your constructor:
public partial class SmartControl : Control
{
// No need to specify OnChanged - automatically discovered!
[StyledProperty(DefaultValue = "Hello")]
public string Title { get; set; }
// Convention: On{PropertyName}Changed
private void OnTitleChanged(AvaloniaPropertyChangedEventArgs<string> e)
{
// Handle title change
}
// Convention: {PropertyName}Coerce
private string TitleCoerce(AvaloniaObject sender, string value)
{
return value?.Trim() ?? "";
}
}
Visual Tree Traversal
using OutWit.Common.MVVM.Avalonia.Utils;
// Find first child of specific type
var button = myPanel.FindFirstChildOf<Button>();
// Find all children
var allButtons = myPanel.FindAllChildrenOf<Button>();
// Find parent
var window = myButton.FindFirstParentOf<Window>();
BindingProxy for DataContext Access
<UserControl.Resources>
<local:BindingProxy x:Key="Proxy" Data="{Binding}" />
</UserControl.Resources>
<DataGrid Items="{Binding Items}">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataTemplate>
<Button Command="{Binding Data.DeleteCommand, Source={StaticResource Proxy}}"
CommandParameter="{Binding}" />
</DataTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
AvaloniaDispatcher
using OutWit.Common.MVVM.Avalonia.Abstractions;
// Get dispatcher for current thread
var dispatcher = AvaloniaDispatcher.UIThread;
// Invoke on UI thread
dispatcher.Invoke(() => UpdateUI());
// Async invoke
await dispatcher.InvokeAsync(() => UpdateUI());
Property Types Comparison
| Type | Use Case | Style System | Performance |
|---|---|---|---|
StyledProperty |
Most properties | Yes | Normal |
DirectProperty |
Frequently changing values | No | Better |
AttachedProperty |
Properties on other objects | Yes | Normal |
StyledProperty Options
| Option | Type | Description |
|---|---|---|
PropertyName |
string |
Override property name (default: {Name}Property) |
DefaultValue |
object |
Default value |
BindsTwoWayByDefault |
bool |
Enable two-way binding by default |
Inherits |
bool |
Value inherited by child elements |
OnChanged |
string |
Property changed callback method name |
Coerce |
string |
Coerce value callback method name |
DirectProperty Options
| Option | Type | Description |
|---|---|---|
PropertyName |
string |
Override property name |
DefaultValue |
object |
Default value (also used as unset value) |
BindsTwoWayByDefault |
bool |
Enable two-way binding by default |
OnChanged |
string |
Property changed callback method name |
AttachedProperty Options
| Option | Type | Description |
|---|---|---|
PropertyName |
string |
Override property name |
DefaultValue |
object |
Default value |
Inherits |
bool |
Value inherited by child elements |
OnChanged |
string |
Property changed callback method name |
Coerce |
string |
Coerce value callback method name |
Related Packages
OutWit.Common.MVVM- Cross-platform base classesOutWit.Common.MVVM.WPF- WPF-specific implementationOutWit.Common.MVVM.Blazor- Blazor-specific implementation
Upgrading to 3.1.0
Two things that used to be silent now work, and both can change behaviour in an existing control.
On{Property}Changed is actually called. Before 3.1.0 the generator found the method and
emitted a comment suggesting you subscribe to it yourself, so the callbacks never ran. If your
control subscribed by hand — a static constructor with
XProperty.Changed.AddClassHandler<T>(…) — and the handler it calls is named by the
convention, it will now run twice. Delete the manual subscription; the generated one
replaces it.
A [DirectProperty] raises change notifications. Its generated setter used to write the
backing field directly, which meant no callback, no PropertyChanged, and no binding out of
it ever updated. It now goes through SetAndRaise. Nothing to change on your side — but code
that relied on a direct property staying quiet will start seeing notifications.
License
Licensed under the Apache License, Version 2.0. See LICENSE.
Attribution (optional)
If you use OutWit.Common.MVVM.Avalonia in a product, a mention is appreciated (but not required), for example: "Powered by OutWit.Common.MVVM.Avalonia (https://ratner.io/)".
Trademark / Project name
"OutWit" and the OutWit logo are used to identify the official project by Dmitry Ratner.
You may:
- refer to the project name in a factual way (e.g., "built with OutWit.Common.MVVM.Avalonia");
- use the name to indicate compatibility (e.g., "OutWit.Common.MVVM.Avalonia-compatible").
You may not:
- use "OutWit.Common.MVVM.Avalonia" as the name of a fork or a derived product in a way that implies it is the official project;
- use the OutWit.Common.MVVM.Avalonia logo to promote forks or derived products without permission.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Avalonia (>= 12.1.1)
- OutWit.Common.Logging (>= 1.3.2)
- OutWit.Common.MVVM (>= 2.1.0)
-
net8.0
- Avalonia (>= 12.1.1)
- OutWit.Common.Logging (>= 1.3.2)
- OutWit.Common.MVVM (>= 2.1.0)
-
net9.0
- Avalonia (>= 12.1.1)
- OutWit.Common.Logging (>= 1.3.2)
- OutWit.Common.MVVM (>= 2.1.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on OutWit.Common.MVVM.Avalonia:
| Package | Downloads |
|---|---|
|
OutWit.Common.MVVM.Navigation.Avalonia
Avalonia half of OutWit.Common.MVVM.Navigation: ViewLocator (an IDataTemplate over the view registry and a naming convention), the NavigationOutlet control that keeps views alive across navigations, window and overlay dialog hosts, application resources for modules, and AddAvaloniaNavigation(). |
GitHub repositories
This package is not used by any popular GitHub repositories.