教程:创建数据绑定

假设你设计并实现了一个漂亮的 UI,其中填充了占位符图像、“lorem ipsum”样板文本以及尚未执行任何功能的控件。 接下来,需要将其连接到真实数据,并将其从设计原型转换为生活应用。

本教程将介绍如何将样板代码替换为数据绑定,并在 UI 和数据之间创建其他直接连接。 你还将了解如何设置数据的格式或转换数据以供显示,并使 UI 和数据保持同步。完成本教程后,你将能够改进 XAML 和 C# 代码的简单性和组织性,从而更轻松地维护和扩展。

你将从 PhotoLab 示例的简化版本开始。 此初学者版本包括完整的数据层和基本的 XAML 页面布局,并排除了许多功能,使代码更易于浏览。 本教程不会构建到完整的应用,因此请务必查看最终版本以查看自定义动画和自适应布局等功能。 可以在 Windows-appsample-photo-lab 存储库的根文件夹中找到最终版本。

PhotoLab 示例应用有两个页面。 主页显示照片库视图以及有关每个图像文件的一些信息。

照相馆主页截图。

详细信息页面 选中后显示单个照片。 浮出控件编辑菜单允许更改、重命名和保存照片。

影像实验室详细信息页的屏幕截图。

先决条件

第 0 部分:从 GitHub 获取入门代码

在本教程中,你将从 PhotoLab 示例的简化版本开始。

  1. 转到示例的 GitHub 页面: https://github.com/Microsoft/Windows-appsample-photo-lab

  2. 接下来,你需要克隆或下载示例。 选择“克隆或下载”按钮。 此时会显示一个子菜单。 PhotoLab 示例 GitHub 页面上的“克隆”或“下载”菜单

    如果不熟悉 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 目录。

  3. 双击 Photolab.sln 以在 Visual Studio 中打开解决方案。

第 1 部分:替换占位符

在这里,在数据模板 XAML 中创建一次性绑定以显示真实图像和图像元数据,而不是占位符内容。

一次性绑定适用于只读、未更改的数据,这意味着它们高性能且易于创建,使你可以在控件中GridViewListView显示大型数据集。

将占位符替换为一次性绑定

  1. 打开 xaml-basics-starting-points\data-binding 文件夹并在 Visual Studio 中启动 PhotoLab.sln 文件。

  2. 确保 解决方案平台 设置为 x86 或 x64,而不是 Arm,然后运行应用。 在添加绑定之前,这将展示带有 UI 占位符的应用状态。

    运行带有占位符图像和文本的应用

  3. 打开 MainPage.xaml 并搜索名为 DataTemplate。 你将更新此模板以使用数据绑定。

    之前:

    <DataTemplate x:Key="ImageGridView_DefaultItemTemplate">
    

    x:Key 值被 ImageGridView 用于选择此模板来显示数据对象。

  4. 向模板添加值 x:DataType

    之后:

    <DataTemplate x:Key="ImageGridView_DefaultItemTemplate"
                  x:DataType="local:ImageFileInfo">
    

    x:DataType 指示这是哪个类型的模板。 在这种情况下,它是 ImageFileInfo 类的一种模板(其中 local: 表示本地命名空间,如文件顶部附近的 xmlns 声明所定义)。

    在数据模板中使用 x:DataType 表达式时,需要 x:Bind,如下所述。

  5. 在 中 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 后面,然后按空格键查看可以绑定的属性列表。

  6. 以相同的方式替换其他 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 技巧查找要显示的属性。

在这里,你将在页面 XAML 中创建单次绑定,将画廊视图连接到图像集合,以替换代码隐藏中执行此操作的现有过程代码。 你还将创建一个 “删除”按钮,以查看从相册中删除图像时库视图的变化。 同时,你将了解如何将事件绑定到事件处理程序,以便比传统事件处理程序提供的灵活性更高。

所有到目前为止涵盖的绑定都位于数据模板内,并引用由x:DataType值指示的类的属性。 页面中的其余 XAML 呢?

x:Bind 数据模板外部的表达式始终绑定到页面本身。 这意味着您可以引用放入代码隐藏文件或在 XAML 中声明的任何内容,包括页面上其他 UI 控件的自定义属性和属性(只要它们具有 x:Name 值)。

在 PhotoLab 示例中,此类绑定的一个用途是将主 GridView 控件直接连接到图像集合中,而不是在后台代码中执行这一操作。 稍后,你将看到其他示例。

将主 GridView 控件绑定到图像集合

  1. 在MainPage.xaml.cs中,找到 GetItemsAsync 该方法并删除设置 ItemsSource的代码。

    之前:

    ImageGridView.ItemsSource = Images;
    

    之后:

    // Replaced with XAML binding:
    // ImageGridView.ItemsSource = Images;
    
  2. 在 MainPage.xaml 中,找到名为 GridViewImageGridView 并添加 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>();

