English 中文(简体)
Using HashSets with ObservableCollection with WPF
原标题:

I m using a ListBox to maintain a list of items in a WPF application. The ListBox data source is a HashSet wrapped in an ObservableCollection. ie, I have the following code :

this.shackSet = new ObservableCollection<Shack>(new HashSet<Shack>());
this.shackListing.ItemsSource = this.shackSet;

... where shackListing is a ListBox control, and shackSet in an ICollection. However, whenever I add anything to shackSet after the addition of the first item, I see multiple items in the ListBox. ie It s like newly added items are getting added to the list regardless of whether they re added to the set. When I look at the signatures of ICollection#Add :

void Add(T obj);

... and HashSet#Add :

bool Add(T obj); 

... this leads me to believe there s a bug that affects wrapped HashSets where newly added items get added to the ListBox regardless because the ObservableCollection has no way of telling whether the object was actually added to the underlaying collection because the return type of ICollection#Add is void. Can anybody else confirm this ?

最佳回答

When you create a new ObservableCollection with another collection you are not wrapping that collection, you create a new one where all items of the passed collection are copied to the ObservableCollection. If you want to use an ObservableCollection for the sole purpose of DataBinding, look no further, you can bind to any IEnumerable in WPF. This unfortuantely has the drawback that WPF will not always correctly pickup changes to the bound collection. If this is an issue you d probably have to create your own obeservable hashset:

public class ObservableHashSet<T> : ObservableCollection<T>  
{ 
    protected override void InsertItem(int index, T item) 
    { 
        if (Contains(item)) 
        {
            throw new ItemExistsException(item); 
        }
        base.InsertItem(index, item); 
    } 

    protected override void SetItem(int index, T item) 
    { 
        int i = IndexOf(item); 
        if (i >= 0 && i != index)
        {
             throw new ItemExistsException(item); 
        }       
        base.SetItem(index, item); 
    } 
}

EDIT: AS already has been pointed out, you can not inherit from HashSet to implement INotifyCollectionChanged. However if you look at the code (using Reflector) for the HashSet class it is pretty simple it should be too hard to mimic that functionality yourself.

问题回答

As per bitbonk s answer, but I wanted to override the add(T item) method, but you can t, so I created an append(T item) method instead:

public class ObservableSetCollection<T> : ObservableCollection<T> {
    public void Append(T item) {
        if (Contains(item)) return;
        base.Add(item);
    }
}

And then in my code behind:

public partial class MainWindow : Window {
    private ObservableSetCollection<string> consolidationHeaders;

    public MainWindow() {
        InitializeComponent();
        initialize();
    }

    private void initialize() {
        consolidationHeaders = new ObservableSetCollection<string>();
        listboxConsolidationColumns.ItemsSource = consolidationHeaders;
    }

    .
    .
    .


    private void listboxAvailableColumns_MouseDoubleClick(object sender, MouseButtonEventArgs e) {
        consolidationHeaders.Append(listboxAvailableColumns.SelectedValue.ToString());
    }

    private void listboxConsolidationColumns_MouseDoubleClick(object sender, MouseButtonEventArgs e) {
        consolidationHeaders.Remove(listboxConsolidationColumns.SelectedValue.ToString());
    }
}

In the above I have two listboxes, listboxAvailableColumns, which has a list of strings that the user can select by double-clicking, which adds the selection to the second listbox, listboxConsolidationColumns. No duplicates are allowed, and this works perfectly with the ObservableSetCollection exactly as above.

The xaml is simply:

<Grid Margin="5,5,5,5">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="1*" />
        <ColumnDefinition Width="1*" />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="1*" />
    </Grid.RowDefinitions>
    <Label Grid.Row="0" Grid.Column="0" Margin="0,0,0,0" VerticalAlignment="Center" HorizontalAlignment="Center" Content="Available Columns"/>
    <Label Grid.Row="0" Grid.Column="1" Margin="0,0,0,0" VerticalAlignment="Center" HorizontalAlignment="Center" Content="Consolidation Columns"/>
    <ListBox  Grid.Row="1" Grid.Column="0" Name="listboxAvailableColumns" MouseDoubleClick="listboxAvailableColumns_MouseDoubleClick" />
    <ListBox  Grid.Row="1" Grid.Column="1" Name="listboxConsolidationColumns" MouseDoubleClick="listboxConsolidationColumns_MouseDoubleClick" />
</Grid>

As bitbonk said, ObservableCollection doesn t wrap the Hashset but copies its elements instead.

If you want an Observable Hashset check out How can I make an Observable Hashset in C# ?





相关问题
Anyone feel like passing it forward?

I m the only developer in my company, and am getting along well as an autodidact, but I know I m missing out on the education one gets from working with and having code reviewed by more senior devs. ...

NSArray s, Primitive types and Boxing Oh My!

I m pretty new to the Objective-C world and I have a long history with .net/C# so naturally I m inclined to use my C# wits. Now here s the question: I feel really inclined to create some type of ...

C# Marshal / Pinvoke CBitmap?

I cannot figure out how to marshal a C++ CBitmap to a C# Bitmap or Image class. My import looks like this: [DllImport(@"test.dll", CharSet = CharSet.Unicode)] public static extern IntPtr ...

How to Use Ghostscript DLL to convert PDF to PDF/A

How to user GhostScript DLL to convert PDF to PDF/A. I know I kind of have to call the exported function of gsdll32.dll whose name is gsapi_init_with_args, but how do i pass the right arguments? BTW, ...

Linqy no matchy

Maybe it s something I m doing wrong. I m just learning Linq because I m bored. And so far so good. I made a little program and it basically just outputs all matches (foreach) into a label control. ...

热门标签