假设你设计并实现了一个漂亮的 UI,其中填充了占位符图像、“lorem ipsum”样板文本以及尚未执行任何功能的控件。 接下来,需要将其连接到真实数据,并将其从设计原型转换为生活应用。
本教程将介绍如何将样板代码替换为数据绑定,并在 UI 和数据之间创建其他直接连接。 你还将了解如何设置数据的格式或转换数据以供显示,并使 UI 和数据保持同步。完成本教程后,你将能够改进 XAML 和 C# 代码的简单性和组织性,从而更轻松地维护和扩展。
你将从 PhotoLab 示例的简化版本开始。 此初学者版本包括完整的数据层和基本的 XAML 页面布局,并排除了许多功能,使代码更易于浏览。 本教程不会构建到完整的应用,因此请务必查看最终版本以查看自定义动画和自适应布局等功能。 可以在 Windows-appsample-photo-lab 存储库的根文件夹中找到最终版本。
PhotoLab 示例应用有两个页面。 主页显示照片库视图以及有关每个图像文件的一些信息。
详细信息页面 选中后显示单个照片。 浮出控件编辑菜单允许更改、重命名和保存照片。
先决条件
- Visual Studio 2019 或更高版本: 下载 Visual Studio (社区版是免费的)。
- Windows SDK (10.0.17763.0 或更高版本): 下载最新的 Windows SDK(免费)
- Windows 10 版本 1809 或更高版本
第 0 部分:从 GitHub 获取入门代码
在本教程中,你将从 PhotoLab 示例的简化版本开始。
转到示例的 GitHub 页面: https://github.com/Microsoft/Windows-appsample-photo-lab。
接下来,你需要克隆或下载示例。 选择“克隆或下载”按钮。 此时会显示一个子菜单。
如果不熟悉 GitHub:
a。 选择 “下载 ZIP ”并在本地保存文件。 这会下载一个 .zip 文件,其中包含所需的所有项目文件。
b. 提取文件。 使用文件资源管理器浏览到刚刚下载的 .zip 文件,右键单击该文件,然后选择 “全部提取...”。
选项c. 浏览到示例的本地副本,并转到
Windows-appsample-photo-lab-master\xaml-basics-starting-points\data-binding
目录。如果你熟悉 GitHub:
a。 在本地克隆存储库的主分支。
b. 浏览到
Windows-appsample-photo-lab\xaml-basics-starting-points\data-binding
目录。双击
Photolab.sln
以在 Visual Studio 中打开解决方案。
第 1 部分:替换占位符
在这里,在数据模板 XAML 中创建一次性绑定以显示真实图像和图像元数据,而不是占位符内容。
一次性绑定适用于只读、未更改的数据,这意味着它们高性能且易于创建,使你可以在控件中GridView
ListView
显示大型数据集。
将占位符替换为一次性绑定
打开
xaml-basics-starting-points\data-binding
文件夹并在 Visual Studio 中启动PhotoLab.sln
文件。确保 解决方案平台 设置为 x86 或 x64,而不是 Arm,然后运行应用。 在添加绑定之前,这将展示带有 UI 占位符的应用状态。
打开 MainPage.xaml 并搜索名为
DataTemplate
的 。 你将更新此模板以使用数据绑定。之前:
<DataTemplate x:Key="ImageGridView_DefaultItemTemplate">
x:Key
值被ImageGridView
用于选择此模板来显示数据对象。向模板添加值
x:DataType
。之后:
<DataTemplate x:Key="ImageGridView_DefaultItemTemplate" x:DataType="local:ImageFileInfo">
x:DataType
指示这是哪个类型的模板。 在这种情况下,它是ImageFileInfo
类的一种模板(其中local:
表示本地命名空间,如文件顶部附近的 xmlns 声明所定义)。在数据模板中使用
x:DataType
表达式时,需要x:Bind
,如下所述。在 中
DataTemplate
,找到Image
命名ItemImage
的元素并替换其Source
值,如下所示。之前:
<Image x:Name="ItemImage" Source="/Assets/StoreLogo.png" Stretch="Uniform" />
之后:
<Image x:Name="ItemImage" Source="{x:Bind ImageSource}" Stretch="Uniform" />
x:Name
标识 XAML 元素,以便可以在 XAML 和后台代码中的其他位置引用它。x:Bind
表达式通过从 数据对象 属性获取值来向 UI 属性提供值。 模板中指示的属性是与x:DataType
所设置的对象相关的属性。 因此,在这种情况下,数据源是ImageFileInfo.ImageSource
属性。注释
该值
x:Bind
还允许编辑器了解数据类型,因此可以使用 IntelliSense,而不是在表达式中x:Bind
键入属性名称。 在刚刚粘贴的代码上尝试:将光标放在x:Bind
后面,然后按空格键查看可以绑定的属性列表。以相同的方式替换其他 UI 控件的值。 请试用 IntelliSense,而不是复制/粘贴,来执行这一操作!
之前:
<TextBlock Text="Placeholder" ... /> <StackPanel ... > <TextBlock Text="PNG file" ... /> <TextBlock Text="50 x 50" ... /> </StackPanel> <muxc:RatingControl Value="3" ... />
之后:
<TextBlock Text="{x:Bind ImageTitle}" ... /> <StackPanel ... > <TextBlock Text="{x:Bind ImageFileType}" ... /> <TextBlock Text="{x:Bind ImageDimensions}" ... /> </StackPanel> <muxc:RatingControl Value="{x:Bind ImageRating}" ... />
运行应用程序以查看它目前的样子。 没有更多占位符! 我们开了个好头。
注释
若要进一步试验,请尝试向数据模板添加新的 TextBlock,并使用 x:Bind IntelliSense 技巧查找要显示的属性。
第 2 部分:使用绑定将图库用户界面连接到图像
在这里,你将在页面 XAML 中创建单次绑定,将画廊视图连接到图像集合,以替换代码隐藏中执行此操作的现有过程代码。 你还将创建一个 “删除”按钮,以查看从相册中删除图像时库视图的变化。 同时,你将了解如何将事件绑定到事件处理程序,以便比传统事件处理程序提供的灵活性更高。
所有到目前为止涵盖的绑定都位于数据模板内,并引用由x:DataType
值指示的类的属性。 页面中的其余 XAML 呢?
x:Bind
数据模板外部的表达式始终绑定到页面本身。 这意味着您可以引用放入代码隐藏文件或在 XAML 中声明的任何内容,包括页面上其他 UI 控件的自定义属性和属性(只要它们具有 x:Name
值)。
在 PhotoLab 示例中,此类绑定的一个用途是将主 GridView
控件直接连接到图像集合中,而不是在后台代码中执行这一操作。 稍后,你将看到其他示例。
将主 GridView 控件绑定到图像集合
在MainPage.xaml.cs中,找到
GetItemsAsync
该方法并删除设置ItemsSource
的代码。之前:
ImageGridView.ItemsSource = Images;
之后:
// Replaced with XAML binding: // ImageGridView.ItemsSource = Images;
在 MainPage.xaml 中,找到名为
GridView
的ImageGridView
并添加ItemsSource
属性。 对于值,请使用x:Bind
表达式来引用代码隐藏中实现的Images
属性。之前:
<GridView x:Name="ImageGridView"
之后:
<GridView x:Name="ImageGridView" ItemsSource="{x:Bind Images}"
该
Images
属性的类型为ObservableCollection<ImageFileInfo>
,因此在GridView
中显示的各个项类型为ImageFileInfo
。 这与第 1 部分中所述的值匹配x:DataType
。
到目前为止,我们查看的所有绑定都是一次性只读绑定,这是纯 x:Bind
表达式的默认行为。 数据仅在初始化时加载,这使得高性能绑定非常适用于支持大型数据集的多个复杂视图。
即使是刚添加的 ItemsSource
绑定也是对不变属性值的一次性只读绑定,但这里有一个重要的区别需要说明。 该属性的未更改值是集合的 Images
单个特定实例,初始化一次,如下所示。
private ObservableCollection<ImageFileInfo> Images { get; }
= new ObservableCollection<ImageFileInfo>();
若要测试这一点,我们将暂时添加一个用于删除当前所选图像的按钮。 此按钮不在最终版本中,因为选择图像会将你带到详细信息页面。 但是,在最终的 PhotoLab 示例中,ObservableCollection<T>
的作用仍然很重要,因为 XAML 在页面构造函数中通过调用 InitializeComponent
方法进行了初始化,但 Images
集合稍后会在 GetItemsAsync
方法中进行填充。
添加删除按钮
在 MainPage.xaml 中,找到名为
CommandBar
的 MainCommandBar,并在缩放按钮之前添加新按钮。 (缩放控件尚未起作用。你将在教程的下一部分中连接这些控件。)<AppBarButton Icon="Delete" Label="Delete selected image" Click="{x:Bind DeleteSelectedImage}" />
如果已经熟悉 XAML,此值
Click
可能看起来异常。 在以前版本的 XAML 中,必须将其设置为具有特定事件处理程序签名的方法,通常包括事件发送方的参数和特定于事件的参数对象。 即使需要事件参数,您仍然可以使用此技术,而使用x:Bind
时,也可以连接到其他方法。 例如,如果你不需要事件数据,可以连接到没有参数的方法,就像我们在这里所做的那样。在MainPage.xaml.cs中添加
DeleteSelectedImage
方法。private void DeleteSelectedImage() => Images.Remove(ImageGridView.SelectedItem as ImageFileInfo);
此方法只是从
Images
集合中删除所选图像。
现在运行应用并使用按钮删除几个图像。 如你所看到的,UI 会自动更新,这要归功于数据绑定和 ObservableCollection<T>
类型。
注释
此代码仅从正在运行的应用中的Images
集合中删除ImageFileInfo
实例。 它不会从计算机中删除映像文件。
第 3 部分:设置缩放滑块
在本部分中,你将从数据模板中的控件到模板外部的缩放滑块创建单向绑定。 你还将了解到,数据绑定可以用于许多控件属性,而不仅仅是最明显的那些,如 TextBlock.Text
和 Image.Source
。
将图像数据模板绑定到缩放滑块
找到名为
DataTemplate
的ImageGridView_DefaultItemTemplate
,并替换模板顶部**Height**
控件的Width
和Grid
值。之前
<DataTemplate x:Key="ImageGridView_DefaultItemTemplate" x:DataType="local:ImageFileInfo"> <Grid Height="200" Width="200" Margin="{StaticResource LargeItemMargin}">
在 后
<DataTemplate x:Key="ImageGridView_DefaultItemTemplate" x:DataType="local:ImageFileInfo"> <Grid Height="{Binding Value, ElementName=ZoomSlider}" Width="{Binding Value, ElementName=ZoomSlider}" Margin="{StaticResource LargeItemMargin}">
你是否注意到这些是 Binding
表达式,而不是 x:Bind
表达式? 这是执行数据绑定的旧方法,它大多已过时。
x:Bind
几乎具备 Binding
的所有功能,甚至更多。 但是,在数据模板中使用 x:Bind
时,它会绑定到值中声明的类型 x:DataType
。 那么,如何在模板中将内容绑定到页面 XAML 或后台代码中的内容? 必须使用旧式 Binding
表达式。
Binding
表达式无法识别值 x:DataType
,但这些 Binding
表达式具有 ElementName
几乎相同的值。 这些属性告诉绑定引擎,绑定值 是与页面上指定元素的 Value
属性绑定,即具有该 x:Name
值的元素。 如果要在后台代码中绑定到一个属性,其形式可能类似于 {Binding MyCodeBehindProperty, ElementName=page}
,其中 page
引用 XAML 中 x:Name
元素中设置的 Page
值。
注释
默认情况下,Binding
表达式是单方式,这意味着当绑定属性值更改时,它们将自动更新 UI。
相比之下,x:Bind
的默认值是一次性,这意味着对绑定属性的任何更改都会被忽略。 这是默认值,因为它是性能最高的选项,大多数绑定是静态只读数据。
此处的教训是,如果将 x:Bind
用于可以更改其值的属性,请务必添加 Mode=OneWay
或 Mode=TwoWay
。 在下一部分中,你将看到此示例。
运行应用并使用滑块更改图像模板尺寸。 正如你所看到的,效果非常强大,不需要大量的代码。
注释
如果想挑战一下,可以尝试将其他 UI 属性绑定到缩放滑块 Value
属性,或者绑定到在缩放滑块之后添加的其他滑块。 例如,可以将 FontSize
的 TitleTextBlock
属性绑定到默认值 为 24的新滑块。 请务必设置合理的最小值和最大值。
第 4 部分:改进缩放体验
在本部分中,你将向后台代码添加自定义 ItemSize
属性,并从图像模板创建到新属性的单向绑定。 缩放滑块和其他因素(如“ItemSize
开关和窗口大小)将更新 值,从而带来更精细的体验。
与内置控件属性不同,自定义属性不会自动更新 UI,即使使用单向绑定和双向绑定也是如此。 它们适用于一时间 绑定,但如果希望属性更改实际显示在 UI 中,则需要执行一些工作。
创建 ItemSize 属性,以便更新 UI
在MainPage.xaml.cs中,更改类的
MainPage
签名,使其实现INotifyPropertyChanged
接口。之前:
public sealed partial class MainPage : Page
之后:
public sealed partial class MainPage : Page, INotifyPropertyChanged
这会通知绑定系统,
MainPage
有一个即将添加的PropertyChanged
事件,绑定可以侦听这个事件以更新 UI。将
PropertyChanged
事件添加到MainPage
类。public event PropertyChangedEventHandler PropertyChanged;
该事件提供了
INotifyPropertyChanged
接口所需的完整实现。 但是,要使其产生任何效果,必须在自定义属性中显式引发该事件。添加
ItemSize
属性并在其 setter 中引发PropertyChanged
事件。public double ItemSize { get => _itemSize; set { if (_itemSize != value) { _itemSize = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ItemSize))); } } } private double _itemSize;
该
ItemSize
属性公开私有_itemSize
字段的值。 使用这样的后盾字段,属性可以在引发可能不必要的PropertyChanged
事件之前检查新值是否与旧值相同。事件本身是由
Invoke
方法触发的。 问号检查PropertyChanged
事件是否为 null,即是否已添加任何事件处理程序。 每一个单向或双向绑定都会在后台添加事件处理程序,但如果没有人正在侦听,这里不会再发生任何事件。 但是,如果PropertyChanged
不是 null,则Invoke
使用对事件源的引用(由关键字表示this
的页面本身)和指示属性名称 的事件参数 对象调用。 使用此信息,ItemSize
属性的任何单向或双向绑定将被通知任何更改,以便更新关联的 UI。在 MainPage.xaml 中,找到名为
DataTemplate
的ImageGridView_DefaultItemTemplate
,并替换模板顶部Height
控件的Width
和Grid
值。 如果在本教程的上一部分中执行了控件到控件绑定,则唯一需要更改的是将Value
替换为ItemSize
,并将ZoomSlider
替换为page
。请确保对Height
和Width
也执行此操作!之前
<DataTemplate x:Key="ImageGridView_DefaultItemTemplate" x:DataType="local:ImageFileInfo"> <Grid Height="{Binding Value, ElementName=ZoomSlider}" Width="{Binding Value, ElementName=ZoomSlider}" Margin="{StaticResource LargeItemMargin}">
在 后
<DataTemplate x:Key="ImageGridView_DefaultItemTemplate" x:DataType="local:ImageFileInfo"> <Grid Height="{Binding ItemSize, ElementName=page}" Width="{Binding ItemSize, ElementName=page}" Margin="{StaticResource LargeItemMargin}">
现在 UI 可以响应 ItemSize
的修改,因此需要进行一些实际的改变。 如前所述, ItemSize
该值是从各种 UI 控件的当前状态计算的,但每当这些控件更改状态时,都必须执行计算。 为此,你将使用事件绑定,以便某些用户界面的更改会调用一个帮助方法来更新 ItemSize
。
更新 ItemSize 属性值
将
DetermineItemSize
方法添加到MainPage.xaml.cs。private void DetermineItemSize() { if (FitScreenToggle != null && FitScreenToggle.IsOn == true && ImageGridView != null && ZoomSlider != null) { // The 'margins' value represents the total of the margins around the // image in the grid item. 8 from the ItemTemplate root grid + 8 from // the ItemContainerStyle * (Right + Left). If those values change, // this value needs to be updated to match. int margins = (int)this.Resources["LargeItemMarginValue"] * 4; double gridWidth = ImageGridView.ActualWidth - (int)this.Resources["DefaultWindowSidePaddingValue"]; double ItemWidth = ZoomSlider.Value + margins; // We need at least 1 column. int columns = (int)Math.Max(gridWidth / ItemWidth, 1); // Adjust the available grid width to account for margins around each item. double adjustedGridWidth = gridWidth - (columns * margins); ItemSize = (adjustedGridWidth / columns); } else { ItemSize = ZoomSlider.Value; } }
在 MainPage.xaml 中,导航到文件开头,并在
Page
元素上添加SizeChanged
事件绑定。之前:
<Page x:Name="page"
之后:
<Page x:Name="page" SizeChanged="{x:Bind DetermineItemSize}"
查找名为
Slider
的ZoomSlider
(在Page.Resources
部分中),并添加ValueChanged
事件绑定。之前:
<Slider x:Name="ZoomSlider"
之后:
<Slider x:Name="ZoomSlider" ValueChanged="{x:Bind DetermineItemSize}"
找到名为
ToggleSwitch
的FitScreenToggle
,并添加Toggled
事件绑定。之前:
<ToggleSwitch x:Name="FitScreenToggle"
之后:
<ToggleSwitch x:Name="FitScreenToggle" Toggled="{x:Bind DetermineItemSize}"
运行应用并使用缩放滑块和 适合屏幕 切换来更改图像模板尺寸。 如你所看到的,最新的更改可实现更精细的缩放/调整大小体验,同时使代码保持井然有序。
注释
为了挑战,尝试在 TextBlock
后添加 ZoomSlider
,并将 Text
属性绑定到 ItemSize
属性。 由于它不在数据模板中,因此可以使用 x:Bind
,而不是 Binding
像在前面的 ItemSize
绑定中一样。
第 5 部分:启用用户编辑
在这里,你将创建双向绑定,使用户能够更新值,包括图像标题、分级和各种视觉效果。
为此,你将更新现有的 DetailPage
,提供单一图像查看器、缩放控件和编辑界面。
首先,然而,你需要附加 DetailPage
,以便用户在图库视图中点击图像时,应用程序能导航到它。
附上DetailPage
在 MainPage.xaml 中,找到名为
GridView
的ImageGridView
。 若要使项可单击,请将IsItemClickEnabled
设置为True
,并且添加ItemClick
事件处理程序。小窍门
如果你手动键入以下更改,而不是复制/粘贴,你将看到一个显示“<新事件处理程序>”的 IntelliSense 弹出提示。 按下 Tab 键时,它会使用默认的方法处理程序名称来填充值,并自动生成下一步中要显示的方法的存根。 然后,可以按 F12 导航到后台代码中的方法。
之前:
<GridView x:Name="ImageGridView">
之后:
<GridView x:Name="ImageGridView" IsItemClickEnabled="True" ItemClick="ImageGridView_ItemClick">
注释
我们在此处使用常规事件处理程序,而不是 x:Bind 表达式。 这是因为我们需要查看事件数据,如下所示。
在MainPage.xaml.cs中,添加事件处理程序(如果在最后一步中使用了那个提示,请完成其设置)。
private void ImageGridView_ItemClick(object sender, ItemClickEventArgs e) { this.Frame.Navigate(typeof(DetailPage), e.ClickedItem); }
此方法直接导航到详细信息页,传入单击的项,这是
ImageFileInfo
用于初始化页面的 对象。 无需在本教程中实现该方法,但可以看看它的作用。(可选)删除或注释掉在之前的播放点中添加的用于当前所选图像的任何控件。 保留它们不会有什么问题,但现在不进入详细信息页面就很难选择图像。
现在,你已连接这两个页面,请运行应用并仔细查看。 除了编辑窗格上的控件外,其他一切正常。当您尝试更改值时,这些控件没有响应。
如你所看到的,标题文本框会显示标题,并允许你键入更改。 必须将焦点更改为另一个控件才能提交更改,但屏幕左上角的标题尚未更新。
所有控件都已使用第 1 部分中介绍的纯 x:Bind
表达式进行绑定。 如果回想一下,这意味着它们都是一次性绑定,这解释了为何未注册值更改。 若要解决此问题,我们只需将它们转换为双向绑定。
使编辑控件成为交互式控件
在 DetailPage.xaml 中,找到名为 TitleTextBlock
的 及其后 RatingControl 控件,并更新其表达式以包括 Mode=TwoWay 。之前:
<TextBlock x:Name="TitleTextBlock" Text="{x:Bind item.ImageTitle}" ... > <muxc:RatingControl Value="{x:Bind item.ImageRating}" ... >
之后:
<TextBlock x:Name="TitleTextBlock" Text="{x:Bind item.ImageTitle, Mode=TwoWay}" ... > <muxc:RatingControl Value="{x:Bind item.ImageRating, Mode=TwoWay}" ... >
对评分控件后面的所有效果滑块进行相同的操作。
<Slider Header="Exposure" ... Value="{x:Bind item.Exposure, Mode=TwoWay}" ... <Slider Header="Temperature" ... Value="{x:Bind item.Temperature, Mode=TwoWay}" ... <Slider Header="Tint" ... Value="{x:Bind item.Tint, Mode=TwoWay}" ... <Slider Header="Contrast" ... Value="{x:Bind item.Contrast, Mode=TwoWay}" ... <Slider Header="Saturation" ... Value="{x:Bind item.Saturation, Mode=TwoWay}" ... <Slider Header="Blur" ... Value="{x:Bind item.Blur, Mode=TwoWay}" ...
正如预期的那样,双向模式意味着每当任一方发生更改时,数据都向两个方向移动。
与前面介绍的单向绑定一样,这些双向绑定现在会在绑定属性发生更改时更新 UI,这要归功于 INotifyPropertyChanged
类中的 ImageFileInfo
实现。 但是,使用双向绑定,每当用户与控件交互时,值也会从 UI 移动到绑定属性。 XAML 端不需要更多内容。
运行应用并尝试编辑控件。 正如你所看到的,当你进行更改时,它现在会影响图像值,当你导航回主页时,这些更改将保持不变。
第 6 部分:通过函数绑定格式化值
最后一个问题仍然存在。 移动效果滑块时,它们旁边的标签仍不会更改。
在本教程的最后一部分中,我们将添加用于格式化滑块值以显示的绑定。
绑定效果滑块的标签并格式化数值以供显示
在
TextBlock
滑块后找到Exposure
,并将Text
值替换为此处显示的绑定表达式。之前:
<Slider Header="Exposure" ... /> <TextBlock ... Text="0.00" />
之后:
<Slider Header="Exposure" ... /> <TextBlock ... Text="{x:Bind item.Exposure.ToString('N', culture), Mode=OneWay}" />
这称为函数绑定,因为你正在绑定到方法的返回值。 如果您处于数据模板中,则必须通过页面的代码隐藏或
x:DataType
类型来访问该方法。 在这种情况下,该方法是大家熟悉的 .NETToString
方法,需要先通过页面的项属性进行访问,然后通过该项的Exposure
属性进行进一步访问。 (这说明了如何绑定到嵌套在连接链中的方法和属性。)函数绑定是设置显示值格式的理想方法,因为可以将其他绑定源作为方法参数传入,绑定表达式将使用单向模式侦听对这些值的更改。 在此示例中,文化 参数是对代码隐藏中实现的不变字段的引用,但它同样可以是一个用于引发
PropertyChanged
事件处理程序的属性。 在这种情况下,对属性值所做的任何更改都会导致x:Bind
表达式使用新值调用ToString
,然后使用结果更新 UI。对标记其他效果滑块的
TextBlock
执行相同的操作。<Slider Header="Temperature" ... /> <TextBlock ... Text="{x:Bind item.Temperature.ToString('N', culture), Mode=OneWay}" /> <Slider Header="Tint" ... /> <TextBlock ... Text="{x:Bind item.Tint.ToString('N', culture), Mode=OneWay}" /> <Slider Header="Contrast" ... /> <TextBlock ... Text="{x:Bind item.Contrast.ToString('N', culture), Mode=OneWay}" /> <Slider Header="Saturation" ... /> <TextBlock ... Text="{x:Bind item.Saturation.ToString('N', culture), Mode=OneWay}" /> <Slider Header="Blur" ... /> <TextBlock ... Text="{x:Bind item.Blur.ToString('N', culture), Mode=OneWay}" />
现在,运行应用时,所有内容都有效,包括滑块标签。
带有工作标签的效果滑块
结论
本教程让您初步了解数据绑定,并向您展示了一些功能。 结束之前,有一点需要注意:不是所有的内容都可绑定,有时你尝试绑定的值与属性不兼容。 绑定具有很大的灵活性,但它并不在所有情况下都有效。
绑定未解决问题的一个示例是控件没有合适的属性要绑定到,就像详细信息页面缩放功能一样。 此缩放滑块需要与显示图像的 ScrollViewer
滑块进行交互,但 ScrollViewer
只能通过其 ChangeView
方法进行更新。 在这种情况下,我们使用传统的事件处理程序来保持ScrollViewer
和缩放滑块同步;有关详细信息,请参阅DetailPage
中的ZoomSlider_ValueChanged
和MainImageScroll_ViewChanged
方法。
尽管如此,绑定是一种强大且灵活的方法来简化代码,并使 UI 逻辑与数据逻辑分开。 这样可以更轻松地调整此划分的任一端,同时降低在另一端引入 bug 的风险。
UI 和数据分离的一个示例是属性 ImageFileInfo.ImageTitle
。 此属性(和 ImageRating
属性)与 ItemSize
第 4 部分中创建的属性略有不同,因为该值存储在文件元数据(通过 ImageProperties
类型公开)而不是字段中。 此外,如果文件元数据中没有标题,ImageTitle
则将 ImageName
返回的值设置为文件名。
public string ImageTitle
{
get => String.IsNullOrEmpty(ImageProperties.Title) ? ImageName : ImageProperties.Title;
set
{
if (ImageProperties.Title != value)
{
ImageProperties.Title = value;
var ignoreResult = ImageProperties.SavePropertiesAsync();
OnPropertyChanged();
}
}
}
可以看到,setter 更新属性 ImageProperties.Title
,然后调用 SavePropertiesAsync
以将新值写入文件。 (这是一个异步方法,但我们不能在属性中使用 await
关键字,你也不应该这样,因为属性的 getter 和 setter 应该立即完成。因此,你应该调用该方法,并忽略它返回的 Task
对象。)
更进一步
现在你已经完成了这个实验室,你具备了足够的知识来独立解决问题。
如前所述,如果更改详细信息页上的缩放级别,则在向后导航时会自动重置该缩放级别,然后再次选择同一图像。 能否单独确定如何保留和还原每个图像的缩放级别? 祝你好运!
本教程中应包含所需的全部信息,但如果需要更多指导,数据绑定文档只需单击一下即可。 在此处开始: