目录
介绍
本文是继在Easy Samples中使用AvaloniaUI进行多平台UI编码——第1部分——AvaloniaUI构建块和简单示例中的多平台Avalonia .NET Framework编程基本概念之后Avalonia文章的第三部分。
本文是基本XAML功能的指南。除了设置Visual Studio和创建Avalonia项目的说明外,您无需阅读前两篇文章即可理解它。
假设本文的读者对XML和C#有一些基本的了解。
Avalonia是一个伟大的新开源软件包,它与WPF非常相似,但与WPF或UWP不同,它适用于大多数平台——Windows、MacOS和各种Linux版本,并且在许多方面比WPF更强大。
与Node.js或Xamarin相比,Avalonia在构建多平台桌面应用程序方面也更加强大和灵活。
Avalonia的源代码可在Avalonia Source Code获得。
本文中的材料涵盖了Avalonia XAML的基础知识,例如名称空间、类型、属性、资源、泛型和面向初学者的基本标记扩展。在这里,我们不会讨论附加属性或绑定如何在XAML中表示(这已在简单示例中的多平台Avalonia .NET Framework编程基本概念中介绍)或模板和样式(这将在以后的文章中介绍)。
尽管这里的所有示例都处理Avalonia,但其中大部分也适用于其他XAML框架,如WPF、UWP和Xamarin。
请注意,我最近发现(来自以下出色的演示TypeArgumentsDemo)Visual Studio 2019的Avalonia扩展支持已编译XAML的泛型——这是WPF团队很久以前承诺添加的,但据我所知,从未这样做过。在本文中,我专门用一节介绍了Avalonia XAML中的泛型。
我还使用了本文的大部分材料和示例,以便很快在Avalonia文档中获得新的Avalonia文档。
本文的所有代码都位于Github NP.Avalonia.Demos存储库中的NP.Avalonia.Demos/NP.Demos.XamlSamples下。
什么是XAML
XAML是用于构建C#(主要是可视)对象的XML。
C#类显示为XML标记,而类属性通常显示为XML属性:
<my_namespace:MyClass Prop1="Hello World"
Prop2="123"/>
上例中的XAML代码从XML命名空间my_namespace创建一个MyClass类型的对象,并将其属性设置Prop1为string "Hello World"和Prop2值为123。请注意,属性将被解析为它们的C#类型,例如,如果Prop2是int类型,它将被解析为123整数值,而如果是string类型,它将被解析为"123" string。如果XAML文件中提到的属性或类型不存在,则包含该XAML的项目的编译将失败,并且Visual Studio通常会在编译之前检测到错误,并用红色虚线强调缺少的类型或属性。
命名空间(在我们的示例中,它是“my_namespace”)通常应该定义在上面或使用它的XML标记内。它可以指向一个C#命名空间或一组C#命名空间(下面将通过适当的示例进行说明)。
XAML文件可以与名为“代码隐藏”的C#文件相关联,以使用“部分类”声明定义相同的类。C#代码隐藏通常包含方法的定义,这些方法用作由XAML文件中定义的元素触发的事件的事件处理程序。这种将XAML元素触发的事件与C#事件处理程序相关联的方式是最简单、最直接的方式,并且已在上一篇文章使用Visual Studio 2019创建和运行简单的Avalonia项目中进行了说明。然而,它也是最糟糕的,因为它破坏了重要的MVVM模式(将在以后的文章中展示)并且几乎不应该使用。
XAML命名空间示例
XAML命名空间是一个string,通常定义在XAML文件的顶层元素(尽管它可以定义在任何标记上),指向包含当前XAML文件的项目所依赖的.NET程序集中的一些C#命名空间。
看看我们第一篇文章介绍示例的MainWindow.xaml文件的前两行:
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" ... >
这两行为整个文件定义了两个XAML命名空间。这些命名空间之一不需要任何前缀(它有一个空前缀),另一个有前缀“x”。两个命名空间都引用了Avalonia包中定义的类型。您可以在Avalonia中定义许多元素(例如button)而无需任何前缀(例如 <Button .../>),因为这些元素位于“https://github.com/avaloniaui” URL引用的默认Avalonia命名空间中。由前缀“x”引用的命名空间包含使用频率稍低的各种类型。例如——许多内置的C#类型,例如,string和object可以在XAML中称为<x:String>...</x:String>和<x:Object>...</x:Object>(它们包含在由“http://schemas.microsoft.com”引用的Avalonia命名空间中)。
重要说明:所谓的XAML命名空间URL不必引用任何真正存在于Web上的有效URL,并且计算机不必为了使它们工作而在线。
显示定义自定义XAML命名空间的各种方法的示例位于NP.Demos.XamlNamespacesSample.sln解决方案下。从Github下载其代码并在Visual Studio(或Rider)中打开解决方案。
可以看到该解决方案由三个项目组成:主项目NP.Demos.XamlNamespacesSample和主项目所依赖的两个项目:Dependency1Proj和Dependency2Proj:
编译并运行解决方案——您将看到以下内容:
一个窗口内垂直堆叠着五个不同颜色的方形按钮。
下面是MainWindow.xaml文件的相关代码:
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dep1="clr-namespace:Dependency1Proj;assembly=Dependency1Proj"
xmlns:dep1_sub_Folder="clr-namespace:Dependency1Proj.SubFolder;
assembly=Dependency1Proj"
xmlns:local="clr-namespace:NP.Demos.XamlNamespacesSample"
xmlns:dep2="https://avaloniademos.com/xaml"
...>
<StackPanel HorizontalAlignment="Center"
VerticalAlignment="Center">
<dep1:RedButton/>
<dep1_sub_Folder:BlueButton/>
<local:BrownButton/>
<dep2:GreenButton/>
<dep2:GrayButton/>
</StackPanel>
</Window>
顶部XML标记的以下行定义了四个自定义命名空间:
xmlns:dep1="clr-namespace:Dependency1Proj;assembly=Dependency1Proj"
xmlns:dep1_test_Folder="clr-namespace:Dependency1Proj.SubFolder;assembly=Dependency1Proj"
xmlns:local="clr-namespace:NP.Demos.XamlNamespacesSample"
xmlns:dep2="https://avaloniademos.com/xaml"
不同的按钮由相应的XAML命名空间前缀引用。让我们来看看<RedButton/>。<RedButton/>类被定义为Dependency1Proj项目下的C#类。这是它的代码:
namespace Dependency1Proj
{
public class RedButton : Button, IStyleable
{
Type IStyleable.StyleKey => typeof(Button);
public RedButton()
{
Background = new SolidColorBrush(Colors.Red);
Width = 30;
Height = 30;
}
}
}
行...
Type IStyleable.StyleKey => typeof(Button);
...(以及RedButton类实现IStyleable接口的事实)确保主题中的默认按钮样式也将应用于RedButton类,其从派生自Avalonia的Button类。按钮的构造函数将按钮颜色指定为红色,并将按钮的高度和宽度设置为30个通用像素。
注意,除了按钮类名、C#名称空间和分配给按钮的颜色外,示例中每个按钮的代码都与RedButton完全相同。
现在看一下定义XAML命名空间前缀dep1的行,我们通过它在XAML文件中引用此按钮:
xmlns:dep1="clr-namespace:Dependency1Proj;assembly=Dependency1Proj"
命名空间的值包含由';'分号分隔的两部分。第一部分是指C#命名空间:
clr-namespace:Dependency1Proj
第二部分是指程序集名称:
assembly=Dependency1Proj
在RedButton的例子中,命名空间和程序集名称具有相同的名称:Dependency1Proj。
在同一个项目(Dependency1Proj)中定义了BlueButton,但在SubFolder文件夹中。它的C#命名空间不是Dependency1Proj(对于RedButton)而是Dependency1Proj.SubFolder。
这是定义在MainWindow.xaml文件中BlueButton引用的XAML命名空间前缀dep1_sub_Folder的行:
xmlns:dep1_sub_Folder="clr-namespace:Dependency1Proj.SubFolder;assembly=Dependency1Proj"
clr-namespace更改为Dependency1Proj.SubFolder,而程序集部分声明相同,因为BlueButton是在相同的Dependency1Proj程序集中定义的。
现在来看看<local:BrownButton\>。BrownButton的C#代码在主项目NP.Demost.XamlNamespacesSample中定义——我们的MainWindow.xaml文件所在的同一项目。因此,我们可以在定义前缀“local”时跳过程序集名称(引用我们的BrownButton)并仅指定clr-namespace部分:
xmlns:local="clr-namespace:NP.Demos.XamlNamespacesSample"
GreenButton和GrayButton定义在Dependency2Proj的两个不同名称空间中。GreenButton定义在项目的主命名空间—— Dependency2Proj下,而GrayButton定义在Dependency2Proj.SubFolder命名空间下。但是,Dependency2Proj也有文件AssemblyInfo.cs,它定义程序集元数据。在这个文件中,我们在底部添加了几行:
[assembly: XmlnsDefinition("https://avaloniademos.com/xaml", "Dependency2Proj")]
[assembly: XmlnsDefinition("https://avaloniademos.com/xaml", "Dependency2Proj.SubFolder")]
这两行将程序集的两个命名空间:Dependency2Proj和Dependency2Proj.SubFolder合并到同一个URL:“https://avaloniademos.com/xaml”中。如上所述,该URL是否存在或计算机是否在线都无关紧要。如果您的URL具有与包含此功能的项目相对应的某些含义,那就太好了。
现在,我们引用GreenButton和GrayButton的XAML前缀dep2通过引用该URL来定义:
xmlns:dep2="https://avaloniademos.com/xaml"
与WPF相比,有一个重要的额外Avalonia功能。在WPF中,只能通过URL引用与想要引用它的XAML文件不在同一个项目中的功能,而在Avalonia中,没有这样的限制——例如,如果我们在同一个Dependency2Proj项目中有一个XAML文件,我们仍然可以放一行…
xmlns:dep2="https://avaloniademos.com/xaml"
... 在它的顶部元素,并引用在同一个项目中由dep2: 前缀定义的GreenButton和GrayButton。
在XAML中访问C#复合属性
我们在上面已经提到,C#内置属性可以作为相应元素的XML属性来访问,例如:
<my_namespace:MyClass Prop1="Hello World"
Prop2="123"/>
Prop1并且Prop2是在MyClass类上定义的简单C#属性,可以在XAML文件的my_namespace前缀引用的C#命名空间中找到。Prop1可能是string类型,而Prop2可以是任何数字类型或string(XAML将自动将string “123”转换为正确的类型)。
但是,如果属性本身是某种复杂类型,包含了它自己的多个属性,会发生什么情况呢?
C#解决方案NP.Demos.AccessPropertiesInXamlSample.sln展示了如何在XAML中创建此类属性。
项目中定义了一个Person类:
namespace NP.Demos.AccessPropertiesInXamlSample
{
public class Person
{
public int AgeInYears { get; set; }
public string? FirstName { get; set; }
public string? LastName { get; set; }
public override string ToString()
{
return $"Person: {FirstName} {LastName}, Age: {AgeInYears}";
}
}
}
我们希望将其显示为Window的内容。这是我们在MainWindow.xaml文件中的内容:
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:NP.Demos.AccessPropertiesInXamlSample"
x:Class="NP.Demos.AccessPropertiesInXamlSample.MainWindow"
Width="300"
Height="200"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Title="AccessPropertiesInXamlSample">
<Window.Content>
<local:Person FirstName="Joe"
LastName="Doe"
AgeInYears="25"/>
</Window.Content>
</Window>
请注意我们将Content的属性分配Window给组合的方式Type:
<Window ...>
<Window.Content>
<local:Person FirstName="Joe"
LastName="Doe"
AgeInYears="25"/>
</Window.Content>
</Window>
我们使用Window.Content带有句点的属性标签,将类的名称与属性的名称分开。
请注意,与分配复合类型属性的方式相同,我们也可以分配原始类型属性,例如,我们可以通过以下代码设置窗口的Width属性:
<Window ...>
<Window.Width>
<x:Double>300</x:Double>
</Window.Width>
</Window>
而不是使用XML属性。当然,这样的符号比XAML属性符号大得多,很少用于基本类型的属性。
注意:因为Window.Content是用ContentAttribte标记的特殊属性,我们根本不需要添加<Window.Content>,可以将<local:Person .../>对象直接放在<Window...>标记下。每个类只有一个属性可以用ContentAttribute标记,所以在很多情况下,无论如何我们都被迫使用这些<Class.Property符号。
XAML特殊属性
有几个特殊属性用前缀“x:”标记,当然前提是我们在文件顶部定义了“x”命名空间前缀:
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
其中最重要的是x:Name和x:Key。
x:Name用于XAML树中的元素,以便能够在C#中轻松找到元素,并且(由某些人)为了为XAML提供一些自我文档并能够轻松识别Avalonia开发工具中的元素。
我们已经展示了如何在在Easy Samples中使用AvaloniaUI进行多平台UI编码——第1部分——AvaloniaUI构建块的C#代码中查找x:Name元素。“使用Visual Studio 2019创建和运行简单的AvaloniaUI项目”部分:可以使用该FindControl(...)方法,例如,对于在XAML中定义的按钮和x:Named “CloseWindowButton”,我们可以在代码隐藏中使用以下方法来查找它:
var button = this.FindControl<button>("CloseWindowButton");</button>
x:Key用于查找Avalonia XAML资源,我们将在专门针对它们的部分中对其进行解释。
标记扩展的简要介绍
标记扩展是一些可以显着简化XAML的C#类。它们用于设置一些XAML属性,使用其中包含大括号('{' 和'}')的一种行符号。有一些非常重要的内置Avalonia标记扩展——最重要的如下:
- StaticResource
- DynamicResource
- x:Static
- Binding
除了Binding之外,我们将为所有这些提供示例(已在Bindings(基础概念的链接)中解释过。
也可以创建自定义标记扩展,但这很少使用,本指南不会涉及。我们将在未来的指南之一中解释它。
Avalonia XAML资源
XAML资源是重用XAML代码和将一些通用XAML代码放置在通用Visual项目中以在多个应用程序中使用的最重要方法之一。
静态资源与动态资源示例
此示例显示了静态资源和动态资源之间的主要区别:资源本身更新时,静态资源目标值不会更新,而动态资源——会。
该示例位于NP.Demos.StaticVsDynamicXamlResourcesSample解决方案下。
打开解决方案并运行它,您将看到以下内容:
按下“更改状态颜色”按钮将导致第三个矩形将其颜色切换为红色:
这是示例的MainWindow.xaml文件:
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="NP.Demos.StaticVsDynamicXamlResourcesSample.MainWindow"
Title="NP.Demos.StaticVsDynamicXamlResourcesSample"
Width="300"
Height="200">
<Window.Resources>
<ResourceDictionary>
<!--We set the XAML resource-->
<SolidColorBrush x:Key="StatusBrush"
Color="Green"/>
</ResourceDictionary>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel x:Name="ElementsPanel"
Orientation="Vertical">
<!--Refer to xaml resource using StaticResource Markup Expression -->
<Border x:Name="Border1"
Background="{StaticResource StatusBrush}"
Height="30"
Width="80"
Margin="0,5"/>
<!--Refer to xaml resource using StaticResource (without markup expression) -->
<Border x:Name="Border2"
Height="30"
Width="80"
Margin="0,5">
<Border.Background>
<StaticResource ResourceKey="StatusBrush"/>
</Border.Background>
</Border>
<!--Refer to xaml resource using DynamicResource Markup Expression -->
<Border x:Name="StatusChangingBorder"
Background="{DynamicResource StatusBrush}"
Height="30"
Width="80"
Margin="0,5"/>
</StackPanel>
<Button x:Name="ChangeStatusButton"
Grid.Row="1"
Width="160"
HorizontalAlignment="Right"
HorizontalContentAlignment="Center"
Content="Change Status Color"
Margin="10"/>
</Grid>
</Window>
我们在同一个MainWindow.xaml文件中将XAML资源定义为窗口的资源:
<Window.Resources>
<ResourceDictionary>
<!--We set the XAML resource-->
<SolidColorBrush x:Key="StatusBrush"
Color="Green"/>
</ResourceDictionary>
</Window.Resources>
x:Key的XAML资源可以被StaticResource和DynamicResource用来引用特定的资源。
然后,我们使用StaticResource设置第一个边界的背景,并使用DynamicResource设置垂直边界堆栈中的第三个边界。
对于堆栈中的第一个边框,我们使用StaticResource标记扩展:
<!--Refer to xaml resource using StaticResource Markup Expression -->
<Border x:Name="Border1"
Background="{StaticResource StatusBrush}"
Height="30"
Width="80"
Margin="0,5"/>
对于第二个边框,我们使用StaticResource没有标记扩展的类(您可以看到相应的XAML更加冗长):
<Border x:Name="Border2"
Height="30"
Width="80"
Margin="0,5">
<Border.Background>
<StaticResource ResourceKey="StatusBrush"/>
</Border.Background>
</Border>
最后,第三个边框使用了DynamicResource标记扩展:
<!--Refer to xaml resource using DynamicResource Markup Expression -->
<Border x:Name="StatusChangingBorder"
Background="{DynamicResource StatusBrush}"
Height="30"
Width="80"
Margin="0,5"/>
按钮“StatusChangingBorder”挂在MainWindow.xaml.cs文件中以将“StatusBrush”资源从“Green”更改为“Red”:
public MainWindow()
{
InitializeComponent();
Button button =
this.FindControl<Button>("ChangeStatusButton");
button.Click += Button_Click;
}
private void Button_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
// getting a Window resource by its name
var statusBrush = this.FindResource("StatusBrush");
// setting the window resource to a new value
this.Resources["StatusBrush"] =
new SolidColorBrush(Colors.Red);
}
尽管所有三个边框的资源都相同,但只有最后一个边框的背景会发生变化——使用DynamicResource。
静态资源和动态资源之间的其他重要区别如下:
- DynamicResource可以引用DynamicResource表达式下方的XAML中定义的XAML资源,而StaticResource应该引用其上方的资源。
- StaticResource可用于在XAML中使用的各种对象上分配简单的C#属性,而DynamicResource语句的目标应始终是特殊的Avalonia属性AvaloniaObject(特殊属性在附加属性中进行了说明)。
- 由于DynamicResource功能更强大(提供更改通知),因此它比StaticResource占用更多的内存资源。因此,当您不需要更改通知时(该属性在程序期间保持不变),您应该始终使用StaticResource。当您想要动态更改应用程序的主题或颜色时DynamicResources非常有用,例如,允许用户根据一天中的时间切换主题或更改颜色。
引用不同XAML文件和项目示例中定义的XAML资源
在此示例中,我们展示了如何引用位于相同或不同项目中不同文件中的XAML资源。
该示例位于NP.Demos.XamlResourcesInMultipleProjects Visual Studio解决方案下。运行示例后,您将看到三个不同颜色的矩形——红色、绿色和蓝色:
该解决方案由两个项目组成——主项目,NP.Demos.XamlResourcesInMultipleProjects以及主项目依赖的另一个项目——Dependency1Proj:
RedBrush资源在Dependency1Proj项目的Themes/BrushResources.axaml文件中定义:
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- Add Resources Here -->
<SolidColorBrush x:Key="GreenBrush"
Color="Green"/>
</ResourceDictionary>
请注意,BrushResources.axaml文件具有“Avalonia XAML”构建操作(就像任何Avalonia XAML资源文件一样):
通过为Visual Studio新项目创建选择“Resource Dictionary (Avalonia)”模板来创建此类文件:
GreenBrush Avalonia资源在Themes/LocalBrushResources.axaml文件中定义(该文件位于主项目中):
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- Add Resources Here -->
<SolidColorBrush x:Key="GreenBrush"
Color="Green"/>
</ResourceDictionary>
这是MainWindow.axaml文件的内容:
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="NP.Demos.XamlResourcesInMultipleProjects.MainWindow"
Title="NP.Demos.XamlResourcesInMultipleProjects"
Width="100"
Height="180">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source="avares://Dependency1Proj/Themes/BrushResources.axaml"/>
<ResourceInclude Source="/Themes/LocalBrushResources.axaml"/>
</ResourceDictionary.MergedDictionaries>
<!-- BlueBrush is defined locally -->
<SolidColorBrush x:Key="BlueBrush"
Color="Blue"/>
</ResourceDictionary>
</Window.Resources>
<StackPanel HorizontalAlignment="Center"
VerticalAlignment="Center">
<Border x:Name="RedBorder"
Width="70"
Height="30"
Background="{StaticResource RedBrush}"
Margin="5"/>
<Border x:Name="GreenBorder"
Width="70"
Height="30"
Background="{StaticResource GreenBrush}"
Margin="5"/>
<Border x:Name="BlueBorder"
Width="70"
Height="30"
Background="{StaticResource BlueBrush}"
Margin="5"/>
</StackPanel>
</Window>
我们有三个垂直堆叠的边框——第一个边框的背景从RedBrush资源中获取其值,第二个边框的从GreenBrush资源获取值和第三个边框从BlueBrush资源获取值。
查看文件顶部的窗口Resources部分:
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source="avares://Dependency1Proj/Themes/BrushResources.axaml"/>
<ResourceInclude Source="/Themes/LocalBrushResources.axaml"/>
</ResourceDictionary.MergedDictionaries>
<!-- BlueBrush is defined locally -->
<SolidColorBrush x:Key="BlueBrush"
Color="Blue"/>
</ResourceDictionary>
</Window.Resources>
<ResourceDictionary.MergedDictionary>标签内的<ResourceInclude .../>标签意味着我们正在将外部定义的资源字典合并到当前字典——我们获取所有键值对的方式。了解WPF的人会注意到其中的区别——在WPF中,我们使用<ResourceDictionary Source="..."/>标签而不是<ResourceInclude Source="..."/>。另请注意,对于依赖项目,我们没有将程序集与URL的其余部分分开,并且我们没有为URL使用神秘的“Component/ ”前缀。这些纯粹是符号上的(不是概念上的)差异,但仍然需要记住。
请注意合并文件的Avalonia XAML url:
- “avares://Dependency1Proj/Themes/BrushResources.axaml”——在不同项目中定义的Avalonia XAML资源文件的url应该以神奇的工作“avares://”开头,后跟程序集名称,然后是路径文件:“avares://<assembly-name>/<path-to-the-avalonia-resource_file>。
- “/Themes/LocalBrushResources.axaml”——在使用它的同一项目中定义的Avalon XAML资源文件的url应仅包含正斜杠,后跟从当前项目的根目录到avalonia资源文件的路径。
在资源部分的末尾,我们定义了BlueBrush资源 - MainWindow.asaml文件的本地资源。
x:Static标记扩展
x:Static标记扩展允许引用在同一项目或某些依赖项目中定义的static属性。示例代码位于NP.Demos.XStaticMarkupExtensionSample解决方案下。它包含两个项目—— NP.Demos.XStaticMarkupExtensionSample(主项目)和依赖项目Dependency1Proj。主项目包含类LocalProjectStaticBrushes,而依赖项目包含DependencyProjectStaticBrushes。
两个C#文件的内容都非常简单——每个文件都为单个static属性定义和设置值。以下是LocalProjectStaticBrushes类内容:
public class LocalProjectStaticBrushes
{
public static Brush GreenBrush { get; set; } =
new SolidColorBrush(Colors.Green);
}
以下是DependencyProjectStaticBrushes类的内容:
public class DependencyProjectStaticBrushes
{
public static Brush RedBrush { get; set; } =
new SolidColorBrush(Colors.Red);
}
运行项目将创建一个带有两个矩形的窗口,红色和绿色:
以下是MainWindow.axaml文件的相关部分:
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dep1="clr-namespace:Dependency1Proj;assembly=Dependency1Proj"
xmlns:local="clr-namespace:NP.Demos.XStaticMarkupExtensionSample"
...>
<StackPanel HorizontalAlignment="Center"
VerticalAlignment="Center">
<Border Width="70"
Height="30"
Background="{x:Static dep1:DependencyProjectStaticBrushes.RedBrush}"
Margin="5" />
<Border Width="70"
Height="30"
Background="{x:Static local:LocalProjectStaticBrushes.GreenBrush}"
Margin="5" />
</StackPanel>
</Window>
在Window标签级别定义了两个重要的命名空间:
xmlns:dep1="clr-namespace:Dependency1Proj;assembly=Dependency1Proj"
xmlns:local="clr-namespace:NP.Demos.XStaticMarkupExtensionSample"
“dep1”对应依赖项目,“local”对应MainWindow.xaml文件的本地项目(主项目)。
使用这些命名空间前缀和x:Static标记扩展,我们可以在两个边框上设置Background属性:
Background="{x:Static dep1:DependencyProjectStaticBrushes.RedBrush}"
和:
Background="{x:Static local:LocalProjectStaticBrushes.GreenBrush}"
Avalonia XAML中的泛型
正如我上面提到的,我从以下优秀的演示TypeArgumentsDemo中了解到Visual Studio 2019编译的Avalonia XAML支持泛型。据我所知,Microsoft从未向WPF添加此类功能,尽管他们曾打算这样做。
泛型演示位于XamlGenericsSamples项目下。
有一个ValuesContainer类有两个泛型类型参数:
public class ValuesContainer<TVal1, TVal2>
{
public TVal1? Value1 { get; set; }
public TVal2? Value2 { get; set; }
}
ValuesContainer定义了两个泛型类型TVal1的值Value1和泛型类型TVal2的值Value2。
其余有趣的代码都位于MainWindow.asaml文件中。
运行示例,您将看到以下内容:
有三个示例——第一个解释创建单个 ValuesContainer 对象,第二个——ValuesContainer 对象列表,第三个—— 将整数映射到ValuesContainer对象的Dictionary。让我们一一解释示例。
单个ValuesContainer 对象示例
这是此示例的代码:
<Grid RowDefinitions="Auto, Auto">
<Grid.DataContext>
<local:ValuesContainer x:TypeArguments="x:Double, x:String"
Value1="300"
Value2="Hello 1"/>
</Grid.DataContext>
<TextBlock Text="ValuesContainer Generics Sample:"
FontWeight="Bold"/>
<Grid Background="Yellow"
Grid.Row="1"
RowDefinitions="Auto, Auto"
Width ="{Binding Path=Value1}"
HorizontalAlignment="Left">
<TextBlock Text="{Binding Path=Value1, StringFormat='Width of Yellow Rectangle=Value1=\{0\}'}"
Margin="5"/>
<TextBlock Text="{Binding Path=Value2, StringFormat='Value2=\'\{0\}\''}"
Grid.Row="1"
Margin="5,0,5,5"/>
</Grid>
</Grid>
我们将ValuesContainer 对象定义为包含示例代码的Grid 的DataContext:
<Grid.DataContext>
<local:ValuesContainer x:TypeArguments="x:Double, x:String"
Value1="300"
Value2="Hello 1"/>
</Grid.DataContext>
ValuesContainer对象的x:TypeArguments属性定义了一个以逗号分隔的泛型类型参数列表——我们将第一个参数定义为double ,第二个参数定义为string。然后我们设置Value1="300"和Value2="Hello 1"。请注意,XML字符串“300”将自动转换为double。由于DataContext是一个沿可视化树向下传播的特殊属性,相同的DataContext将定义在Grid的所有后代上。我们可以将后代TextBlocks'Text属性绑定到Value1和Value2以显示这些值。同样为了证明Value1确实是double类型(而不是string),我们将内部网格(黄色背景)的宽度绑定到DataContext属性的Value1:
<Grid Background="Yellow"
...
Width ="{Binding Path=Value1}" ...>
这样黄色矩形的宽度将是300。
对象列表ValuesContainer 示例
在查看示例的XAML代码之前,请注意我们在XAML文件的顶部定义了一个collections命名空间:xmlns:collections="clr-namespace:System.Collections.Generic;assembly=System.Collections"。此命名空间指向C#命名空间和定义泛型集合(例如Dictionary<...>和List<...>)的程序集。
这是相应的XAML代码:
<Grid RowDefinitions="Auto, Auto">
<Grid.DataContext>
<collections:List x:TypeArguments="local:ValuesContainer(x:Int32, x:String)">
<local:ValuesContainer x:TypeArguments="x:Int32, x:String"
Value1="1"
Value2="Hello 1"/>
<local:ValuesContainer x:TypeArguments="x:Int32, x:String"
Value1="2"
Value2="Hello 2"/>
<local:ValuesContainer x:TypeArguments="x:Int32, x:String"
Value1="3"
Value2="Hello 3"/>
</collections:List>
</Grid.DataContext>
<TextBlock Text="List of ValuesContainer Generics Sample:"
FontWeight="Bold"/>
<ItemsControl Items="{Binding}"
Grid.Row="1">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Value1,
StringFormat='Value1=\{0\}'}"/>
<TextBlock Text="{Binding Path=Value2,
StringFormat='Value2=\'\{0\}\''}"
Margin="10,0,0,0"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
这次容器的DataContext被定义为List<ValuesContainer<int, string>>,即我们有两个级别的类型参数递归:
<collections:List x:TypeArguments="local:ValuesContainer(x:Int32, x:String)">
...
</collections:List>
然后,由于List<...> 有一个Add方法,我们可以简单地在List<...>中添加单个对象:
<collections:List x:TypeArguments="local:ValuesContainer(x:Int32, x:String)">
<local:ValuesContainer x:TypeArguments="x:Int32, x:String"
Value1="1"
Value2="Hello 1"/>
<local:ValuesContainer x:TypeArguments="x:Int32, x:String"
Value1="2"
Value2="Hello 2"/>
<local:ValuesContainer x:TypeArguments="x:Int32, x:String"
Value1="3"
Value2="Hello 3"/>
</collections:List>
请注意,对于每个ValuesContainer对象,Value1将自动转换为int。
然后我们将ItemsControl的Items属性绑定到列表并使用ItemsControl的ItemTemplate来显示每个单独的项目的Value1和Value2:
<ItemsControl Items="{Binding}"
Grid.Row="1">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Value1,
StringFormat='Value1=\{0\}'}"/>
<TextBlock Text="{Binding Path=Value2,
StringFormat='Value2=\'\{0\}\''}"
Margin="10,0,0,0"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
整数字典到ValuesContainer对象示例
我们的最后一个示例更有趣。我们展示了如何创建和显示泛型Dictionary<string, ValuesContainer<int, string>>的上下文。
以下是示例的相关代码:
<Grid RowDefinitions="Auto, Auto">
<Grid.DataContext>
<collections:Dictionary x:TypeArguments="x:String, local:ValuesContainer(x:Int32, x:String)">
<local:ValuesContainer x:TypeArguments="x:Int32, x:String"
x:Key="Key1"
Value1="1"
Value2="Hello 1"/>
<local:ValuesContainer x:TypeArguments="x:Int32, x:String"
x:Key="Key2"
Value1="2"
Value2="Hello 2"/>
<local:ValuesContainer x:TypeArguments="x:Int32, x:String"
x:Key="Key3"
Value1="3"
Value2="Hello 3"/>
</collections:Dictionary>
</Grid.DataContext>
<TextBlock Text="Dictionary of ValuesContainer Generics Sample:"
FontWeight="Bold"/>
<ItemsControl Items="{Binding}"
Grid.Row="1">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Key,
StringFormat='Key=\'\{0\}\''}"/>
<TextBlock Text="{Binding Path=Value.Value1,
StringFormat='Value1=\{0\}'}"
Margin="10,0,0,0"/>
<TextBlock Text="{Binding Path=Value.Value2,
StringFormat='Value2=\'\{0\}\''}"
Margin="10,0,0,0"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
这是我们如何定义Dictionary<string, ValuesContainer<int, string>>: <collections:Dictionary x:TypeArguments="x:String, local:ValuesContainer(x:Int32, x:String)">。
这是字典的填充方式:
<collections:Dictionary x:TypeArguments="x:String, local:ValuesContainer(x:Int32, x:String)">
<local:ValuesContainer x:TypeArguments="x:Int32, x:String"
x:Key="Key1"
Value1="1"
Value2="Hello 1"/>
<local:ValuesContainer x:TypeArguments="x:Int32, x:String"
x:Key="Key2"
Value1="2"
Value2="Hello 2"/>
<local:ValuesContainer x:TypeArguments="x:Int32, x:String"
x:Key="Key3"
Value1="3"
Value2="Hello 3"/>
</collections:Dictionary>
请注意,我们只是在字典中创建ValuesContainer对象,但每个对象都有一个设置为唯一值的x:Key属性。此x:Key属性指定字典的键,而ValuesContainer 对象成为值。请注意,在我们的例子中,字典的键是类型string,但如果它是其他一些众所周知的类型,例如int,Avalonia XAML编译器会将键值转换为整数。
上面的XAML代码用三个KeyValuePair<string, ValuesContainer<int, string>>对象填充我们的字典,其json表示如下:
[
{ Key="Key1"
Value = new ValuesContainer{ Value1=1, Value2="Hello 1"},
{ Key="Key2"
Value = new ValuesContainer{ Value1=2, Value2="Hello 2"},
{ Key="Key3"
Value = new ValuesContainer{ Value1=3, Value2="Hello 3"}
]
下面是我们如何将ItemsControl 绑定到这些对象并使用ItemTemplate显示它们:
<ItemsControl Items="{Binding}"
Grid.Row="1">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Key,
StringFormat='Key=\'\{0\}\''}"/>
<TextBlock Text="{Binding Path=Value.Value1,
StringFormat='Value1=\{0\}'}"
Margin="10,0,0,0"/>
<TextBlock Text="{Binding Path=Value.Value2,
StringFormat='Value2=\'\{0\}\''}"
Margin="10,0,0,0"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
第一个TextBlock's Text 属性绑定到KeyValuePair<...>的Key ,第二个到Value1和第三个到Value2。
在XAML中引用资产
在Avalonia Lingo——资产通常是二进制图像(例如png或jpg)文件。在本节中,我们将展示如何从XAML中的Image控件引用此类文件。
该示例的代码位于NP.Demos.ReferringToAssetsInXaml NP.Demos.ReferringToAssetsInXaml解决方案下。这是解决方案的代码:
们在依赖项目下有Themes/avalonia-32.png文件,在主项目Dependency1Proj下有Themes/LinuxIcon.jpg文件。
请注意,资产文件的构建操作应为“AvaloniaResource”(与我们看到的XAML资源文件不同,它设置为“Avalonia XAML”):
构建并运行示例,您将看到以下内容:
有四个垂直堆叠的图像——这是相应的代码:
<Image Source="/Assets/LinuxIcon.jpg"
Width="50"
Height="50"
Margin="5"/>
<Image Source="avares://Dependency1Proj/Assets/avalonia-32.png"
Width="50"
Height="50"
Margin="5"/>
<Image x:Name="LinuxIconImage2"
Width="50"
Height="50"
Margin="5"/>
<Image x:Name="AvaloniaIconImage2"
Width="50"
Height="50"/>
对于前两个图像——在XAML中设置Source,对于最后一个——在后面的C#代码中。
请注意,定义为同一项目的本地资产的图像包含使用它的MainWindow.axaml文件,可以使用源URL的简化版本:
Source="/Assets/LinuxIcon.jpg"
虽然位于不同项目中的图像应该使用前缀为avares://的完整版本的URL。
Source="avares://Dependency1Proj/Assets/avalonia-32.png"
请注意,这与XAML资源字典文件的情况相同,但与WPF不同,程序集名称(在我们的例子中为“Dependency1Proj”)是URL的一部分,并且没有组件前缀。
最后两个图像的Source属性在MainWindow.axaml.cs代码隐藏文件中设置。以下是相关代码:
public MainWindow()
{
InitializeComponent();
...
// get the asset loader from Avalonia container
var assetLoader = AvaloniaLocator.Current.GetService<IAssetLoader>();
// get the Image control from XAML
Image linuxIconImage2 = this.FindControl<Image>("LinuxIconImage2");
// set the image Source using assetLoader
linuxIconImage2.Source =
new Bitmap
(
assetLoader.Open(
new Uri("avares://NP.Demos.ReferringToAssetsInXaml/Assets/LinuxIcon.jpg")));
// get the Image control from XAML
Image avaloniaIconImage2 = this.FindControl<Image>("AvaloniaIconImage2");
// set the image Source using assetLoader
avaloniaIconImage2.Source =
new Bitmap
(
assetLoader.Open(
new Uri("avares://Dependency1Proj/Assets/avalonia-32.png")));
}
请注意,即使对于本地文件“LinuxIcon.jpg”(与使用它的MainWindow.xaml.cs文件在同一项目中定义的文件),我们也需要使用“avares://<assembly-name>/"前缀提供完整的URL。
非可视XAML代码
最后一个示例将证明即使是完全不可视的代码也可以使用XAML。该示例位于NP.Demos.NonVisualXamlSample解决方案下。与之前的示例不同——它是一个控制台应用程序,仅引用一个(不是三个)Avalonia nuget包:
主程序位于Program.cs文件下,非常简单:
public static void Main(string[] args)
{
Course course = new Course();
}
您可以在该行之后放置一个断点并调查course对象的内容:
查看Course.asaml/Course.asaml.cs文件。这是Course.asaml文件的内容:
<x:Object xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:NP.Demos.NonVisualXamlSample"
x:Class="NP.Demos.NonVisualXamlSample.Course"
NumberStudents="5">
<local:Person FirstName="Joe" LastName="Doe" Age="100" />
</x:Object>
类型Course的顶部标记包含一个类型Person对象。顶部对象的NumberStudents属性设置为5,而Person's属性设置为FirstName="Joe",LastName="Doe"和Age="100"。
Course.axaml.cs文件定义了Course类的属性:
public partial class Course
{
public Course()
{
AvaloniaXamlLoader.Load(this);
}
public int NumberStudents { get; set; }
[Content]
public Person? Teacher { get; set; }
}
请注意,它的构造函数还定义了加载XAML文件的方法,并且该类被标记为“partial”(另一部分是从XAML生成的)。还要注意Teacher属性有ContentAttribute——这就是为什么我们不需要使用<local:Course.Teacher>标签来放置Person对象。
最后,这是Person类的代码:
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
public double Age { get; set; }
}
结论
本文通过示例详细说明了基本的 XAML 功能。
https://www.codeproject.com/Articles/5314369/Basics-of-XAML-in-Easy-Samples-for-Multiplatform-A