【 核心实现】WPF实现抽屉效果控件

文章简要说明

本文是参照其他作者提供的片段来构建WPF抽屉效果, 原创作者:https://cplusplus.blog.csdn.net/article/details/137198896.说实话搞了个VIP,有点恶心,因为是原创作者只能引用, 看的是其他作者分享的. 代码也是片面的很, 没办法要用啊,就只能改,有现成封装的,但是都是一大个框架, 没多少代码搞一堆框架,就用一个功能有些鸡肋了.代码修改完成全部分享,并给出优缺点.

效果展示

右侧显示

左侧显示

半透明效果

颜色背景效果

代码实现

抽屉容器样式

xml 复制代码
<!--  抽屉容器样式  -->
<Style TargetType="drawer:GDrawerPanel">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="drawer:GDrawerPanel">
                <Grid
                    x:Name="DrawerGrid"
                    MinWidth="{TemplateBinding MinWidth}"
                    MinHeight="100"
                    HorizontalAlignment="Left"
                    VerticalAlignment="Stretch"
                    Background="White">
                    <!--  边框定义  -->
                    <Border x:Name="BorderPanel">
                        <ContentPresenter />
                    </Border>
                    <Button
                        x:Name="CloseButton"
                        Margin="10"
                        HorizontalAlignment="Right"
                        VerticalAlignment="Top"
                        Background="White">
                        <Button.Template>
                            <ControlTemplate TargetType="Button">
                                <Border
                                    Name="Bor"
                                    Padding="10,0,10,0"
                                    Background="White"
                                    BorderBrush="Black"
                                    BorderThickness="1"
                                    CornerRadius="14">
                                    <StackPanel
                                        HorizontalAlignment="Center"
                                        VerticalAlignment="Center"
                                        Orientation="Horizontal">
                                        <Image
                                            Name="Img"
                                            Width="16"
                                            Height="16"
                                            Source="/resource/images/deletes5.png" />
                                        <Label Name="Tx" VerticalContentAlignment="Center">关闭</Label>
                                    </StackPanel>
                                </Border>
                                <ControlTemplate.Triggers>
                                    <!--  鼠标移入时触发  -->
                                    <Trigger Property="IsMouseOver" Value="True">
                                        <Setter Property="Background" Value="Orange" />
                                        <Setter Property="Cursor" Value="Hand" />
                                        <Setter TargetName="Bor" Property="BorderBrush" Value="Green" />
                                        <Setter TargetName="Bor" Property="CornerRadius" Value="0" />
                                        <Setter TargetName="Tx" Property="Foreground" Value="Red" />
                                        <Setter TargetName="Tx" Property="FontWeight" Value="Bold" />
                                    </Trigger>
                                </ControlTemplate.Triggers>
                            </ControlTemplate>
                        </Button.Template>
                    </Button>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<!-- 滚动条样式自己根据情况定义 -->

页面使用

xml 复制代码
<!-- 组件drawer引用, 头部类引用 -->
<!--
 xmlns:drawer="clr-namespace:PreviewManagement.com.drawer"
 -->
 
