首页 > 代码库 > 详解Adorner Layer(zz)

详解Adorner Layer(zz)

首先,千万不要觉得Adorner离你很远,因为最简单的WPF界面也会用到Adorner。在WPF中,下面的几个很常见的功能,都是用Adorner实现的。

    1. 光标(caret)

    2. 焦点(focus)

    3. 高亮(highlight)

    4. 拖拽预览(drag and drop)

    5. 拼写错误提示

    6. 数据绑定中用来提示错误的Error Template

当然还有别的,用Reflector很容易找到一些WPF中自带的Adorner。如下图所示。

很多吧!这些Adorner都是放在一个叫Adorner Layer的层上。MSDN解释说Adorner Layer是置于一个窗口内所有其它控件之上的。而且AdornerLayer类又没有public的结构函数,只能用下面的代码来取得某个控件的Adorner Layer的实例:

但是上面的方式会让人产生两个错觉:

    1. Adorner Layer是WPF自带的,内置的,我们管不了。

    2. 每个控件都有自己的Adorner Layer。

如果继续看看GetAdornerLayer的代码,就很容易知道Adorner Layer是从何而来的了。鉴于这个函数的源代码比较丑陋,就不贴在这里给微软丢人了。但是从源代码我们可以知道:

    1. 不是每个控件都有Adorner Layer,其实只有AdornerDecorator和ScrollContentPresenter附带有Adorner Layer。

    2. 对某个element取到的Adorner Layer,一般是其Ancestor的。

顺便解释一下,Decorator,很熟悉吧,就是装饰模式在WPF中的产物。是个比Adorner更宽泛的东西,Adorner就是Decorator的一种。我们常见的Border和 Viewbox等都属于Decorator。关于Decorator的更多信息可以看这里。

这样说来,AdornerDecorator就是Adorner Layer的提供者,我们再来看一下Window的默认Template。取自Blend中的aero.normalcolor.xaml文件。

<ControlTemplate TargetType="{x:Type Window}">
    <Border Background="{TemplateBinding Background}"
            BorderBrush="{TemplateBinding BorderBrush}"
            BorderThickness="{TemplateBinding BorderThickness}">
        <AdornerDecorator>
            <ContentPresenter/>
        </AdornerDecorator>
    </Border>
</ControlTemplate>

也就是说所有的使用默认Template的Window都会有一个AdornerDecorator为其提供Adorner Layer。所以如果要自定义Window的Template也一定要记得为ContentPresenter加一个AdornerDecorator。

下面是AdornerDecorator的ArrangeOverride函数定义:

protected override Size ArrangeOverride(Size finalSize)
{
    Size size = base.ArrangeOverride(finalSize);
    if (VisualTreeHelper.GetParent(this._adornerLayer) != null)
    {
        this._adornerLayer.Arrange(new Rect(finalSize));
    }
 
    return size;
}

在base.ArrangeOverride中,Window中的Content得以渲染。然后AdornerDecorator才去Arrange自带的_adornerLayer。这样这个Adorner layer就位于所有Window Content之上了。

但是,是不是说就没有办法把东西放在AdornerLayer之上了呢?请读者自己想一想吧。

这里我也很想啰嗦一下,在重写的函数里,要不要调用base函数?在什么地方调用?都是很值得注意的。这个问题是要看具体情况具体分析的。而要想正确地调用base函数,就要对基类有一定了解。所以在需要的时候阅读源代码,了解其工作原理,是很有必要的。这个公理,也可以引出这样一个推论——在短时间内学通一项技术的想法或产品,都是不现实的。(学通的定义:如果把WPF从.NET里删除,理论上,能够自己重写一套出来)

知道了Adorner Layer的来源,我们再来看一下Adorner Layer上的Adorner。Adorner Layer里只能放Adorner,而Adorner也没有无参构造函数。所以有关Adorner的一切操作,在默认情况下,都只能在C#代码中进行(当然总有办法在XAML中定义Adorner)。在构造Adorner的时候,必须把Adorned Element做为参数传给Adorner。因为Adorner需要知道Adorned Element的位置和大小等信息。以便让Adorner Layer知道在什么地方渲染这个Adorner。所有的这些信息由Adorner Layer保存在一个叫Adorner Info的内部类中。

其中包含了渲染每个Adorner时所需要的一些信息。其中RenderSize和Transform都是根据Adorner的AdornedElement计算出来的。一但某个UIElement的位置或Transform等发生了变化。这个Element所关联到的所有的Adorner的AdornerInfo都会被更新一次。这样Adorner看上去和Adorned Element是一个控件一样,其实只是用Trasform把二者从位置关系上粘在了一起而已。

上面算是把Adorner Layer的原理介绍完了。简言之,一般一个Window有唯一的一个Adorner Layer,在渲染时,所有的Adorner都被放在了窗口的左上角,再用RenderTransform把这个Adorner移动到其关联的Adorned Element上。

Adorner继承于FrameworkElement,是个连Content属性,Child属性或是ControlTemplate属性都没有的东西。后果就是你要自己做一个Adorner,就要先继承Adorner类,再重写OnRender函数,并在里面,一条线,一条线地画出你想要的效果。当然谁也不想真的用DrawingContext是画个东西出来,解决方案也有不少。一个就是先给Adorner加上个UIElement类型的Child属性,然后就可以住这个UIElementAdorner里加WPF常见控件了不是?这里也给出了实现方式。

最后一个问题就是应该在什么时候用Adorner?我想应该从其设计理念上来回答这个问题。Adorner本身属于Decorator的一种,能够在不改变原有XAML结构的条件下,提供为每个独立控件,附着其它界面元素或装饰物的手段。这是一个很强大的设计思想。至于你可以用它来做什么?从第一张图中你应该可以看出一些端倪。但是在实际项目中,还要看大家自己的发挥了。下面大致给大家列举一些例子。(微软已经实现的就不再例出来了)

    1. ListView列头中,表示当前排序方式的小箭头。我在之前的文章中已经给出了实现方式。

    2. 图表中,如果需要每点一下鼠标,可以在图表上留下一个标记。这个标记就可以放在Adorner Layer中。

    3. 程序加载或某个操作进行中时,为整个界面加的蒙版与Processing动画。

    4. 界面设计工具中,用来调节控件大小的锚点。

这里已经给出了实现方式。

    5. 放大镜控件中,放大出来的图像。(没实例,想象中)

    6. 鼠标拖出来的选框,这个用图描述比较简洁。

在WPF程序中,善用Adorner Layer,相信不仅能够带来一些出众的效果,也能所软件的架构和模块更加明晰。