WPF 实现抽屉菜单

Posted dotNET跨平台

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WPF 实现抽屉菜单相关的知识,希望对你有一定的参考价值。

 分享一个WPF 实现抽屉菜单

抽屉菜单

作者:WPFDevelopersOrg

原文链接:https://github.com/WPFDevelopersOrg/WPFDevelopers

  • 框架使用大于等于.NET40

  • Visual Studio 2022;

  • 项目使用 MIT 开源许可协议;

  • 更多效果可以通过GitHub[1]|码云[2]下载代码;

  • 由于在WPF中没有现成的类似UWP的抽屉菜单,所以我们自己实现一个。

1) DrawerMenu.cs 代码如下。

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

namespace WPFDevelopers.Controls

    public class DrawerMenu : ContentControl
    
        public new static readonly DependencyProperty ContentProperty =
            DependencyProperty.Register("Content", typeof(List<DrawerMenuItem>), typeof(DrawerMenu),
                new FrameworkPropertyMetadata(null));

        public static readonly DependencyProperty IsOpenProperty =
            DependencyProperty.Register("IsOpen", typeof(bool), typeof(DrawerMenu), new PropertyMetadata(true));

        public static readonly DependencyProperty MenuIconColorProperty =
            DependencyProperty.Register("MenuIconColor", typeof(Brush), typeof(DrawerMenu),
                new PropertyMetadata(Brushes.White));

        public static readonly DependencyProperty SelectionIndicatorColorProperty =
            DependencyProperty.Register("SelectionIndicatorColor", typeof(Brush), typeof(DrawerMenu),
                new PropertyMetadata(DrawingContextHelper.Brush));

        public static readonly DependencyProperty MenuItemForegroundProperty =
            DependencyProperty.Register("MenuItemForeground", typeof(Brush), typeof(DrawerMenu),
                new PropertyMetadata(Brushes.Transparent));

        static DrawerMenu()
        
            DefaultStyleKeyProperty.OverrideMetadata(typeof(DrawerMenu),
                new FrameworkPropertyMetadata(typeof(DrawerMenu)));
        

        public new List<DrawerMenuItem> Content
        
            get => (List<DrawerMenuItem>)GetValue(ContentProperty);
            set => SetValue(ContentProperty, value);
        

        public bool IsOpen
        
            get => (bool)GetValue(IsOpenProperty);
            set => SetValue(IsOpenProperty, value);
        


        public Brush MenuIconColor
        
            get => (Brush)GetValue(MenuIconColorProperty);
            set => SetValue(MenuIconColorProperty, value);
        


        public Brush SelectionIndicatorColor
        
            get => (Brush)GetValue(SelectionIndicatorColorProperty);
            set => SetValue(SelectionIndicatorColorProperty, value);
        

        public Brush MenuItemForeground
        
            get => (Brush)GetValue(MenuItemForegroundProperty);
            set => SetValue(MenuItemForegroundProperty, value);
        

        public override void BeginInit()
        
            Content = new List<DrawerMenuItem>();
            base.BeginInit();
        
    

2) DrawerMenuItem.cs 代码如下。

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace WPFDevelopers.Controls

    public class DrawerMenuItem : ListBoxItem
    
        public static readonly DependencyProperty TextProperty =
            DependencyProperty.Register("Text", typeof(string), typeof(DrawerMenuItem),
                new PropertyMetadata(string.Empty));

        public static readonly DependencyProperty IconProperty =
            DependencyProperty.Register("Icon", typeof(ImageSource), typeof(DrawerMenuItem),
                new PropertyMetadata(null));

        public static readonly DependencyProperty SelectionIndicatorColorProperty =
            DependencyProperty.Register("SelectionIndicatorColor", typeof(Brush), typeof(DrawerMenuItem),
                new PropertyMetadata(DrawingContextHelper.Brush));

        public static readonly DependencyProperty SelectionCommandProperty =
            DependencyProperty.Register("SelectionCommand", typeof(ICommand), typeof(DrawerMenuItem),
                new PropertyMetadata(null));

        static DrawerMenuItem()
        
            DefaultStyleKeyProperty.OverrideMetadata(typeof(DrawerMenuItem),
                new FrameworkPropertyMetadata(typeof(DrawerMenuItem)));
        

        public string Text
        
            get => (string)GetValue(TextProperty);
            set => SetValue(TextProperty, value);
        


        public ImageSource Icon
        
            get => (ImageSource)GetValue(IconProperty);
            set => SetValue(IconProperty, value);
        

        public Brush SelectionIndicatorColor
        
            get => (Brush)GetValue(SelectionIndicatorColorProperty);
            set => SetValue(SelectionIndicatorColorProperty, value);
        

        public ICommand SelectionCommand
        
            get => (ICommand)GetValue(SelectionCommandProperty);
            set => SetValue(SelectionCommandProperty, value);
        
    

