XAML 如何在WinUI 3中将数据类型绑定到控件?

jtoj6r0c  于 2022-12-07  发布在  其他
关注(0)|答案(1)|浏览(331)

我有一个数据类型(模型),我希望通过使用数据绑定显示几个属性来在UI中显示其数据。它在GridViewListView中工作,但当我只希望绑定单个模型而不是集合时,我如何做到这一点?
要对集合执行此操作,请在ListView中执行以下操作:

<ListView x:Name="MyListView"
          ItemsSource="{x:Bind Shapes, Mode=OneWay}">
    <ListView.ItemTemplate>
        <DataTemplate x:DataType="models:Shape">
            <StackPanel>
                <TextBlock Text="{x:Bind Name}"></TextBlock>
                <TextBlock Text="{x:Bind NumberOfSides}"></TextBlock>
                <TextBlock Text="{x:Bind Color}"></TextBlock>
            </StackPanel>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

在具有Shape类型的ObservableCollection(称为Shapes)的页面上:

public sealed partial class MyPage : Page
{
    // ...
    public ObservableCollection<Shape> Shapes { get; set; }
    // ...
}

具有以下型号Shape

public class Shape
{
    public string Name { get; set; }
    public string NumberOfSides { get; set; }
    public string Color { get; set; }
}

做这样的事情,但这行不通:

<Grid>
    <StackPanel>
        <TextBlock Text="{x:Bind Name}"></TextBlock>
        <TextBlock Text="{x:Bind NumberOfSides}"></TextBlock>
        <TextBlock Text="{x:Bind Color}"></TextBlock>
    </StackPanel>
</Grid>
g0czyy6m

g0czyy6m1#

数据绑定实际上是在ListView上完成的,DataTemplate只是声明了显示绑定模型的布局。
要使用单个绑定项而不是集合来实现这一点,您需要使用仍然具有模板属性的控件。(Microsoft's official documentation)。ContentControl具有ContentTemplate属性,它可以像ListViewGridView一样包含DataTemplate!然后可以在C#代码中设置ContentControlContent属性,或者绑定到它(与绑定到ListViewGridViewItemsSource属性的方式相同,只是使用单个项而不是集合)。
简单的方法
下面的示例有效(请注意,DataTemplate及其所有子项与它们在ListViewGridView中的显示方式相同):

<ContentControl x:Name="MyContentControl">
    <ContentControl.ContentTemplate>
        <DataTemplate x:DataType="models:Shape">
            <StackPanel>
                <TextBlock Text="{x:Bind Name}"></TextBlock>
                <TextBlock Text="{x:Bind NumberOfSides}"></TextBlock>
                <TextBlock Text="{x:Bind Color}"></TextBlock>
            </StackPanel>
        </DataTemplate>
    <ContentControl.ContentTemplate>
</ContentControl>

然后在C#代码中:

public sealed partial class MyPage : Page
{
    // ...
    public void SetShape(Shape shape)
    {
        this.MyContentControl.Content = shape;
    }
    // ...
}

完整的数据绑定方式

您也可以使用数据绑定来绑定到shape属性,但这需要更多的工作。首先将绑定添加到ContentControl,如下所示:

<ContentControl x:Name="MyContentControl"
                Content="{x:Bind MyShape}">
    <ContentControl.ContentTemplate>
         <!-- Contents all the same as before -->
    <ContentControl.ContentTemplate>
</ContentControl>

并在MyPage上添加要绑定到的MyShape属性:

public sealed partial class MyPage : Page
{
    // ...
    public Shape MyShape { get; set; }
    // ...
}

这样做是行不通的。当你一开始设置它的时候,它可能会工作,但是如果你改变MyShape,绑定的UI将不会更新。
请注意,如果使用ObservableCollection(例如在ListView示例中),您可以在调用ObservableCollectionAdd()Remove()函数时更新UI,但是 * 当您更改ObservableCollection引用本身时 * 则不会发生这种情况。原因是ObservableCollection实现了INotifyPropertyChanged,它告诉绑定在您更改集合中的项集时进行更新。以下不会自动工作:

public sealed partial class MyPage : Page
{
    // ...
    public Shape MyShape { get; set; }
    // ...
    public void UpdateShape(Shape newShape)
    {
        this.MyShape = newShape;
    }
}

要实现这一点,您需要在MyPage上实现INotifyPropertyChanged。这需要三个步骤(听起来可能有点吓人,但对任何属性都是一样的):
1.实作界面INotifyPropertyChanged
1.添加PropertyChanged事件。
1.修改MyShape setter以引发PropertyChanged事件。
实作界面INotifyPropertyChanged

public sealed partial class MyPage : Page, INotifyPropertyChanged
{
    // ...
}

添加PropertyChanged事件。

public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raise the PropertChanged event for the given property name.
/// </summary>
/// <param name="name">Name of the property changed.</param>
public void RaisePropertyChanged(string name)
{
    // Ensure a handler is listening for the event.
    if (this.PropertyChanged != null)
    {
        this.PropertyChanged(this, new PropertyChangedEventArgs(name));
    }
}

修改MyShape setter以引发PropertyChanged事件。

private Shape myShape;
public Shape MyShape
{
    get => this.myShape;
    set
    {
        this.myShape = value;
        this.RaisePropertyChanged("MyShape");
    }
}

最终的C#代码将如下所示:

public sealed partial class MyPage : Page, INotifyPropertyChanged
{
    // ...

    private Shape myShape;
    public Shape MyShape
    {
        get => this.myShape;
        set
        {
            this.myShape = value;
            this.RaisePropertyChanged("MyShape");
        }
    }

    // ...

    public event PropertyChangedEventHandler PropertyChanged;
    /// <summary>
    /// Raise the PropertChanged event for the given property name.
    /// </summary>
    /// <param name="name">Name of the property changed.</param>
    public void RaisePropertyChanged(string name)
    {
        // Ensure a handler is listening for the event.
        if (this.PropertyChanged != null)
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(name));
        }
    }

    // ...

    public void UpdateShape(Shape newShape)
    {
        this.MyShape = newShape;
    }
}

现在,您的ContentControl将按预期使用不同的BindingMode值(OneTimeOneWayTwoWay)工作。
如果希望在更改形状的属性时更新ContentControl内的绑定控件,例如在更改形状的属性时更新<TextBlock Text="{x:Bind Name}">

this.MyShape.Name = "A New Name";

您可以使用相同的基本步骤在Shape类本身上实现INotifyPropertyChanged。无论您使用的是ContentControlGridViewListView还是任何其他数据绑定控件,这都是相同的。基本上,每个您希望能够更新其属性并更新数据绑定UI的层,您需要这样做。无论您使用了此答案中的两种方法中的哪一种,都需要这样做。您可以参考my answer here了解有关此方面的详细信息。

相关问题