Compare commits

...

2 Commits

Author SHA1 Message Date
zengwenjie
f9138ff444 定义注册附属视图需要的方法 2025-09-30 18:42:49 +08:00
zengwenjie
71c55abbcb 除去注册「取消」按钮,其它关于抽屉视图的功能全部完成 2025-09-30 18:30:38 +08:00
4 changed files with 273 additions and 26 deletions

View File

@@ -20,6 +20,10 @@
<Button Content="测试的标题"/>
</UniformGrid>
</deedy:WindowBorder.Header>
<Border BorderBrush="Red" BorderThickness="1"/>
<Border BorderBrush="Red" BorderThickness="1">
<UniformGrid>
<Button Content="打开抽屉" Click="Button_Click"/>
</UniformGrid>
</Border>
</deedy:WindowBorder>
</Window>

View File

@@ -31,7 +31,34 @@ namespace Deedy.Testing
private void WindowBorder_Click_1(object sender, RoutedEventArgs e)
{
drawer?.HideWithAnimation();
}
private DrawerViewer? drawer;
private void Button_Click(object sender, RoutedEventArgs e)
{
if (this.Content is UIElement windowBorder)
{
DrawerViewer? _drawer = null;
_drawer = new DrawerViewer(windowBorder, (s, e) =>
{
_drawer?.HideWithAnimation();
}, Dock.Right, Button.ClickEvent)
{
Content = new Button()
{
Background = Brushes.Red,
Opacity = 0.75,
Visibility = Visibility.Visible,
Content = "测试按钮"
},
Background = Brushes.Green,
Margin = new Thickness(100, 40, 0, 0),
};
var al = AdornerLayer.GetAdornerLayer(this.Content as UIElement);
al.Add(_drawer);
_drawer.ShowWithAnimation();
drawer = _drawer;
}
}
}
}

View File

