Использование HashSets с ObservableCollection с WPF

Я использую ListBox для поддержки списка элементов в приложении WPF. Источник данных ListBox - это HashSet, заключенный в ObservableCollection. т.е. у меня есть следующий код:

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

... где shackListing - это элемент управления ListBox, а shackSet - в коллекции ICollection. Однако всякий раз, когда я добавляю что-либо в shackSet после добавления первого элемента, я вижу несколько элементов в ListBox. т.е. как будто недавно добавленные элементы добавляются в список независимо от того, добавлены ли они в набор. Когда я смотрю на подписи ICollection # Add:

void Add(T obj);

... и HashSet # Добавить:

bool Add(T obj); 

... это наводит меня на мысль, что есть ошибка, которая влияет на обернутые HashSets, когда вновь добавленные элементы добавляются в ListBox независимо от того, что ObservableCollection не имеет возможности узнать, действительно ли объект был добавлен в базовую коллекцию, потому что возвращаемый тип ICollection # Добавить недействительно. Кто-нибудь еще может это подтвердить?


person Alex Marshall    schedule 24.11.2009    source источник


Ответы (3)


Когда вы создаете новую коллекцию ObservableCollection с другой коллекцией, вы не обертываете эту коллекцию, вы создаете новую, в которой все элементы переданной коллекции копируются в ObservableCollection. Если вы хотите использовать ObservableCollection для единственной цели DataBinding, не смотрите дальше, вы можете выполнить привязку к любому IEnumerable в WPF. Это, к сожалению, имеет недостаток, заключающийся в том, что WPF не всегда правильно принимает изменения в связанной коллекции. Если это проблема, вам, вероятно, придется создать свой собственный наблюдаемый хеш-набор:

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); 
    } 
}

РЕДАКТИРОВАТЬ: как уже было указано, вы не можете наследовать от HashSet для реализации INotifyCollectionChanged. Однако, если вы посмотрите на код (используя Reflector) для класса HashSet, он довольно прост, и вам будет слишком сложно имитировать эту функциональность самостоятельно.

person bitbonk    schedule 24.11.2009
comment
Конечно, вы можете выполнить привязку напрямую к хеш-набору, но если элемент добавлен, WPF не заметит. - person Cameron MacFarland; 25.11.2009
comment
+1 за объяснение. Однако, что касается предлагаемого решения, я бы предпочел унаследовать от HashSet и реализовать INotifyCollectionChanged ... - person Thomas Levesque; 25.11.2009
comment
+1 к тому, что сказал Томас, за исключением того, что вы не можете наследовать от HashSet, а также реализовывать INotifyCollectionChanged, поскольку HashSet методы не являются виртуальными и не могут быть переопределены. Вам нужно будет реализовать ICollection, и ваша реализация должна будет обернуть HashSet и генерировать уведомления для всех методов изменения. - person Pavel Minaev; 25.11.2009
comment
@Pavel: хороший момент ... Должен признаться, я не проверял, были ли методы Hashset виртуальными;) - person Thomas Levesque; 25.11.2009
comment
Я хотел бы отметить, что, хотя у этого класса может быть интерфейс хеш-набора, он не работает. Хэш-набор называется так потому, что он использует хэш-коды для поиска элементов в (в идеале) постоянном времени. Ваша реализация занимает линейное время. Таким образом, я бы использовал его только в ситуациях, которые не критичны к производительности. Кроме того, я бы, вероятно, просто назвал это ObservableSet (без Hash) частью. - person Daniel Wolf; 16.07.2012
comment
Это не наблюдаемый HashSet. HashSet - это неупорядоченные наборы с повышением производительности. Взгляните на этот github.com/BellaCode/Public/tree/master/ObservableHashSet - person Vijay Chavda; 16.06.2017

В соответствии с ответом bitbonk, но я хотел переопределить метод add (T item), но вы не можете, поэтому вместо этого я создал метод append (T item):

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

И затем в моем коде позади:

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());
    }
}

В приведенном выше примере у меня есть два списка, listboxAvailableColumns, в которых есть список строк, которые пользователь может выбрать двойным щелчком, который добавляет выбор во второе окно списка, listboxConsolidationColumns. Дубликаты не допускаются, и это отлично работает с ObservableSetCollection точно так же, как указано выше.

Xaml просто:

<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>
person SurfingSanta    schedule 22.04.2015

Как сказал битбонк, ObservableCollection не обертывает Hashset, а вместо этого копирует его элементы.

Если вам нужен Observable Hashset, ознакомьтесь с Как сделать Observable Хешсет на C #?

person Cameron MacFarland    schedule 24.11.2009
comment
Есть ли у Microsoft какой-либо механизм для отправки запросов на добавление функций? Мне кажется, что Observable (Hash) Set - это то, что уже должно быть в .NET framework. - person Alex Marshall; 25.11.2009