3) DrawerMenu.xaml 代码如下。

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:po="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options"
                    xmlns:controls="clr-namespace:WPFDevelopers.Controls">
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="Basic/ControlBasic.xaml"/>
    </ResourceDictionary.MergedDictionaries>

    <Style x:Key="DrawerMenuToggleButton" TargetType="ToggleButton" BasedOn="StaticResource ControlBasicStyle">
        <Setter Property="IsChecked" Value="False"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="x:Type ToggleButton">
                    <Grid Background="TemplateBinding Background">
                        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Style.Triggers>
            <Trigger Property="IsMouseOver" Value="True">
                <Setter Property="Opacity" Value="0.8" />
                <Setter Property="Cursor" Value="Hand" />
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="x:Type ToggleButton">
                            <Border
                                        Background="TemplateBinding Background"
                                        BorderBrush="Black"
                                        BorderThickness="1">
                                <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
                            </Border>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Trigger>
        </Style.Triggers>
    </Style>
    <Style x:Key="DrawerMenuListBox" TargetType="ListBox" BasedOn="StaticResource ControlBasicStyle">
        <Setter Property="Background" Value="Transparent" />
        <Setter Property="BorderBrush" Value="Transparent" />
        <Setter Property="BorderThickness" Value="0"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate>
                    <ScrollViewer>
                        <ItemsPresenter Margin="0" />
                    </ScrollViewer>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <Style x:Key="ButtonFocusVisual">
        <Setter Property="Control.Template">
            <Setter.Value>
                <ControlTemplate>
                    <Border>
                        <Rectangle 
            Margin="2"
            StrokeThickness="1"
            Stroke="#60000000"
            StrokeDashArray="1 2"/>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    <!-- Fill Brushes -->

    <SolidColorBrush x:Key="NormalBrush" Color="Transparent"  po:Freeze="True"/>
    <SolidColorBrush x:Key="DarkBrush" Color="#ddd"  po:Freeze="True"/>
    <SolidColorBrush x:Key="PressedBrush" Color="#80FFFFFF"  po:Freeze="True"/>
    <SolidColorBrush x:Key="DisabledForegroundBrush" Color="Transparent"  po:Freeze="True"/>
    <SolidColorBrush x:Key="DisabledBackgroundBrush" Color="Transparent"  po:Freeze="True"/>

    <!-- Border Brushes -->

    <SolidColorBrush x:Key="NormalBorderBrush" Color="Transparent"  po:Freeze="True"/>
    <SolidColorBrush x:Key="PressedBorderBrush" Color="Transparent"  po:Freeze="True"/>
    <SolidColorBrush x:Key="DefaultedBorderBrush" Color="Transparent"  po:Freeze="True"/>
    <SolidColorBrush x:Key="DisabledBorderBrush" Color="Transparent"  po:Freeze="True"/>


    <Style x:Key="DrawerMenuItemButtonStyle" TargetType="Button" BasedOn="StaticResource ControlBasicStyle">
        <Setter Property="FocusVisualStyle" Value="StaticResource ButtonFocusVisual"/>
        <Setter Property="MinHeight" Value="23"/>
        <Setter Property="MinWidth" Value="75"/>
        <Setter Property="Cursor" Value="Hand" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Button">
                    <Border 
          x:Name="Border"  
          CornerRadius="0" 
          BorderThickness="0"
          Background="Transparent"
          BorderBrush="Transparent">
                        <ContentPresenter 
            HorizontalAlignment="Stretch"
            VerticalAlignment="Stretch"
            RecognizesAccessKey="True"/>
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsKeyboardFocused" Value="true">
                            <Setter TargetName="Border" Property="BorderBrush" Value="DynamicResource DefaultedBorderBrush" />
                        </Trigger>
                        <Trigger Property="IsDefaulted" Value="true">
                            <Setter TargetName="Border" Property="BorderBrush" Value="DynamicResource DefaultedBorderBrush" />
                        </Trigger>
                        <Trigger Property="IsMouseOver" Value="true">
                            <Setter TargetName="Border" Property="Background" Value="DynamicResource DarkBrush" />
                        </Trigger>
                        <Trigger Property="IsPressed" Value="true">
                            <Setter TargetName="Border" Property="Background" Value="DynamicResource PressedBrush" />
                            <Setter TargetName="Border" Property="BorderBrush" Value="DynamicResource PressedBorderBrush" />
                        </Trigger>
                        <Trigger Property="IsEnabled" Value="false">
                            <Setter TargetName="Border" Property="Background" Value="DynamicResource DisabledBackgroundBrush" />
                            <Setter TargetName="Border" Property="BorderBrush" Value="DynamicResource DisabledBorderBrush" />
                            <Setter Property="Foreground" Value="DynamicResource DisabledForegroundBrush"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>


    <Style TargetType="controls:DrawerMenuItem">
        <Setter Property="HorizontalContentAlignment" Value="Stretch" />
        <Setter Property="Foreground" Value="Binding RelativeSource=RelativeSource AncestorType=x:Type controls:DrawerMenu, Path=MenuItemForeground"/>
        <Setter Property="SelectionIndicatorColor" Value="Binding RelativeSource=RelativeSource AncestorType=x:Type controls:DrawerMenu, Path=SelectionIndicatorColor"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="controls:DrawerMenuItem">
                    <Button x:Name="PART_Button" Height="44"
                            Command="TemplateBinding SelectionCommand" 
                            ToolTip="TemplateBinding Text"
                            HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch"
                            Style="StaticResource DrawerMenuItemButtonStyle">
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="5"/>
                                <ColumnDefinition/>
                            </Grid.ColumnDefinitions>
                            <Grid Grid.ColumnSpan="2">
                                <Grid Width="300">
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="45"/>
                                        <ColumnDefinition/>
                                    </Grid.ColumnDefinitions>
                                    <Image Grid.Column="0" Source="TemplateBinding Icon" Margin="10,5,5,5"/>
                                    <TextBlock Text="TemplateBinding Text" Grid.Column="1"
                                                   Margin="10,0,0,0" HorizontalAlignment="Left" 
                                                   VerticalAlignment="Center" 
                                                   FontSize="StaticResource TitleFontSize"
                                                   Foreground="TemplateBinding Foreground"
                                                   TextWrapping="Wrap"/>
                                </Grid>
                            </Grid>
                            <Grid Name="PART_ItemSelectedIndicator" 
                                  Grid.Column="0" 
                                  Background="TemplateBinding SelectionIndicatorColor" 
                                  Visibility="Collapsed" />
                        </Grid>
                    </Button>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsSelected" Value="True">
                            <Setter TargetName="PART_ItemSelectedIndicator" Property="Visibility" Value="Visible" />
                        </Trigger>
                        <Trigger SourceName="PART_Button" Property="IsPressed" Value="True">
                            <Trigger.ExitActions>
                                <BeginStoryboard>
                                    <Storyboard>
                                        <BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="IsSelected">
                                            <DiscreteBooleanKeyFrame KeyTime="00:00:00" Value="True" />
                                        </BooleanAnimationUsingKeyFrames>
                                    </Storyboard>
                                </BeginStoryboard>
                            </Trigger.ExitActions>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    <Style TargetType="controls:DrawerMenu">
        <Setter Property="Width" Value="50"/>
        <Setter Property="Visibility" Value="Visible"/>
        <Setter Property="IsOpen" Value="True"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="controls:DrawerMenu">
                    <Grid Background="TemplateBinding Background">
                        <ToggleButton HorizontalAlignment="Left" Background="#333"
                                      VerticalAlignment="Top" Height="40" Width="50"
                                      IsChecked="Binding RelativeSource=RelativeSource AncestorType=x:Type controls:DrawerMenu, Path=IsOpen"
                                      Style="StaticResource DrawerMenuToggleButton">
                            <Path HorizontalAlignment="Center" 
                                  VerticalAlignment="Center" 
                                  Stretch="Uniform" Width="20" 
                                  Fill="TemplateBinding MenuIconColor"
                                  Data="StaticResource PathMenu"/>
                        </ToggleButton>
                        <ListBox ItemsSource="TemplateBinding Content" 
                                 HorizontalAlignment="Left" Margin="0,40,0,0" 
                                 VerticalAlignment="Top" 
                                 Style="StaticResource DrawerMenuListBox"
                                 ScrollViewer.HorizontalScrollBarVisibility="Disabled" 
                                 SelectedIndex="0"/>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Style.Triggers>
            <Trigger Property="IsOpen" Value="False">
                <Trigger.EnterActions>
                    <BeginStoryboard>
                        <Storyboard>
                            <DoubleAnimation 
                                         Storyboard.TargetProperty="Width"
                                         To="180"
                                         Duration="0:0:0.2"/>
                        </Storyboard>
                    </BeginStoryboard>
                </Trigger.EnterActions>
                <Trigger.ExitActions>
                    <BeginStoryboard>
                        <Storyboard>
                            <DoubleAnimation 
                                         Storyboard.TargetProperty="Width"
                                         To="50"
                                         Duration="0:0:0.2"/>
                        </Storyboard>
                    </BeginStoryboard>
                </Trigger.ExitActions>
            </Trigger>
        </Style.Triggers>
    </Style>
