如何绑定到 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)绑定到 Message
的 Title
属性。这按预期工作,显示了 10 条消息的正确标题。然后我在 ListView
之外的 Window
添加了一个 MessageControl
(在评论中表示为 TEST 2)绑定到单个 Message
属性 Window
。这也完全符合预期。
我错过了什么吗?为什么直接绑定到模型的特定情况(与该模型上的 属性 相反 - 即没有路径的绑定)在数据存在并与其他绑定配置一起使用时不起作用?
事实证明,这个特定案例揭示了 XAML 中绑定工作方式的一些顺序。要了解这里发生的事情:
- 正在初始化
Window
,这会初始化它的UI,包括ListView
。
- 然后检索
Message
模型并将其添加到 ObservableCollection
。
- 当每个
Message
添加到 ObservableCollection
时,ListView
使用那个 ObservableCollection
作为它的 ItemsSource
然后创建一个新的 Item
使用 XAML 中指定的 ItemTemplate
,然后初始化并将新初始化的 MessageControl
绑定到 Message
。测试 1 验证了这种情况。
这里的问题在第 3 步:MessageControl
被初始化,然后绑定到。当绑定发生时,UI 永远不会被通知它需要更新绑定。这就是为什么将 Mode=OneWay
添加到 {x:Bind}
不能解决问题的原因。 OneWay
模式告诉绑定在模型更新时更新...但是 XAML 仍然不知道模型已更新。
我不完全确定为什么这与其他两个绑定(TEST 1 和 TEST 2 注释)不同,但这似乎是用户定义类型 (类) 的一个问题,特别是,而不是原始类型或内置类型属性(例如 string
和 int
)。
修复是为了使模型能够通知(或“通知”)已更新的 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
模型本身的问题。
我正在尝试直接绑定到模型(
我有一个 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)绑定到 Message
的 Title
属性。这按预期工作,显示了 10 条消息的正确标题。然后我在 ListView
之外的 Window
添加了一个 MessageControl
(在评论中表示为 TEST 2)绑定到单个 Message
属性 Window
。这也完全符合预期。
我错过了什么吗?为什么直接绑定到模型的特定情况(与该模型上的 属性 相反 - 即没有路径的绑定)在数据存在并与其他绑定配置一起使用时不起作用?
事实证明,这个特定案例揭示了 XAML 中绑定工作方式的一些顺序。要了解这里发生的事情:
- 正在初始化
Window
,这会初始化它的UI,包括ListView
。 - 然后检索
Message
模型并将其添加到ObservableCollection
。 - 当每个
Message
添加到ObservableCollection
时,ListView
使用那个ObservableCollection
作为它的ItemsSource
然后创建一个新的Item
使用 XAML 中指定的ItemTemplate
,然后初始化并将新初始化的MessageControl
绑定到Message
。测试 1 验证了这种情况。
这里的问题在第 3 步:MessageControl
被初始化,然后绑定到。当绑定发生时,UI 永远不会被通知它需要更新绑定。这就是为什么将 Mode=OneWay
添加到 {x:Bind}
不能解决问题的原因。 OneWay
模式告诉绑定在模型更新时更新...但是 XAML 仍然不知道模型已更新。
我不完全确定为什么这与其他两个绑定(TEST 1 和 TEST 2 注释)不同,但这似乎是用户定义类型 (类) 的一个问题,特别是,而不是原始类型或内置类型属性(例如 string
和 int
)。
修复是为了使模型能够通知(或“通知”)已更新的 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
模型本身的问题。