Having a hard time binding an ObservableCollection
of Tuples to XAML in a Visual Studio 2022 extension.
I'm at least partially trying to follow this tutorial, though it's not quite doing the same thing.
ObservableCollection
property of named tuples.DataContext
to this
of my extension's UserControl
per tutorial:
ItemsSource
of my ItemsControl
in the C#.<Checkbox Foreground="LightGreen" Content="{Binding Name}" IsChecked="{Binding IsChecked}"></CheckBox>
Result:
I get two CheckBox
es with no Content
.
public partial class MyWindowControl : UserControl
{
private ObservableCollection<(string Name, bool IsChecked)> ChutzpahItems { get; set; } =
new ObservableCollection<(string Name, bool IsChecked)>()
{
("Thy Smy Name", true),
("Thy Smy OtherName", true),
};
/// <summary>
/// Initializes a new instance of the <see cref="MyWindowControl"/> class.
/// </summary>
public MyWindowControl()
{
DataContext = this;
this.InitializeComponent();
this.itcChutzpah.ItemsSource = ChutzpahItems;
CheckForPrereqs();
}
// ...
<UserControl x:Class="MyWindowExtension.MyWindowControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:vsshell="clr-namespace:Microsoft.VisualStudio.Shell;assembly=Microsoft.VisualStudio.Shell.15.0"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Name="MyToolWindow">
<Grid>
<DockPanel>
<StackPanel
DockPanel.Dock="Top"
Orientation="Vertical"
>
<!-- some other widget stuff removed. -->
<ItemsControl Name="itcChutzpah">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<CheckBox
Foreground="LightGreen"
Content="{Binding Name}"
IsChecked="{Binding IsChecked}"
></CheckBox>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
<!-- some other widget stuff removed. -->
</DockPanel>
</Grid>
</UserControl>
I mean pretty clearly I AM getting a bound collection, but my bindings have the wrong path or something.
Two questions:
Going to put @Yarik's comment as a wiki answer here. @Clemens also was pointing towards the same answer.
If either wants to add an answer, I'll delete this one.
As both @Yarik and @Clemens mentioned, Debug >>> Windows >>> XAML Binding Failures
is a good source for debugging.
That window gives us this, which shows we aren't binding because Name & IsChecked aren't properties, just as the comments warned.
Severity Count Data Context Binding Path Target Target Type Description
Error 2 ValueTuple`2 Name CheckBox.Content Object Name property not found on object of type ValueTuple`2.
Error 2 ValueTuple`2 IsChecked CheckBox.IsChecked Nullable`1 IsChecked property not found on object of type ValueTuple`2.
As @Yarik and @Clemens mention:
From the documentation of tuple types it seems that the names generate fields, not properties.
and
Tuple<,>
is not the same asValueTuple<,>
. The first one is a class with properties (so it's ok to use with binding), the second one is a struct with fields.
And since XAML needs properties to bind to, the ValueTuple
isn't going to work.
This means that simply taking away the names, expecting the behavior to match what's discussed over at Bind wpf listbox to ObservableCollection of tuples simply because you have Item1
and Item2
won't work either. It's still a value tuple.
Which means that...
private ObservableCollection<(string, bool)> ChutzpahItems { get; set; } =
new ObservableCollection<(string, bool)>()
{
("Thy Smy Name", true),
("Thy Smy OtherName", true),
};
and
<CheckBox
Foreground="LightGreen"
Content="{Binding Item1}"
IsChecked="{Binding Item2}"
></CheckBox>
... gives the same sorts of errors.
Severity Count Data Context Binding Path Target Target Type Description
Error 4 ValueTuple`2 Item1 CheckBox.Content Object Item1 property not found on object of type ValueTuple`2.
Error 4 ValueTuple`2 Item2 CheckBox.IsChecked Nullable`1 Item2 property not found on object of type ValueTuple`2.
Using old school Tuple<string, bool>
does work, (one caveat and an explanation) just as @Yarik & @Clements predicted.
private ObservableCollection<Tuple<string, bool>> ChutzpahItems { get; set; } =
new ObservableCollection<Tuple<string, bool>>()
{
new Tuple<string, bool>("Thy Smy Name", true),
new Tuple<string, bool>("Thy Smy OtherName", true),
};
<CheckBox
Foreground="LightGreen"
Content="{Binding Item1}"
IsChecked="{Binding Item2, Mode=OneWay}"
></CheckBox>
And there you have it.
Better, of course, would be to create a class that
public class CheckboxInfo
{
public string Name { get; set; }
public bool IsChecked { get; set; }
public CheckboxInfo(string name, bool isChecked)
{
Name = name;
IsChecked = isChecked;
}
}
Which just goes to show that "Sometimes "just the data" is the Right Thing", but, and I think Skeet would agree, most of the time it's not.