How does one add a header in a ListBox in WPF? I have the below code and where the "Header - .." text is I'd like a header/group name of all the items below up to the next header:
<ListBox
x:Name="ItemsListBox"
Margin="0 16 0 16"
Style="{StaticResource MaterialDesignNavigationPrimaryListBox}">
<ListBox.Resources>
<Style TargetType="ScrollBar" BasedOn="{StaticResource MaterialDesignScrollBarMinimal}"/>
</ListBox.Resources>
Header - Config
<Separator/>
<ListBoxItem>Config menu 1</ListBoxItem>
<ListBoxItem>Config menu 2</ListBoxItem>
<ListBoxItem>Config menu 3</ListBoxItem>
<ListBoxItem>Config menu 4</ListBoxItem>
<ListBoxItem>Config menu 5</ListBoxItem>
<ListBoxItem>Config menu 6</ListBoxItem>
<ListBoxItem>Config menu 7</ListBoxItem>
Header - Tasks
<Separator/>
<ListBoxItem>Task menu 1</ListBoxItem>
<ListBoxItem>Task menu 2</ListBoxItem>
<ListBoxItem>Task menu 3</ListBoxItem>
<ListBoxItem>Task menu 4</ListBoxItem>
<ListBoxItem>Task menu 5</ListBoxItem>
<ListBoxItem>Task menu 6</ListBoxItem>
<ListBoxItem>Task menu 7</ListBoxItem>
<Separator/>
</ListBox>
Below is an example of what I'd like to achieve:
It doesn't neccesarily need to have icons with each listbox item, but mainly the headers like Options, User Settings and Administraton.
I'm only seeing other threads where people have multiple columns, but that creates two columns with different data and I would like to have 1 column but just add headers in bold and size higher font to separate the different menus from each other.
I'm using the MaterialDesign Theme for WPF - whether that is of any importance or relevance...
Thanks!
EDIT 1: As I haven't mentioned before - it doesn't have to be a ListBox, I used it as it is used in the code I used to write my menu:
<materialDesign:DrawerHost
IsLeftDrawerOpen="{Binding ElementName=MenuToggleButton, Path=IsChecked}">
<materialDesign:DrawerHost.LeftDrawerContent>
<DockPanel MinWidth="220">
<ToggleButton
Style="{StaticResource MaterialDesignHamburgerToggleButton}"
DockPanel.Dock="Top"
HorizontalAlignment="Right"
Margin="16"
IsChecked="{Binding ElementName=MenuToggleButton, Path=IsChecked, Mode=TwoWay}"/>
<TextBox
x:Name="DemoItemsSearchBox"
Text="{Binding SearchKeyword, UpdateSourceTrigger=PropertyChanged}"
DockPanel.Dock="Top"
Margin="16, 4"
Width="200"
materialDesign:HintAssist.Hint="Search"
materialDesign:HintAssist.IsFloating="True"
materialDesign:TextFieldAssist.HasClearButton="True"
materialDesign:TextFieldAssist.HasOutlinedTextField="True"
materialDesign:TextFieldAssist.DecorationVisibility="Collapsed"
materialDesign:TextFieldAssist.TextFieldCornerRadius="4"/>
<ListBox
x:Name="ItemsListBox"
Margin="0 16 0 16"
Style="{StaticResource MaterialDesignNavigationPrimaryListBox}">
<ListBox.Resources>
<Style TargetType="ScrollBar" BasedOn="{StaticResource MaterialDesignScrollBarMinimal}"/>
</ListBox.Resources>
-- This is where the ListBox items would be --
</ListBox>
</DockPanel>
</materialDesign:DrawerHost.LeftDrawerContent>
<DockPanel>
<materialDesign:ColorZone
Padding="16"
materialDesign:ShadowAssist.ShadowDepth="Depth2"
Mode="PrimaryMid"
DockPanel.Dock="Top">
<DockPanel>
<StackPanel Orientation="Horizontal">
<ToggleButton
x:Name="MenuToggleButton"
Style="{StaticResource MaterialDesignHamburgerToggleButton}"
IsChecked="False"
Click="MenuToggleButton_OnClick"
AutomationProperties.Name="HamburgerToggleButton"/>
</StackPanel>
<materialDesign:PopupBox
DockPanel.Dock="Right"
PlacementMode="BottomAndAlignRightEdges"
StaysOpen="False">
<StackPanel>
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock
Text="Light"
Margin="0 0 10 0"/>
<ToggleButton
x:Name="DarkModeToggleButton"
Click="MenuDarkModeButton_Click"
Grid.Column="1"/>
<TextBlock
Text="Dark"
Margin="10 0 0 0"
Grid.Column="2"/>
<TextBlock
Text="Enabled"
Margin="0 10 10 0"
Grid.Row="1"/>
<ToggleButton
x:Name="ControlsEnabledToggleButton"
Margin="0 10 0 0"
IsChecked="{Binding ControlsEnabled}"
Grid.Row="1"
Grid.Column="1"/>
</Grid>
<Separator/>
<Button
Content="Hello World"
/>
<Button
Content="Nice Popup"
/>
<Button
Content="Can't Touch This"
IsEnabled="False"/>
<Separator/>
<Button
Content="Goodbye"
/>
</StackPanel>
</materialDesign:PopupBox>
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="22"
Margin="-152,0,0,0"
Text="Hi"/>
</DockPanel>
</materialDesign:ColorZone>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ScrollViewer
x:Name="MainScrollViewer"
Grid.Row="1"
materialDesign:ScrollViewerAssist.IsAutoHideEnabled="True"
HorizontalScrollBarVisibility="{Binding SelectedItem.HorizontalScrollBarVisibilityRequirement, FallbackValue=Disabled}"
VerticalScrollBarVisibility="{Binding SelectedItem.VerticalScrollBarVisibilityRequirement, FallbackValue=Disabled}" >
<ContentControl
Margin="{Binding MarginRequirement, FallbackValue=16}"/>
</ScrollViewer>
<materialDesign:Snackbar
x:Name="MainSnackbar"
MessageQueue="{materialDesign:MessageQueue}"
Grid.Row="1"/>
</Grid>
</DockPanel>
</materialDesign:DrawerHost>
CodePudding user response:
Probably not my Final answer, as still uses the MVVM from the demo, but using the demo project:
in the MainWindowViewModel.cs, add public ListCollectionView DemoLCV { get; }
after public ObservableCollection<DemoItem> DemoItems { get; }
and add
DemoLCV = new ListCollectionView(DemoItems);
DemoLCV.GroupDescriptions.Add(new PropertyGroupDescription("Name"));
after
MoveNextCommand = new AnotherCommandImplementation(
_ =>
{
if (!string.IsNullOrWhiteSpace(SearchKeyword))
SearchKeyword = string.Empty;
SelectedIndex ;
},
_ => SelectedIndex < DemoItems.Count - 1);
then in the MainWindow.xaml amend the ListBox to:
<ListBox
x:Name="DemoItemsListBox"
Margin="0 16 0 16"
SelectedIndex="{Binding SelectedIndex}"
SelectedItem="{Binding SelectedItem, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding DemoLCV}"
PreviewMouseLeftButtonUp="UIElement_OnPreviewMouseLeftButtonUp"
AutomationProperties.Name="DemoPagesListBox"
Style="{StaticResource MaterialDesignNavigationPrimaryListBox}">
<ListBox.Resources>
<Style TargetType="ScrollBar" BasedOn="{StaticResource MaterialDesignScrollBarMinimal}"/>
</ListBox.Resources>
<ListBox.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListBox.GroupStyle>
<ListBox.ItemTemplate>
<DataTemplate DataType="domain:DemoItem">
<TextBlock Text="{Binding Name}" Margin="24 4 0 4" AutomationProperties.AutomationId="DemoItemPage"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This adds the menu item name as a category heading for each item.
This is just to demonstrate the concept.
to expand on the idea, with useful headings, add another property to the DemoItem class, e.g. categoryName, and populate this with the different categories on initialisation of the DemoItems collection.
Then change DemoLCV.GroupDescriptions.Add(new PropertyGroupDescription("Name"));
in this example to DemoLCV.GroupDescriptions.Add(new PropertyGroupDescription("categoryName"));
to group by your categories.
You can then play with the style of the TextBlock in the ListBox.GroupStyle to display the heading as desired.
I'll try and pop in an example later, plus see if I can come up with a non-MVVM option.
CodePudding user response:
For a hardcoded xaml, non-MVVM solution, I would use a Menu instead of a ListBox. I generally only use ListBoxes for binding to lists of data/items.
We can also achieve your icons with this method.
Obviously, style to your liking, but if you take out your ListBox, you can replace with Menu as follows:
<ScrollViewer>
<Menu>
<Menu.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</Menu.ItemsPanel>
<Menu.Resources>
<Style TargetType="ScrollBar" BasedOn="{StaticResource MaterialDesignScrollBarMinimal}"/>
</Menu.Resources>
<TextBlock Text="Config" FontWeight="Bold" FontSize="20" IsEnabled="False" />
<Separator/>
<MenuItem>
<MenuItem.Header>
<DockPanel>
<materialDesign:PackIcon Kind="Settings" Margin="3" />
<TextBlock Text="Config item 1" Margin="3" />
</DockPanel>
</MenuItem.Header>
</MenuItem>
<MenuItem >
<MenuItem.Header>
<DockPanel>
<materialDesign:PackIcon Kind="Settings" Margin="3" />
<TextBlock Text="Config item 2" Margin="3" />
</DockPanel>
</MenuItem.Header>
</MenuItem>
<MenuItem >
<MenuItem.Header>
<DockPanel>
<materialDesign:PackIcon Kind="Settings" Margin="3" />
<TextBlock Text="Config item 3" Margin="3" />
</DockPanel>
</MenuItem.Header>
</MenuItem>
<MenuItem >
<MenuItem.Header>
<DockPanel>
<materialDesign:PackIcon Kind="Settings" Margin="3" />
<TextBlock Text="Config item 4" Margin="3" />
</DockPanel>
</MenuItem.Header>
</MenuItem>
<MenuItem >
<MenuItem.Header>
<DockPanel>
<materialDesign:PackIcon Kind="Settings" Margin="3" />
<TextBlock Text="Config item 5" Margin="3" />
</DockPanel>
</MenuItem.Header>
</MenuItem>
<MenuItem >
<MenuItem.Header>
<DockPanel>
<materialDesign:PackIcon Kind="Settings" Margin="3" />
<TextBlock Text="Config item 6" Margin="3" />
</DockPanel>
</MenuItem.Header>
</MenuItem>
<MenuItem >
<MenuItem.Header>
<DockPanel>
<materialDesign:PackIcon Kind="Settings" Margin="3" />
<TextBlock Text="Config item 7" Margin="3" />
</DockPanel>
</MenuItem.Header>
</MenuItem>
<TextBlock Text="Tasks" FontWeight="Bold" FontSize="20" />
<Separator/>
<MenuItem Header="Task item 1" />
<MenuItem Header="Task item 2" />
<MenuItem Header="Task item 3" />
<MenuItem Header="Task item 4" />
<MenuItem Header="Task item 5" />
<MenuItem Header="Task item 6" />
<MenuItem Header="Task item 7" />
</Menu>
</ScrollViewer>
This produces (I have only added icons to the top half, to illustrate how):