<!--  抽屉容器  -->
<drawer:GDrawerPanel
    x:Name="leftDrawer"
    MinWidth="300"
    Position="Right"
    Visibility="Collapsed">
    <Grid MaxWidth="300">
        <!--  鼠标穿透: Background="Transparent" IsHitTestVisible="False"  -->
        <ScrollViewer
            Height="{Binding ElementName=MainWindow, Path=ActualHeight}"
            HorizontalAlignment="Left"
            HorizontalScrollBarVisibility="Auto"
            IsHitTestVisible="True"
            VerticalScrollBarVisibility="Auto">
            <StackPanel
                Name="gdrawerPanelContent"
                MinWidth="300"
                Margin="10"
                HorizontalAlignment="Left">
                <GroupBox HorizontalAlignment="Left" Header="用户信息">
                    <StackPanel>
                        <TextBlock Text="姓名:张三" />
                        <TextBlock Text="年龄:25" />
                        <TextBlock
                            HorizontalAlignment="Center"
                            VerticalAlignment="Center"
                            FontSize="18"
                            Foreground="Red">
                            这是模版内容SSSggggDDDDDDDF热热FFFFFFFF
                        </TextBlock>
                    </StackPanel>
                </GroupBox>

                <TextBlock
                    HorizontalAlignment="Left"
                    VerticalAlignment="Center"
                    FontSize="18"
                    Foreground="Red">
                    这是模版内容SSS
                </TextBlock>
                <TextBlock
                    HorizontalAlignment="Left"
                    VerticalAlignment="Center"
                    FontSize="18"
                    Foreground="Red">
                    这是模版内容SSS
                </TextBlock>
                <TextBlock
                    HorizontalAlignment="Left"
                    VerticalAlignment="Center"
                    FontSize="18"
                    Foreground="Red">
                    这是模版内容SSS
                </TextBlock>
                <TextBlock
                    HorizontalAlignment="Left"
                    VerticalAlignment="Center"
                    FontSize="18"
                    Foreground="Red">
                    这是模版内容SSS
                </TextBlock>
                <TextBlock
                    HorizontalAlignment="Left"
                    VerticalAlignment="Center"
                    FontSize="18"
                    Foreground="Red">
                    这是模版内容SSS
                </TextBlock>
                <TextBlock
                    HorizontalAlignment="Left"
                    VerticalAlignment="Center"
                    FontSize="18"
                    Foreground="Red">
                    这是模版内容SSS
                </TextBlock>
                <TextBlock
                    HorizontalAlignment="Left"
                    VerticalAlignment="Center"
                    FontSize="18"
                    Foreground="Red">
                    这是模版内容SSS
                </TextBlock>
                <TextBlock
                    HorizontalAlignment="Left"
                    VerticalAlignment="Center"
                    FontSize="18"
                    Foreground="Red">
                    这是模版内容SSS
                </TextBlock>
                <TextBlock
                    HorizontalAlignment="Left"
                    VerticalAlignment="Center"
                    FontSize="18"
                    Foreground="Red">
                    这是模版内容SSS
                </TextBlock>
                <TextBlock
                    HorizontalAlignment="Left"
                    VerticalAlignment="Center"
                    FontSize="18"
                    Foreground="Red">
                    这是模版内容SSS
                </TextBlock>
                <TextBlock
                    HorizontalAlignment="Left"
                    VerticalAlignment="Center"
                    FontSize="18"
                    Foreground="Red">
                    这是模版内容SSS
                </TextBlock>
                <TextBlock
                    HorizontalAlignment="Left"
                    VerticalAlignment="Center"
                    FontSize="18"
                    Foreground="Red">
                    这是模版内容SSS
                </TextBlock>
                <TextBlock
                    HorizontalAlignment="Left"
                    VerticalAlignment="Center"
                    FontSize="18"
                    Foreground="Red">
                    这是模版内容SSS
                </TextBlock>
                <TextBlock
                    HorizontalAlignment="Left"
                    VerticalAlignment="Center"
                    FontSize="18"
                    Foreground="Red">
                    这是模版内容SSS
                </TextBlock>
                <TextBlock
                    HorizontalAlignment="Left"
                    VerticalAlignment="Center"
                    FontSize="18"
                    Foreground="Red">
                    这是模版内容SSS
                </TextBlock>
                <TextBlock
                    HorizontalAlignment="Left"
                    VerticalAlignment="Center"
                    FontSize="18"
                    Foreground="Red">
                    这是模版内容SSS
                </TextBlock>
                <TextBlock
                    HorizontalAlignment="Left"
                    VerticalAlignment="Center"
                    FontSize="18"
                    Foreground="Red">
                    这是模版内容SSS
                </TextBlock>
                <TextBlock
                    HorizontalAlignment="Left"
                    VerticalAlignment="Center"
                    FontSize="18"
                    Foreground="Red">
                    这是模版内容SSS
                </TextBlock>
                <TextBlock
                    HorizontalAlignment="Left"
                    VerticalAlignment="Center"
                    FontSize="18"
                    Foreground="Red">
                    这是模版内容SSS
                </TextBlock>
                <TextBlock
                    HorizontalAlignment="Left"
                    VerticalAlignment="Center"
                    FontSize="18"
                    Foreground="Red">
                    这是模版内容SSS
                </TextBlock>
                <TextBlock
                    HorizontalAlignment="Left"
                    VerticalAlignment="Center"
                    FontSize="18"
                    Foreground="Red">
                    这是模版内容SSS
                </TextBlock>
                <TextBlock
                    HorizontalAlignment="Left"
                    VerticalAlignment="Center"
                    FontSize="18"
                    Foreground="Red">
                    这是模版内容SSS
                </TextBlock>
            </StackPanel>
        </ScrollViewer>
    </Grid>
