views:

718

answers:

2

I have a listbox defined in XAML as:

<ListBox x:Name="directoryList"
                 MinHeight="100" 
                 Grid.Row="0"
                 ItemsSource="{Binding Path=SelectedDirectories}"/>

The SelectedDirectories is a property on the lists DataContext of type List<DirectoryInfo>

The class which is the datacontext for the listbox implements INotifyPropertyChanged. When the collection changes the items are added successfully to the list however the display does not update until I force the listbox to redraw by resizing it.

Any ideas why?

EDIT: INotifyPropertyChanged implementation

public class FileScannerPresenter : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private FileScanner _FileScanner;

        public FileScannerPresenter()
        {
            this._FileScanner = new FileScanner();
        }

        public List<DirectoryInfo> SelectedDirectories
        {
            get
            {
                return _FileScanner.Directories;
            }
        }

        public void AddDirectory(string path)
        {
            this._FileScanner.AddDirectory(path);
            OnPropertyChanged("SelectedDirectories");
        }

        public void OnPropertyChanged(string property)
        {
            if (this.PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(property));
            }
        }
    }
+3  A: 

Try

ObservableCollection<DirectoryInfo>

instead - you're triggering a refresh of the entire ListBox for no reason, and you don't need to make your hosting class implement INotifyPropertyChanged - it could easily just be a property of the window. The key is to never set the property to a new instance. So:

class SomeWindow : Window {
    public ObservableCollection<DirectoryInfo> SelectedDirectories {get; private set;}

    SomeWindow() { SelectedDirectories = new ObservableCollection<DirectoryInfo>(); }

    public void AddDirectory(string path) {
        SelectedDirectories.Add(new DirectoryInfo(path));
    }
}

If you end up using that FileScanner class, you need to implement INotifyCollectionChanged instead - that way, the ListBox knows what to add/remove dynamically.

Paul Betts
You're right w.r.t. why my previous code snippet worked. Updated. +1
Gishu
What I did end up doing was abstracting the collection in to a presenter class in an ObservableCollection and then only passing that to the scanner class when needed.
benPearce
A: 

(See Update below). WPF seems to be working alright. I put your code into a new project. The listbox updates whenever I click the button to invoke AddDirectory. You should not need any more code changes. The problem seems to be something else.. Are there multiple threads in your UI?

I didnt have the FileScanner type. So I created a dummy as follows.

public class FileScanner
   {
      string _path;
      public FileScanner()
      {     _path = @"c:\";      }
      public List<DirectoryInfo> Directories
      {
         get
         {
            return Directory.GetDirectories(_path).Select(path => new DirectoryInfo(path)).ToList();
         }
      }

      internal void AddDirectory(string path)
      {         _path = path;      }
   }

No changes to your FileScannerPresenter class. Or your listbox XAML. I created a Window with a DockPanel containing your listbox, a textbox and a button.

Update: Paul Betts is right. It works because I return a new list each time from the Bound property. Data binding with lists always messes me up. With more tinkering, the easy way to do this is:

  • Make FileScanner#Directories return an ObservableCollection<DirectoryInfo> (which implements INotifyCollectionChanged for you). Change all signatures all the way up to return this type instead of a List<DirectoryInfo>
  • FileScanner and FileScannerPresenter themselves do not have to implement any INotifyXXX interface.

    //  in FileScanner class def         
      public ObservableCollection<DirectoryInfo> Directories
      {
         get
         {  return _DirList;  }
      }
      internal void AddDirectory(string path)
      {
         _path = path;
         //var newItems = Directory.GetDirectories(_path).Select(thePath => new DirectoryInfo(thePath)).ToList();
         //_DirList.Concat( newItems );  -- doesn't work for some reason.
         foreach (var info in Directory.GetDirectories(_path).Select(thePath => new DirectoryInfo(thePath)).ToList())
         {
            _DirList.Add(info);
         }
      }
    
Gishu
Your code works because the Getter of the List<> returns a new list every time - if he returned the same list (i.e. a list that is instantiated at the .ctor of FileScanner, this wouldn't work afaik)
Paul Betts