多平台Avalonia .NET Framework简单示例中的XAML基础知识

目录

介绍

什么是XAML

XAML命名空间示例

在XAML中访问C#复合属性

XAML特殊属性

标记扩展的简要介绍

Avalonia XAML资源

静态资源与动态资源示例

引用不同XAML文件和项目示例中定义的XAML资源

x:Static标记扩展

Avalonia XAML中的泛型

单个ValuesContainer 对象示例

对象列表ValuesContainer 示例

整数字典到ValuesContainer对象示例

在XAML中引用资产

非可视XAML代码

结论


介绍

本文是继在Easy Samples中使用AvaloniaUI进行多平台UI编码——第1部分——AvaloniaUI构建块简单示例中的多平台Avalonia .NET Framework编程基本概念之后Avalonia文章的第三部分。

本文是基本XAML功能的指南。除了设置Visual Studio和创建Avalonia项目的说明外,您无需阅读前两篇文章即可理解它。

假设本文的读者对XMLC#有一些基本的了解。

Avalonia是一个伟大的新开源软件包,它与WPF非常相似,但与WPFUWP不同,它适用于大多数平台——WindowsMacOS和各种Linux版本,并且在许多方面比WPF更强大。

Node.jsXamarin相比,Avalonia在构建多平台桌面应用程序方面也更加强大和灵活。

Avalonia的源代码可在Avalonia Source Code获得。

本文中的材料涵盖了Avalonia XAML的基础知识,例如名称空间、类型、属性、资源、泛型和面向初学者的基本标记扩展。在这里,我们不会讨论附加属性或绑定如何在XAML中表示(这已在简单示例中的多平台Avalonia .NET Framework编程基本概念中介绍)或模板和样式(这将在以后的文章中介绍)。

尽管这里的所有示例都处理Avalonia,但其中大部分也适用于其他XAML框架,如WPFUWPXamarin

请注意,我最近发现(来自以下出色的演示TypeArgumentsDemoVisual Studio 2019Avalonia扩展支持已编译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类型的对象,并将其属性设置Prop1string "Hello World"Prop2值为123。请注意,属性将被解析为它们的C#类型,例如,如果Prop2int类型,它将被解析为123整数值,而如果是string类型,它将被解析为"123string。如果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#类型,例如,stringobject可以在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和主项目所依赖的两个项目:Dependency1ProjDependency2Proj

编译并运行解决方案——您将看到以下内容:

一个窗口内垂直堆叠着五个不同颜色的方形按钮。

下面是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类,其从派生自AvaloniaButton类。按钮的构造函数将按钮颜色指定为红色,并将按钮的高度和宽度设置为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\>BrownButtonC#代码在主项目NP.Demost.XamlNamespacesSample中定义——我们的MainWindow.xaml文件所在的同一项目。因此,我们可以在定义前缀local时跳过程序集名称(引用我们的BrownButton)并仅指定clr-namespace部分:

xmlns:local="clr-namespace:NP.Demos.XamlNamespacesSample"  

GreenButtonGrayButton定义在Dependency2Proj的两个不同名称空间中。GreenButton定义在项目的主命名空间—— Dependency2Proj下,而GrayButton定义在Dependency2Proj.SubFolder命名空间下。但是,Dependency2Proj也有文件AssemblyInfo.cs,它定义程序集元数据。在这个文件中,我们在底部添加了几行:

[assembly: XmlnsDefinition("https://avaloniademos.com/xaml", "Dependency2Proj")]
[assembly: XmlnsDefinition("https://avaloniademos.com/xaml", "Dependency2Proj.SubFolder")]  

这两行将程序集的两个命名空间:Dependency2ProjDependency2Proj.SubFolder合并到同一个URL“https://avaloniademos.com/xaml”中。如上所述,该URL是否存在或计算机是否在线都无关紧要。如果您的URL具有与包含此功能的项目相对应的某些含义,那就太好了。

现在,我们引用GreenButtonGrayButtonXAML前缀dep2通过引用该URL来定义:

xmlns:dep2="https://avaloniademos.com/xaml"  

WPF相比,有一个重要的额外Avalonia功能。在WPF中,只能通过URL引用与想要引用它的XAML文件不在同一个项目中的功能,而在Avalonia中,没有这样的限制——例如,如果我们在同一个Dependency2Proj项目中有一个XAML文件,我们仍然可以放一行

xmlns:dep2="https://avaloniademos.com/xaml" 

... 在它的顶部元素,并引用在同一个项目中由dep2: 前缀定义的GreenButtonGrayButton

XAML中访问C#复合属性

我们在上面已经提到,C#内置属性可以作为相应元素的XML属性来访问,例如:

<my_namespace:MyClass Prop1="Hello World"
                      Prop2="123"/>

Prop1并且Prop2是在MyClass类上定义的简单C#属性,可以在XAML文件的my_namespace前缀引用的C#命名空间中找到。Prop1可能是string类型,而Prop2可以是任何数字类型或stringXAML将自动将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:Namex: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资源,我们将在专门针对它们的部分中对其进行解释。

标记扩展的简要介绍

标记扩展是一些可以显着简化XAMLC#类。它们用于设置一些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:KeyXAML资源可以被StaticResourceDynamicResource用来引用特定的资源。

然后,我们使用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属性绑定到Value1Value2以显示这些值。同样为了证明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

然后我们将ItemsControlItems属性绑定到列表并使用ItemsControlItemTemplate来显示每个单独的项目的Value1Value2  

<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,但如果它是其他一些众所周知的类型,例如intAvalonia 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——资产通常是二进制图像(例如pngjpg)文件。在本节中,我们将展示如何从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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值