</drawer:GDrawerPanel>

函数调用

csharp 复制代码
 /// <summary>
 /// 测试, 函数调用, 调用封装一个函数处理全部
 /// </summary>
 /// <param name="sender"> </param>
 /// <param name="e">      </param>
 private void TestBtnAction(object sender, RoutedEventArgs e) {
     this.ShowModal(rootGrid, Colors.Transparent, 0);
 }

/// <summary>
/// 显示抽屉容器
/// </summary>
/// <param name="visual">容器</param>
/// <param name="color">抽屉背景颜色</param>
/// <param name="opacity">透明度</param>
 public void ShowModal(Visual visual, Color? color = null, double opacity = 0.5) {
     // 移除GDrawer对象, 取消根容器产生连接错误
     rootGrid.Children.Remove(leftDrawer);
     leftDrawer.Visibility = Visibility.Visible;

     //获取visual上面的第一个AdornerLayer
     AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(visual);
     //创建我们定义的Adorner
     //var drawer = FindResource("LeftDrawer") as GDrawerPanel;
     var drawer = leftDrawer as GDrawerPanel;
     drawer.Closed = () => {
         adornerLayer.Remove(_adorner);
         leftDrawer.Visibility = Visibility.Collapsed;
     };

     _adorner = _adorner ?? new GDrawerPanelAdorner(adornerLayer, color, opacity) {
         Child = new Border() {
             // 容器背景颜色 可透明
             Background = new SolidColorBrush(color.Equals(Colors.Transparent) ? Color.FromArgb(0, 0, 0, 0) : Color.FromArgb(150, 0, 0, 0)),
             Child = drawer,
             IsHitTestVisible = true
         }
     };
     adornerLayer.Add(_adorner);
 }

功能类

GDrawerPanel.cs 抽屉容器模版类