</ResourceDictionary>

4) DrawerMenuExample.xaml 代码如下。

<UserControl x:Class="WPFDevelopers.Samples.ExampleViews.DrawerMenu.DrawerMenuExample"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WPFDevelopers.Samples.ExampleViews.DrawerMenu"
             xmlns:wpfdev="https://github.com/WPFDevelopersOrg/WPFDevelopers"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid Background="#FF7B7BFF">
        <Grid.ColumnDefinitions>
            <ColumnDefinition  Width="Auto"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <wpfdev:DrawerMenu Background="#eee"
                          SelectionIndicatorColor="StaticResource PrimaryPressedSolidColorBrush" 
                          MenuItemForeground="StaticResource BlackSolidColorBrush" HorizontalAlignment="Left">
            <wpfdev:DrawerMenu.Content>
                <wpfdev:DrawerMenuItem Icon="pack://application:,,,/Images/CircularMenu/2.png" Text="主页"
                                      SelectionCommand="Binding HomeCommand,RelativeSource=RelativeSource AncestorType=local:DrawerMenuExample"/>
                <wpfdev:DrawerMenuItem Icon="pack://application:,,,/Images/CircularMenu/4.png" Text="Edge"
                                      SelectionCommand="Binding EdgeCommand,RelativeSource=RelativeSource AncestorType=local:DrawerMenuExample"/>
                <wpfdev:DrawerMenuItem Icon="pack://application:,,,/Images/CircularMenu/1.png" Text="云盘"
                                      SelectionCommand="Binding CloudCommand,RelativeSource=RelativeSource AncestorType=local:DrawerMenuExample"/>
                <wpfdev:DrawerMenuItem Icon="pack://application:,,,/Images/CircularMenu/8.png" Text="邮件"
                                      SelectionCommand="Binding MailCommand,RelativeSource=RelativeSource AncestorType=local:DrawerMenuExample"/>
                <wpfdev:DrawerMenuItem Icon="pack://application:,,,/Images/CircularMenu/6.png" Text="视频"
                                      SelectionCommand="Binding VideoCommand,RelativeSource=RelativeSource AncestorType=local:DrawerMenuExample"/>
            </wpfdev:DrawerMenu.Content>
        </wpfdev:DrawerMenu>
        <Frame Name="myFrame" Grid.Column="1" Margin="0,40,0,0"
               NavigationUIVisibility="Hidden"></Frame>
    </Grid>
