zoukankan      html  css  js  c++  java
  • wpf 菜单样式和绑定树形数据

    前言

    在wpf开发中,经常会使用到Menu和ContentMenu。但是原生的样式比较简陋,对于比较追求界面美好的人来说是十分不友好的。那么,这就涉及到对Menu的样式修改了。与此同时,我们还希望Menu自动Binding到视图数据模型上,根据数据项自动展开MenuItem。接下来就对这些想法做一简单实现。

    视图模型

    假设我们的菜单项里有描述意图的缩略图和文字需要展示。那么我们需要有名字和存有图片路径的属性。额外的,还需要一个Children集合来存放子项,以形成树形数据。

    视图模型Class

    public class CommonTreeModel
    {
        /// <summary>
        /// 名字
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// 图片的路径
        /// </summary>
        public string IconPath { get; set; }
        /// <summary>
        /// 子项
        /// </summary>
        public ObservableCollection<CommonTreeModel> Children { get; set; }
    }
    

    数据mock

    我们使用Bogus进行模拟数据的产生。使用nuget搜索Bogus添加即可。

    /// <summary>
    /// 菜单项数据集,前端将binding到该属性上
    /// </summary>
    public ObservableCollection<CommonTreeModel> MenuTreeSource { get; set; }
    
    private void InitData()
    {
        var general = new Bogus.Faker<CommonTreeModel>()
            .RuleFor(t => t.Name, t => t.Commerce.Product())//名字:商业产品
            .RuleFor(t => t.IconPath, t => t.Image.LoremFlickrUrl(32, 32));//图片:使用LoremFlick网站的图片
        var rd = new Random(DateTime.Now.Millisecond);//随机数
        MenuTreeSource = GenerateTreeData(general, rd, 10, 3, 10);
    }
    
    private ObservableCollection<CommonTreeModel> GenerateTreeData(Faker<CommonTreeModel> faker, Random rd, int topCount, int subMin, int subMaxm, int level = 0, int levelLimit = 4)
    {
        var list = new ObservableCollection<CommonTreeModel>(faker.Generate(level == 0 ? topCount : rd.Next(subMin, subMaxm)));
        level++;
        if (level < levelLimit)
        {
            foreach (var item in list)
            {
                if (rd.Next() % 2 == 0)
                {
                    item.Children = GenerateTreeData(faker, rd, topCount, subMin, subMaxm, level, levelLimit);
                }
            }
        }
        return list;
    }
    

    到这里,我们生成了最大层级可能为4的一棵树:MenuTreeSource

    WPF中的Menu

    我们先写前端Menu绑定到数据。

    <Menu ItemsSource="{Binding MenuTreeSource}" HorizontalAlignment="Center">
        <Menu.ItemTemplate>
            <HierarchicalDataTemplate ItemsSource="{Binding Children}">
                <Label Content="{Binding Name}"></Label>
            </HierarchicalDataTemplate>
        </Menu.ItemTemplate>
    </Menu>
    

    原始的样式展示效果如下,总共有四级,并且第一级和后三级不一致。

    我们只需重写MenuItem的样式,就能够改变菜单的展示效果:

    <Style TargetType="MenuItem">
        <Setter Property="FontSize" Value="20"></Setter>
        <Setter Property="Foreground" Value="#b8d00a"></Setter>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate>
                    <Grid>
                        <Grid.Style>
                            <Style TargetType="Grid">
                                <Setter Property="Background" Value="#b8d00a"/>
                                <Style.Triggers>
                                    <Trigger Property="IsMouseOver" Value="True">
                                        <Setter Property="Background" Value="#f46a56"></Setter>
                                    </Trigger>
                                </Style.Triggers>
                            </Style>
                        </Grid.Style>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="auto"></ColumnDefinition>
                            <ColumnDefinition Width="*"></ColumnDefinition>
                            <ColumnDefinition Width="auto"></ColumnDefinition>
                        </Grid.ColumnDefinitions>
                        <Image Width="32" Height="32" Margin="5" Source="{Binding IconPath}" />
                        <Label Content="{Binding Name}" Margin="10 0" Grid.Column="1" VerticalContentAlignment="Center"/>
                        <Label Name="MoreLbl" Content=">>" Grid.Column="2" VerticalContentAlignment="Center">
                            <Label.Style>
                                <Style TargetType="Label">
                                    <Setter Property="Visibility" Value="Visible"></Setter>
                                    <Style.Triggers>
                                        <DataTrigger Binding="{Binding Children}" Value="{x:Null}">
                                            <Setter Property="Visibility" Value="Collapsed"></Setter>
                                        </DataTrigger>
                                    </Style.Triggers>
                                </Style>
                            </Label.Style>
                        </Label>
                        <Popup AllowsTransparency="True"
                            IsOpen="{Binding Path=IsSubmenuOpen, RelativeSource={RelativeSource TemplatedParent}}"
                            Placement="Right" x:Name="SubMenuPopup" Focusable="false">
                            <Border x:Name="SubMenuBorder"  BorderThickness="1" BorderBrush="Black">
                                <StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Cycle"/>
                            </Border>
                        </Popup>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    

    控件Popup用于展示子级菜单项。其中StackPanel上的IsItemsHost="True"保证正确处理子级。

    名为MoreLbl的Label控件用于提示是否有子级。这里简略用>>标识一下,读者可以图片Image或者Path控件做一个漂亮的样式。

    最终效果如下,一级和次级依然不统一。

    统一Menu的一级和次级

    为了统一,我们需要对Menu的样式加以调整。最简单的方式是对子项承载容器进行替换,我们用StackPanel控件,并把内容排序设置为垂直方向即可。

    <Menu ItemsSource="{Binding MenuTreeSource}" HorizontalAlignment="Center">
        <Menu.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel Orientation="Vertical"></StackPanel>
            </ItemsPanelTemplate>
        </Menu.ItemsPanel>
        <Menu.ItemTemplate>
            <HierarchicalDataTemplate ItemsSource="{Binding Children}"></HierarchicalDataTemplate>
        </Menu.ItemTemplate>
    </Menu>
    

    最终效果如下:

    WPF中的ContextMenu控件

    ContextMenu与Menu不同,他可以作为很多控件的Popup形式的鼠标右键弹出菜单,比如作为ListBox,Label,Grid等的内容菜单都是可以的,我们只需鼠标右键就可以将其弹出。

    我们用Label简单举例:

    <Label Content="鼠标右键弹出ContextMenu" HorizontalAlignment="Center"HorizontalContentAlignment="Center" FontSize="25" Background="#221a12"Foreground="#b8d00a"
         MouseDown="Label_MouseDown">
        <Label.ContextMenu>
            <ContextMenu ItemsSource="{Binding MenuTreeSource}">
                <ContextMenu.ItemTemplate>
                    <HierarchicalDataTemplate ItemsSource="{Binding Children}" />
                </ContextMenu.ItemTemplate>
            </ContextMenu>
        </Label.ContextMenu>
    </Label>
    

    之前的样式都是全局样式,默认会自动使用。效果如下:

    能够发现一级的样式稍微有些不一样,我们需要改ContextMenu的样式

    <Style TargetType="{x:Type ContextMenu}">
        <Setter Property="SnapsToDevicePixels" Value="True"/>
        <Setter Property="OverridesDefaultStyle" Value="True"/>
        <Setter Property="Grid.IsSharedSizeScope" Value="true"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ContextMenu}">
                    <Border 
                        BorderBrush="Black"
                        BorderThickness="1" >
                        <StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Cycle"/>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    

    简单重写Template即可。

    总结

    这里只简单介绍如何去重写样式模板来改变菜单展示效果。具体使用中大家需要更好看的效果请自行设计。该wpf项目框架使用dotnet core3.1版本。在farmwork中应该一样。

  • 相关阅读:
    About chrysanthemum and matrimony vine
    time stamp in javascript
    Feeling kind of the sorrow
    从零开始入门 K8s | Kubernetes API 编程利器:Operator 和 Operator Framework
    从零开始入门 K8s | Kubernetes API 编程范式
    Apache Flink 1.10.0 发布 | 云原生生态周报 Vol. 38
    回顾 | Kubernetes SIG-Cloud-Provider-Alibaba 首次网研会(含 PPT 下载)
    开发函数计算的正确姿势——运行 Selenium Java
    Serverless 解惑——函数计算如何访问 Mongo 数据库
    开发函数计算的正确姿势——使用 brotli 压缩大文件
  • 原文地址:https://www.cnblogs.com/hsxian/p/12521501.html
Copyright © 2011-2022 走看看