摘自:http://www.cnblogs.com/Curry/archive/2009/09/16/1567757.html

顾名思义就是装饰用的,也就是说不改变原有的控件结构,但可以为控件添加一些新的功能,或是为控件的显示外观增加些东西。如MSDN中的例子:

本来TextBox四角没有圆点,但是通过装饰器可以为它加上。所以可以看成在TextBox上加了层。

这样就“无痛”的给控件进行了装饰。当然应用不单单这样加几个点而已,修饰嘛比如拖动控件的修饰

而之前比较著名的层拖拽是Bea StollinitzHow can I drag and drop items between data bound ItemsControls?

一.AdornerLayer

我们说层,是覆盖在控件上的一层东西,那么控件上能不能覆盖多个层呢?

答案当然是可以的,而这些层自然的要放在一个容器中,这个容器就叫做AdornerLayer

然后问题又来了这个层是如何产生的?是我们人为放的,还是自动产生的(虽然自动实际上也是需要有人写的)?

我们知道AdornerLayer有个方法

public static AdornerLayer GetAdornerLayer(Visual visual);

可以得到某个Visual的所在的层,我们打开Reflector进行查看

public static AdornerLayer GetAdornerLayer(Visual visual)
{if (visual == null){throw new ArgumentNullException("visual");}for (Visual visual2 = VisualTreeHelper.GetParent(visual) as Visual; visual2 != null; visual2 = VisualTreeHelper.GetParent(visual2) as Visual){if (visual2 is AdornerDecorator){return ((AdornerDecorator)visual2).AdornerLayer;}if (visual2 is ScrollContentPresenter){return ((ScrollContentPresenter)visual2).AdornerLayer;}}return null;
}

很容易我们就可以看出它实际是通过可视树进行查找,然后判断元素是否为AdornerDecorator或ScrollContentPresenter,如果是的话则取他们的AdornerLayer属性,也就是说AdornerLayer是由AdornerDecorator或ScrollContentPresenter产生的,打开本地MSDN ,键入ScrollContentPresenter

由红框中的文字可以得知ScrollContentPresenter属于ScrollViewer,也就是说有ScrollViewer的地方就会有AdornerLayer,打开ScrollViewer的链接我们又可以了解到ScrollViewer通常需要包装Panel控件

那么哪些控件默认样式是用到ScrollViewer的呢,据我所知继承于ItemsControl的控件,还有Window等常用控件等,当然这里就不一一列举了。

如果实在没有ScrollViewer的地方,或者需要直接在控件上加层,我们也可以手动在控件外面包个AdornerDecorator来产生AdornerLayer。

<AdornerDecorator><TextBox Text="可以得到AdornerLayer"/>
</AdornerDecorator>

那么AdornerLayer到底是种什么概念,为什么总会在控件之上呢?

再用Reflector打开ScrollContentPresenter或AdornerDecorator在GetVisualChild(int index)中应该会注意到下面的代码(下面代码在ScrollContentPresenter中获得)

private readonly AdornerLayer _adornerLayer = new AdornerLayer();protected override Visual GetVisualChild(int index)
{if (base.TemplateChild == null){throw new ArgumentOutOfRangeException("index", index, SR.Get("Visual_ArgumentOutOfRange"));}switch (index){case 0:return base.TemplateChild;case 1:return this._adornerLayer;}throw new ArgumentOutOfRangeException("index", index, SR.Get("Visual_ArgumentOutOfRange"));
}

这里就很明白了,index 0通常是我们需要装饰的控件,1就是AdornerLayer。我们知道系统首先会画0层的东西,再画1层 ,导致1永远都在0上。所以其实AdornerLayer也存在于可视树,可以通过VisualTreeHelper来找到。而且你不管调整控件的z-index都是无用的,人家写死了嘛。

二.Adorner

有了容器,自然的要往里面添加东西,要不然不是空空如也么,有了也等于没有。而AdornerLayer规定能够加入它这个容器的只能是Adorner的派生类。在此淫威下所以我们也不得不臣服,把类继承于Adorner这个抽象类。