属性值永远不会更改,但由于属性的类型为 ,集合 的 内容可以更改,绑定会自动检测到更改并更新 UI。

若要测试这一点,我们将暂时添加一个用于删除当前所选图像的按钮。 此按钮不在最终版本中,因为选择图像会将你带到详细信息页面。 但是,在最终的 PhotoLab 示例中,ObservableCollection<T> 的作用仍然很重要,因为 XAML 在页面构造函数中通过调用 InitializeComponent 方法进行了初始化,但 Images 集合稍后会在 GetItemsAsync 方法中进行填充。

添加删除按钮

  1. 在 MainPage.xaml 中,找到名为 CommandBarMainCommandBar,并在缩放按钮之前添加新按钮。 (缩放控件尚未起作用。你将在教程的下一部分中连接这些控件。)

    <AppBarButton Icon="Delete"
                  Label="Delete selected image"
                  Click="{x:Bind DeleteSelectedImage}" />
    

    如果已经熟悉 XAML,此值 Click 可能看起来异常。 在以前版本的 XAML 中,必须将其设置为具有特定事件处理程序签名的方法,通常包括事件发送方的参数和特定于事件的参数对象。 即使需要事件参数,您仍然可以使用此技术,而使用 x:Bind时,也可以连接到其他方法。 例如,如果你不需要事件数据,可以连接到没有参数的方法,就像我们在这里所做的那样。

  2. 在MainPage.xaml.cs中添加 DeleteSelectedImage 方法。

    private void DeleteSelectedImage() =>
        Images.Remove(ImageGridView.SelectedItem as ImageFileInfo);
    

    此方法只是从 Images 集合中删除所选图像。

现在运行应用并使用按钮删除几个图像。 如你所看到的,UI 会自动更新,这要归功于数据绑定和 ObservableCollection<T> 类型。

注释

此代码仅从正在运行的应用中的Images集合中删除ImageFileInfo实例。 它不会从计算机中删除映像文件。

第 3 部分:设置缩放滑块

在本部分中,你将从数据模板中的控件到模板外部的缩放滑块创建单向绑定。 你还将了解到,数据绑定可以用于许多控件属性,而不仅仅是最明显的那些,如 TextBlock.TextImage.Source

将图像数据模板绑定到缩放滑块

  • 找到名为 DataTemplateImageGridView_DefaultItemTemplate,并替换模板顶部 **Height** 控件的 WidthGrid 值。

    之前

    <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=OneWayMode=TwoWay。 在下一部分中,你将看到此示例。

运行应用并使用滑块更改图像模板尺寸。 正如你所看到的,效果非常强大,不需要大量的代码。

正在运行的应用,带有缩放滑块显示

注释

如果想挑战一下,可以尝试将其他 UI 属性绑定到缩放滑块 Value 属性,或者绑定到在缩放滑块之后添加的其他滑块。 例如,可以将 FontSizeTitleTextBlock 属性绑定到默认值 为 24的新滑块。 请务必设置合理的最小值和最大值。

第 4 部分:改进缩放体验

在本部分中,你将向后台代码添加自定义 ItemSize 属性,并从图像模板创建到新属性的单向绑定。 缩放滑块和其他因素(如“ItemSize 开关和窗口大小)将更新 值,从而带来更精细的体验。

与内置控件属性不同,自定义属性不会自动更新 UI,即使使用单向绑定和双向绑定也是如此。 它们适用于一时间 绑定,但如果希望属性更改实际显示在 UI 中,则需要执行一些工作。

