I am trying to make an AvaloniaUI user control. I want to be able to set a property at design time from inside my XAML files and see the result in the designer in Rider, such as the "Hello World!" text below.
The approach I have taken is to add a property to the control that gets and sets the Text
property of a TextBlock
that is defined in the XAML.
public partial class SectionHeader : UserControl
{
public string Caption
{
get { return this.FindControl<TextBlock>("CaptionText").Text; }
set { this.FindControl<TextBlock>("CaptionText").Text = value; }
}
public SectionHeader()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:BasicMvvmSample.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="AvTest.Controls.SectionHeader">
<Design.DataContext>
<viewModels:MyViewModel />
</Design.DataContext>
<TextBlock Background="#c71e32"
Foreground="White"
Padding="2,3"
FontWeight="Bold"
Margin="2"
Height="25"
Name="CaptionText"
>
</TextBlock>
</UserControl>
This approach works, but it does not use the patterns described in the documentation that rely on AvaloniaProperty
. I am new to both Avalonia and XAML and wonder if my approach is sensible, or if there is a better and more "XAML way" to achieve this.
1 Answer 1
By 'default' the preference is to use a StyledProperty
, as these can be bound or styled. If you don't need that, then you get away with something simpler, but if you really don't need anything more, then perhaps consider using a TextBlock
with a class (e.g. Classes="SectionHeader"
) and styling to go with it. Custom controls are mostly useful for providing new behaviours; if you just want to keep the styling clean, then use styles.
To set up a StyledProperty
in the class (which I'd suggest doing, because binding gives you so much power (e.g. to bind to a localisable resource key for globalisation)) you'd generally add something like
public static readonly StyledProperty<string> CaptionProperty = AvaloniaProperty.Register<SectionHeader, string>(nameof(Caption));
public string Caption
{
get => this.GetValue(CaptionProperty);
set => this.SetValue(CaptionProperty, value);
}
as you can find in the documentation you've linked, and then bind this property in your XAML for the control (so that the above property is reflected in the control interface), for the TextBlock
*:
Text="{Binding $parent[UserControl].Caption}"
and when using SectionHeader
:
<controls:SectionHeader Caption="Hello, World!" />
<controls:SectionHeader Caption="{Binding Whatever.SectionCaption}" />
<controls:SectionHeader Caption="{DynamicResource LocalisedSectionCaption}" />
Again, you're not using any of the power of a UserControl
, but I'd say there's nothing wrong with using one here.
When accessing named controls, consider adding a reference to XamlNameReferenceGenerator
: if you're using .NET7+ it'll use a source-generator to generate fields for your named controls, so you don't have to find them by name (this makes the code cleaner, quicker to write and harder to get wrong or later break)
* $parent[UserControl]
is one way to access the properties of the UserControl
, rather than it's DataContext
: see https://docs.avaloniaui.net/docs/data-binding/binding-to-controls for more information
-
\$\begingroup\$ Thank you very much, @VisualMelon. That makes perfect sense and has some very good extension points for my XAML/Avalonia learning path. Much appreciated! \$\endgroup\$Sean Kearon– Sean Kearon2023年02月05日 11:47:19 +00:00Commented Feb 5, 2023 at 11:47