public class SimpleTextBlockAdorner : Adorner
{private TextBlock _textBlock;public SimpleTextBlockAdorner(UIElement adornedElement): base(adornedElement) {_textBlock = new TextBlock();_textBlock.Foreground = Brushes.Green;_textBlock.Text = "AdornerText";}protected override Visual GetVisualChild(int index){return _textBlock;}protected override int VisualChildrenCount{get{return 1;}}protected override Size ArrangeOverride(Size finalSize){//为控件指定位置和大小_textBlock.Arrange(new Rect(new Point(-10, 20), _textBlock.DesiredSize));return base.ArrangeOverride(finalSize);}
}

CS中代码如下:

public Window1()
{InitializeComponent();TextBlock textBlock = new TextBlock();textBlock.FlowDirection = FlowDirection.RightToLeft;textBlock.Text = "FlowDirection.RightToLeft";//放一个层容器AdornerDecorator adornerDecorator = new AdornerDecorator();adornerDecorator.Child = textBlock;this.Content = adornerDecorator;//得到层容器var adornerLayer = AdornerLayer.GetAdornerLayer(textBlock);//在层容器中加层adornerLayer.Add(new SimpleTextBlockAdorner(textBlock));
}

当改变textBlock的FlowDirection属性时,会出现如下所示的结果。也就是说被装饰的元素的FlowDirection效果对层上的元素有影响。

 

探其究竟便又要用到Reflector了

原来是做了绑定,不过我一直未探明白的是DispatcherPriority的顺序意义是什么,到底适用在哪些场景,还望高手多多指点。

其次我们关心的另外一件事是,这个层有多大?是覆盖整个被装饰的控件还是整个屏幕或是整个窗口。一般MeasureOverride可以指定控件的大小,顺序是Measure->Arrange->Render(本想贴我布局中的那张图,可回头看有些地方居然是错的,不知道当时怎么想的,囧RZ)。那么来看看MeasureOverride到底干了什么?

从红线框中我们很容易看出他是用了装饰控件的呈现尺寸来修饰的。当然这只是默认,你也可以自己重载MeasureOverride来指定大小。

可要是装饰控件(本文中TextBox)的大小改变了,装饰器(本文中的SimpleTextBlockAdorner)怎么侦测的到?

实际上当我们改变装饰控件的大小时候,多是改变控件的Height或Width, 以改变Height为例,他是产生于FrameworkElement中的,定义如下

public static readonly DependencyProperty HeightProperty = DependencyProperty.Register("Height", typeof(double), _typeofThis, new FrameworkPropertyMetadata((double) 1.0 / (double) 0.0, FrameworkPropertyMetadataOptions.AffectsMeasure, new PropertyChangedCallback(FrameworkElement.OnTransformDirty)), new ValidateValueCallback(FrameworkElement.IsWidthHeightValid));

就是说改变Height的时候会触发Measure方法,而Measure方法会沿可视树向上找到父容器(在本例中是AdornerDecorator),然后调用它的OnChildDesiredSizeChanged方法,而OnChildDesiredSizeChanged中调用的是父容器本身的Measure方法,Measure方法会重新改变子容器的大小,装饰控件(TextBox)和装饰层(AdornerLayer)本来就同属于AdornerDecorator,所以在AdornerDecorator的Measure方法中会调用装饰控件和装饰层的Measure方法,装饰层又会用同样的方法刷新它的子类也就是我们的SimpleTextBlockAdorner,子调用父,父又调用子,子接着调用父?不是死循环了么?所以这里WPF用了变量MeasureInProgress和MeasureDirty来控制,如果已经在Measure中,则不需要循环调用。

这样下来你是不是感觉布局系统是很耗费资源的呢?^ 0 ^

另外对于Adorner中的GetDesiredTransform方法,其实看过AdornerLayer中的布局方法ArrangeOverride就可窥其详了

protected override Size ArrangeOverride(Size finalSize)
{DictionaryEntry[] array = new DictionaryEntry[this._zOrderMap.Count];this._zOrderMap.CopyTo(array, 0);for (int i = 0; i < array.Length; i++){ArrayList list = (ArrayList)array[i].Value;int num2 = 0;while (num2 < list.Count){AdornerInfo info = (AdornerInfo)list[num2++];if (!info.Adorner.IsArrangeValid){Point location = new Point();info.Adorner.Arrange(new Rect(location, info.Adorner.DesiredSize));
                GeneralTransform desiredTransform = info.Adorner.GetDesiredTransform(info.Transform);GeneralTransform proposedTransform = this.GetProposedTransform(info.Adorner, desiredTransform);int index = this._children.IndexOf(info.Adorner);if (index >= 0){Transform transform3 = (proposedTransform != null) ? proposedTransform.AffineTransform : null;((Adorner)this._children[index]).AdornerTransform = transform3;}}if (info.Adorner.IsClipEnabled){info.Adorner.AdornerClip = info.Clip;}else if (info.Adorner.AdornerClip != null){info.Adorner.AdornerClip = null;}}}return finalSize;
}

GeneralTransform desiredTransform = info.Adorner.GetDesiredTransform(info.Transform);

((Adorner)this._children[index]).AdornerTransform = transform3;

从中我们可以看出,分配的时候就是把从GetDesiredTransform得到的值又返回给Adorner的AdornerTransform属性,而AdornerTransform属性其实

RenderTransform属性我们总熟悉了吧,不熟悉?那看这个吧http://msdn.microsoft.com/zh-cn/library/system.windows.uielement.rendertransform.aspx

用RenderTransform可以比较肯定的说,速度要比普通布局快,因为它是在布局之后弄的,并不牵涉到反复的可视树传递引发,所以动画尽量以改变此值为主。

我另外标示的GeneralTransform proposedTransform = this.GetProposedTransform(info.Adorner, desiredTransform); 也一定好奇吧,做什么呢?其实就是开始说的FlowDirection问题,反转上面的控件用的。

private GeneralTransform GetProposedTransform(Adorner adorner, GeneralTransform sourceTransform)
{if (adorner.FlowDirection == base.FlowDirection){return sourceTransform;}GeneralTransformGroup group = new GeneralTransformGroup();Matrix matrix = new Matrix(-1.0, 0.0, 0.0, 1.0, adorner.RenderSize.Width, 0.0);MatrixTransform transform = new MatrixTransform(matrix);group.Children.Add(transform);if ((sourceTransform != null) && (sourceTransform != Transform.Identity)){group.Children.Add(sourceTransform);}return group;
}

三.默认控件的应用

GridSplitter   Grid上的拖拉控件,我想大家应该不用吃惊吧,它是写了个PreviewAdorner来移动。在网上看到了这个链接http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/dfff9b89-81a8-4bfc-852d-d08ccdffe6bb 提问者改变了Window的模板,并在模板中放了Grid和GridSplitter,为什么会报错?现在我们知道了,Window默认的模板中有ScrollViewer,可以产生AdornerLayer,而改变的模板中没有AdornerLayer的容器,而且Window已经是窗口的最高层控件,沿可视树向上找也不会有其他的控件,所以GridSplitter不可能获取到AdornerLayer,因此就抛了NullReferenceException。解决办法便是在GridSplitter  外面套一层有AdornerLayer的东西,或ScrollViewer或AdornerDecorator,在链接中回答者给出的是AdornerDecorator。

       Validation 验证时候用的模板,其实你看到的这些感叹号,外框都是在层上的,他和GridSplitter 不同的是他可以在外面定义个模板,可以让用户自己指定要呈现的东西,为此他它写了个TemplatedAdorner,为什么找不到Validation 的默认模板,因为它用代码写死了。当然如果你发现惊叹号,外框不在该有地方,也容易做了——肯定层的位置有问题嘛。

private static ControlTemplate CreateDefaultErrorTemplate()
{ControlTemplate template = new ControlTemplate(typeof(Control));FrameworkElementFactory factory = new FrameworkElementFactory(typeof(Border), "Border");factory.SetValue(Border.BorderBrushProperty, Brushes.Red);factory.SetValue(Border.BorderThicknessProperty, new Thickness(1.0));FrameworkElementFactory child = new FrameworkElementFactory(typeof(AdornedElementPlaceholder), "Placeholder");factory.AppendChild(child);template.VisualTree = factory;template.Seal();return template;
}

至于AdornedElementPlaceholder这个占位符,它的大小是验证控件(TextBox)的大小,可他却是在模板中定义的,那么他如何来知道具体的验证控件是什么呢,这里它经过TemplatedAdorner中的AdornedElement来达到效果。可以说的上奇巧淫技,它使得AdornedElementPlaceholder知道具体的TemplatedAdorner,可TemplatedAdorner并不知晓具体的AdornedElementPlaceholder,但在AdornedElementPlaceholder同时观察到

所以他的有效作用只有一个。好的控件是能够更好的解耦,可解耦的前提是原来的控件有一定的预留,TemplatedAdorner便是预留了ReferenceElement来达到效果。

四.自定义个遮罩控件

说了这么多是不是技痒了,那先来做个简单的吧,有时当我们读取数据希望未显示完的列表不需要让客户操作,所以需要要这个遮罩层来档下,一方面为了不让客户操作具体控件,令一方面可以让客户看到事情进度或操作信息。那怎么做比较舒服呢?自然的,我希望遮罩只针对某个控件而已,因为其他地方并不影响,依然可以操作。在Demo上就简化了。显示信息的模板可以自定义修改,有没有感觉和刚才说的TemplatedAdorner模板有类似。所以发挥拿来主义的精神。

代码就不在这里列举了,不过要注意的是要把上面的模板控件加入可视树,要不然会穿越,就达不到阻挡的作用,同理如若需要穿越操作的话,可以不加入可视树。

对于代码有些人喜欢完全的附加属性如Validation 那样的赋值,我个人比较喜欢用类赋值,如果不喜欢可以动动手自己改掉,调用代码如下:

<ListView ItemsSource="{Binding Employees}"><ControlLibrary:MaskAttach.MaskAttach><ControlLibrary:MaskAttach x:Name="fff" DataContext="{Binding Progress}" Open="{Binding IsLoading}"><ControlLibrary:MaskAttach.Template><DataTemplate><Grid><Grid.ColumnDefinitions><ColumnDefinition /><ColumnDefinition Width="40"/></Grid.ColumnDefinitions><Rectangle Grid.ColumnSpan="2" Fill="Black" Opacity="0.7"/><TextBlock Grid.Column="1" Margin="5" Foreground="White"  HorizontalAlignment="Right"  VerticalAlignment="Center"><AccessText Text="{Binding}"/><AccessText>%</AccessText></TextBlock><ProgressBar Margin="10" Value="{Binding Mode=OneWay}"  Height="20"/></Grid></DataTemplate></ControlLibrary:MaskAttach.Template></ControlLibrary:MaskAttach></ControlLibrary:MaskAttach.MaskAttach>

在这里还要说明的是,AdornerLayer.GetAdornerLayer取得层的时候最后是放在d.Dispatcher.BeginInvoke中取,因为有时候需要等上面加载完,对于DispatcherPriority我一般选的是Render

而DependencyProperty的属性值改变侦测,如果用Binding的话则需要对象一定要从FrameworkElement派生的,但可以利用DependencyPropertyDescriptor侦测:

var dpdDataContext = DependencyPropertyDescriptor.FromProperty(MaskAttach.DataContextProperty, maskAttach.GetType());
dpdDataContext.AddValueChanged(maskAttach, delegate{d.SetValue(MaskAttach.DataContextProperty, maskAttach.DataContext);});

五.Decorator

当看到Decorator让人更容易的想到Decorator模式,Decorator在我的印象中更接近一个包装器,把原有的方法放入包装类的一个同名方法中,在同名方法中再加些其他的功能罢了。

如果说里面颜色块代表功能大小的化,很明显包装类的功能更强大。而且他增加功能的话又对原来的A类结构是无损的,用户通过接口来操作的话也无须知道实体类。

就WPF而言,用户所要的是呈现效果,在外包一层改变了显示效果但是不影响原有控件的效果和功能。所以他可以操作控件的外观常用的Border控件,以及改变控件现实大小的ViewBox都是继承于Decorator类,还有就是我们之上提到的AdornerDecorator。都是对原来控件外观或控制的扩展。

Decorator本身只是个单容器控件,只能对一个控件进行装饰,Panel的话是多容器,可以对多控件进行装饰,控件大小的位置的改变也是装饰的一种表现形式。本想做个简单的拖拽控件,网上搜索了下发现已经有人做了。http://codeblitz.wordpress.com/2009/06/10/wpf-dragdrop-decorator-for-itemscontrol/

网页似乎被墙,我看的是快照,示例程序我也放上来了以免以后下不到。

对于默认拖拽的显示和做法,下面也记录下:

1.拖拽一般是两个控件之间的数据交互,就是拖拉的时候(MouseDown)把数据放到一个地方,拖拉完之后(MouseUp)再把这个数据放到另一个控件中,所以拖拽的两个控件本身要提供拖拽,继承于UIElement的控件可以直接把AllowDrop属性设置为True.

2.在鼠标点击也就是MouseDown,一般注册MouseDown事件或直接重载OnMouseDown方法,把选中的数据放到变量中,你选中的是控件?控件是数据的呈现,所以你应该能拿的到数据。除非那个控件真的没有数据,那也就不需要拉了,有数据的话,我们把数据放到一个变量_mouseDownData中。

3.在鼠标移动的过程中,我们看到鼠标的样式是会动的(鼠标下面会有小方块),而且我们传数据也需要个方法传对吧,所在鼠标移动的时候有MouseMove做下面这句

DragDrop.DoDragDrop((ItemsControl)sender, _mouseDownData, DragDropEffects.Move | DragDropEffects.Copy);

4.鼠标放开就就是MouseUp的话,把传递的数据的变量mouseDownData清空或赋值为Null.

5.拖拉有两方,假设要把A数据拖到B上,那么A调用了DragDrop.DoDragDrop方法去放,B怎么接收的呢?B控件要注册PreviewDrop事件,通过DragEventArgs e参数来获得e.Data,其中数据类型一般先e.Data.GetDataPresent(typeof(数据类型))来判断有没有,然后通过e.Data.GetData(typeof(数据类型))具体拿值,e.Effects可以用来判断操作:是否要把数据添加进B控件。

6.假定拖拉到一般要取消怎么办?控件注册PreviewQueryContinueDrag事件在QueryContinueDragEventArgs e中对e.Action进行赋值操作,可以DragAction.Cancel当然也可以DragAction.Drop或者DragAction.Continue了。

关于拖拽这里还有个文章:http://www.cnblogs.com/taowen/archive/2008/10/30/1323329.html

六.纯粹的个人感概

在我学习WPF的第一个月,可以说自我感觉最良好的时候,当时认为什么都可以做了,WPF不过偶尔,凭借着以前的Winform开发思路,在OnRender中大放光彩,认为什么都能做,所以之前的控件都是自己重做的,看微软默认的不爽就重写,没有的就自己造,后来慢慢的,开始MVVM,开始大量的转变控件,虽然默认的控件大部分到最后也都是Draw出来的,但是使用默认控件拼出新控件却是团队沟通的桥梁,默认控件一般都能满足需求,自己定义的话可能效率有一定优势,开始的时候也方便,到最后做大做复杂也挺麻烦,最主要的是团队成员的样式套用就麻烦,整体效果就有影响。之前自己有个想法,就是控件加快开发进度的,所以不好用的就不用了,实际上是也没怎么认真去想怎么用。默认控件的模式和思路都是值得研究和学习的。当然如果你的控件需要一定的运行效率那就只能重做一份了。

关于模式,面向对象开发,都是希望把责任分的更清晰,把功能切的更细,那把责任和功能切的更细的意义是什么呢,易于维护,可团队里的每个人的思维形态并不一样,你认为这样好,可人家却很难理解,难理解之后沟通是不是也难了,开发效率怎么上的去?所以利于沟通的设计才是好的设计。当团队思想慢慢的趋于一致,再发挥你的才干,你可能会得到更好的帮助和建议。有些事不必要急于一时。

在这里感谢很多热心人的帮助,特别是周永恒,高手虽多可忙的不少,乐于解答的更少,帮你分析的少之又少。而周永恒却可以帮你具体讲解分析,在这里表示由衷的谢意。

转载于:https://www.cnblogs.com/jojinshallar/articles/3372323.html

WPF自定义控件 —— 装饰器相关推荐

  1. WPF 用装饰器制作抽屉效果

    wpf实现抽屉效果,一般就一个动画显示就完事了,我这用到了,就研究了一下,用装饰器给控件添加遮罩层,然后在上面添加抽屉控件,虽然麻烦了点,也算是自己研究的成果了. 看看效果: 下面就看看代码: 首先是 ...

  2. 关于WPF装饰器的笔记

    关于装饰器 参见这几篇文章,较为详尽 详解Adorner Layer http://www.cnblogs.com/nankezhishi/archive/2010/07/10/adornerlaye ...

  3. WPF自定义控件——顶级控件

    作为一个WPF程序员,我最希望看到的是WPF的应用,或者更确切的说是绚丽的应用,虽然限于自身的实力还不能拿出成绩来,但看到别人的作品时,心里还是有很大的宽慰--WPF是可以做出更加动人地产品的,只要你 ...

  4. WPF和Expression Blend开发实例:Adorner(装饰器)应用实例

    装饰器-- 表示用于修饰 UIElement 的 FrameworkElement 的抽象类 简单来说就是,在不改变一个UIElement结构的情况下,将一个Visual对象加到它上面. 应用举例: ...

  5. Python 闭包、单个装饰器、多个装饰器、装饰器修饰类、应用场景

    1. 闭包 在 Python 中,函数也可以作为参数.我们可以执行下面的代码: def func(a, b):return a + bprint(func) 我们直接输出函数名,而没有加括号.输出结果 ...

  6. python重难点之装饰器详解

    背景 虽然之前看过装饰器的相关内容,但是今天想起来,一直没有好好总结一下,所以特地记录下关于装饰器的一系列用法. 要想理解装饰器首先要明确颇python中的三个概念: 1.一切函数皆为对象 2.高阶函 ...

  7. python装饰器学习

    装饰器 要学会装饰器,必须首先知道什么是闭包. 闭包: 在函数中提出的概念 就是内层函数对外层函数(非全局变量的运用)并且返回值是内部函数的引用. 格式: - def 外部函数:def内部函数:ret ...

  8. 对装饰器@wraps的解释(一看就懂)-- 并对装饰器详解

    1. 先看一段代码 def is_login(func):def foo(*args,**kwargs):return func(*args,**kwargs)return foodef test() ...

  9. java设计模式----装饰器模式

    Decorator装饰器,顾名思义,就是动态地给一个对象添加一些额外的职责,就好比为房子进行装修一样.因此,装饰器模式具有如下的特征: 它必须具有一个装饰的对象. 它必须拥有与被装饰对象相同的接口. ...

最新文章

  1. linux和windows双系统引导,windows和linux双系统引导问题
  2. 王金锋:母婴菌群传递的7大关键词
  3. 系统集成项目管理之项目采购管理
  4. 给定一个投资组合的收益序列,以沪深300作为参照,分解该投资组合的α和β
  5. CreateDesktop 创建虚拟桌面
  6. AI:PR的数学表示-传统方法PR
  7. npm中package.json详解
  8. 职场中,要善于交换利益
  9. python 检测端口是否被占用
  10. Linux后台运行python程序并输出到日志文件
  11. Mysql 分区(range,list,hash)转载
  12. 创建一个war类型的maven项目
  13. vision画流程图的软件_程序流程图用什么软件画?
  14. 利用pytorch实现图像分类
  15. 阿里2018届实习生内推经历
  16. DiMP:Learning Discriminative Model Prediction for Tracking
  17. python鼠标键盘事件代码_Python鼠标键盘事件
  18. CynosDB 与传统数据库有什么不同?CynosDB 的兼容性怎么样?
  19. java的几个生命周期(部分简单总结)
  20. ESP32 sample code 编译报错: FAILED: esp-idf/mbedtls/x509_crt_bundle

热门文章

  1. css字体自定义,bootstrap自定义字体
  2. 前端学习(3181):ant-design的button介绍
  3. 前端学习(3060):vue+element今日头条管理-处理展示文章封面
  4. [html] 你有了解HTML5的地理定位吗?怎么使用?
  5. 前端学习(2381):代码提交
  6. 前端学习(2148):webpack之图片的处理
  7. 前端学习(1748):前端调试值之console的一些设置
  8. 前端学习(1743):前端调试值之调试元素的hover样式
  9. 前端学习(1384):多人管理项目4资源的路径
  10. 前端学习(977):本地存储导读