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

您非常接近一个有效的解决方案。关键是知道组模板中控件的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();
}

我刚刚用分组组合框进行了类似的练习,所以我回答这个问题是希望虽然它可能不再与你相关,但它可能会帮助其他处于类似困境的人。

最新更新