The data-binding is actually being done to the ListView
, and the DataTemplate
is simply declaring the layout to display the bound model with.
To accomplish this with a single bound item instead of a collection, you need to use a control that still has a template property. This is where the ContentControl
comes in (Microsoft's official documentation). The ContentControl
has a ContentTemplate
property, which can contain a DataTemplate
the same way a ListView
or GridView
can! You can then set the Content
property of the ContentControl
in the C# code, or bind to it (the same way you would bind to an ItemsSource
property of a ListView
or GridView
, only with a single item instead of a collection).
The Simple Way
The following example works (Note that the DataTemplate
and all of it's children are identical to how they would appear in a ListView
or GridView
):
<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>
Then in your C# code:
public sealed partial class MyPage : Page
{
// ...
public void SetShape(Shape shape)
{
this.MyContentControl.Content = shape;
}
// ...
}
The FULL Data Binding Way
You can also use data binding to bind to the shape property, but this will require some more work. Start by adding the binding to the ContentControl
as follows:
<ContentControl x:Name="MyContentControl"
Content="{x:Bind MyShape}">
<ContentControl.ContentTemplate>
<!-- Contents all the same as before -->
<ContentControl.ContentTemplate>
</ContentControl>
And add the MyShape
property to bind to on MyPage
:
public sealed partial class MyPage : Page
{
// ...
public Shape MyShape { get; set; }
// ...
}
As is, this will not work. It may work when you set it initially, but if you change MyShape
, the bound UI will not update.
Notice that if you were using ObservableCollection
(such as in the ListView
example), you can get the UI to update when you call Add()
or Remove()
functions of the ObservableCollection
, but not when you change the ObservableCollection
reference itself. The reason is that the ObservableCollection
implements INotifyPropertyChanged
which is what tells the bindings to update when you change the set of items in the collection. The following will not automatically work:
public sealed partial class MyPage : Page
{
// ...
public Shape MyShape { get; set; }
// ...
public void UpdateShape(Shape newShape)
{
this.MyShape = newShape;
}
}
To get this to work, you need to implement INotifyPropertyChanged
on MyPage
. This requires three steps (which may sound intimidating, but work the same way for any property):
- Implement the interface
INotifyPropertyChanged
.
- Add the
PropertyChanged
event.
- Modify the
MyShape
setter to raise the PropertyChanged
event.
Implement the interface INotifyPropertyChanged
.
public sealed partial class MyPage : Page, INotifyPropertyChanged
{
// ...
}
Add the PropertyChanged
event.
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));
}
}
Modify the MyShape
setter to raise the PropertyChanged
event.
private Shape myShape;
public Shape MyShape
{
get => this.myShape;
set
{
this.myShape = value;
this.RaisePropertyChanged("MyShape");
}
}
Your final C# code will look like this:
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;
}
}
NOW your ContentControl
will work as expected with the different BindingMode
values (OneTime
, OneWay
, and TwoWay
).
If you want your bound controls WITHIN the ContentControl
to update when you change a property of the shape, such as have the <TextBlock Text="{x:Bind Name}">
update when you do:
this.MyShape.Name = "A New Name";
You can similarly implement INotifyPropertyChanged
on your Shape
class itself with the same basic steps. This is the same whether you are using a ContentControl
, GridView
, ListView
, or any other data-bound control. Basically, each layer you want to be able to update the properties of, and have a data bound UI update, you need to do this. This also needs to be done regardless of which of the two ways you used from this answer. You can refer to my answer here for details on this.