csharp 复制代码
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace PreviewManagement.com.drawer {

    /// <summary>
    /// 显示位置
    /// </summary>
    public enum ShowPosition {
        /// <summary>
        /// 左
        /// </summary>
        Left,
        /// <summary>
        /// 上
        /// </summary>
        Top,
        /// <summary>
        /// 右
        /// </summary>
        Right,
        /// <summary>
        /// 下
        /// </summary>
        Bottom
    }

    /// <summary>
    /// 抽屉容器Panel
    /// </summary>
    [TemplatePart(Name = BorderPanel, Type = typeof(Border))]
    [TemplatePart(Name = DrawerGrid, Type = typeof(Grid))]
    [TemplatePart(Name = CloseButton, Type = typeof(Button))]
    public class GDrawerPanel : ContentControl {
        public Action Closed;
        private const string DrawerGrid = "DrawerGrid";
        private const string CloseButton = "CloseButton";
        private const string BorderPanel = "BorderPanel";

        private Grid _drawerGrid;
        private Button _closeButton;
        private Border _borderPanel;
        private static ShowPosition state = ShowPosition.Left;

        public override void OnApplyTemplate() {
            base.OnApplyTemplate();
            _drawerGrid = GetTemplateChild(DrawerGrid) as Grid;
            _closeButton = GetTemplateChild(CloseButton) as Button;
            _borderPanel = GetTemplateChild(BorderPanel) as Border;
            _closeButton.Click += (s, e) => IsOpen = false;
            Loaded += OnLoaded;
        }

        private void OnLoaded(object sender, RoutedEventArgs e) {
            IsOpen = true;
        }

        public bool IsOpen {
            get { return (bool)GetValue(IsOpenProperty); }
            set { SetValue(IsOpenProperty, value); }
        }

        public ShowPosition Position {
            get { return (ShowPosition)GetValue(PositionProperty); }
            set { SetValue(PositionProperty, value); }
        }

        public static readonly DependencyProperty IsOpenProperty =
            DependencyProperty.Register("IsOpen", typeof(bool), typeof(GDrawerPanel), new PropertyMetadata(false, (s, e) => {
                var drawer = s as GDrawerPanel;
                if (e.NewValue is bool b && b) {
                    drawer.StartAnimationIn();
                } else {
                    drawer.StartAnimationOut();
                }
            }));

        public static readonly DependencyProperty PositionProperty =
           DependencyProperty.Register("Position", typeof(ShowPosition), typeof(GDrawerPanel), new PropertyMetadata(ShowPosition.Left, (s, e) => {
               var drawer = s as GDrawerPanel;
               switch (e.NewValue) {
                   case ShowPosition.Left:
                       state = ShowPosition.Left;
                       break;

                   case ShowPosition.Right:
                       state = ShowPosition.Right;
                       break;

                   case ShowPosition.Top:
                       state = ShowPosition.Top;
                       break;

                   case ShowPosition.Bottom:
                       state = ShowPosition.Bottom;
                       break;
               }
           }));

        /// <summary>
        /// 更新边框位置
        /// </summary>
        private void updateBorder() {
            int v = 3;
            LinearGradientBrush linearGradientBrush = new LinearGradientBrush();
            linearGradientBrush.GradientStops.Add(new GradientStop(Colors.DarkCyan, 0));
            linearGradientBrush.GradientStops.Add(new GradientStop(Colors.Transparent, 0.5));
            linearGradientBrush.GradientStops.Add(new GradientStop(Colors.Orange, 1));
            switch (state) {
                case ShowPosition.Left:
                    _borderPanel.BorderThickness = new Thickness(0, 0, v, 0);
                    linearGradientBrush.StartPoint = new Point(0, 0);
                    linearGradientBrush.EndPoint = new Point(1, 1);
                    break;

                case ShowPosition.Top:
                    break;

                case ShowPosition.Right:
                    _borderPanel.BorderThickness = new Thickness(v, 0, 0, 0);
                    linearGradientBrush.StartPoint = new Point(1, 1);
                    linearGradientBrush.EndPoint = new Point(0, 0);
                    break;

                case ShowPosition.Bottom:
                    break;
            }

            //_borderPanel.BorderBrush = new SolidColorBrush(Colors.DarkCyan);
            _borderPanel.BorderBrush = linearGradientBrush;
        }

        /// <summary>
        /// 进入
        /// </summary>
        /// <param name="seconds"></param>
        private async void StartAnimationIn(float seconds = 0.3f) {
            var sb = new Storyboard();
            var offset = _drawerGrid.ActualWidth;
            ThicknessAnimation animation = null;
            // state = state.Equals(ShowPosition.Left) ? ShowPosition.Right : ShowPosition.Left;

            // 右侧
            if (state.Equals(ShowPosition.Right)) {
                animation = new ThicknessAnimation {
                    Duration = new Duration(TimeSpan.FromSeconds(seconds)),
                    From = new Thickness(this.ActualWidth, 0, 0, 0),
                    To = new Thickness(this.ActualWidth - offset, 0, 0, 0),
                };
            }
            // 左侧
            else if (state.Equals(ShowPosition.Left)) {
                animation = new ThicknessAnimation {
                    Duration = new Duration(TimeSpan.FromSeconds(seconds)),
                    From = new Thickness(-offset, 0, offset, 0),
                    To = new Thickness(0),
                };
            }

            // 更新边框样式
            this.updateBorder();

            Storyboard.SetTargetProperty(animation, new PropertyPath("Margin"));
            sb.Children.Add(animation);
            sb.Begin(_drawerGrid);
            await Task.Delay((int)(seconds * 1000));
        }

        /// <summary>
        /// 移出
        /// </summary>
        /// <param name="seconds"></param>
        private async void StartAnimationOut(float seconds = 0.3f) {
            var sb = new Storyboard();
            var offset = _drawerGrid.ActualWidth;
            ThicknessAnimation animation = null;
            if (state.Equals(ShowPosition.Right)) {
                animation = new ThicknessAnimation {
                    Duration = new Duration(TimeSpan.FromSeconds(seconds)),
                    From = new Thickness(this.ActualWidth - offset, 0, 0, 0),
                    To = new Thickness(this.ActualWidth, 0, 0, 0),
                };
            } else if (state.Equals(ShowPosition.Left)) {
                animation = new ThicknessAnimation {
                    Duration = new Duration(TimeSpan.FromSeconds(seconds)),
                    From = new Thickness(0),
                    To = new Thickness(-offset, 0, offset, 0),
                };
            }

            Storyboard.SetTargetProperty(animation, new PropertyPath("Margin"));
            sb.Children.Add(animation);
            sb.Begin(_drawerGrid);
            await Task.Delay((int)(seconds * 1000));
            Closed?.Invoke();
        }
    }
}

