如何在 WPF 列表框中添加标题

Posted

技术标签:

【中文标题】如何在 WPF 列表框中添加标题【英文标题】:How to add headers in WPF ListBox 【发布时间】:2021-12-18 17:58:40 【问题描述】:

如何在 WPF 的 ListBox 中添加标题?我有下面的代码,并且“标题 - ..”文本在哪里我想要下面所有项目的标题/组名称,直到下一个标题:

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

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

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

我只看到人们有多个列的其他线程,但这会创建具有不同数据的两列,我希望有 1 列,但只需添加粗体标题和更大的字体以将不同的菜单彼此分开.

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

谢谢!

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

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

【问题讨论】:

您能添加一张您想要达到的目标的图片吗? @DenisSchaf 完成。 必须是 ListBox 吗? 有可能吗? (您可以使用 ListBox 实现 - 我会使用绑定和 ListBox.GroupStyle - 但菜单可能更适合您想要实现的目标) @SimonEvans 不,不一定是 ListBox,只要能在我的 &lt;materialDesign:DrawerHost&gt; 中工作即可 【参考方案1】:

对于硬编码的 xaml、非 MVVM 解决方案,我会使用菜单而不是列表框。我通常只使用 ListBoxes 来绑定数据/项目列表。

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

很明显,你喜欢的风格,但如果你拿出你的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>

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

【讨论】:

这工作得很好——我唯一的问题是是否可以像 ListBox 一样过滤&lt;menu&gt;?如果没有,也不是问题,只是想知道它是否适用于该解决方案的任何未来用途。 Menu 确实支持绑定到 ItemsSource,因此如果通过绑定完成,您可以在您的集合上应用过滤器,但我认为该控件没有任何直接过滤选项。您始终可以附加到文本框的 KeyUp 事件,然后在后面的代码中适当地设置菜单项的可见性。 我试过这个private void ItemsSearchBox_TextChanged(object sender, TextChangedEventArgs e) foreach(MenuItem menuItem in ItemsMenu.Items) var menuItemText_split = menuItem.ToString().Split(' ',1); if (menuItemText_split[1].StartsWith(ItemsSearchBox.Text)) menuItem.Visibility = Visibility.Collapsed; 问题是我们的菜单不仅包含MenuItems,还包含TextBlockSeparator 在类型对象而不是 MenuItem 上循环,然后使用 GetType 测试 MenuItems【参考方案2】:

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

在 MainWindowViewModel.cs 中,在public ObservableCollection&lt;DemoItem&gt; 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 类添加另一个属性,例如categoryName,并在初始化 DemoItems 集合时使用不同的类别填充它。

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

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

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

【讨论】:

以上是关于如何在 WPF 列表框中添加标题的主要内容,如果未能解决你的问题,请参考以下文章

从 WPF 中的列表框中获取复选框项目

如何使 WPF 列表框中的列对于所有项目具有相同的宽度?

WPF:仅当列表框中的一项被选中时才启用按钮

WPF:删除样式列表框中焦点项目周围的虚线边框

WPF 从列表框中拖动项目并放入文本框中

WPF - 在列表框中上下拖放数据模板