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

Posted

技术标签:

【中文标题】如何绑定到 ListView 中的模型而不是 WinUI3 中的属性?【英文标题】:How to bind to a model in ListView instead of property in WinUI3? 【发布时间】:2021-12-31 21:39:54 【问题描述】:

我正在尝试直接绑定到模型 (similar to the situation here),而不是该模型上的属性,但它不起作用。 I previously asked a question about this,答案在 UWP 中有效。我现在在 WinUI3(打包的 UWP)中工作,不确定是否适用相同的解决方案。

我有一堂课:

public class Message

    public string Title  get; set; 
    public string Content  get; set; 

我有一个显示该类的 UserControl:

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
    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();
    

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

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

【问题讨论】:

【参考方案1】:

事实证明,这个特定案例揭示了绑定在 XAML 中如何工作的一些顺序。要了解这里发生了什么:

    Window 正在初始化,这将初始化其 UI,包括 ListView。 然后检索 Message 模型并将其添加到 ObservableCollection。 当每个Message 被添加到ObservableCollection 时,使用ObservableCollection 作为其ItemsSourceListView 然后使用XAML 中指定的ItemTemplate 创建一个新的Item,然后初始化并将新初始化的MessageControl绑定到Message。 TEST 1 验证这是否正在发生。

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

我不完全确定为什么这与其他两个绑定(TEST 1 和 TEST 2 cmets)不同,但这似乎是用户定义类型(类)的问题,而不是原始类型或构建的问题-in 类型属性(例如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 模型上实现 INotifyPropertyChanged 将允许 MessageControl(或任何其他数据绑定 UI,例如注释的 TEST 1)在 Message 模型的特定属性更新但不修复时更新Message 模型本身在 MessageControl 上的问题正在更新。

【讨论】:

如果您已经解决了您的问题,请mark它被接受 我会的! Github 不允许我在 48 小时后接受我自己的答案。

以上是关于如何绑定到 ListView 中的模型而不是 WinUI3 中的属性?的主要内容,如果未能解决你的问题,请参考以下文章

如何指定一个请求参数绑定到某个控制器方法参数而不是Spring MVC中的模型属性?

如何在 ListView 中绑定 Label 的 FontSize

Xamarin 表单从 listview 绑定到 viewmodel

WPF如何获得ListView内各单元格控件

从视图模型绑定到 ListView 项目的点击属性

ListView绑定到一个集合,如何访问父类中的事件?