GDrawerPanelAdorner.cs 抽屉容器装饰器

csharp 复制代码
using System.Windows;
using System.Windows.Documents;
using System.Windows.Media;

namespace PreviewManagement.com.drawer {

    public class GDrawerPanelAdorner : Adorner {
        private UIElement child;
        private UIElement adornedElement;
        /// <summary>
        /// 背景颜色设置
        /// </summary>
        private Color bgColor = Colors.Black;
        /// <summary>
        /// 背景透明度
        /// </summary>
        private double opacity = 0.5;

        public GDrawerPanelAdorner(UIElement adornedElement) : base(adornedElement) {
            this.adornedElement = adornedElement;
        }

        /// <summary>
        /// 自定义背景属性
        /// </summary>
        /// <param name="adornedElement"></param>
        /// <param name="bgColor"></param>
        /// <param name="opacity"></param>
        public GDrawerPanelAdorner(UIElement adornedElement, Color? bgColor = null, double opacity = 0.5) : base(adornedElement) {
            if (bgColor == null) {
                this.bgColor = Colors.Black;
            } else {
                this.bgColor = bgColor.Value;
            }
            this.opacity = opacity;
            //  this.UpdateLayout();
        }

        //Adorner 直接继承自 FrameworkElement,
        //没有Content和Child属性,
        //自己添加一个,方便向其中添加我们的控件
        public UIElement Child {
            get => child;
            set {
                if (value == null) {
                    RemoveVisualChild(child);
                } else {
                    AddVisualChild(value);
                }
                child = value;
            }
        }

        //重写VisualChildrenCount 表示此控件只有一个子控件
        protected override int VisualChildrenCount => 1;

        //控件计算大小的时候,我们在装饰层上添加的控件也计算一下大小
        protected override Size ArrangeOverride(Size finalSize) {
            //Border? border = child as Border;
            //GDrawerPanel? gdrawerPanel = border.Child as GDrawerPanel;
            // 获取主窗口实例
            //var mainWindow = Application.Current.MainWindow;

            child?.Arrange(new Rect(finalSize));
            return finalSize;
        }

        //重写GetVisualChild,返回我们添加的控件
        protected override Visual GetVisualChild(int index) {
            if (index == 0 && child != null)
                return child;
            return base.GetVisualChild(index);
        }

        protected override void OnRender(DrawingContext drawingContext) {
            // 用被装饰元素的尺寸作为背景区域
            Rect rect = new Rect(AdornedElement.RenderSize);

            // 半透明红色背景,Opacity 控制透明度
            var brush = new SolidColorBrush(this.bgColor) {
                Opacity = this.opacity
            };

            drawingContext.DrawRectangle(
              brush,
              null,
              rect
           );
        }
    }
}

功能优缺点

  1. 原版实现是定义在style样式中固定死的, 不可变, 次抽屉控件可多页面添加,修改ShowModal函数leftDrawer要绑定的GDrawerPanel即可
  2. 可实现左侧右侧, 如果你想折腾添加上下即可GDrawerPanel.cs 已经定义了
  3. 可实现背景遮罩层不透明, 指定透明度,和完全透明(颜色设置Colors.Transparent即可)
  4. 缺点: 由于是遮罩层为容器,所以你看着透明但是鼠标穿透不过去, 设置容器属性为:IsHitTestVisible="False" 能穿透,但是容器里面的控件功能完全失效了, 呵呵, 这也是唯一的不足吧. 如果不是遮罩层就不存在鼠标穿不穿透了.

欢迎参与讨论

还是希望大家多多支持哦, 在这里感谢各位了.

相关推荐
JAVA-博弈人生-超哥3 年前
【D3.js Tidy tree绘制树形图,单棵树,左右树,平移,拖拽,树形中的天花板实现,源码实现】
d3.js·ui鉴赏·d3 绘制树形·tidy tree·树形图表
EverSteins3 年前
enote笔记法之附录2——5w1h2k关联词(ver0.22)
原创文章