Home > Software design >  Data Binding for ListBox ItemsSource
Data Binding for ListBox ItemsSource

Time:02-24

I'm having trouble setting up an automatic binding.

I have a simple WPF application with two classes - MovieInfo (contains information about movie files on my filesystem) and a MediaScanner class that just returns a List<MovieInfo>. In my MainWindow.xaml, I have a ListBox:

<ListBox x:Name="listBox" HorizontalAlignment="Left" Height="242" Margin="10,35,0,0" VerticalAlignment="Top" Width="237" d:ItemsSource="{Binding MovieList}"/>

Also in the XAML added to the <Window ...> is

Name="MainWindow1"
DataContext="{Binding ElementName=MainWindow1}"

In the code behind, I made a public property of the MainWindow : Window:

public ObservableCollection<MovieInfo> MovieList { get; set; }

In the constructor:

public MainWindow()
{
    DataContext = this;
    MovieList = new ObservableCollection<MovieInfo>();
    InitializeComponent();
    //this doesn't do anything for me
    //listBox.ItemsSource = MovieList; 
}

I have a button that calls:

private void button_Click(object sender, RoutedEventArgs e)
{
    var scanner = new MediaScanner();
    MovieList = new ObservableCollection<MovieInfo>(scanner.ScanAll().OrderBy(x => x.Title));
    //listBox.ItemsSource = MovieList;
    
}

It's my understanding that this should take care of everything, yet the ListBox won't populate unless I uncomment the listBox.ItemsSource = MovieList; where it is in the button_Click.

What am I missing?

CodePudding user response:

The ListBox does not bind the MovieList at runtime because you prefixed ItemsSource with d.

After adding the namespaces, you can put d: in front of any attribute or control to show it only in the XAML Designer but not at runtime. [...]

You can use d: with attributes for any UWP or WPF .NET Core control, like colors, font sizes, and spacing. You can even add it to the control itself.

These namespaces are defined on your XAML root element.

xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"

In order to make the binding work at runtime, remove the d: or add another ItemsSource without the d: prefix so that you have different sources for design and runtime.

<ListBox x:Name="listBox" HorizontalAlignment="Left" Height="242" Margin="10,35,0,0" VerticalAlignment="Top" Width="237" ItemsSource="{Binding MovieList}"/>
<ListBox x:Name="listBox" HorizontalAlignment="Left" Height="242" Margin="10,35,0,0" VerticalAlignment="Top" Width="237" d:ItemsSource="{Binding DesignTimeMovieList}" ItemsSource="{Binding MovieList}"/>

Another issue is that you neither implement a dependency property for your movie list collection nor INotifyPropertyChanged. In practice this means although you assign a new collection to the MovieList property in your button click event handler, the binding does not get notified of the change and will still use the old collection instance. Of course listBox.ItemsSource = MovieList; would work here, but it assigns the collection directly and overwrites the binding, so this is a different solution.

In the long run, you should probably apply the MVVM pattern and separate the data to be bound in a view model that implements INotifyPropertyChanged, which solves your issue and at the same time separates your view and your logic and data that are then connected via bindings.


Example of a dependency property for your window.

public partial class MainWindow : Window
{
   public ObservableCollection<MovieInfo> MovieList
   {
      get => (ObservableCollection<MovieInfo>)GetValue(MovieListProperty);
      set => SetValue(MovieListProperty, value);
   }

   public static readonly DependencyProperty MovieListProperty = DependencyProperty.Register(
      nameof(MovieList), typeof(ObservableCollection<MovieInfo>), typeof(MainWindow), new PropertyMetadata(null));

   public MainWindow()
   {
      DataContext = this;
      MovieList = new ObservableCollection<MovieInfo>();
      InitializeComponent();
      // ...other code.
   }
   
   private void button_Click(object sender, RoutedEventArgs e)
   {
      var scanner = new MediaScanner();
      MovieList = new ObservableCollection<MovieInfo>(scanner.ScanAll().OrderBy(x => x.Title));
   }
   
   // ...other code.
}

Example for a view model in case you want to move to MVVM.

public class MyViewModel : INotifyPropertyChanged
{
   private ObservableCollection<MovieInfo> _movieList;

   public ObservableCollection<MovieInfo> MovieList
   {
      get => _movieList;
      set
      {
         if (_movieList == value)
            return;

         _movieList = value;
         OnPropertyChanged();
      }
   }

   // ...other code.

   public event PropertyChangedEventHandler PropertyChanged;

   protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
   {
      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
   }
}

As a final remark, you should consider reusing the ObservableCollection<MovieInfo> MovieList instead of creating and assigning a new one each time. This type of collection provides change notifications for adding, removing and replacing items, which will automatically be reflected in the user interface. Exposing an observable collection, but instead of modifying, replacing it, is pointless. If a collection is always replaced, a simple List<T> will do the same.

  • Related