@@ -0,0 +1,214 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace Deedy
{
/// <summary>
/// 抽屉窗口装饰器
/// </summary>
public class DrawerViewer : Adorner
{
private readonly VisualCollection _visualChildren;
private readonly Border _border;
public Brush Background { get => (Brush)GetValue(BackgroundProperty); set => SetValue(BackgroundProperty, value); }
public static readonly DependencyProperty BackgroundProperty = Border.BackgroundProperty.AddOwner(typeof(DrawerViewer));
public Brush BorderBrush { get => (Brush)GetValue(BorderBrushProperty); set => SetValue(BorderBrushProperty, value); }
public static readonly DependencyProperty BorderBrushProperty = Border.BorderBrushProperty.AddOwner(typeof(DrawerViewer));
public Thickness BorderThickness { get => (Thickness)GetValue(BorderThicknessProperty); set => SetValue(BorderThicknessProperty, value); }
public static readonly DependencyProperty BorderThicknessProperty = Border.BorderThicknessProperty.AddOwner(typeof(DrawerViewer));
public CornerRadius CornerRadius { get => (CornerRadius)GetValue(CornerRadiusProperty); set => SetValue(CornerRadiusProperty, value); }
public static readonly DependencyProperty CornerRadiusProperty = Border.CornerRadiusProperty.AddOwner(typeof(DrawerViewer));
private readonly ContentPresenter _contentPresenter;
public object Content { get => GetValue(ContentProperty); set => SetValue(ContentProperty, value); }
public static readonly DependencyProperty ContentProperty = ContentPresenter.ContentProperty.AddOwner(typeof(DrawerViewer));
public DataTemplate ContentTemplate { get => (DataTemplate)GetValue(ContentTemplateProperty); set => SetValue(ContentTemplateProperty, value); }
public static readonly DependencyProperty ContentTemplateProperty = ContentPresenter.ContentTemplateProperty.AddOwner(typeof(DrawerViewer));
public DataTemplateSelector ContentTemplateSelector { get => (DataTemplateSelector)GetValue(ContentTemplateSelectorProperty); set => SetValue(ContentTemplateSelectorProperty, value); }
public static readonly DependencyProperty ContentTemplateSelectorProperty = ContentPresenter.ContentTemplateSelectorProperty.AddOwner(typeof(DrawerViewer));
/// <summary>
/// 动画方向如果为「null」则使用缩放动画
/// </summary>
private readonly Dock? _direction;
/// <summary>
/// 根据抽屉加载方向创建边距动画所需要的「Thickness」对象
/// </summary>
/// <returns>动画需要的「Thickness」对象</returns>
private Thickness GetMargin4Animation()
{
switch (this._direction)
{
case Dock.Left: return new Thickness(0, 0, this.AdornedElement.RenderSize.Width, 0);
case Dock.Right: return new Thickness(this.AdornedElement.RenderSize.Width, 0, 0, 0);
case Dock.Top: return new Thickness(0, 0, 0, this.AdornedElement.RenderSize.Height);
case Dock.Bottom: return new Thickness(0, AdornedElement.RenderSize.Height, 0, 0);
default:
double horMargin = this.AdornedElement.RenderSize.Width / 2;
double verMargin = this.AdornedElement.RenderSize.Height / 2;
return new Thickness(horMargin, verMargin, horMargin, verMargin);
}
}
/// <summary>
/// 注册附属视图;注册前请确保布局属性被正确设置
/// </summary>
/// <param name="attachedView">要注册的附属视图;注册前请确保布局属性被正确设置</param>
/// <param name="dock">在注册的方向</param>
/// <returns>当前装饰器对象,方便连续操作;不会创建新的装饰器</returns>
public DrawerViewer RegisterAttachedView(UIElement attachedView, Dock? dock)
{
dock ??= (Dock)(((int)(this._direction ?? Dock.Left) + 2) % 4);
return this;
}
/// <summary>
/// 在特定「UIElement」上创建「WindowDrawer」蒙层
/// </summary>
/// <param name="adornedElement">目标「UIElement」元素</param>
/// <param name="direction">抽屉的加载方向,如果不设置则使用缩放动画</param>
public DrawerViewer(UIElement adornedElement, Dock? direction = null) : base(adornedElement)
{
this.Opacity = 0;
this._direction = direction;
_visualChildren = new VisualCollection(this);
// 创建边框并绑定属性
_border = new Border() { };
var backgroundBinding = new System.Windows.Data.Binding(BackgroundProperty.Name) { Source = this };
var borderBrushBinding = new System.Windows.Data.Binding(BorderBrushProperty.Name) { Source = this };
var borderThicknessBinding = new System.Windows.Data.Binding(BorderThicknessProperty.Name) { Source = this };
var cornerRadiusBinding = new System.Windows.Data.Binding(CornerRadiusProperty.Name) { Source = this };
_border.SetBinding(Border.BackgroundProperty, backgroundBinding);
_border.SetBinding(Border.BorderBrushProperty, borderBrushBinding);
_border.SetBinding(Border.BorderThicknessProperty, borderThicknessBinding);
_border.SetBinding(Border.CornerRadiusProperty, cornerRadiusBinding);
_visualChildren.Add(_border);
// 创建内容并绑定属性
_contentPresenter = new ContentPresenter
{
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch
};
var contentBinding = new System.Windows.Data.Binding(ContentProperty.Name) { Source = this };
var contentTemplateBinding = new System.Windows.Data.Binding(ContentTemplateProperty.Name) { Source = this };
var contentTemplateSelectorBinding = new System.Windows.Data.Binding(ContentTemplateSelectorProperty.Name) { Source = this };
_contentPresenter.SetBinding(ContentPresenter.ContentProperty, contentBinding);
_contentPresenter.SetBinding(ContentPresenter.ContentTemplateProperty, contentTemplateBinding);
_contentPresenter.SetBinding(ContentPresenter.ContentTemplateSelectorProperty, contentTemplateSelectorBinding);
_border.Child = _contentPresenter;
}
[AllowNull]
private readonly RoutedEvent[] _routedEvents;
private readonly RoutedEventHandler? _routedEventHandler;
/// <summary>
/// 在特定「UIElement」上创建「WindowDrawer」蒙层同时绑定特定路由事件
/// </summary>
/// <param name="adornedElement">目标「UIElement」元素</param>
/// <param name="routedEventHandler">路由事件处理程序</param>
/// <param name="direction">抽屉的加载方向,如果不设置则使用缩放动画</param>
/// <param name="routedEvents">要绑定的路由事件</param>
/// <remarks>
/// 如果不设置「routedEvents」参数则注册「ButtonBase.ClickEvent」事件
/// </remarks>
public DrawerViewer(UIElement adornedElement, RoutedEventHandler routedEventHandler, Dock? direction, params RoutedEvent[] routedEvents)
: this(adornedElement, direction)
{
if (routedEvents.Length == 0) routedEvents = [ButtonBase.ClickEvent];
if (routedEventHandler != null)
{
_routedEvents = routedEvents;
_routedEventHandler = routedEventHandler;
foreach (var routedEvent in routedEvents)
this.AddHandler(routedEvent, routedEventHandler);
}
}
/// <summary>
/// 在特定「UIElement」上创建「WindowDrawer」蒙层同时绑定特定路由事件
/// </summary>
/// <param name="adornedElement">目标「UIElement」元素</param>
/// <param name="routedEventHandler">路由事件处理程序</param>
/// <param name="routedEvents">要绑定的路由事件</param>
/// <remarks>
/// <para>如果不设置「routedEvents」参数则注册「ButtonBase.ClickEvent」事件</para>
/// <para>抽屉采用缩放方式加载</para>
/// </remarks>
public DrawerViewer(UIElement adornedElement, RoutedEventHandler routedEventHandler, params RoutedEvent[] routedEvents)
: this(adornedElement, routedEventHandler, null, routedEvents) { }
protected override int VisualChildrenCount => _visualChildren.Count;
protected override Visual GetVisualChild(int index) => _visualChildren[index];
protected override Size MeasureOverride(Size constraint)
{
_border.Measure(constraint);
return base.MeasureOverride(constraint);
}
protected override Size ArrangeOverride(Size finalSize)
{
_border.Arrange(new Rect(0, 0, finalSize.Width, finalSize.Height));
return finalSize;
}
public void ShowWithAnimation(double seconds = 1)
{
var doubleAnimation = new DoubleAnimation
{
From = 0,
To = 1,
Duration = TimeSpan.FromSeconds(seconds),
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseIn }
};
BeginAnimation(OpacityProperty, doubleAnimation);
var marginAnimation = new ThicknessAnimation
{
From = this.GetMargin4Animation(),
To = new Thickness(0),
Duration = TimeSpan.FromSeconds(seconds),
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseIn }
};
_border.BeginAnimation(MarginProperty, marginAnimation);
}
public void HideWithAnimation(double seconds = 1)
{
var doubleAnimation = new DoubleAnimation
{
From = 1,
To = 0,
Duration = TimeSpan.FromSeconds(seconds),
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut }
};
doubleAnimation.Completed += (s, e) =>
{
if (this._routedEventHandler != null)
foreach (var routedEvent in this._routedEvents)
this.RemoveHandler(routedEvent, this._routedEventHandler);
var layer = AdornerLayer.GetAdornerLayer(AdornedElement);
layer?.Remove(this);
};
BeginAnimation(OpacityProperty, doubleAnimation);
var marginAnimation = new ThicknessAnimation
{
From = new Thickness(0),
To = this.GetMargin4Animation(),
Duration = TimeSpan.FromSeconds(seconds),
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut }
};
_border.BeginAnimation(MarginProperty, marginAnimation);
}
}
}

