Callum Linington
Callum Linington

Reputation: 14417

Binding ObservableCollection contained in ViewModel to ListView

I've searched and searched but can't obtain a proper, helpful answer.

I have a MainWindow wpf window. Its DataContext is set to its ViewModel.

I have a ListView which is binded to an ObservableCollection in the ViewModel:

        <ListView Grid.Row="1" Grid.Column="0" Margin="2" Name="sources_ListView" Grid.RowSpan="1" ItemsSource="{Binding Path=Sources}">
            <ListView.View>
                <GridView>
                    <GridViewColumn Width="290" Header="Name"
                                    DisplayMemberBinding="{Binding Path=OriginalPath}"/>
                    <GridViewColumn Width="80" Header="Type" 
                                    DisplayMemberBinding="{Binding Path=Type}"/>
                </GridView>
            </ListView.View>
        </ListView>

RelayCommand:

        public ICommand BrowseFileFolderCommand
        {
            get
            {
                if (_browseFileFolderCommand == null)
                {
                    _browseFileFolderCommand = new RelayCommand(o => 
                               {
                                  _sources.Add(new SourceItem(selectedPath, new DirectoryInfo(selectedPath)));
                               }, null);
                }
                return _browseFileFolderCommand;
            }
        }

Now obviously what the Lambda function does, wouldn't work in the real world as I have taken it out of context, but accept the fact that it does add SourceItem to the ObservableCollection _sources and that there is a Public Sources which gets the _sources. I have also made the type that ObservableCollection takes use INotifyChangedProperty.

When I use that RelayCommand which is inside a button which adds a source to the ObservableCollection, the ListView doesn't update?

Thanks for any help

EDIT SourceItem:

public class SourceItem : ISourceItem, INotifyPropertyChanged
{
    DirectoryInfo _sourceFolder;
    public DirectoryInfo SourceFolder { get { return _sourceFolder; } private set { _sourceFolder = value; } }

    FileInfo _sourceFile;
    public FileInfo SourceFiles { get { return _sourceFile; } private set { _sourceFile = value; } }

    string _originalPath;
    public string OriginalPath { get { return _originalPath; } private set { _originalPath = value; OnPropertyChanged("OriginalPath"); } }

    bool _isFolder;
    public bool IsFolder { get { return _isFolder; } }

    // display friendly property of IsFolder
    public string Type { get { return _isFolder == true ? "Folder" : "File"; } }

    public SourceItem(string originalPath, DirectoryInfo sourceFolder)
    {
        _originalPath = originalPath;
        _sourceFolder = sourceFolder;
        _sourceFile = null;

        _isFolder = true;
    }

    public SourceItem(string originalPath, FileInfo sourceFile)
    {
        _originalPath = originalPath;
        _sourceFile = sourceFile;
        _sourceFolder = null;

        _isFolder = false;
    }



    #region INotifyPropertyChanged Members

    /// <summary>
    /// Raised when a property on this object has a new value.
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Raises this object's PropertyChanged event.
    /// </summary>
    /// <param name="propertyName">The property that has a new value.</param>
    protected virtual void OnPropertyChanged(string propertyName)
    {
        this.VerifyPropertyName(propertyName);

        PropertyChangedEventHandler handler = this.PropertyChanged;
        if (handler != null)
        {
            var e = new PropertyChangedEventArgs(propertyName);
            handler(this, e);
        }
    }

    #endregion // INotifyPropertyChanged Members

    #region Debugging Aides

    /// <summary>
    /// Warns the developer if this object does not have
    /// a public property with the specified name. This 
    /// method does not exist in a Release build.
    /// </summary>
    [Conditional("DEBUG")]
    [DebuggerStepThrough]
    public void VerifyPropertyName(string propertyName)
    {
        // Verify that the property name matches a real.
        // public, instance property on this object
        if (TypeDescriptor.GetProperties(this)[propertyName] == null)
        {
            string msg = String.Format("Invalid property name: {0}", propertyName);

            if (this.ThrowOnInvalidPropertyName)
                throw new Exception(msg);
            else
                Debug.Fail(msg);
        }
    }

    /// <summary>
    /// Returns whether an exception is thrown, or if a Debug.Fail() is used
    /// when an invalid property name is passed to the VerifyPropertyName method.
    /// The default value is false, but subclasses used by unit tests might 
    /// override this property's getter to return true.
    /// </summary>
    protected virtual bool ThrowOnInvalidPropertyName { get; private set; }

    #endregion
}

Upvotes: 1

Views: 1544

Answers (1)

Rachel
Rachel

Reputation: 132618

Use the public version of the property to add the new item

Sources.Add(new SourceItem(selectedPath, new DirectoryInfo(selectedPath)));

You are currently adding the item to the private version of your property (_sources), while your UI is bound to the public version of the property (Sources), so your UI does not get the CollectionChanged notification the private version of the property raises, so does not know it needs to update.

The alternative is to simply raise the PropertyChanged event for your class manually to tell the UI to update. This is usually what I do when I want to add a lot of items to my collection at the same time, but only have the UI update once.

_sources.Add(new SourceItem(selectedPath, new DirectoryInfo(selectedPath)));
RaisePropertyChanged("Sources");

Upvotes: 1

Related Questions