WPF 制作侧边栏菜单之MenuItem

  • Post author:
  • Post category:其他


有小伙伴问我有没有做过菜单栏,这我确实没做过,不过现在做还不晚吧,
e3cb6c59ca54e4285fe289b4d5cead9d.png

先来做一个MenuItem,使用MVVM模式写,这样创建菜单的时候,只要绑定datacontext,就ok了,使用极为方便,还可以自定义颜色相关的属性。

先来看一下效果:

下面就来看看代码喽:

首先创建一个自定义控件类:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
using System.Windows.Media;


namespace WPFDemos
{
    [DefaultProperty("MenuItems")]
    [ContentProperty("MenuItems")]
    [TemplatePart(Name = LB, Type = typeof(ListBox))]
    public class SideMenuItem : Control
    {
        private const string LB = "LB";
        private ListBox _listBox;
        public bool IsExpanded
        {
            get { return (bool)GetValue(IsExpandedProperty); }
            set { SetValue(IsExpandedProperty, value); }
        }
        public static readonly DependencyProperty IsExpandedProperty =
            DependencyProperty.Register("IsExpanded", typeof(bool), typeof(SideMenuItem), new PropertyMetadata(false));


        public List<object> MenuItems
        {
            get { return (List<object>)GetValue(MenuItemsProperty); }
            set { SetValue(MenuItemsProperty, value); }
        }
        public static readonly DependencyProperty MenuItemsProperty =
            DependencyProperty.Register("MenuItems", typeof(List<object>), typeof(SideMenuItem), new PropertyMetadata(default(List<object>)));


        public Brush ToggleBackground
        {
            get { return (Brush)GetValue(ToggleBackgroundProperty); }
            set { SetValue(ToggleBackgroundProperty, value); }
        }
        public static readonly DependencyProperty ToggleBackgroundProperty =
            DependencyProperty.Register("ToggleBackground", typeof(Brush), typeof(SideMenuItem), new PropertyMetadata(new SolidColorBrush(Color.FromRgb(0x2d, 0x2d, 0x30))));


        public Brush MenuItemBackground
        {
            get { return (Brush)GetValue(MenuItemBackgroundProperty); }
            set { SetValue(MenuItemBackgroundProperty, value); }
        }
        public static readonly DependencyProperty MenuItemBackgroundProperty =
            DependencyProperty.Register("MenuItemBackground", typeof(Brush), typeof(SideMenuItem), new PropertyMetadata(new SolidColorBrush(Color.FromRgb(0x16, 0x18, 0x1D))));






        public Brush MenuItemSelectedBackground
        {
            get { return (Brush)GetValue(MenuItemSelectedBackgroundProperty); }
            set { SetValue(MenuItemSelectedBackgroundProperty, value); }
        }
        public static readonly DependencyProperty MenuItemSelectedBackgroundProperty =
            DependencyProperty.Register("MenuItemSelectedBackground", typeof(Brush), typeof(SideMenuItem), new PropertyMetadata(Brushes.Green));


        public static readonly RoutedEvent MenuItemSelectedChangedEvent = EventManager.RegisterRoutedEvent("MenuItemSelectedChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(SideMenuItem));


        public event RoutedEventHandler MenuItemSelectedChanged
        {
            add { AddHandler(MenuItemSelectedChangedEvent, value); }
            remove { RemoveHandler(MenuItemSelectedChangedEvent, value); }
        }
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            _listBox = (GetTemplateChild(LB) as ListBox) ?? throw new Exception("listbox Named with \"LB\" not found in the Template");
            _listBox.SelectionChanged -= _listBoxSelectionChanged;
            _listBox.SelectionChanged += _listBoxSelectionChanged;
        }


        private void _listBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (!(e.Source is ListBox)) return;
            RoutedEventArgs args = new RoutedEventArgs()
            {
                RoutedEvent = MenuItemSelectedChangedEvent,
                Source = _listBox,
            };
            RaiseEvent(args);
        }
    }
}

