Can't successfully bind ObservableCollection of Tuples to XAML (VS Extension)
Asked Answered
P

1

1

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.

  • I set up an ObservableCollection property of named tuples.
  • I initialize that collection with two such tuples.
  • I set DataContext to this of my extension's UserControl per tutorial:
    • "DataContext must be an object, this object is the “ViewModel” of our GUI and all the data bindings will be done on this object. If the data we use are not all combined into one class but exist as our MainWindow’s properties, we set the DataContext to be the MainWindow itself:"
  • I set the ItemsSource of my ItemsControl in the C#.
  • I bind to properties of the named Tuple (which seems like fair game) in the XAML.
    • <Checkbox Foreground="LightGreen" Content="{Binding Name}" IsChecked="{Binding IsChecked}"></CheckBox>

Result:

I get two CheckBoxes with no Content.

screenshot of two checkboxes with no labels

C#

    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();
        }
// ...

XAML

<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:

  1. What am I doing wrong?
  2. How should I have debugged this?

Pico answered 4/5, 2023 at 15:44 Comment(4)
From the documentation of tuple types it seems that the names generate fields, not properties. WPF data binding requires properties. You should observe data binding error messages in the Output Window in Visual Studio when you debug the application.Didi
As of how to debug, open Debug - Windows - XAML Binding Failures. Your binding errors should appear there once you start debugging.Gastrotrich
@Pico Tuple<,> is not the same as ValueTuple<,>. The first one is a class with properties (so it's ok to use with binding), the second one is a struct with fields.Gastrotrich
@Gastrotrich I think that's the answer, if you want to take your last comment and enter it as one. Thank you. I should've stayed suspicious.Pico
P
1

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.


Debugging bindings

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.         

ValueTuple vs. Tuple

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 as ValueTuple<,>. 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.

No, really, you can't use ValueTuples

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 an old school Tuple

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>

working after swapping to true/non-value tuples

And there you have it.

There's a better way

Better, of course, would be to create a class that

  1. Has named properties and
  2. Isn't read-only like a Tuple.
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.

Pico answered 4/5, 2023 at 15:45 Comment(1)
@BowmanZhu-MSFT Yeah painfully StackOverflow makes you wait to accept after you enter your own answer, even if it's a wiki answer, which doesn't make a ton of sense. Thanks for reminding me to come back. /sigh Still game if Yarik or Clemons wants to add one.Pico

© 2022 - 2024 — McMap. All rights reserved.