View File

@@ -82,32 +82,34 @@
</Style.Triggers>
</Style>
</ControlTemplate.Resources>
<DockPanel Background="{TemplateBinding Background}">
<Border x:Name="TitleBar" Background="{TemplateBinding Headground}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"
<AdornerDecorator>
<DockPanel Background="{TemplateBinding Background}">
<Border x:Name="TitleBar" Background="{TemplateBinding Headground}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"
DockPanel.Dock="Top" MinHeight="32" Height="{TemplateBinding HeaderHeight}">
<DockPanel>
<Image Margin="{TemplateBinding Padding}" Stretch="Uniform" Source="{Binding Icon, RelativeSource={RelativeSource AncestorType=Window}}"/>
<TextBlock VerticalAlignment="Center" Margin="0,0,6,0" Text="{Binding Title, RelativeSource={RelativeSource AncestorType=Window}}"/>
<DockPanel x:Name="Controller" DockPanel.Dock="Right" MinWidth="147" Background="#01FFFFFF" VerticalAlignment="Stretch">
<Button x:Name="CloseWin" DockPanel.Dock="Right" Padding="{TemplateBinding Padding}" Style="{DynamicResource ControlButtonStyle}" Width="{Binding ActualHeight, RelativeSource={RelativeSource Self}}">
<Path Stretch="Uniform" Stroke="Transparent" Fill="{TemplateBinding Foreground}"
Data="M 1 0 L 0 1 L 23 24 L 24 23 Z M 1 24 L 0 23 L 11 12 L 12 13 Z M 12 11 L 23 0 L 24 1 L 13 12 Z"/>
</Button>
<Button x:Name="Maximize" DockPanel.Dock="Right" Padding="{TemplateBinding Padding}" Style="{DynamicResource ControlButtonStyle}" Width="{Binding ActualHeight, RelativeSource={RelativeSource Self}}">
<Path Stretch="Uniform" Stroke="Transparent" Fill="{TemplateBinding Foreground}"
Data="M 0 6 V 24 H 18 V 6 H 16.5 V 22.5 H 1.5 V 7.5 H 16.5 V 6 Z M 6 0 H 24 V 18 H 22.5 V 1.5 H 6 Z"/>
</Button>
<Button x:Name="Minimize" DockPanel.Dock="Right" Padding="{TemplateBinding Padding}" Style="{DynamicResource ControlButtonStyle}" Width="{Binding ActualHeight, RelativeSource={RelativeSource Self}}">
<Path Stretch="Uniform" Stroke="Transparent" Fill="{TemplateBinding Foreground}"
Data="M 0 0 V 24 M 0 11.25 V 12.75 H 24 V 11.25 Z"/>
</Button>
<Menu x:Name="MainMenu" Background="#01FFFFFF" Foreground="{TemplateBinding HoverBrush}"/>
<DockPanel>
<Image Margin="{TemplateBinding Padding}" Stretch="Uniform" Source="{Binding Icon, RelativeSource={RelativeSource AncestorType=Window}}"/>
<TextBlock VerticalAlignment="Center" Margin="0,0,6,0" Text="{Binding Title, RelativeSource={RelativeSource AncestorType=Window}}"/>
<DockPanel x:Name="Controller" DockPanel.Dock="Right" MinWidth="147" Background="#01FFFFFF" VerticalAlignment="Stretch">
<Button x:Name="CloseWin" DockPanel.Dock="Right" Padding="{TemplateBinding Padding}" Style="{DynamicResource ControlButtonStyle}" Width="{Binding ActualHeight, RelativeSource={RelativeSource Self}}">
<Path Stretch="Uniform" Stroke="Transparent" Fill="{TemplateBinding Foreground}"
Data="M 1 0 L 0 1 L 23 24 L 24 23 Z M 1 24 L 0 23 L 11 12 L 12 13 Z M 12 11 L 23 0 L 24 1 L 13 12 Z"/>
</Button>
<Button x:Name="Maximize" DockPanel.Dock="Right" Padding="{TemplateBinding Padding}" Style="{DynamicResource ControlButtonStyle}" Width="{Binding ActualHeight, RelativeSource={RelativeSource Self}}">
<Path Stretch="Uniform" Stroke="Transparent" Fill="{TemplateBinding Foreground}"
Data="M 0 6 V 24 H 18 V 6 H 16.5 V 22.5 H 1.5 V 7.5 H 16.5 V 6 Z M 6 0 H 24 V 18 H 22.5 V 1.5 H 6 Z"/>
</Button>
<Button x:Name="Minimize" DockPanel.Dock="Right" Padding="{TemplateBinding Padding}" Style="{DynamicResource ControlButtonStyle}" Width="{Binding ActualHeight, RelativeSource={RelativeSource Self}}">
<Path Stretch="Uniform" Stroke="Transparent" Fill="{TemplateBinding Foreground}"
Data="M 0 0 V 24 M 0 11.25 V 12.75 H 24 V 11.25 Z"/>
</Button>
<Menu x:Name="MainMenu" Background="#01FFFFFF" Foreground="{TemplateBinding HoverBrush}"/>
</DockPanel>
<Decorator x:Name="Container" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"/>
</DockPanel>
<Decorator x:Name="Container" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"/>
</DockPanel>
</Border>
<ContentPresenter/>
</DockPanel>
</Border>
<ContentPresenter/>
</DockPanel>
</AdornerDecorator>
</ControlTemplate>
</Setter.Value>
</Setter>