I am trying to directly bind to a model (similar to the situation here), instead of the property on that model, but it is not working. I previously asked a question about this, and the answer had worked in UWP. I am now working in WinUI3 (packaged UWP) and am not sure if the same solution applies.
I have a class:
public class Message
{
public string Title { get; set; }
public string Content { get; set; }
}
I have a UserControl that displays that class:
public sealed partial class MessageControl : UserControl
{
/// <summary>
/// The message to display.
/// </summary>
public Message Message { get; set; }
public MessageControl()
{
this.InitializeComponent();
}
}
With 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>
Now, I have a Window I am displaying that control on, inside of a list view:
<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>
And the code behind for the Window is:
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();
}
}
When I run this code (which compiles and runs), I get a window with the expected number of items in the ListView
(10), but they are all blank. There are also no XAML binding failures being reported (via the new feature in Visual Studio 2022). In an attempt to investigate this, I added in a textblock inside of the listview (denoted as TEST 1 in the comments) bound to the Title
property of the Message
. This worked as expected, displaying the correct titles for the 10 messages. I then added a single MessageControl
to the Window
outside of the ListView
(denoted as TEST 2 in the comments) bound to a single Message
property on the Window
. This also worked exactly as expected.
Am I missing something? Why does the specific case of binding to a model directly (as opposed to a property on that model - i.e. a binding with no path) not work, when the data is there and works with other binding configurations?
CodePudding user response:
Turns out this specific case exposes some of the ordering of how bindings work in XAML. To understand what is happening here:
- The
Window
is being initialized, which initializes its UI, including theListView
. - The
Message
models are then retrieved and added to theObservableCollection
. - When each
Message
is added to theObservableCollection
, theListView
using thatObservableCollection
as itsItemsSource
then creates a newItem
using theItemTemplate
specified in the XAML, and then initializes and binds the newly initializedMessageControl
to theMessage
. The TEST 1 verifies this is happening.
The problem here is in step 3: The MessageControl
s are initialized AND THEN bound to. When the binding happens, the UI is never notified that it needs to update the binding. This is why adding Mode=OneWay
to the {x:Bind}
doesn't fix the issue. The OneWay
mode tells the binding to update whenever the model updates... but the XAML is still never aware the model was updated.
I am not entirely sure why this is different than the other two bindings (the TEST 1 and TEST 2 comments), but this seems to be an issue with user-defined types (classes) specifically, and NOT with primitive or built-in type properties (such as string
and int
).
The fix is to enable the model to inform (or "notify") the UI that is has been updated. This is done be implementing the INotifyPropertyChanged
interface. Update the MessageControl
to:
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();
}
}
A final note: implementing INotifyPropertyChanged
MUST be done on the MessageControl
for this to work. Implementing INotifyPropertyChanged
on the Message
model would allow for the MessageControl
(or any other data bound UI, such as the commented TEST 1) to update if a particular property of the Message
model updates, but does not fix the issue of the Message
model iself on the MessageControl
being updated.