然后创建一个控件的ViewModel类,BaseViewModel类就是大家都知道的基类,就不粘代码了,如果需要可以联系我:

using System.Collections.Generic;
using System.Windows.Media;
namespace WPFDemos
{
    public class SideMenuItemViewModel : BaseViewModel
    {
        private string _headerText;
        public string HeaderText
        {
            get { return _headerText; }
            set
            {
                _headerText = value;
                OnPropertyChanged(nameof(HeaderText));
            }
        }
        private List<object> _items = new List<object>();
        public List<object> Items
        {
            get { return _items; }
            set
            {
                _items = value;
                OnPropertyChanged(nameof(Items));
            }
        }
        private Geometry _iconGeometry;
        public Geometry IconGeometry
        {
            get { return _iconGeometry; }
            set {
                _iconGeometry = value;
                OnPropertyChanged(nameof(IconGeometry));
            }
        }
    }
}

然后在创建一个资源字典SideMenu.xaml,添加控件所需的样式:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:WPFDemos">
    <Geometry x:Key="DownGeometry">M445.406 731.963L93.35 339.737A87.354 87.354 0 0 1 71 281.387C71 233.123 110.15 194 158.444 194h704.111a87.476 87.476 0 0 1 58.39 22.336c35.95 32.226 38.951 87.474 6.704 123.4L575.593 731.964a87.415 87.415 0 0 1-6.705 6.7c-35.95 32.227-91.235 29.227-123.482-6.7z</Geometry>
    <Geometry x:Key="UpGeometry">M575.594 216.037L927.65 608.263a87.354 87.354 0 0 1 22.35 58.35C950 714.877 910.85 754 862.556 754H158.445a87.476 87.476 0 0 1-58.39-22.336c-35.95-32.226-38.951-87.474-6.704-123.4l352.056-392.227a87.415 87.415 0 0 1 6.705-6.7c35.95-32.227 91.235-29.227 123.482 6.7z</Geometry>
    <Geometry x:Key="IconInfo">M497 87c245.214 0 444 198.786 444 444S742.214 975 497 975 53 776.214 53 531 251.786 87 497 87z m1.15 331.275c-18.423 0-33.357 14.934-33.357 33.357v331.275c0 18.423 14.934 33.357 33.357 33.357 18.423 0 33.358-14.934 33.358-33.357V451.632c0-18.423-14.935-33.357-33.358-33.357zM497 254.938c-24.14 0-43.71 19.054-43.71 42.56 0 23.504 19.57 42.559 43.71 42.559s43.71-19.055 43.71-42.56c0-23.505-19.57-42.56-43.71-42.56z</Geometry>


    <Style x:Key="ToggleButtonStyle1" TargetType="ToggleButton">
        <Setter Property="Background" Value="Transparent"/>
        <Setter Property="VerticalContentAlignment" Value="Center"/>
        <Setter Property="Padding" Value="0"/>
        <Setter Property="Margin" Value="0"/>
        <Setter Property="VerticalAlignment" Value="Center"/>
        <Setter Property="HorizontalContentAlignment" Value="Center"/>
        <Setter Property="BorderThickness" Value="0"/>
        <Setter Property="HorizontalAlignment" Value="Center"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ToggleButton">
                    <Border SnapsToDevicePixels="true"
                            BorderThickness="{TemplateBinding BorderThickness}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            Background="{TemplateBinding Background}">
                        <ContentPresenter Name="UnCheckedElement"
                            Margin="{TemplateBinding Padding}"
                            HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                            VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                            RecognizesAccessKey="True"
                            SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter Property="Opacity" Value="0.9"/>
                        </Trigger>
                        <Trigger Property="IsPressed" Value="True">
                            <Setter Property="Opacity" Value="0.6"/>
                        </Trigger>
                        <Trigger Property="IsEnabled" Value="False">
                            <Setter Property="Opacity" Value="0.4"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