</UserControl>

5) DrawerMenuExample.xaml.cs 代码如下。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using WPFDevelopers.Samples.Helpers;

namespace WPFDevelopers.Samples.ExampleViews.DrawerMenu

    /// <summary>
    /// Win10MenuExample.xaml 的交互逻辑
    /// </summary>
    public partial class DrawerMenuExample : UserControl
    
        private List<Uri> _uriList = new List<Uri>()
        
            new Uri("ExampleViews/DrawerMenu/HomePage.xaml",UriKind.Relative),
            new Uri("ExampleViews/DrawerMenu/EdgePage.xaml",UriKind.Relative),
        ;
        public DrawerMenuExample()
        
            InitializeComponent();
            myFrame.Navigate(_uriList[0]);
        

        public ICommand HomeCommand => new RelayCommand(obj =>
        
            myFrame.Navigate(_uriList[0]);
        );
        public ICommand EdgeCommand => new RelayCommand(obj =>
        
            myFrame.Navigate(_uriList[1]);
        );
        public ICommand CloudCommand => new RelayCommand(obj =>
        
            WPFDevelopers.Minimal.Controls.MessageBox.Show("点击了云盘","提示");
        );
        public ICommand MailCommand => new RelayCommand(obj =>
        
            WPFDevelopers.Minimal.Controls.MessageBox.Show("点击了邮件","提示");
        );
        public ICommand VideoCommand => new RelayCommand(obj =>
        
            WPFDevelopers.Minimal.Controls.MessageBox.Show("点击了视频","提示");
        );
    

