BrilBroeder
BrilBroeder

Reputation: 1579

WPF Listbox not populated with items from ObservableCollection

The main-window is listening for plugging in/out USB-devices. If it is an usb-key/disk it collects a file-list from that device and show that list in a second window.

While debugging I can see that the NewUsbFiles observablecollection get's populated with 117 items. I see that the property UsbFile (before calling the showdialog) has 117 items, but nevertheless the listbox is empty.

Any thoughts ?

The method to populate / create that second window:

NewUsbFiles = new ObservableCollection<UsbFile>();
UpdateNewUsbFiles(driveName);

Application.Current.Dispatcher.Invoke(delegate
{
   var usbFileSelector = new UsbFileSelector()
   {
       Owner = this,
       UsbFiles = NewUsbFiles
    };
    usbFileSelector.ShowDialog();
});

The UsbFile-class:

 public class UsbFile 
    {
        public string UsbFileName { get; set; }
        public string OnTableFileName { get; set; }
        public bool Ignored { get; set; } = false;

        public UsbFile(string fileName)
        {
            var fileInfo = new FileInfo(fileName);
            UsbFileName = fileInfo.FullName;
            OnTableFileName = $"{fileInfo.CreationTime:yyMMddHHmmsss}_{fileInfo.Name}";
        }
    }

The XAML of the second window :

<Window
        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:MainWindow="clr-namespace:PartyPictures.WPF.MainWindow" x:Name="wUsbFileSelector"
    x:Class="PartyPictures.WPF.UsbFileSelector"
        mc:Ignorable="d"
        Title="USB" HorizontalAlignment="Center" VerticalAlignment="Center" WindowStyle="ToolWindow" ScrollViewer.VerticalScrollBarVisibility="Auto" SizeToContent="WidthAndHeight">
    <StackPanel x:Name="spUsbFileList">
        <ListBox  x:Name="ImageListbox"
                  DataContext="{Binding ElementName=wUsbFileSelector}"
                 ItemsSource="{Binding UsbFiles}" 
                 Background="AliceBlue" ScrollViewer.HorizontalScrollBarVisibility="Disabled" MinWidth="200" MinHeight="200">
        </ListBox>
    </StackPanel>
</Window>

The code-behind of the second window :

public partial class UsbFileSelector : Window
    {
        public ObservableCollection<UsbFile> UsbFiles { get; set; } = new ObservableCollection<UsbFile>();

        public UsbFileSelector()
        {
            InitializeComponent();
        }
    }

Upvotes: 1

Views: 412

Answers (4)

Jeff R.
Jeff R.

Reputation: 1521

All the answers already given are correct, the heart of your problem is the

UsbFiles = NewUsbFiles

which causes the binding to "break" - UsbFiles is no longer pointing to the collection that is bound to the Listbox.

Another possible way to solve this would be to simply leave the bound collection alone and just repopulate the contents.

var usbFileSelector = new UsbFileSelector()
{
   Owner = this,
   UsbFiles.Clear();
   foreach (var uf in NewUsbFiles) {
       UsbFiles.Add(uf);
   }
};

Upvotes: 0

Bogdan Borivets
Bogdan Borivets

Reputation: 151

Inside the window you can see InitializeComponent method. It creates all of the stuff defined in XAML and applies all bindings. After binding has been appplied with your empty collecton (that you have created with default property value) the binding will not know about any change of that property, that was the right answer.

But implementing INotifyPropertyChanged is more about viewmodel instances, not visual.

I really suggest you use Dependency Property for windows and controls if you want to bind. There are some reasons for that:

  1. Dependency property setter has built-in notify mechanism.
  2. If you bind one DP to another DP, value is shared in between.
  3. After all, it is WPF approach =)

Here is how your window will look like after change

    public partial class UsbFileSelector : Window
    {
        public static readonly DependencyProperty UsbFilesProperty = 
            DependencyProperty.Register("UsbFiles", typeof(ObservableCollection<UsbFile>), typeof(UsbFileSelector));

        public ObservableCollection<UsbFile> UsbFiles 
        {
            get { return (ObservableCollection<UsbFile>) GetValue(UsbFilesProperty); }
            set { SetValue(UsbFilesProperty, value); }
        }

        public UsbFileSelector()
        {
            InitializeComponent();
        }
    }

Also I strongly recommend you to use some WPF inspector tool while developing for the WPF, for example, snoop. You can navigate through the controls and properties while app is running and find issues much quickly you can from the code or from stackoverflow =)

Upvotes: 1

BrilBroeder
BrilBroeder

Reputation: 1579

Someone posted (and deleted the comment) that I should add DataContext = this;

To

public UsbFileSelector()
{
   InitializeComponent();
   DataContext = this;
}

Someone else mentioned (that comment too was deleted) that this was not necessary because of the

DataContext="{Binding ElementName=wUsbFileSelector}"

in the XAML.

BUT it turned out that removing the DataContext line from the XAML and setting it to this in code was the sollution. No idea why but that did it.


EDIT just to make clear that this is not a good solution and works only by accident, try the following:

// this works
var usbFileSelector = new UsbFileSelector();
usbFileSelector.Owner = this;
usbFileSelector.UsbFiles = NewUsbFiles;
usbFileSelector.ShowDialog();

// this does not
var usbFileSelector = new UsbFileSelector();
usbFileSelector.Owner = this;
await Task.Delay(10);
usbFileSelector.UsbFiles = NewUsbFiles;
usbFileSelector.ShowDialog();

Upvotes: 0

Clemens
Clemens

Reputation: 128060

In

var usbFileSelector = new UsbFileSelector()
{
   Owner = this,
   UsbFiles = NewUsbFiles
};

you are assigning a new value to the UsbFiles property without firing a property change notification for that property.

You could either implement INotifyPropertyChanged and fire the PropertyChanged event or make UsbFiles a dependency property.

Or you pass NewUsbFiles as constructor argument and assign it before calling InitializeComponent

public UsbFileSelector(ObservableCollection<UsbFile> usbFiles)
{
    UsbFiles = usbFiles;
    InitializeComponent();
}

and call it like this:

var usbFileSelector = new UsbFileSelector(NewUsbFiles)
{
   Owner = this
};

Note that if you always pass a new collection, using ObservableCollection isn't actually necessary. You never add or remove elements to/from the collection, so there is no need for a change notification.

Upvotes: 0

Related Questions