0

I'm new to WPF so I tried some tutorials. Now in my second project there is something that drives me crazy...

Here is the C# code:

using System.Windows;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace WpfDataBindingTest1
{
    public partial class MainWindow : Window
    {
        private ICollection<TestClass> testClassCollection;
        public ICollection<TestClass> TestClassCollection
        {
            get { return this.testClassCollection; }
            set { if (this.testClassCollection != value) { this.testClassCollection = value; } }
        }
        public MainWindow()
        {
            InitializeComponent();

            this.testClassCollection = new ObservableCollection<TestClass>()
            {
                new TestClass() {ID = 1, Name = "Name1" },
                new TestClass() {ID = 2, Name = "Name2" },
                new TestClass() {ID = 3, Name = "Name3" }
            };
        }
    }

    public class TestClass
    {
        public int ID { get; set; }
        public string Name { get; set; }
    }
}

Here is the XAML Code:

<Window x:Class="WpfDataBindingTest1.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:WpfDataBindingTest1"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <ListBox
            x:Name="lbTestClassItems"
            ItemsSource="{Binding Source=TestClassCollection}">

        </ListBox>
    </Grid>
</Window>

So when compiling and running this, I only get displayed each letter of the Word "TestClassCollection" in a new line in the listbox defined in the XAML file.

When adding DisplayMemberPath="ID" to the definition if the listbox, there is nothing displayed.

After some search I found this: why is it displaying class name instead of property?

But this didn't helped.

Edit: Mysteriously if I do it via code (set itemsource and DisplayMemberPath), the correct data is displayed. But if I do it via XAML, see above

Thanks in advance!

Radinator
  • 1,048
  • 18
  • 58

3 Answers3

1

Try to use an ItemTemplate:

<ListBox x:Name="lbTestClassItems" ItemsSource="{Binding Path=TestClassCollection}">
    <ListBox.ItemTemplate>
      <DataTemplate>
        <StackPanel Orientation="Horizontal">
          <Label Content="{Binding Id}" />
        </StackPanel>
      </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>
rbr94
  • 2,227
  • 3
  • 23
  • 39
1

You need to use Path instead of Source otherwise you're binding to string TestClassCollection hence your one letter per item. Ideally, if your item is a complex object, you would also either set DisplayMemberPath or use ItemTemplate

<ListBox 
    x:Name="lbTestClassItems" 
    ItemsSource="{Binding Path=TestClassCollection}" 
    DisplayMemberPath="Name"/>

Another problem is that you don't set DataContext anywhere. All binding work in binding context.

public MainWindow()
{
    InitializeComponent();        
    this.testClassCollection = ...;
    this.DataContext = this;
}

You need to do it in this other because your MainWindow does not implement INotifyPropertyChanged interface and TestClassCollection property does not raise PropertyChanged event. After that you can add/remove to TestClassCollection and it should be picked up by UI because ObservableCollection<T> implements INotifyCollectionChanged interface

dkozl
  • 32,814
  • 8
  • 87
  • 89
1

You need a DataContext. Rather than having your "view model" code in the code behind for the main window, put it in a separate class. Like this:

public class ViewModel
{
    public ViewModel()
    {
        this.testClassCollection = new ObservableCollection<TestClass>()
        {
            new TestClass() {ID = 1, Name = "Name1" },
            new TestClass() {ID = 2, Name = "Name2" },
            new TestClass() {ID = 3, Name = "Name3" }
        };
    }

    private ICollection<TestClass> testClassCollection;
    public ICollection<TestClass> TestClassCollection
    {
        get { return this.testClassCollection; }
        set { if (this.testClassCollection != value) { this.testClassCollection = value; } }
    }

    public class TestClass
    {
        public int ID { get; set; }
        public string Name { get; set; }
    }

}

Then create an instance of the ViewModel class as the data context.

<Grid>
    <ListBox
        x:Name="lbTestClassItems"
        ItemsSource="{Binding TestClassCollection}" >
        <ListBox.DataContext>
            <local:ViewModel/>
        </ListBox.DataContext>
    </ListBox>
</Grid>

I believe what is happening with your original implementation is that it is binding to the ToString of the property name. A string is considered a array of char, hence you get one letter per list item.

Ideally you probably wouldn't create a separate data context for the listbox. It would more likely inherit from it's parent. (i.e. the Window) But this illustrates the concept.

Steve In CO
  • 5,746
  • 2
  • 21
  • 32