I have a ListBox
where I did the grouping based on a property like this :
CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(listbox.ItemsSource); PropertyGroupDescription groupDescription = new PropertyGroupDescription("CurrentDate"); view.GroupDescriptions.Add(groupDescription);
And after grouping I want to add a vertical separator between the groups and I wrote a code like this:
<ListBox.GroupStyle> <GroupStyle> <GroupStyle.HeaderTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <Separator Style="{StaticResource {x:Static ToolBar.SeparatorStyleKey}}" /> <TextBlock Text="{Binding Path=Name}" FontWeight="Bold"/> </StackPanel> </DataTemplate> </GroupStyle.HeaderTemplate> <GroupStyle.Panel> <ItemsPanelTemplate> <VirtualizingStackPanel Orientation="Horizontal"/> </ItemsPanelTemplate> </GroupStyle.Panel> </GroupStyle> </ListBox.GroupStyle>
But it's appearing like this:
Whereas I want a separator to go down totally but when I am trying to increase the height of the separator the items goes down along with that.
2 Answers
Answers 1
Diagnosis
When you group items in a ListBox
using CollectionView
+ GroupStyle
what happens is the ListBox
displays a list of GroupItem
controls, each representing a group of items. A GroupItem
basically consists of a ContentPresenter
(for presenting the header) and an ItemsPresenter
(for presenting grouped items) put in a StackPanel
.
When you specify GroupStyle.HeaderTemplate
it will be used as ContentTemplate
for the mentioned ContentPresenter
. So if you increase the height of the Separator
it will still be contained in the ContentPresenter
causing it to grow vertically, and the items will still be stacked below it - hence your result.
Solution
What you need to do to achieve your goal is to re-template the GroupItem
so that the Separator
is displayed alongside the ContentPresenter
and ItemsPresenter
, and then wire it using GroupStyle.ContainerStyle
. For convenience, let's put it in ListBox.Resources
dictionary:
<ListBox (...)> <ListBox.Resources> <ControlTemplate x:Key="GroupItemTemplate" TargetType="{x:Type GroupItem}"> <DockPanel> <Separator DockPanel.Dock="Left" Style="{StaticResource {x:Static ToolBar.SeparatorStyleKey}}" /> <StackPanel> <ContentPresenter /><!-- This will be automatically wired --> <ItemsPresenter Margin="5,0,0,0" /><!-- So will this --> </StackPanel> </DockPanel> </ControlTemplate> </ListBox.Resource> <ListBox.GroupStyle> <GroupStyle> <GroupStyle.HeaderTemplate> <DataTemplate> <TextBlock Text="{Binding Path=Name}" FontWeight="Bold" /> </DataTemplate> </GroupStyle.HeaderTemplate> <GroupStyle.ContainerStyle> <Style TargetType="{x:Type GroupItem}"> <Setter Property="Template" Value="{StaticResource GroupItemTemplate}" /> </Style> </GroupStyle.ContainerStyle> (...) </GroupStyle> </ListBox.GroupStyle> (...) </ListBox>
Notice that I removed the separator from the header template.
Here are possible outcomes you may want to get (I put a blue border around the ListBox
to distinguish #3 and #4):
The code excerpt I provided will by default give you #1 (all separators are stretched vertically across the whole ListBox
).
To achieve #2 (separators stretch down only to the last item of the corresponding group) you should add <Setter Property="VerticalAlignment" Value="Top" />
to the GroupStyle.ContainerStyle
. Alternatively you could put it on the DockPanel
inside the GroupItem
template instead.
To get #3 (separators stretch to the height of the largest group) you should add VerticalAlignment="Top"
to the panel inside the GroupStyle.Panel
(the VirtualizingStackPanel
in your case).
And finally #4 (the ListBox
itself is restricted to the size of the largest group) can be achievied by putting VerticalAlignment="Top"
on the ListBox
itself.
Answers 2
I cannot imagine an out-of-the-box solution to this, since you are trying to pivot groups. I've made an example, yet it cant resize the columns' width within the itemsarea but at header without using Seperators:
Code-Behind
public partial class Window1 { public Window1() { InitializeComponent(); this._items.Add(new Item { Name = "one", DateTime = DateTime.Today }); this._items.Add(new Item { Name = "two", DateTime = DateTime.Today.Subtract(new TimeSpan(1, 0, 0, 0)) }); this._items.Add(new Item { Name = "three", DateTime = DateTime.Today.Subtract(new TimeSpan(1, 0, 0, 0)) }); this._items.Add(new Item { Name = "four", DateTime = DateTime.Today.Add(new TimeSpan(1, 0, 0, 0)) }); this._items.Add(new Item { Name = "five", DateTime = DateTime.Today.Add(new TimeSpan(1, 0, 0, 0)) }); this.DataContext = this; } private ObservableCollection<Item> _items = new ObservableCollection<Item>(); public ObservableCollection<Item> Items => _items; } public abstract class ViewModelBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; [NotifyPropertyChangedInvocator] protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } public class Item : ViewModelBase { private string _name; private DateTime _dateTime; public string Name { get { return this._name; } set { if (value == this._name) return; this._name = value; this.OnPropertyChanged(); } } public DateTime DateTime { get { return this._dateTime; } set { if (value.Equals(this._dateTime)) return; this._dateTime = value; this.OnPropertyChanged(); } } }
Grouping with resources
<Window.Resources> <CollectionViewSource x:Key="CollectionViewSource" Source="{Binding Items}"> <CollectionViewSource.GroupDescriptions> <PropertyGroupDescription PropertyName="DateTime" /> </CollectionViewSource.GroupDescriptions> </CollectionViewSource> </Window.Resources>
ListBox
<ListBox ItemsSource="{Binding Source={StaticResource CollectionViewSource}}" Width="400" Height="200"> <ListBox.GroupStyle> <GroupStyle> <GroupStyle.HeaderTemplate> <DataTemplate> <GridViewColumnHeader Content="{Binding Name}"/> </DataTemplate> </GroupStyle.HeaderTemplate> <GroupStyle.Panel > <ItemsPanelTemplate> <VirtualizingStackPanel Orientation="Horizontal"/> </ItemsPanelTemplate> </GroupStyle.Panel> </GroupStyle> </ListBox.GroupStyle> <ListBox.ItemContainerStyle> <Style TargetType="ListBoxItem"> <Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter> <Setter Property="VerticalContentAlignment" Value="Stretch"></Setter> </Style> </ListBox.ItemContainerStyle> <ListBox.ItemTemplate> <DataTemplate> <Border BorderBrush="DarkGray" BorderThickness="0,0,1,0" Margin="-6,-2,-6,-2"> <StackPanel Margin="6,2,6,2"> <TextBlock Text="{Binding Name}"/> </StackPanel> </Border> </DataTemplate> </ListBox.ItemTemplate> </ListBox>
The keystone to this Workaround is to use a GridViewColumnHeader
as HeaderTemplate for the GroupStyle.
Probably a better solution might be to change the ListBox to a ListView and set the ListView's View
-Property to GridView
. This requires to change your datastructure though.
Note
The ListBox's grouping was never meant to perform the Task you are trying to do. The default way with listbox and grouping is to have expanders in the ListBox's content area as described here
0 comments:
Post a Comment