跨平台WPF框架Avalonia教程 七
数据绑定
Avalonia使用数据绑定将数据从应用程序对象传递到UI控件,根据用户输入更改应用程序对象中的数据,并在响应用户命令时对应用程序对象进行操作。
在这种安排中,控件是绑定目标,而对象是数据源。
Avalonia运行数据绑定系统来完成大部分上述活动,而无需您添加大量额外的代码,只需在XAML中声明简单的映射即可。
数据绑定映射是在Avalonia控件的属性和应用程序对象的属性之间使用XML定义的。一般来说,语法如下:
<SomeControl Attribute="{Binding PropertyName}" />
这些映射可以是双向的:即绑定应用程序对象的属性的更改将反映在控件中,而控件中的更改(无论是由用户引起的还是其他原因)都将应用于底层对象。双向绑定的一个示例是将文本输入绑定到对象的字符串属性。XML可能如下所示:
<TextBox Text="{Binding FirstName}" />
如果用户在文本框中编辑文本,则底层对象的FirstName
属性将自动更新。另一方面,如果底层对象的FirstName
属性更改,则文本框中可见的文本将更新。
绑定可以是单向的:即绑定应用程序对象的属性的更改将反映在控件中,但用户不能更改控件的值。这样的一个例子是文本块控件,它是只读的。
<TextBlock Text="{Binding StatusMessage}" />
绑定与MVVM(Model-View-ViewModel)架构模式一起使用,这是使用Avalonia UI的主要方式之一。
信息
有关如何在Avalonia中使用MVVM模式的更多信息,请参阅概念页面。
信息
有关MVVM模式在 Microsoft 中的起源和发展的背景信息,请参阅 Microsoft Patterns and Practices。
数据上下文
当Avalonia执行数据绑定时,它必须定位要绑定的应用程序对象。这个位置由数据上下文表示。
Avalonia中的每个控件都有一个名为DataContext
的属性,包括内置控件、用户控件和窗口。
在绑定时,Avalonia会从逻辑控件树中进行分层搜索,从定义绑定的控件开始,直到找到要使用的数据上下文。
这意味着在窗口中定义的控件可以使用窗口的数据上下文;或者(如上所示),在窗口中的控件中定义的控件可以使用窗口的数据上下文。
信息
有关Avalonia中的控件树以及如何在运行时查看它们的信息,请参阅这里.
示例
如果您使用 Avalonia MVVM Application 模板创建一个新项目,您可以看到窗口的数据上下文是如何设置的。找到并打开App.axaml.cs文件查看代码:
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow
{
DataContext = new MainWindowViewModel(),
};
}
base.OnFrameworkInitializationCompleted();
}
您可以在MainWindowViewModel.cs文件中追踪设置到窗口数据上下文的对象。代码如下:
public class MainWindowViewModel : ViewModelBase
{
public string Greeting => "Welcome to Avalonia!";
}
在主窗口文件MainWindow.axaml中,您可以看到窗口内容区由一个文本块组成,该文本块的文本属性被绑定到Greeting
属性。
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:AvaloniaMVVMApplication2.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="AvaloniaMVVMApplication2.Views.MainWindow"
Icon="/Assets/avalonia-logo.ico"
Title="AvaloniaMVVMApplication2">
<Design.DataContext>
<vm:MainWindowViewModel/>
</Design.DataContext>
<TextBlock Text="{Binding Greeting}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Window>
项目运行时,数据绑定器从文本块开始向上搜索逻辑控件树,找到在主窗口级别设置的数据上下文。因此,绑定的文本显示为:
设计时数据上下文
您可能已经注意到,在首次编译此项目后,预览窗格也显示了问候语。
这是因为Avalonia还可以为控件设置设计时数据上下文。这对您非常有用,因为这意味着预览窗格在您调整布局和样式时可以显示一些真实的数据。
您可以在XAML中看到设计时数据上下文的设置:
<Design.DataContext>
<vm:MainWindowViewModel/>
</Design.DataContext>
提示
有关如何使用设计时数据上下文的更详细指南,请参阅这里.
信息
进一步讨论数据绑定需要您对MVVM(Model-View-ViewModel)编程模式有所了解。如果您想了解MVVM模式的基本概念,请参阅这里.
数据绑定语法
在Avalonia中,您可以使用XAML或代码定义数据绑定。要在XAML中定义数据绑定,您可以使用数据绑定标记扩展,其语法如下所述。
数据绑定标记扩展
数据绑定标记扩展使用关键字Binding
,结合定义数据源和其他选项的参数。标记扩展的格式如下:
<SomeControl SomeProperty="{Binding Path, Mode=ModeValue, StringFormat=Pattern}" />
当有多个选项参数时,它们之间用逗号分隔。
参数 | 描述 |
---|---|
Path | 数据绑定路径。 |
Mode | 绑定模式之一,见下文。 |
StringFormat | 显示值的格式化模式。 |
ElementName | 可以通过在路径中使用#来缩短。 |
Converter | 用于转换值的函数。 |
RelativeSource | 在视觉树中工作,而不是逻辑树。 |
数据绑定路径
第一个参数通常是数据源的路径。数据源是Avalonia在执行数据绑定时找到的数据上下文中的对象。
在这里,不需要使用参数名Path
。因此,以下绑定是等效的:
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Path=Name}"/>
绑定路径可以是单个属性,也可以是属性链。例如,如果数据源有一个Student
属性,该属性返回的对象具有一个Name
属性,您可以使用以下语法绑定到学生姓名:
<TextBlock Text="{Binding Student.Name}"/>
如果数据源有一个数组或列表(带有索引器),则可以将索引添加到绑定路径中,如下所示:
<TextBlock Text="{Binding Students[0].Name}"/>
空的数据绑定路径
您可以指定没有路径的数据绑定。这将绑定到控件本身的数据上下文(绑定定义的位置)。以下两种语法是等效的:
<TextBlock Text="{Binding}"/>
<TextBlock Text="{Binding .}"/>
数据绑定模式
您可以通过指定数据绑定模式来更改数据在数据绑定中的移动方式。
例如:
<TextBlock Text="{Binding Name, Mode=OneTime}">
可用的绑定模式有:
模式 | 描述 |
---|---|
OneWay | 数据源的更改会自动传播到绑定目标 |
TwoWay | 数据源的更改会自动传播到绑定目标,反之亦然。 |
OneTime | 从数据源传播的值在初始化时传播到绑定目标,但后续更改将被忽略 |
OneWayToSource | 绑定目标的更改会传播到数据源,但反之不会。 |
Default | 绑定模式基于代码中属性的默认模式。见下文。 |
如果未指定模式,则将始终使用默认模式。对于不因用户交互而改变值的控件属性,默认模式通常是OneWay
。对于因用户输入而改变值的控件属性,默认模式通常是TwoWay
。
例如,TextBlock.Text
属性的默认模式是OneWay
,而TextBox.Text
属性的默认模式是TwoWay
。
转换绑定的值
有多种方法可以将数据绑定提供的值转换为目标控件中实际显示的值。
字符串格式化
您可以对绑定应用模式来定义要如何显示该值。这里有几种语法:
模式索引从零开始,必须始终位于花括号内。当花括号位于模式的开头时,即使也在单引号内,它们也必须被转义。这可以通过在模式前面添加额外的一对花括号或使用反斜杠转义花括号来实现。
这意味着,当您的模式以零开头时,您可以使用一对花括号来转义模式,然后在第二对花括号内提供模式本身。例如:
<TextBlock Text="{Binding FloatProperty, StringFormat={}{0:0.0}}" />
或者,您可以使用反斜杠转义模式所需的花括号。例如:
<TextBlock Text="{Binding FloatValue, StringFormat=\{0:0.0\}}" />
但是,如果您的模式不以零开头,则不需要转义。此外,如果模式中有空格,则必须用单引号括起来。例如:
<TextBlock Text="{Binding Animals.Count, StringFormat='I have {0} animals.'}" />
请注意,这意味着如果模式以您绑定的值开头,则需要转义。例如:
<TextBlock Text="{Binding Animals.Count,
StringFormat='{}{0} animals live in the farm.'}" />
信息
当StringFormat
参数存在时,绑定的值实际上将使用StringFormatValueConverter
进行转换(这是内置转换器之一——见下文)。
内置转换器
Avalonia 拥有许多内置的数据绑定转换器,包括:
- 字符串格式化转换器
- 空值测试转换器
- 布尔操作转换器
信息
有关Avalonia内置数据绑定转换器的完整信息,请参阅此处的参考文档。
自定义转换器
如果内置转换器都不满足您的要求,您可以实现自定义转换器。
信息
自定义转换器的一个示例是绑定图像文件。有关如何为图像创建自定义转换器的指南,请参阅此处。
编译绑定
在XAML中定义的绑定使用反射来查找和访问您的ViewModel
中请求的属性。在Avalonia中,您还可以使用编译绑定,它有一些好处:
- 如果您使用编译绑定,并且找不到要绑定的属性,您将获得一个编译时错误。因此,您将获得更好的调试体验。
- 已知反射较慢(请参阅此文章)。因此,使用编译绑定可以提高应用程序的性能。
启用和禁用编译绑定
信息
根据创建 Avalonia 项目时使用的模板,默认情况下可能启用也可能不启用编译绑定。您可以在项目文件中查看。
全局启用和禁用
如果您希望应用程序默认情况下全局使用编译绑定,可以将以下内容添加到您的项目文件中:
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
您仍然需要为您想要绑定的对象提供x:DataType
,但是您不需要为每个UserControl
或Window
设置x:CompileBindings="[True|False]"
。
在每个UserControl或Window中启用和禁用
要启用编译绑定,您需要首先定义要绑定的对象的DataType
。在DataTemplates中,有一个DataType
属性,对于所有其他元素,您可以通过x:DataType
来设置它。最可能在根节点中设置x:DataType
,例如在Window
或UserControl
中。您还可以直接在Binding
中指定DataType
。
现在,您可以通过设置x:CompileBindings="[True|False]"
来启用或禁用编译绑定。所有子节点都将继承此属性,因此您可以在根节点中启用它,并在需要时禁用特定子节点。
<!-- 设置DataType并启用编译绑定 -->
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:MyApp.ViewModels"
x:DataType="vm:MyViewModel"
x:CompileBindings="True">
<StackPanel>
<TextBlock Text="Last name:" />
<TextBox Text="{Binding LastName}" />
<TextBlock Text="Given name:" />
<TextBox Text="{Binding GivenName}" />
<TextBlock Text="E-Mail:" />
<!-- 在Binding标记中设置DataType -->
<TextBox Text="{Binding MailAddress, DataType={x:Type vm:MyViewModel}}" />
<Button Content="Send an E-Mail"
Command="{Binding SendEmailCommand}" />
</StackPanel>
</UserControl>
CompiledBinding标记
如果您不希望为所有子节点启用编译绑定,还可以使用CompiledBinding
标记。您仍然需要定义DataType
,但可以省略x:CompileBindings="True"
。
<!-- 设置DataType -->
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:MyApp.ViewModels"
x:DataType="vm:MyViewModel">
<StackPanel>
<TextBlock Text="Last name:" />
<!-- 使用CompiledBinding标记进行绑定 -->
<TextBox Text="{CompiledBinding LastName}" />
<TextBlock Text="Given name:" />
<TextBox Text="{CompiledBinding GivenName}" />
<TextBlock Text="E-Mail:" />
<TextBox Text="{CompiledBinding MailAddress}" />
<!-- 这个命令将使用ReflectionBinding,因为它是默认值 -->
<Button Content="Send an E-Mail"
Command="{Binding SendEmailCommand}" />
</StackPanel>
</UserControl>
ReflectionBinding标记
如果您已在根节点启用了编译绑定(通过x:CompileBindings="True"
),并且您要么不想在某个位置使用编译绑定,要么遇到了已知的限制,则可以使用ReflectionBinding
标记。
<!-- 设置DataType -->
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:MyApp.ViewModels"
x:DataType="vm:MyViewModel"
x:CompileBindings="True">
<StackPanel>
<TextBlock Text="Last name:" />
<TextBox Text="{Binding LastName}" />
<TextBlock Text="Given name:" />
<TextBox Text="{Binding GivenName}" />
<TextBlock Text="E-Mail:" />
<TextBox Text="{Binding MailAddress}" />
<!-- 我们使用ReflectionBinding -->
<Button Content="Send an E-Mail"
Command="{ReflectionBinding SendEmailCommand}" />
</StackPanel>
</UserControl>
类型转换
在某些情况下,绑定表达式的目标类型无法自动计算。在这种情况下,您必须在绑定表达式中提供一个明确的类型转换。
<ItemsRepeater ItemsSource="{Binding MyItems}">
<ItemsRepeater.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding DisplayName}"/>
<Grid>
<Button Command="{Binding $parent[ItemsRepeater].((vm:MyUserControlViewModel)DataContext).DoItCommand}"
CommandParameter="{Binding ItemId}"/>
</Grid>
</StackPanel>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
在这种情况下,按钮命令将绑定到“父级”DataContext
,而不是绑定到项目的DataContext
。单个项目将使用绑定到项目的DataContext
的CommandParameter
进行标识。因此,您必须通过强制转换表达式((vm:MyUserControlViewModel)DataContext)
来指定“父级”DataContext
的类型。
数据模板
数据模板(Data Templates)在Avalonia中为您提供了定义数据的可视化表示的强大方法。它们允许您指定数据的展示方式和格式,从而创建动态和可定制的用户界面。本文档将介绍Avalonia中的数据模板概念,并演示如何在应用程序中有效使用它们。
什么是数据模板?
数据模板本质上是一种可重用的定义,用于指定如何展示特定类型的数据。它定义了数据在用户界面中显示时的可视化结构和外观。在Avalonia中,数据模板通常与列表控件(如ListBox
或ItemsControl
)相关联,负责渲染该控件中的各个数据项。
将数据模板应用于ListBox
要将数据模板应用于ListBox
,通常使用控件的ItemTemplate
属性。
例如,如果您有一个ListBox
,应使用定义的数据模板来显示Item
对象的集合,可以像这样设置ItemTemplate
属性:
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<Image Source="{Binding ImageSource}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
在这个例子中,数据模板定义了一个使用StackPanel
容器的可视化布局。在StackPanel
中,我们有一个绑定到项的Name
属性的TextBlock
,以及一个绑定到ImageSource
属性的Image
控件。
自定义数据模板
数据模板可以根据特定情况进行自定义和调整。您可以包含额外的可视化元素,应用样式,甚至在数据模板中定义嵌套模板。通过利用数据绑定表达式和转换器,您可以根据数据属性动态填充和格式化可视化元素。