如何在 WPF ListBox 中添加 headers

How to add headers in WPF ListBox

如何在 WPF 的 ListBox 中添加 header?我有以下代码,其中“Header - ..”文本是我想要下面所有项目的 header/group 名称,直到下一个 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>

下面是我想要实现的示例:

不一定需要每个列表框项目都有图标,但主要是 header 选项、用户设置和管理等图标。

我只看到其他线程,其中人们有多个列,但这会创建两列不同的数据,我想有 1 列,但只需添加粗体的 headers 和更大的字体将不同的菜单彼此分开。

我正在为 WPF 使用 MaterialDesign 主题 - 无论它是否重要或相关...

谢谢!

编辑 1:正如我之前没有提到的 - 它不一定是列表框,我使用它是因为它在我用来编写菜单的代码中使用:

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

可能不是我的最终答案,因为仍然使用演示中的 MVVM,但使用演示项目:

在MainWindowViewModel.cs中,在public ObservableCollection<DemoItem> DemoItems { get; }

之后添加public ListCollectionView DemoLCV { get; }

并添加

DemoLCV = new ListCollectionView(DemoItems);
DemoLCV.GroupDescriptions.Add(new PropertyGroupDescription("Name"));

之后

MoveNextCommand = new AnotherCommandImplementation(
           _ =>
           {
               if (!string.IsNullOrWhiteSpace(SearchKeyword))
                   SearchKeyword = string.Empty;

               SelectedIndex++;
           },
           _ => SelectedIndex < DemoItems.Count - 1);

然后在MainWindow.xaml中修改ListBox为:

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

这会将菜单项名称添加为每个项目的类别标题。

这只是为了演示这个概念。

要使用有用的标题扩展这个想法,将另一个 属性 添加到 DemoItem class,例如categoryName,并在 DemoItems collection.

初始化时用不同的类别填充它

然后将此示例中的 DemoLCV.GroupDescriptions.Add(new PropertyGroupDescription("Name")); 更改为 DemoLCV.GroupDescriptions.Add(new PropertyGroupDescription("categoryName")); 以按您的类别分组。

然后您可以使用 ListBox.GroupStyle 中的 TextBlock 样式来根据需要显示标题。

稍后我将尝试弹出一个示例,并看看我是否可以想出一个 non-MVVM 选项。

对于硬编码 xaml、非 MVVM 解决方案,我会使用菜单而不是列表框。我通常只使用 ListBoxes 绑定到 data/items.

列表

我们也可以用这种方法实现你的图标。

当然,风格随你喜欢,但如果你取出你的ListBox,你可以用Menu替换如下:

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

这会产生(我只在上半部分添加了图标,以说明如何):