有小伙伴问我有没有做过菜单栏,这我确实没做过,不过现在做还不晚吧,WPF 制作侧边栏菜单之MenuItem_ide

先来做一个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();
}
}
}

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

WPF 制作侧边栏菜单之MenuItem_xml_02


结束喽~


如果喜欢,点个赞呗~

-----------------------------------