3

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...

Squirrel5853
  • 2,376
  • 1
  • 18
  • 34
  • Yer gonna need to post the full exception including the stack trace... – Neil Barnwell Sep 03 '13 at 13:14
  • Why are you updating the ItemsSource every time? If the objects in that list implement INotifyPropertyChanged then you can change them, and the WPF databinding will update the UI for you anyway. You should really only be setting this stuff up once. – Neil Barnwell Sep 03 '13 at 13:16
  • 1
    In fact, if you can, use the databinding to set the source in the first place, rather than doing it with code-behind. – Neil Barnwell Sep 03 '13 at 13:16
  • @NeilBarnwell not full stack trace, but I think it highlights where it is erroring... Yes I have read about using ObservableCollection but I dont see why I should have to... I just want to update my collection myself and use a control to display my collection... should I just dynamically add Items instead of using ItemsSource? – Squirrel5853 Sep 03 '13 at 13:18
  • No, quite the opposite - as I said in my second comment - you should set the ItemsSource *once* only, to an `ObserableCollection` or similar, where each item implements `INotifyPropertyChanged`. Once that's all set up (with an empty list) and bound to the listview, you can add items, edit them, remove them etc, and the databinding will update the UI for you. – Neil Barnwell Sep 03 '13 at 13:21
  • @NeilBarnwell what I dont understand is why my hand is forced to use a collection which implements INotifyPropertyChanged... why can I not just set a datasource and be done? – Squirrel5853 Sep 03 '13 at 13:22
  • Well that's just the way WPF was meant to be used. You appear to be using WPF in the way that WinForms was designed to work. You can do that, but the idea is to separate UI from code-behind using the data binding system. – Neil Barnwell Sep 03 '13 at 13:27
  • Can you post your `SelectionChanged` implementation? – Neil Barnwell Sep 03 '13 at 13:30
  • @NeilBarnwell added SelectionChanged code – Squirrel5853 Sep 03 '13 at 13:39
  • Out of interest, why are you setting `Handled = true`? – Neil Barnwell Sep 03 '13 at 13:40
  • @NeilBarnwell just because... I was have been trying everything... – Squirrel5853 Sep 03 '13 at 13:45
  • @NeilBarnwell I have added an update to try and show what I am doing now... – Squirrel5853 Sep 03 '13 at 13:55
  • @NeilBarnwell I am now getting odd behaviour whereby after an update and then triggering the `SelectionChanged` event the selectedItem has not changed... – Squirrel5853 Sep 03 '13 at 14:00
  • This is just a messed up design. Why are you using LINQ to get the selected item when you have the selected item (m_SelectedSetting). If two items have the same name then it will find the first. Just implement INotifyProperty changed on FileSetting and the UI will be updated. Do NOT rebind lstViewFolderSettings.ItemsSource. I get you are new to WPF binding and UI update but you have to learn it some time. Notification works that way for a reason - you can accept or fight it but fighting it does not seem to be working. You could bind txtFolder to selected item in XAML. – paparazzo Sep 03 '13 at 14:13
  • @Blam yes I am starting to realise that it seems to be a one-way street with this... I am from a web background whereby I can simply set `DataSource` `DataBind` and done. I am really annoyed to be honest that I am having to implement a whole `ViewModel` design for what a simple test application I am working on... – Squirrel5853 Sep 03 '13 at 14:32
  • But you don't have to implement a ViewModel for a simple app. Update to the UI are notified two ways. For an exiting item via INotifyPropertyChanged. For add and remove via an ObservableCollection. – paparazzo Sep 03 '13 at 14:39
  • I think I have found part of the issue I have... I cannot seem to remove or clear `SelectedItems` collection which is a property on the `ListView` after calling either methods `Clear()` or `RemoveAt(0)` the collection count of `SelectedItems` remains 1. So when I go to get the SelectedItem it always selects the first of the 2 `SelectedItems` and then when it finally triggers it tries to add the item into this `SelectedItems` collection which already has both items... this is why I get the `ArgumentException`... – Squirrel5853 Sep 03 '13 at 14:49

2 Answers2

1

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.

Community
  • 1
  • 1
Squirrel5853
  • 2,376
  • 1
  • 18
  • 34
  • It's not "mad" -- the whole point of a hash code is for use with hash tables, to quickly locate an object in the hash table. The HashCode of an object must be an immutable property, not changing after the object is constructed. I encountered the same bug when I installed .NET 4.5, which now uses a hash table for selected items – Jim Balter Mar 25 '14 at 22:30
0

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.

Neil Barnwell
  • 41,080
  • 29
  • 148
  • 220
  • 1
    I've had this same error before and I *do* use data binding on *all* of my UI controls, so I'm not sure if this *is* the fix for this problem. Still, I would recommend that the user uses this binding methodology in general anyway. – Sheridan Sep 03 '13 at 13:27
  • Chances are the problem is in his `OnSelected` handler, then. I'll mention it. – Neil Barnwell Sep 03 '13 at 13:29
  • Still get the same error, following the same steps as provided above... plus my UI is not handled automatically. I find I am having to `CollectionViewSource.GetDefaultView(lstViewFolderSettings).Refresh();`. – Squirrel5853 Sep 03 '13 at 13:35
  • Yeah the automatic refresh probably isn't happening if you're not using something that notifies of updates. Not sure why you're getting that error though. I don't see where you are replacing the ItemsSource though still, in your example? – Neil Barnwell Sep 03 '13 at 13:46
  • @NeilBarnwell missed the setting of `DataContext` of the `ListView` which is now binding without me explictly having to set it in codeBehind... however I am still getting major issues with this... surely it should not be this hard to set a datasource of a `ListView`? – Squirrel5853 Sep 03 '13 at 13:46
  • @NeilBarnwell I have now removed all updating of `ItemsSource` and instead work with the collection property of the class which is bound to the `ListView`, however this is not working as expected. – Squirrel5853 Sep 03 '13 at 13:48
  • It's really not. It's just hard without a full picture of how all this fits together to see what is causing that error. Fact is, you shouldn't be setting the ItemsSource to null then back again. Does it go away without that call? Could you post a gist or something that's a minimal example that reproduces the error that I could look at? – Neil Barnwell Sep 03 '13 at 13:48
  • @SecretSquirrel Did you implement INotifyPropertyChanged on FileSetting? If you had you should not have to .Refresh. Post the code from FileSetting. – paparazzo Sep 03 '13 at 14:24