</Style>
    <Style x:Key="MenuHeaderStyle1" TargetType="Expander">
        <Setter Property="Foreground" Value="LightGray" />
        <Setter Property="Background" Value="#2D2D30" />
        <Setter Property="HorizontalContentAlignment" Value="Left" />
        <Setter Property="VerticalContentAlignment" Value="Center" />
        <Setter Property="BorderThickness" Value="0" />
        <Setter Property="MinHeight" Value="50" />
        <Setter Property="MinWidth" Value="220" />
        <Setter Property="FontSize" Value="14" />
        <Setter Property="Template" >
            <Setter.Value>
                <ControlTemplate TargetType="Expander">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="Auto" />
                        </Grid.RowDefinitions>
                        <Border x:Name="BorderHeader"
                                ClipToBounds="True"
                                BorderBrush="{TemplateBinding BorderBrush}" 
                                BorderThickness="{TemplateBinding BorderThickness}" 
                                Background="{TemplateBinding Background}">
                            <ToggleButton HorizontalAlignment="Stretch" 
                                          HorizontalContentAlignment="Stretch" 
                                          Focusable="False" 
                                          Padding="10,0,0,0" 
                                          Foreground="{TemplateBinding Foreground}" 
                                          IsChecked="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" 
                                          Width="{TemplateBinding Width}" 
                                          Height="{TemplateBinding MinHeight}" 
                                          Style="{StaticResource ToggleButtonStyle1}">
                                <Grid>
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="40" />
                                        <ColumnDefinition />
                                        <ColumnDefinition Width="32" />
                                    </Grid.ColumnDefinitions>
                                    <Path Name="PathIcon" 
                                          IsHitTestVisible="False" 
                                          Grid.Column="0" 
                                          Stretch="Uniform" 
                                          Fill="{TemplateBinding Foreground}" 
                                          Height="16"
                                          Data="{Binding DataContext.IconGeometry,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=local:SideMenuItem}}"
                                          VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                                          HorizontalAlignment="Center" />
                                    <ContentPresenter ContentSource="Header" 
                                                      Grid.Column="1"
                                                      HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
                                                      VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
                                    <Path Name="PathArrow" 
                                          IsHitTestVisible="False" 
                                          Grid.Column="2" 
                                          Stretch="Uniform" 
                                          Fill="{TemplateBinding Foreground}" 
                                          Data="{StaticResource DownGeometry}" 
                                          Margin="0,0,10,0" Width="12" 
                                          Height="12" 
                                          VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                                          HorizontalAlignment="Center" />
                                </Grid>
                            </ToggleButton>
                        </Border>
                        <Border x:Name="LeftFlag" Width="5" Visibility="Collapsed" Background="#009688" HorizontalAlignment="Left"/>
                        <ContentPresenter Name="ExpandSite" Visibility="Collapsed" Grid.Row="1" />
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsExpanded" Value="true">
                            <Setter Property="Visibility" TargetName="ExpandSite" Value="Visible" />
                            <Setter Property="Data" TargetName="PathArrow" Value="{StaticResource UpGeometry}" />
                        </Trigger>
                        <Trigger Property="IsMouseOver" Value="True" SourceName="BorderHeader">
                            <Setter Property="Visibility" Value="Visible" TargetName="LeftFlag" />
                            <Setter Property="Foreground" Value="White" />
                        </Trigger>
                        <Trigger Property="IsExpanded" Value="True">
                            <Setter Property="Visibility" Value="Visible" TargetName="LeftFlag" />
                            <Setter Property="Foreground" Value="White" />
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
</Style>
    <Style x:Key="ListBoxStyle1" TargetType="ListBox">
        <Setter Property="BorderThickness" Value="0"/>
        <Setter Property="Margin" Value="0"/>
        <Setter Property="Padding" Value="0"/>
        <Setter Property="Background" Value="Red"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ListBox">
                    <Border  BorderThickness="{TemplateBinding BorderThickness}">
                        <ScrollViewer Margin="{TemplateBinding Padding}"
                                    Focusable="false">
                            <StackPanel IsItemsHost="True" />
                        </ScrollViewer>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
