Home > Software design >  How to add headers in WPF ListBox
How to add headers in WPF ListBox

Time:11-05

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:

enter image description here

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):

Snip of Menu

  • Related