yanjinhua

V1

2022/07/28阅读:9主题:蓝莹

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>

参考① 参考②

参考资料

[1]

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

[2]

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

分类:

后端

标签:

C#

作者介绍

yanjinhua
V1