如何绑定到 ListView 中的模型而不是 WinUI3 中的 属性?

How to bind to a model in ListView instead of property in WinUI3?

我正在尝试直接绑定到模型(), instead of the property on that model, but it is not working. ,答案在 UWP 中有效。我现在在 WinUI3(打包的 UWP)中工作,不确定是否适用相同的解决方案。

我有一个 class:

public class Message
{
    public string Title { get; set; }
    public string Content { get; set; }
}

我有一个显示 class:

的用户控件
public sealed partial class MessageControl : UserControl
{
    /// <summary>
    /// The message to display.
    /// </summary>
    public Message Message { get; set; }

    public MessageControl()
    {
        this.InitializeComponent();
    }
}

与XAML:

<UserControl
    x:Class="MessageClient.Controls.EmailControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:MessageClient.Controls"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid>
        <StackPanel>
            <!-- Displays the title of the message. -->
            <TextBlock Name="HeaderTextBlock"
                       Text="{x:Bind Message.Title, Mode=OneWay}"></TextBlock>
        
            <!-- Displays the content of the message. -->
            <TextBlock Name="ContentPreviewTextBlock"
                       Text="{x:Bind Message.Content, Mode=OneWay}"></TextBlock>
        </StackPanel>
    </Grid>
</UserControl>

现在,我有一个 Window 我正在列表视图中显示该控件:

<Window
    x:Class="MessageClient.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:EmailClient"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    xmlns:controls="using:MessageClient.Controls" xmlns:models="using:MessageClient.Models"
    mc:Ignorable="d">

    <Grid>
        <!-- The list of messages. -->
        <ListView x:Name="MessagesListView"
                  ItemsSource="{x:Bind Messages}">
            <ListView.ItemTemplate>
                <DataTemplate x:DataType="models:Message">
                    <controls:MessageControl Margin="5"
                                           Message="{x:Bind Mode=OneWay}"></controls:MessageControl>

                    <!-- TEST 1 -->
                    <!--<TextBlock Text="{x:Bind Title}"></TextBlock>-->
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>

        <!-- TEST 2 -->
        <!--<controls:MessageControl Message="{x:Bind TestMessage, Mode=OneWay}"></controls:MessageControl>-->
    </Grid>
</Window>

Window 背后的代码是:

public sealed partial class MainWindow : Window
{
    public ObservableCollection<Message> Messages;
    public Message TestMessage;

    public MainWindow()
    {
        this.InitializeComponent();

        // Populate the test message.
        this.PopulateTestMessages();
    }

    private void PopulateTestMessages()
    {
        // The TestDataGenerator just returns a staticaly defined list of test messages.
        this.Messages = new ObservableCollection<Message>(TestDataGenerator.GetTestMessages(10));
        this.TestMessage = this.Messages.First();
    }
}

当我 运行 这段代码(编译和 运行s)时,我得到一个 window,其中包含 ListView (10) 中的预期项目数, 但它们都是空白的。也没有报告 XAML 绑定失败(通过 Visual Studio 2022 中的新功能)。为了对此进行调查,我在列表视图中添加了一个文本块(在注释中表示为 TEST 1)绑定到 MessageTitle 属性。这按预期工作,显示了 10 条消息的正确标题。然后我在 ListView 之外的 Window 添加了一个 MessageControl (在评论中表示为 TEST 2)绑定到单个 Message 属性 Window。这也完全符合预期。

我错过了什么吗?为什么直接绑定到模型的特定情况(与该模型上的 属性 相反 - 即没有路径的绑定)在数据存在并与其他绑定配置一起使用时不起作用?

事实证明,这个特定案例揭示了 XAML 中绑定工作方式的一些顺序。要了解这里发生的事情:

  1. 正在初始化Window,这会初始化它的UI,包括ListView
  2. 然后检索 Message 模型并将其添加到 ObservableCollection
  3. 当每个 Message 添加到 ObservableCollection 时,ListView 使用那个 ObservableCollection 作为它的 ItemsSource 然后创建一个新的 Item 使用 XAML 中指定的 ItemTemplate,然后初始化并将新初始化的 MessageControl 绑定到 Message。测试 1 验证了这种情况。

这里的问题在第 3 步:MessageControl 被初始化,然后绑定到。当绑定发生时,UI 永远不会被通知它需要更新绑定。这就是为什么将 Mode=OneWay 添加到 {x:Bind} 不能解决问题的原因。 OneWay 模式告诉绑定在模型更新时更新...但是 XAML 仍然不知道模型已更新。

我不完全确定为什么这与其他两个绑定(TEST 1 和 TEST 2 注释)不同,但这似乎是用户定义类型 (类) 的一个问题,特别是,而不是原始类型或内置类型属性(例如 stringint)。

修复是为了使模型能够通知(或“通知”)已更新的 UI。这是通过实现 INotifyPropertyChanged 接口完成的。将 MessageControl 更新为:

public sealed partial class MessageControl : UserControl, INotifyPropertyChanged
{
    private Message _message;
    /// <summary>
    /// The message to display.
    /// </summary>
    public Message Message
    {
        get { return this._message; }
        set
        {
            this._message = value;
            this.RaisePropertyChanged("Message");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void RaisePropertyChanged(string name)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(name));
        }
    }

    public EmailControl()
    {
        this.InitializeComponent();
    }
}

最后一点:实现 INotifyPropertyChanged 必须在 MessageControl 上完成才能工作。如果特定的 属性 Message 模型更新,但未修复正在更新的 MessageControl 上的 Message 模型本身的问题。