文章简要说明
本文是参照其他作者提供的片段来构建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
);
}
}
}
功能优缺点
- 原版实现是定义在style样式中固定死的, 不可变, 次抽屉控件可多页面添加,修改ShowModal函数leftDrawer要绑定的GDrawerPanel即可
- 可实现左侧右侧, 如果你想折腾添加上下即可GDrawerPanel.cs 已经定义了
- 可实现背景遮罩层不透明, 指定透明度,和完全透明(颜色设置Colors.Transparent即可)
- 缺点: 由于是遮罩层为容器,所以你看着透明但是鼠标穿透不过去, 设置容器属性为:IsHitTestVisible="False" 能穿透,但是容器里面的控件功能完全失效了, 呵呵, 这也是唯一的不足吧. 如果不是遮罩层就不存在鼠标穿不穿透了.
欢迎参与讨论
还是希望大家多多支持哦, 在这里感谢各位了.