0

This is probably a silly question, but I can't make a simple example with ItemsSource work. My XAML:

<Window x:Class="TestDataGrid.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TestDataGrid"
        mc:Ignorable="d" Height="250" Width="300" Name="MyWindow">
    <ListBox ItemsSource="{Binding MyItems, ElementName=MyWindow}" Background="{Binding MyBrush, ElementName=MyWindow}"/>
</Window>

Code:

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    private ObservableCollection<Item> items;

    public MainWindow()
    {
        InitializeComponent();
        items = new ObservableCollection<Item>();
        items.Add(new Item { Key = "Key1", Value = "Value1" });
        items.Add(new Item { Key = "Key2", Value = "Value2" });
        items.Add(new Item { Key = "Key3", Value = "Value3" });
    }

    public ObservableCollection<Item> MyItems
    {
        get { return items; }
    }

    public Brush MyBrush
    {
        get { return Brushes.LightPink; }
    }
}

public class Item
{
    public string Key { get; set; }
    public string Value { get; set; }
}

It works if I set ItemsSource in code, or if I set DataContext=this and then remove ElementName from my binding. But why doesn't it work with ElementName? I can bind the background color like this, but not collection.

I know how to make it work with DataContext, or in code-behind, but I'm interested in why this particular example doesn't work, am I missing something?

username
  • 3,378
  • 5
  • 44
  • 75

2 Answers2

3

I know how to make it work with DataContext, or in code-behind, but I'm interested in why this particular example doesn't work, am I missing something?

As pointed out by @Clemens your binding will actually work if you populate the source collection before the InitializeComponent() is called.

The difference between setting a DataContext in code behind and creating an ElementName Binding in XAML is that the latter is already established during the InitializeComponent call. Items that are added later - to a collection that does not implement INotifyCollectionChanged - are ignored.

public MainWindow()
{
    items = new ObservableCollection<Item>();
    items.Add(new Item { Key = "Key1", Value = "Value1" });
    items.Add(new Item { Key = "Key2", Value = "Value2" });
    items.Add(new Item { Key = "Key3", Value = "Value3" });
    InitializeComponent();
}

You could also bind to the parent window using a RelativeSource though, This works:

<ListBox ItemsSource="{Binding MyItems, RelativeSource={RelativeSource AncestorType=Window}}"/>
Clemens
  • 123,504
  • 12
  • 155
  • 268
mm8
  • 163,881
  • 10
  • 57
  • 88
  • 1
    "Because "MyItems" is not a property of the base type Window with which the root element is declared in your XAML markup." This explanation is wrong. The actual problem is just timing. If you fill the `items` collection before calling `InitializeComponent`, the ElementName Binding works well. – Clemens Feb 02 '17 at 16:19
  • I don't think it works he is binding `MyItems`, not `items` and he didn't do a setter in `MyItems` to link it to `items` – Safe Feb 02 '17 at 16:43
  • Even with a RelativeSource Binding it works only by chance. Better always use an ObservableCollection. – Clemens Feb 02 '17 at 16:44
  • @Safe Didn't you notice the `MyItems` getter in the question? – Clemens Feb 02 '17 at 16:44
  • @Clemens ah yes my bad I was on the first version of this answer xD – Safe Feb 02 '17 at 16:48
  • Isn't the problem that at the time InitializeComponent() is called, the collection was not created and set to null? If I create collection before InitializeComponent() and fill items afterwards, then it works too. – username Feb 02 '17 at 18:00
1

You set your MyItems after initializing your windows and since MyItems in your example doesn't implement a setter and the INotifyPropertyChanged manner, your UI thread is never warn by the change you made on your collection.

Here two solutions you can set up:

SOLUTION 1

Instantiate your Observable collection directly in the get:

public ObservableCollection<Item> MyItems {
        get {
            var _items = new ObservableCollection<Item>();
            _items.Add(new Item { Key = "Key1", Value = "Value1" });
            _items.Add(new Item { Key = "Key2", Value = "Value2" });
            _items.Add(new Item { Key = "Key3", Value = "Value3" });
            return _items;
        }
    }

SOLUTION 2

Use INotifyPropertyChanged interface:

Like that when you set your items, Your UI thread knows there is a change to do in the xaml part

public partial class MainWindow : Window, INotifyPropertyChanged {
    public event PropertyChangedEventHandler PropertyChanged = delegate { };
    private ObservableCollection<Item> _items;
    public ObservableCollection<Item> MyItems {
        get { return _items; }
        set {
            _items = value;
            PropertyChanged(this, new PropertyChangedEventArgs(nameof(MyItems)));
        }
    }

    public MainWindow () {
        InitializeComponent();
        MyItems = new ObservableCollection<Item>();
        MyItems.Add(new Item { Key = "Key1", Value = "Value1" });
        MyItems.Add(new Item { Key = "Key2", Value = "Value2" });
        MyItems.Add(new Item { Key = "Key3", Value = "Value3" });
    }

....
Safe
  • 313
  • 3
  • 13
  • Your first "solution" gives a really bad advice. You should by no means do it that way. – Clemens Feb 02 '17 at 16:40
  • Yes I don't like the second solution too in a way, I was just giving simple examples, the first solution should be more like @mm8 answer. – Safe Feb 02 '17 at 16:53
  • No, it does something very different. It creates a new collection each time the property getter is called. You should avoid that. – Clemens Feb 02 '17 at 16:54