6) HomePage.xaml.cs 代码如下。

<Page x:Class="WPFDevelopers.Samples.ExampleViews.DrawerMenu.HomePage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      xmlns:local="clr-namespace:WPFDevelopers.Samples.ExampleViews.DrawerMenu"
      mc:Ignorable="d" 
      d:DesignHeight="450" d:DesignWidth="800"
      Title="HomePage" Background="StaticResource PrimaryTextSolidColorBrush">

    <Grid>
        <TextBlock VerticalAlignment="Center"
                   HorizontalAlignment="Center"
                   Width="400" TextAlignment="Center"
                   TextWrapping="Wrap"
                   Margin="0,0,0,40"
                   FontSize="StaticResource NormalFontSize">
            <Run Foreground="White">Home</Run>
            <Run Text="微信公众号 WPFDevelopers" FontSize="40"
                           Foreground="#A9CC32" FontWeight="Bold"></Run>
            <LineBreak/>
            <Hyperlink NavigateUri="https://github.com/WPFDevelopersOrg/WPFDevelopers.git"
                       RequestNavigate="GithubHyperlink_RequestNavigate"> Github 源代码</Hyperlink>
            <Run/>
            <Run/>
            <Run/>
            <Hyperlink NavigateUri="https://gitee.com/yanjinhua/WPFDevelopers.git"
                       RequestNavigate="GiteeHyperlink_RequestNavigate"> 码云源代码</Hyperlink>
        </TextBlock>
    </Grid>
</Page>

7) EdgePage.xaml.cs 代码如下。

<Page x:Class="WPFDevelopers.Samples.ExampleViews.DrawerMenu.EdgePage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      xmlns:local="clr-namespace:WPFDevelopers.Samples.ExampleViews.DrawerMenu"
      mc:Ignorable="d" 
      d:DesignHeight="450" d:DesignWidth="800"
      Title="EdgePage"  Background="DynamicResource PrimaryPressedSolidColorBrush">

    <Grid>
        <StackPanel VerticalAlignment="Center"
                   Margin="0,0,0,40">
            <Image Source="pack://application:,,,/Images/CircularMenu/4.png" Stretch="Uniform"
                   Width="50"/>
            <TextBlock VerticalAlignment="Center"
                   HorizontalAlignment="Center"
                   TextAlignment="Center"
                   Foreground="White"
                   Text="即将跳转至Edge浏览器"
                   FontSize="StaticResource TitleFontSize"
                   Padding="0,10">
            </TextBlock>
        </StackPanel>
    </Grid>
</Page>

参考①[3]参考②[4]

参考资料

[1]

GitHub: https://github.com/WPFDevelopersOrg/WPFDevelopers

[2]

码云: https://github.com/WPFDevelopersOrg/WPFDevelopers

[3]

参考①: https://github.com/WPFDevelopersOrg/WPFDevelopers/tree/master/src/WPFDevelopers/Controls/DrawerMenu

[4]

参考②: https://gitee.com/WPFDevelopersOrg/WPFDevelopers/tree/master/src/WPFDevelopers/Controls/DrawerMenu

以上是关于WPF 实现抽屉菜单的主要内容,如果未能解决你的问题,请参考以下文章

Wpf 抽屉效果

C# WPF抽屉效果实现

WPF 实现抽屉效果 在线等待 急急急!!!!

WPF实现环(圆)形菜单

如何创建带有菜单项的滑动抽屉?

如何实现从右侧打开抽屉菜单