创建 ItemSize 属性,以便更新 UI

  1. 在MainPage.xaml.cs中,更改类的 MainPage 签名,使其实现 INotifyPropertyChanged 接口。

    之前:

    public sealed partial class MainPage : Page
    

    之后:

    public sealed partial class MainPage : Page, INotifyPropertyChanged
    

    这会通知绑定系统,MainPage 有一个即将添加的 PropertyChanged 事件,绑定可以侦听这个事件以更新 UI。

  2. PropertyChanged 事件添加到 MainPage 类。

    public event PropertyChangedEventHandler PropertyChanged;
    

    该事件提供了INotifyPropertyChanged接口所需的完整实现。 但是,要使其产生任何效果,必须在自定义属性中显式引发该事件。

  3. 添加 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。

  4. 在 MainPage.xaml 中,找到名为 DataTemplateImageGridView_DefaultItemTemplate,并替换模板顶部 Height 控件的 WidthGrid 值。 如果在本教程的上一部分中执行了控件到控件绑定,则唯一需要更改的是将 Value 替换为 ItemSize,并将 ZoomSlider 替换为 page。请确保对 HeightWidth也执行此操作!

    之前

    <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 属性值

  1. 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;
        }
    }
    
  2. 在 MainPage.xaml 中,导航到文件开头,并在 Page 元素上添加 SizeChanged 事件绑定。

    之前:

    <Page x:Name="page"
    

    之后:

    <Page x:Name="page"
          SizeChanged="{x:Bind DetermineItemSize}"
    
  3. 查找名为 SliderZoomSlider(在 Page.Resources 部分中),并添加 ValueChanged 事件绑定。

    之前:

    <Slider x:Name="ZoomSlider"
    

    之后:

    <Slider x:Name="ZoomSlider"
            ValueChanged="{x:Bind DetermineItemSize}"
    
  4. 找到名为 ToggleSwitchFitScreenToggle,并添加 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

  1. 在 MainPage.xaml 中,找到名为 GridViewImageGridView。 若要使项可单击,请将IsItemClickEnabled设置为True,并且添加ItemClick事件处理程序。

    小窍门

    如果你手动键入以下更改,而不是复制/粘贴,你将看到一个显示“<新事件处理程序>”的 IntelliSense 弹出提示。 按下 Tab 键时,它会使用默认的方法处理程序名称来填充值,并自动生成下一步中要显示的方法的存根。 然后,可以按 F12 导航到后台代码中的方法。

    之前:

    <GridView x:Name="ImageGridView">
    

    之后:

    <GridView x:Name="ImageGridView"
              IsItemClickEnabled="True"
              ItemClick="ImageGridView_ItemClick">
    

    注释

    我们在此处使用常规事件处理程序,而不是 x:Bind 表达式。 这是因为我们需要查看事件数据,如下所示。

  2. 在MainPage.xaml.cs中,添加事件处理程序(如果在最后一步中使用了那个提示,请完成其设置)。

    private void ImageGridView_ItemClick(object sender, ItemClickEventArgs e)
    {
        this.Frame.Navigate(typeof(DetailPage), e.ClickedItem);
    }
    

    此方法直接导航到详细信息页,传入单击的项,这是 ImageFileInfo 用于初始化页面的 对象。 无需在本教程中实现该方法,但可以看看它的作用。

  3. (可选)删除或注释掉在之前的播放点中添加的用于当前所选图像的任何控件。 保留它们不会有什么问题,但现在不进入详细信息页面就很难选择图像。

现在,你已连接这两个页面,请运行应用并仔细查看。 除了编辑窗格上的控件外,其他一切正常。当您尝试更改值时,这些控件没有响应。

如你所看到的,标题文本框会显示标题,并允许你键入更改。 必须将焦点更改为另一个控件才能提交更改,但屏幕左上角的标题尚未更新。

所有控件都已使用第 1 部分中介绍的纯 x:Bind 表达式进行绑定。 如果回想一下,这意味着它们都是一次性绑定,这解释了为何未注册值更改。 若要解决此问题,我们只需将它们转换为双向绑定。

使编辑控件成为交互式控件

  1. 在 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}"
                            ... >
    
  2. 对评分控件后面的所有效果滑块进行相同的操作。

    <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 部分:通过函数绑定格式化值

最后一个问题仍然存在。 移动效果滑块时,它们旁边的标签仍不会更改。

带有默认标签值的效果滑块

在本教程的最后一部分中,我们将添加用于格式化滑块值以显示的绑定。

绑定效果滑块的标签并格式化数值以供显示

  1. 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 类型来访问该方法。 在这种情况下,该方法是大家熟悉的 .NET ToString 方法,需要先通过页面的项属性进行访问,然后通过该项的 Exposure 属性进行进一步访问。 (这说明了如何绑定到嵌套在连接链中的方法和属性。)

    函数绑定是设置显示值格式的理想方法,因为可以将其他绑定源作为方法参数传入,绑定表达式将使用单向模式侦听对这些值的更改。 在此示例中,文化 参数是对代码隐藏中实现的不变字段的引用,但它同样可以是一个用于引发 PropertyChanged 事件处理程序的属性。 在这种情况下,对属性值所做的任何更改都会导致 x:Bind 表达式使用新值调用 ToString ,然后使用结果更新 UI。

  2. 对标记其他效果滑块的 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_ValueChangedMainImageScroll_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 对象。)

更进一步

现在你已经完成了这个实验室,你具备了足够的知识来独立解决问题。

如前所述,如果更改详细信息页上的缩放级别,则在向后导航时会自动重置该缩放级别,然后再次选择同一图像。 能否单独确定如何保留和还原每个图像的缩放级别? 祝你好运!

本教程中应包含所需的全部信息,但如果需要更多指导,数据绑定文档只需单击一下即可。 在此处开始: