WPF学习笔记系列
第一章 【WPF系列】【Adorner】
文章目录
前言
关于WPF系列Adorner的学习笔记。Adorner在WPF中的界面设计中很常见,它可以用来实现更加美观的界面视觉效果,也可以设计事件动态效果等等。
一、Adorner介绍
- Adorner,抽象基类,所有具有装饰器的实现都从该类继承;
- Adorner,是一种特殊类型的FrameworkElement,用于向用户提供可视化提示;
- Adorner,简单地说,就是WPF装饰器,作用是给WPF的控件上一层装饰效果;
- Adorner,是WPF窗口中独立的一层,支持在界面元素之上执行独立的绘制及用户交互;
- Adorner ,不包括任何继承呈现行为,确保装饰器呈现是装饰器实施者的责任。 实现呈现行为的常见方式是重写 OnRenderSizeChanged 方法,并使用一个或多个 DrawingContext 对象来按需呈现装饰器的视觉效果;
- AdornerLayer,表示一个或多个装饰元素的装饰器的呈现层;
- AdornerDecorator,使装饰器层与元素集合相关联;
- WPF对Adorner的绘制是在单独的一个层AdornerLayer中完成的,这是一个始终位于装饰元素或装饰元素集合上方的呈现图面,在执行布局计算时单独地执行measure-arrange流程;
- 在WPF中,从编辑框控件中光标的显示和选中效果的支持,到具有数据焦点的控件所具有的虚线外框,都是通过Adorner实现的;
- 放置在 AdornerLayer 中的所有内容均呈现在您已设置的任何其余样式的顶部。也就是说,装饰器始终以可见的方式位于顶部,无法使用 z 顺序重写;
- Adorner 就像任何其他 FrameworkElement 一样接收输入事件, 若要对装饰器下的元素启用传递命中测试,请在装饰器上将命中测试 IsHitTestVisible 的属性设置为 false;
二、Adorner的常见应用
2.1.在界面元素上提供视觉效果,以提示用户当前元素处于特定状态。
如在特定button周围绘制角框装饰
通常情况下,我们是按照以下的形式将Adorner绑定到特定的界面元素:
1.调用静态方法AdornerLayer.GetAdornerLayer(Visual visual),将需要被Adorner装饰的界面元素当作参数传入。该函数会从该界面元素开始沿视觉树向上查找,并返回它所发现的第一个AdornerLayer。
2.调用AdornerLayer.Add(Adorner adorner),函数将需要添加的装饰器加入AdornerLayer中。
3.通过remove实现移除装饰。
4.一般情况下,Adorner的派生类型需要考虑通过重写OnRender()或AddVisualChild()函数来指定Adorner如何绘制其外观。
namespace WpfApp6
{
/// <summary>
/// FrmAdorner.xaml 的交互逻辑
/// </summary>
public partial class FrmAdorner : UserControl
{
public FrmAdorner()
{
InitializeComponent();
}
private void BtnAdd_Click(object sender, RoutedEventArgs e)
{
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(btn1);
adornerLayer.Add(new ButtonAdorner(btn1));
}
private void BtnClr_Click(object sender, RoutedEventArgs e)
{
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(btn1);
Adorner[] adorners = adornerLayer.GetAdorners(btn1);
if (adorners != null)
{
for (int i = adorners.Length - 1; i >= 0; i--)
{
adornerLayer.Remove(adorners[i]);
}
}
}
}
/// <summary>
/// 创建装饰器
/// 后缀加上Adorner规范命名,继承Adorner类,命名空间:System.Windows.Documents
/// </summary>
public class ButtonAdorner : Adorner
{
//必须生成构造函数
public ButtonAdorner(UIElement adornedElement) : base(adornedElement)
{
}
//重写OnRender
protected override void OnRender(DrawingContext drawingContext)
{
Rect adornedElementRect = new Rect(this.AdornedElement.RenderSize);
Pen renderPen = new Pen(new SolidColorBrush(Colors.Red), 1.0);
//绘制角框
drawingContext.DrawLine(renderPen, new Point(adornedElementRect.TopLeft.X - 3, adornedElementRect.TopLeft.Y - 3), new Point(adornedElementRect.TopLeft.X + 3, adornedElementRect.TopLeft.Y - 3));
drawingContext.DrawLine(renderPen, new Point(adornedElementRect.TopLeft.X - 3, adornedElementRect.TopLeft.Y - 3), new Point(adornedElementRect.TopLeft.X - 3, adornedElementRect.TopLeft.Y + 3));
drawingContext.DrawLine(renderPen, new Point(adornedElementRect.TopRight.X + 3, adornedElementRect.TopRight.Y - 3), new Point(adornedElementRect.TopRight.X - 3, adornedElementRect.TopRight.Y - 3));
drawingContext.DrawLine(renderPen, new Point(adornedElementRect.TopRight.X + 3, adornedElementRect.TopRight.Y - 3), new Point(adornedElementRect.TopRight.X + 3, adornedElementRect.TopRight.Y + 3));
drawingContext.DrawLine(renderPen, new Point(adornedElementRect.BottomLeft.X - 3, adornedElementRect.BottomLeft.Y + 3), new Point(adornedElementRect.BottomLeft.X + 3, adornedElementRect.BottomLeft.Y + 3));
drawingContext.DrawLine(renderPen, new Point(adornedElementRect.BottomLeft.X - 3, adornedElementRect.BottomLeft.Y + 3), new Point(adornedElementRect.BottomLeft.X - 3, adornedElementRect.BottomLeft.Y - 3));
drawingContext.DrawLine(renderPen, new Point(adornedElementRect.BottomRight.X + 3, adornedElementRect.BottomRight.Y + 3), new Point(adornedElementRect.BottomRight.X - 3, adornedElementRect.BottomRight.Y + 3));
drawingContext.DrawLine(renderPen, new Point(adornedElementRect.BottomRight.X + 3, adornedElementRect.BottomRight.Y + 3), new Point(adornedElementRect.BottomRight.X + 3, adornedElementRect.BottomRight.Y - 3));
}
}
}
xaml:
<StackPanel>
<WrapPanel>
<Button Content="添加装饰" Height="30" Width="100" Margin="10" Click="BtnAdd_Click"/>
<Button Content="移除装饰" Height="30" Width="100" Margin="10" Click="BtnClr_Click"/>
</WrapPanel>
<StackPanel>
<Button Name="btn1" Height="50" Width="50" Margin="30"/>
</StackPanel>
</StackPanel>
2.2.从视觉上遮盖或重写UIElement的一部分或全部
如IE上方的搜索栏在没有输入时显示当前搜索引擎的名称
private void SetTextBoxAdorner()
{
TextBox maskTextBox = new TextBox();
maskTextBox.Text = "搜索...";
maskTextBox.BorderThickness = new Thickness(1, 1, 0, 0);
maskTextBox.Opacity = 0.6;
AddAdorner(new AdornerContentPresenter(btn4, maskTextBox), btn4);
}
private void btn4_TextChanged(object? sender, TextChangedEventArgs? e)
{
if (sender is TextBox textBox)
{
if (textBox.Text.ToString().Trim() != string.Empty)
{
RemoveAdorner(btn4);
}
else
{
SetTextBoxAdorner();
}
}
}
public class AdornerContentPresenter : Adorner
{
private VisualCollection _Visuals;
private ContentPresenter _ContentPresenter;
public AdornerContentPresenter(UIElement adornedElement) : base(adornedElement)
{
_Visuals = new VisualCollection(this);
_ContentPresenter = new ContentPresenter();
_Visuals.Add(_ContentPresenter);
}
public AdornerContentPresenter(UIElement adornedElement, Visual content) : this(adornedElement)
{
Content = content;
}
protected override Size MeasureOverride(Size constraint)
{
_ContentPresenter.Measure(constraint);
return _ContentPresenter.DesiredSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
//布局位置和大小
_ContentPresenter.Arrange(new Rect(0, 0, finalSize.Width, finalSize.Height));
return _ContentPresenter.RenderSize;
}
protected override Visual GetVisualChild(int index)
{
return _Visuals[index];
}
protected override int VisualChildrenCount
{
get { return _Visuals.Count; }
}
public object Content
{
get { return _ContentPresenter.Content; }
set { _ContentPresenter.Content = value; }
}
}
<TextBox Name="btn4" Margin="30" TextChanged="btn4_TextChanged"/>
2.3.向UIElement添加功能控点,使用户可以操作元素(调整大小、旋转、重新定位等)
下面通过实例来是呀Adorner实现控制UIElement的大小调整
public class BtnGripAdorner : Adorner
{
double RectOffset = 0;
private VisualCollection _Visuals;
private ContentPresenter _ContentPresenter;
UIElement _adornedElement;
//必须生成构造函数
public BtnGripAdorner(UIElement adornedElement, double offset) : base(adornedElement)
{
_adornedElement = adornedElement;
double _offset = 2;
RectOffset = offset + _offset;
Thumb _leftThumb = new Thumb();
_leftThumb.Width = _offset;
_leftThumb.HorizontalAlignment = HorizontalAlignment.Left;
_leftThumb.Cursor = Cursors.SizeWE;//获取双向水平(西/东)大小调整光标
Thumb _topThumb = new Thumb();
_topThumb.Height = _offset;
_topThumb.VerticalAlignment = VerticalAlignment.Top;
_topThumb.Cursor = Cursors.SizeNS;//获取双向垂直(北/南)大小调整光标
Thumb _rightThumb = new Thumb();
_rightThumb.Width = _offset;
_rightThumb.HorizontalAlignment = HorizontalAlignment.Right;
_rightThumb.Cursor = Cursors.SizeWE;//获取双向水平(西/东)大小调整光标
Thumb _bottomThumb = new Thumb();
_bottomThumb.Height = _offset;
_bottomThumb.VerticalAlignment = VerticalAlignment.Bottom;
_bottomThumb.Cursor = Cursors.SizeNS;//获取双向垂直(北/南)大小调整光标
SetLineThumbStyle(_leftThumb);
SetLineThumbStyle(_topThumb);
SetLineThumbStyle(_rightThumb);
SetLineThumbStyle(_bottomThumb);
Thumb _lefTopThumb = new Thumb();
_lefTopThumb.HorizontalAlignment = HorizontalAlignment.Left;
_lefTopThumb.VerticalAlignment = VerticalAlignment.Top;
_lefTopThumb.Cursor = Cursors.SizeNWSE;//获取双向对角线(西北/东南)大小调整光标
Thumb _rightTopThumb = new Thumb();
_rightTopThumb.HorizontalAlignment = HorizontalAlignment.Right;
_rightTopThumb.VerticalAlignment = VerticalAlignment.Top;
_rightTopThumb.Cursor = Cursors.SizeNESW;//获取双向对角线(东北/西南)大小调整光标
Thumb _rightBottomThumb = new Thumb();
_rightBottomThumb.HorizontalAlignment = HorizontalAlignment.Right;
_rightBottomThumb.VerticalAlignment = VerticalAlignment.Bottom;
_rightBottomThumb.Cursor = Cursors.SizeNWSE;//获取双向对角线(西北/东南)大小调整光标
Thumb _leftbottomThumb = new Thumb();
_leftbottomThumb.HorizontalAlignment = HorizontalAlignment.Left;
_leftbottomThumb.VerticalAlignment = VerticalAlignment.Bottom;
_leftbottomThumb.Cursor = Cursors.SizeNESW;//获取双向对角线(东北/西南)大小调整光标
SetPointThumbStyle(_lefTopThumb);
SetPointThumbStyle(_rightTopThumb);
SetPointThumbStyle(_rightBottomThumb);
SetPointThumbStyle(_leftbottomThumb);
Grid _grid = new Grid();
_grid.Children.Add(_leftThumb);
_grid.Children.Add(_topThumb);
_grid.Children.Add(_rightThumb);
_grid.Children.Add(_bottomThumb);
_grid.Children.Add(_lefTopThumb);
_grid.Children.Add(_rightTopThumb);
_grid.Children.Add(_rightBottomThumb);
_grid.Children.Add(_leftbottomThumb);
_Visuals = new VisualCollection(this);
_ContentPresenter = new ContentPresenter();
_ContentPresenter.Content = _grid;
_Visuals.Add(_ContentPresenter);
}
private void SetLineThumbStyle(Thumb thumb)
{
thumb.BorderBrush = Brushes.LightSlateGray;
thumb.BorderThickness = new Thickness(1);
thumb.DragDelta += Thumb_DragDelta;
}
private void SetPointThumbStyle(Thumb thumb)
{
thumb.Width = RectOffset;
thumb.Height = RectOffset;
thumb.Margin = new Thickness(-RectOffset / 2);
thumb.BorderThickness = new Thickness(RectOffset/2);
thumb.Background = Brushes.LightSlateGray;
thumb.BorderBrush = Brushes.LightSlateGray;
thumb.Template = new ControlTemplate(typeof(Thumb))
{
VisualTree = GetFactory(Brushes.LightSlateGray)
};
thumb.DragDelta += Thumb_DragDelta;
}
FrameworkElementFactory GetFactory(Brush back)
{
FrameworkElementFactory fef = new FrameworkElementFactory(typeof(Rectangle));
fef.SetValue(Ellipse.FillProperty, back);
fef.SetValue(Ellipse.StrokeProperty, back);
fef.SetValue(Ellipse.StrokeThicknessProperty, (double)2.5);
return fef;
}
private void Thumb_DragDelta(object sender, DragDeltaEventArgs e)
{
if (_adornedElement is FrameworkElement element && sender is FrameworkElement thumb)
{
double width = 0;
double height = 0;
double minWidth = 10;
double minHeight = 10;
Thickness margin = element.Margin;
if (thumb.HorizontalAlignment == HorizontalAlignment.Left)
{
margin.Left += e.HorizontalChange;
width = element.Width - e.HorizontalChange;
if (width >= minWidth)
{
element.Margin = margin;
element.Width = width;
}
}
else if (thumb.HorizontalAlignment == HorizontalAlignment.Right)
{
margin.Right -= e.HorizontalChange;
width = element.Width + e.HorizontalChange;
if (width >= minWidth)
{
element.Margin = margin;
element.Width = width;
}
}
if (thumb.VerticalAlignment == VerticalAlignment.Top)
{
margin.Top += e.VerticalChange;
height = element.Height - e.VerticalChange;
if (height >= minHeight)
{
element.Margin = margin;
element.Height = height;
}
}
else if (thumb.VerticalAlignment == VerticalAlignment.Bottom)
{
margin.Bottom -= e.VerticalChange;
height = element.Height + e.VerticalChange;
if (height >= minHeight)
{
element.Margin = margin;
element.Height = height;
}
}
}
}
protected override Visual GetVisualChild(int index)
{
return _Visuals[index];
}
protected override int VisualChildrenCount
{
get { return _Visuals.Count; }
}
protected override Size MeasureOverride(Size constraint)
{
_ContentPresenter.Measure(constraint);
return _ContentPresenter.DesiredSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
//布局位置和大小
_ContentPresenter.Arrange(new Rect(-RectOffset/2, -RectOffset/2, this.AdornedElement.RenderSize.Width + RectOffset, this.AdornedElement.RenderSize.Height + RectOffset));
return _ContentPresenter.RenderSize;
}
}
2.4.作为控件的附加属性来控制控件外观显示的变化
Adorner装饰类
public class MDButtonAdorner : Adorner
{
private VisualCollection _Visuals;
private ContentPresenter _ContentPresenter;
double offset = 20;
public MDButtonAdorner(UIElement adornedElement) : base(adornedElement)
{
_Visuals = new VisualCollection(this);
_ContentPresenter = new ContentPresenter();
Border _br = new Border();
_br.CornerRadius = new CornerRadius(50);
_br.Background = Brushes.Red;
TextBlock _txt = new TextBlock();
_txt.Text = "1";
_txt.Width = offset;
_txt.Height = offset;
_txt.Foreground = Brushes.White;
_txt.TextAlignment = TextAlignment.Center;
_br.Child = _txt;
Canvas _grid = new Canvas();
_grid.Children.Add(_br);
_grid.IsHitTestVisible = false;
_ContentPresenter.Content = _grid;
_Visuals.Add(_ContentPresenter);
}
protected override Visual GetVisualChild(int index)
{
return _Visuals[index];
}
protected override int VisualChildrenCount
{
get { return _Visuals.Count; }
}
protected override Size MeasureOverride(Size constraint)
{
_ContentPresenter.Measure(constraint);
return _ContentPresenter.DesiredSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
//布局位置和大小
_ContentPresenter.Arrange(new Rect(this.AdornedElement.RenderSize.Width - offset / 2, -offset / 2, finalSize.Width, finalSize.Height));
return _ContentPresenter.RenderSize;
}
public Visibility IsVisibility
{
get { return _ContentPresenter.Visibility; }
set { _ContentPresenter.Visibility = value; }
}
}
控件增加附加属性
public class MDButton : Button
{
public static readonly DependencyProperty ShowAdorner1Property;
public bool ShowAdorner1
{
get => (bool)GetValue(ShowAdorner1Property);
set => SetValue(ShowAdorner1Property, value);
}
static MDButton()
{
ShowAdorner1Property = DependencyProperty.RegisterAttached("ShowAdorner1", typeof(bool), typeof(MainWindow), new PropertyMetadata(false, Method));
}
public MDButton() { }
private static void Method(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ele = d as Visual;
var uie = ele as UIElement;
if (AdornerLayer.GetAdornerLayer(ele) is AdornerLayer adolay && uie is not null)
{
var ados = adolay.GetAdorners(uie);
if (ados == null)
{
adolay.Add(new MDButtonAdorner(uie));
}
ados = adolay.GetAdorners(uie);
if (ados != null && ados.Count() != 0)
{
if (ados.FirstOrDefault() is MDButtonAdorner ado)
{
ado.IsVisibility = (bool)e.NewValue ? Visibility.Visible : Visibility.Collapsed;
}
}
}
}
}
Xaml
<local:MDButton x:Name="btn5" Height="50" Width="50" Margin="30"/>
实现方式
//显示装饰
btn5.ShowAdorner1 = true;
//隐藏装饰
btn5.ShowAdorner1 = false;
三、展现效果
四、源码下载
总结
不积硅步,何以至千里