Управление группировкой ссылок ItemsControl

У меня есть ItemControl с ListCollectionView в качестве источника данных. ItemControl использует элементы управления Expander для группировки элементов. Когда я делаю ListCollectionView.Refresh(), тогда расширенные элементы управления Expander сворачиваются. Как оставить расширенные элементы управления расширенными?

<ItemsControl HorizontalAlignment="Stretch" ItemsSource="{Binding}" Name="ItemsControl1">
        <ItemsControl.Resources>
            <DataTemplate DataType="{x:Type GroupingWithExpander:DataItem}">
                <TextBlock Text="{Binding Text}" />
            </DataTemplate>
        </ItemsControl.Resources>
        <ItemsControl.GroupStyle>
            <GroupStyle>
                <GroupStyle.ContainerStyle>
                    <Style TargetType="GroupItem">
                        <Setter Property="Template">
                            <Setter.Value>
                                <ControlTemplate TargetType="GroupItem">
                                    <Expander BorderThickness="1" BorderBrush="Gray" IsExpanded="{Binding IsExpanded}">
                                        <Expander.Resources>
                                            <GroupingWithExpander:DataGroupToNameConverter x:Key="DataGroupToNameConverter" />
                                        </Expander.Resources>
                                        <Expander.Header>
                                            <TextBlock Text="{Binding Name, Converter={StaticResource DataGroupToNameConverter}}" />
                                        </Expander.Header>
                                        <StackPanel Orientation="Vertical">
                                            <ItemsPresenter Margin="5 0" />
                                        </StackPanel>
                                    </Expander>
                                </ControlTemplate>
                            </Setter.Value>
                        </Setter>
                    </Style>
                </GroupStyle.ContainerStyle>
            </GroupStyle>
        </ItemsControl.GroupStyle>
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <VirtualizingStackPanel Orientation="Vertical" IsItemsHost="True" />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    </ItemsControl>
    <TextBox Name="TextBox1" />
    <Button Click="Button_Click">
        <Button.Content>
            <TextBlock Text="do something" />
        </Button.Content>
    </Button>



 public partial class MainWindow : Window{
public MainWindow()
            {
                InitializeComponent();
                var group1 = new DataGroup {IsExpanded = false, Name = "group #1"};
                var group2 = new DataGroup {IsExpanded = true, Name = "group #2"};
                const int itemsCount = 6;
                DataItem[] dataItems = new DataItem[itemsCount];
                for (int i = 0; i < itemsCount; i++)
                {
                    DataItem item = new DataItem
                                        {
                                            Group = (i%2 == 0 ? group1 : group2),
                                            Text = System.IO.Path.GetRandomFileName()
                                        };
                    dataItems[i] = item;
                }
                ListCollectionView v = new ListCollectionView(dataItems);            
                v.GroupDescriptions.Add(new PropertyGroupDescription("Group"));
                v.Filter = FilterDataItem;
                this.DataContext = v;            
            }

            private bool FilterDataItem(object o)
            {
                DataItem item = o as DataItem;
                return item.Contains(this.TextBox1.Text);            
            }

            private void Button_Click(object sender, RoutedEventArgs e)
            {
                ListCollectionView v = (ListCollectionView) this.DataContext;
                v.Refresh();
            }
    }

    class DataGroup : IEquatable<DataGroup>, INotifyPropertyChanged
{
    public string Name { get; set; }

    private bool _isExpanded;
    public bool IsExpanded
    {
        get { return _isExpanded; }
        set 
        { 
            _isExpanded = value; 
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs("IsExpanded"));
        }
    }

    public bool Equals(DataGroup other)
    {
        return other != null && this.Name == other.Name;
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

    class DataItem
{
    public string Text { get; set; }
    public DataGroup Group { get; set; }

    public virtual bool Contains(string filterString)
    {
        return Text != null && (string.IsNullOrEmpty(filterString) || Text.Contains(filterString));
    }
}




    class DataGroupToNameConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (!(value is DataGroup)) throw new ArgumentException("type DataGroup is expected", "value");
        DataGroup g = (DataGroup) value;
        return g.Name;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

1 ответ

Вы были очень близки к рабочему решению. Ключ зная, что DataContext для элементов управления в шаблоне группы будет CollectionViewGroup (на самом деле потомок этого, так как CollectionViewGroup абстрактно). Каркас создает эти группы для вас, когда он связан с источником данных, к которому применена группировка.

CollectionViewGroup имеет свойство с именем Itemsэто коллекция, которая содержит элементы, принадлежащие к группе, представленной CollectionViewGroup, Поскольку в каждой группе будет хотя бы один член, вы можете использовать первого члена Items чтобы добраться до DataGroupнапример, Items[0].Group.IsExpanded, но есть еще более простой способ.

CollectionViewGroup также имеет несколько вводящее в заблуждение свойство, называемое Name, На первый взгляд, вы можете подумать, что это будет string содержит название группы, но на самом деле это object который содержит ссылку на экземпляр того, что вы сказали своему источнику данных для группировки.

В вашем примере, вы сказали это сгруппировать по Group свойство, которое является DataGroup, так Name на самом деле будет указывать DataGroup экземпляр для этой группы.

Итак, вы можете добраться до свойств DataGroup используя пунктирный путь в ваших привязках, то есть:

<Expander IsExpanded="{Binding Name.IsExpanded}">
    <Expander.Header>
        <TextBlock Text="{Binding Name.Name}" />
    </Expander.Header>
    <StackPanel Orientation="Vertical">
        <ItemsPresenter Margin="5 0" />
    </StackPanel>
</Expander>

Я внес эти изменения, и ваш пример теперь работает нормально. Вам также больше не нужен конвертер значений.

Я также изменил событие нажатия кнопки, чтобы продемонстрировать, что привязка работает в обоих направлениях (она переключает состояние раскрытия каждой группы при нажатии кнопки):

private void Button_Click(object sender, RoutedEventArgs e)
{
    ListCollectionView v = (ListCollectionView)this.DataContext;

    var groups = new List<DataGroup>();
    for (int i = 0; i < v.Count; i++)
    {
        var item = v.GetItemAt(i) as DataItem;
        if (item != null)
        {
            if (!groups.Contains(item.Group))
            {
                groups.Add(item.Group);
            }
        }
    }

    foreach (var group in groups)
    {
        group.IsExpanded = !group.IsExpanded;
    }

    v.Refresh();
}

Я только что выполнил аналогичное упражнение с сгруппированным ComboBox, поэтому я отвечаю на это в надежде, что, хотя оно может больше не относиться к вам, оно может помочь кому-то, кто находится в подобном затруднительном положении.

Другие вопросы по тегам