Squirrel5853
Squirrel5853

Reputation: 2406

After re-setting the ItemsSource of a WPF ListView it throws an ArgumentException

I have a ListView within my application which is currently populated with 2 items.

<ListView Name="lstViewFolderSettings" Grid.Column="0" Grid.ColumnSpan="3" Grid.Row="0" SelectionMode="Single" SelectionChanged="lstViewFolderSettings_SelectionChanged">
    <ListView.View>
        <GridView>
            <GridViewColumn Width="100" Header="Type" DisplayMemberBinding="{Binding Name}"  />
            <GridViewColumn Width="250" Header="Folder" DisplayMemberBinding="{Binding FolderPath}" />
            <GridViewColumn Width="350" Header="XPath" DisplayMemberBinding="{Binding XPath}" />
        </GridView>
    </ListView.View>
</ListView>

I am then setting my ItemsSource like the following

lstViewFolderSettings.ItemsSource = fileSeperationSettings.FileSettings;

on SelectionChanged event I get the selected item which populates some controls. I then click save I then update my collection and reset the ItemsSource again

lstViewFolderSettings.ItemsSource = null;
lstViewFolderSettings.ItemsSource = fileSeperationSettings.FileSettings;

I have to set to null first otherwise the ListView does not update in the view

This all seems to work fine until I change my selection twice on the same item.

i.e.
select item 1 -> change -> update
select item 2
select item 1
select item 2 -> BANG!

The BANG! I refer to is

ArgumentException was unhandled
An item with the same key has already been added.

StackTrace:

at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
at System.Collections.Generic.Dictionary`2..ctor(IDictionary`2 dictionary, IEqualityComparer`1 comparer)
at System.Windows.Controls.Primitives.Selector.InternalSelectedItemsStorage..ctor(InternalSelectedItemsStorage collection, IEqualityComparer`1 equalityComparer)
at System.Windows.Controls.Primitives.Selector.SelectionChanger.ApplyCanSelectMultiple()
at System.Windows.Controls.Primitives.Selector.SelectionChanger.End()
at System.Windows.Controls.Primitives.Selector.SetSelectedHelper(Object item, FrameworkElement UI, Boolean selected)
at System.Windows.Controls.Primitives.Selector.NotifyIsSelectedChanged(FrameworkElement container, Boolean selected, RoutedEventArgs e)
at System.Windows.Controls.Primitives.Selector.OnSelected(Object sender, RoutedEventArgs e)
at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
at System.Windows.UIElement.RaiseEvent(RoutedEventArgs e)
at System.Windows.Controls.ListBoxItem.OnSelected(RoutedEventArgs e)

--- Update --- SelectionChanged event handler code.

private void lstViewFolderSettings_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    m_SelectedSetting = lstViewFolderSettings.SelectedItem as FileSetting;
    txtFolder.Text = m_SelectedSetting.FolderPath;
    txtType.Text = m_SelectedSetting.Name;
    txtXPath.Text = m_SelectedSetting.XPath;

     e.Handled = true;
}

-- Updated ----

So I now have this

ObservableCollection<FileSetting> _fileSettings;
public ObservableCollection<FileSetting> FileSettings
{
    get 
    {
        if (_fileSettings == null)
  {
            FileSeperationSettings fileSeperationSettings = m_config.GetSection("fileSeperationSettings") as FileSeperationSettings;

            _fileSettings = new ObservableCollection<FileSetting>(fileSeperationSettings.FileSettings.Cast<FileSetting>());
        }

        return _fileSettings;
    }
}

I add and remove from this collection

FileSettings.Add(fsSetting);
FileSettings.Remove(fsSetting);

I get the selected Item

m_SelectedSetting = lstViewFolderSettings.SelectedItem as FileSetting;

txtFolder.Text = m_SelectedSetting.FolderPath;
txtType.Text = m_SelectedSetting.Name;
txtXPath.Text = m_SelectedSetting.XPath;

I update the item

FileSetting fs = FileSettings.First(x => x.Name == m_SelectedSetting.Name);
fs.Name = txtType.Text;
fs.FolderPath = txtFolder.Text;
fs.XPath = txtXPath.Text;

The error occurs after I do an update and then change the selected Item for the second time...

Upvotes: 3

Views: 2436

Answers (2)

Squirrel5853
Squirrel5853

Reputation: 2406

I worked around this issue by reading the following can't clear WPF ListBox.SelectedItems collection I realised that the reason it would not remove my selectedItem was because it did not exist in the collection (HashCode had changed), or something mad like that...

So I changed the selectionChanged event to the following

FileSetting selectedItem;
private void lstViewFolderSettings_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    selectedItem = lstViewFolderSettings.SelectedItem as FileSetting;
    txtFolder.Text = selectedItem.FolderPath;
    txtType.Text = selectedItem.Name;
    txtXPath.Text = selectedItem.XPath;

    lstViewFolderSettings.UnselectAll();
}

So I now keep track of the selected item myself. This means I can set and re-set the ItemsSource as I please.

Upvotes: 1

Neil Barnwell
Neil Barnwell

Reputation: 42155

I'd move away from the code-behind and make more use of data binding.

Your collection should be a property of the UI's DataContext:

public class MyViewModelOrCodeBehindClass
{
    public FileSetting SelectedItem { get; set; }

    public ObservableCollection<FileSetting> FileSettings 
    {
        get; 
        private set; 
    }

    public MyViewModel()
    {
        FileSettings = new ObservableCollection<FileSetting>();
        // If you're using codebehind rather than having something 
        // else set this class as the datacontext:
        DataContext = this;
    }
}

In your view:

<ListView ItemsSource="{Binding FileSettings}" SelectedItem="{Binding SelectedItem}" />
<TextBlock Text="{Binding SelectedItem.Folder}" />
<TextBlock Text="{Binding SelectedItem.Name}" />
<TextBlock Text="{Binding SelectedItem.XPath}" />

In your code-behind/viewmodel you can then just add/remove items and the databinding will do the rest. Even better, if each item in the list implements INotifyPropertyChanged, then edits to those items will also be automatically updated in the UI. You shouldn't need to handle SelectionChanged really at all.

Upvotes: 0

Related Questions