5.0 KiB
Warning This tool hasn't been extensively tested, so use at your own risk.
C# SourceGenerator for Typed Avalonia x:Name References
This is a C# SourceGenerator built for generating strongly-typed references to controls with x:Name (or just Name) attributes declared in XAML (or, in .axaml). The sandbox app is targeting net5 which is still in preview, so this source generator is an early proof-of-concept. The idea is that you include your Avalonia XAML files into your project via <AdditionalFiles Include="**\*.xaml" /> and then decorate your view class with [GenerateTypedNameReferences] and the source generator will look for the xaml (or axaml) file with the same name as your C# class. The source generator then parses the XML markup, finds all XML tags with x:Name attributes and generates the C# code.
Getting Started
So in your project file you write the following code:
<ItemGroup>
<Compile Update="**\*.xaml.cs">
<DependentUpon>%(Filename)</DependentUpon>
</Compile>
<AvaloniaResource Include="**\*.xaml">
<SubType>Designer</SubType>
</AvaloniaResource>
<!-- Note this AdditionalFiles directive. -->
<AdditionalFiles Include="**\*.xaml" />
</ItemGroup>
And then you reference the source generator by installing a NuGet package:
dotnet add package XamlNameReferenceGenerator
Or, if you are using submodules, reference the generator as such:
<ItemGroup>
<ProjectReference Include="../XamlNameReferenceGenerator/XamlNameReferenceGenerator.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
</ItemGroup>
Finally, you declare your view class as partial and decorate it with [GenerateTypedNameReferences]:
using Avalonia.Controls;
[GenerateTypedNameReferences] // Note the 'partial' keyword.
public partial class SignUpView : Window
{
public SignUpView()
{
AvaloniaXamlLoader.Load(this);
UserNameTextBox.Text = "Joseph"; // Coolstuff!
}
}
What do the generated sources look like?
For the SignUpView view class from the sandbox project, we get the following generated output:
// <auto-generated />
using Avalonia.Controls;
namespace XamlNameReferenceGenerator.Sandbox
{
partial class SignUpView
{
internal XamlNameReferenceGenerator.Sandbox.Controls.CustomTextBox UserNameTextBox => this.FindControl<XamlNameReferenceGenerator.Sandbox.Controls.CustomTextBox>("UserNameTextBox");
internal Avalonia.Controls.TextBlock UserNameValidation => this.FindControl<Avalonia.Controls.TextBlock>("UserNameValidation");
internal Avalonia.Controls.TextBox PasswordTextBox => this.FindControl<Avalonia.Controls.TextBox>("PasswordTextBox");
internal Avalonia.Controls.TextBlock PasswordValidation => this.FindControl<Avalonia.Controls.TextBlock>("PasswordValidation");
internal Avalonia.Controls.TextBox ConfirmPasswordTextBox => this.FindControl<Avalonia.Controls.TextBox>("ConfirmPasswordTextBox");
internal Avalonia.Controls.TextBlock ConfirmPasswordValidation => this.FindControl<Avalonia.Controls.TextBlock>("ConfirmPasswordValidation");
internal Avalonia.Controls.Button SignUpButton => this.FindControl<Avalonia.Controls.Button>("SignUpButton");
internal Avalonia.Controls.TextBlock CompoundValidation => this.FindControl<Avalonia.Controls.TextBlock>("CompoundValidation");
}
}
Why do I need this?
The typed x:Name references might be useful if you decide to use e.g. ReactiveUI code-behind bindings:
[GenerateTypedNameReferences] // UserNameValidation and PasswordValidation are auto generated.
public partial class SignUpView : ReactiveWindow<SignUpViewModel>
{
public SignUpView()
{
AvaloniaXamlLoader.Load(this);
this.WhenActivated(disposables =>
{
this.BindValidation(ViewModel, x => x.UserName, x => x.UserNameValidation.Text)
.DisposeWith(disposables);
this.BindValidation(ViewModel, x => x.Password, x => x.PasswordValidation.Text)
.DisposeWith(disposables);
});
}
}