</Style>
    <Style x:Key="MenuItemStyle1" TargetType="ListBoxItem">
        <Setter Property="SnapsToDevicePixels" Value="true"/>
        <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
        <Setter Property="Foreground" Value="LightGray"/>
        <Setter Property="Margin" Value="0"/>
        <Setter Property="Padding" Value="0"/>
        <Setter Property="MinHeight" Value="40"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ListBoxItem">
                    <Border Name="Border"
                            BorderThickness="0"
                                Padding="40 0 0 0" 
                                Background="{Binding MenuItemBackground,Mode=TwoWay,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=local:SideMenuItem}}" 
                                SnapsToDevicePixels="True">
                        <ContentPresenter VerticalAlignment="Center"/>
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsSelected" Value="true">
                            <Setter TargetName="Border" Property="Background" Value="{Binding MenuItemSelectedBackground,Mode=TwoWay,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=local:SideMenuItem}}"/>
                            <Setter Property="Foreground" Value="White"/>
                        </Trigger>
                        <Trigger Property="IsEnabled" Value="false">
                            <Setter Property="Foreground" Value="LightGray"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
</Style>


    <Style TargetType="local:SideMenuItem">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:SideMenuItem">
                    <Expander BorderThickness="0"
                              Header="{Binding HeaderText}" 
                              IsExpanded="{Binding IsExpanded,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=local:SideMenuItem}}"
                              Background="{TemplateBinding ToggleBackground}" 
                              Style="{StaticResource MenuHeaderStyle1}">
                        <ListBox x:Name="LB"
                            Style="{StaticResource ListBoxStyle1}"
                                 ItemsSource="{Binding Items}"
                                 ItemContainerStyle="{StaticResource MenuItemStyle1}">
                        </ListBox>
                    </Expander>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
</Style>
</ResourceDictionary>

然后在App.xaml里面添加引用:

<Application x:Class="WPFDemos.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:WPFDemos">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="pack://application:,,,/WPFDemos;component/Styles/SideMenu.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

最后就是创建一个窗体使用控件喽:

<Window x:Class="WPFDemos.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPFDemos"
        mc:Ignorable="d"
        x:Name="widnow"
        UseLayoutRounding="True"
        Background="LightBlue"
        Title="下拉菜单控件" Height="450" Width="800">
    <Grid>
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0 20 0 0">
            <local:SideMenuItem DataContext="{Binding ItemViewModel}"
                            MenuItemSelectedChanged="S"/>


            <local:SideMenuItem DataContext="{Binding ItemViewModel}"
                            Margin="10 0 0 0"
                            MenuItemBackground="#24ACF2"
                            MenuItemSelectedBackground="YellowGreen"
                            ToggleBackground="#007ACC"
                            MenuItemSelectedChanged="S"/>
        </StackPanel>
        <TextBlock x:Name="log" HorizontalAlignment="Center" FontSize="30" VerticalAlignment="Bottom" Margin="0 0 0 50"/>
    </Grid>
</Window>

窗体的后台代码如下,定义了viewmodel实体和绑定了方法:

using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace WPFDemos
{
    public partial class MainWindow : Window
    {
        public SideMenuItemViewModel ItemViewModel { get; set; }
        public MainWindow()
        {
            ItemViewModel = new SideMenuItemViewModel()
            {
                HeaderText = "快速开始",
                IconGeometry = FindResource("IconInfo") as Geometry,
                Items = new List<object>()
                {
                    "5.0新变化",
                    "第一个项目",
                    "第一个模块",
                    "自定义用户",
                    "捐赠",
                    "FAQ"
                }
            };
            InitializeComponent();
            DataContext = this;
        }
        private void S(object sender, RoutedEventArgs e)
        {
            var s = e.OriginalSource as ListBox;
            var str = s.SelectedItem;
            log.Text = str.ToString();
        }
    }
}

这里,视频中演示的效果就实现啦,效果图如下:

c4c18d1241c450aaac4b1b715e4d70af.png

结束喽~

如果喜欢,点个赞呗~

———————————–

公众号【Csharp编程大全】,需要进技术群交流的,请添加小编mm1552923!