在 XAML 中定义 WPF Adorners






4.97/5 (84投票s)
介绍一个允许在 XAML 中定义 Adorners 的自定义类。
- 下载 SimpleAdornedControlSample - 23.06 KB
- 下载 AdvancedAdornedControlSample - 25.3 KB
- 下载 ImprovedAdornedControlSample - 18.4 KB
引言
本文介绍了我用来在 XAML 中定义 WPF Adorners 的一种技术。该技术需要两个自定义类。
这些类用于定义
- 要被 Adorn 的控件;以及
- 构成 Adorner 的 UI 元素
主类是 AdornedControl
,它继承自 ContentControl。AdornedControl
的内容会以正常方式显示在 UI 的视觉树中。Adorner 使用 AdornedControl
的 AdornerContent
属性进行定义。此属性在 XAML 中的内容定义了将附加到 AdornedControl
的 Adorner 中显示的 UI 元素。
我附带了两个示例项目来配合本文。简单示例展示了本文所述的 AdornedControl
的一个非常简单的用法。高级示例展示了一个更高级的用法,满足了下面“背景”部分所述的需求。
假设知识
假设您已经了解 C#,并对 WPF 和 XAML 的使用有基本了解。了解 WPF 的视觉树和逻辑树也会有所帮助。
背景
最近,我发现我需要一种方法,在用户将鼠标悬停在特定控件上时显示辅助控件。
该特定控件是嵌入在 Canvas 中的一个流程图节点,看起来是这样的

辅助控件的目的是使用户能够拖动节点在 Canvas 中移动,并能够从 Canvas 中移除节点。
带有辅助控件显示的节点是这样的

通常,辅助控件不会显示。它们只在鼠标悬停在节点上时可见。当鼠标移开节点并且经过一段时间后,它们会保持可见。我决定 Adorners 很适合这种情况,因为它们是在节点视觉树之外定义的,因此不会干扰节点的正常布局和视觉效果。
创建 Adorners 并将它们添加到 Adorner 层通常是通过过程代码完成的。我真正想要的是在 XAML 中设计被 Adorn 的控件及其 Adorners。我创建了两个协同工作的自定义类来实现这一点:AdornedControl
和 FrameworkElementAdorner
。
但首先,让我们回顾一下使用 Adorners 的常规方法。
使用 Adorners - 常规方法
使用 Adorners 的常规方法是过程式的。您需要创建一个派生自 Adorner 的类。在派生类中,您要么包含一些自定义的渲染代码,要么将 UI 元素和视觉对象作为子项附加到 Adorner。接下来,您需要创建派生 Adorner 类的实例。要被 Adorn 的 UI 元素被传递到 Adorner 的构造函数中。最后,Adorner 被添加到 Adorner 层。所有这些都是在 C# 代码中通过过程方式实现的。
为了说明这一点,这里有一个简单的例子。
首先,这是从 Adorner 派生的类
class MyAdorner : Adorner
{
public MyAdorner(UIElement adornedElement) :
base(adornedElement)
{
// ... other initialisation ...
}
// ... class members ...
}
自定义渲染代码被添加到 'OnRender
' 中,您可以在其中直接使用 'drawingContext
'。
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
// ... add custom rendering code here ...
}
当您准备好显示 Adorner 并允许用户与之交互时,必须将其添加到 Adorner 层。调用 AdornerLayer.GetAdornerLayer
,通常传递要被 Adorn 的 UI 元素的引用。GetAdornerLayer
会向上搜索视觉树以找到合适的 Adorner 层来使用。
Control parentControl = ...
UIElement adornedElement = ...
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(parentControl);
MyAdorner myAdorner = new MyAdorner(adornedElement);
adornerLayer.Add(myAdorner);
编写自定义渲染代码的替代方法是将子项添加到 Adorner 的视觉树中。这意味着任何派生自 Visual 的类都可以被实例化并添加到 Adorner 下方的视觉树中。我在 FrameworkElementAdorner
中使用了这种技术。
这只是对过程式使用 Adorners 的简要讨论。我文章的主要目的是讨论如何在 XAML 中使用 Adorners。
使用 AdornedControl 在 XAML 中定义 Adorners
在 XAML 中定义 UI 有很多优点。它不仅比编写等效的过程代码更简单,而且更安全,不易出错。不幸的是,如上所示,Adorners 通常是过程式创建的,默认情况下无法在 XAML 中定义。这似乎是 WPF 的一个不必要的遗漏,所以我实现了一个允许这样做的类。
AdornedControl
允许在 XAML 中定义 Adorners。它同时定义了 Adorner 和要被 Adorn 的控件。
这是 AdornedControl
在 XAML 中最基本的定义
>> <local:AdornedControl
>> Width="50"
>> Height="50"
>> >
>> </local:AdornedControl>
AdornedControl
继承自 ContentControl
。因此,它可以包装任何其他内容、视觉对象或 UI 元素。例如,这定义了一个矩形作为要被 Adorn 的 UI 元素
<local:AdornedControl
Width="50"
Height="50"
>
>> <Rectangle
>> Stroke="Blue"
>> />
</local:AdornedControl>
下一个示例定义了一个椭圆作为矩形的 Adorner
<local:AdornedControl
Width="50"
Height="50"
>
<Rectangle
Stroke="Blue"
/>
>> <local:AdornedControl.AdornerContent>
>> <Ellipse
>> Width="50"
>> Height="50"
>> Stroke="Green"
>> />
>> </local:AdornedControl.AdornerContent>
</local:AdornedControl>
现在我们使用 AdornedControl
的 HorizontalAdornerPlacement
结合 Adorner 的 HorizontalAlignment
来水平放置 Adorner,并使其位于被 Adorn 的控件的右侧外部。
<local:AdornedControl
Width="50"
Height="50"
>> HorizontalAdornerPlacement="Outside"
>
<Rectangle
Stroke="Blue"
/>
<local>AdornedControl.AdornerContent>
<Ellipse
Width="50"
Height="50"
Stroke="Green"
>> HorizontalAlignment="Right"
/>
</local:AdornedControl.AdornerContent>
</local:AdornedControl>
AdornedControl
有各种显示和隐藏 Adorner 的方式,但在这里我们使用 IsAdornerVisible 属性使其默认可见
<local:AdornedControl
Width="50"
Height="50"
HorizontalAdornerPlacement="Outside"
>> IsAdornerVisible="True"
>
<Rectangle
Stroke="Blue"
/>
<local:AdornedControl.AdornerContent>
<Ellipse
Width="50"
Height="50"
Stroke="Green"
HorizontalAlignment="Right"
/>
</local:AdornedControl.AdornerContent>
</local:AdornedControl>
IsAdornerVisible 也可以通过过程方式设置来显示或隐藏 Adorner。或者,可以使用 Show()
和 Hide()
函数以及 Show
和 Hide
命令。在高级示例中,我使用动画在显示和隐藏 Adorner 时将其淡入淡出。
最后,我尚未提及的另一个自定义类是 FrameworkElementAdorner
。这个类由 AdornedControl
在内部使用。它继承自 Adorner
,并在视觉树中引用一个 FrameworkElement
作为其子项。这使得我们可以将任何 FrameworkElement
添加到 Adorner 的视觉树中。
FrameworkElementAdorner
基于 Josh Smith 的 UIElementAdorner
,可以在 这里 找到。我对其进行了改编,以支持 FrameworkElement
,并进行了一些我需要的修改。FrameworkElementAdorner
的代码是创建具有视觉子项的 Adorner 的一个好例子。结论
本示例解释了如何使用 AdornedControl
在 XAML 中定义被 Adorn 的控件及其 Adorner。
历史
- 2010 年 2 月 7 日:文章更新
- 现在会监视被 Adorn 元素的
SizeChanged
事件,以便更新 Adorner 的位置。 - 修复了 Adorner 在被 Adorn 元素的左上角外部放置的问题。
- 现在会监视被 Adorn 元素的
- 2010 年 2 月 27 日:代码更新
- 更新只是为了移除在项目生成时自动添加的 Assembly.cs 中的版权信息。我测试了代码仍然可以编译并运行。
- 2010 年 6 月 18 日:文章更新
AdornedControl
的 'Focusable
' 属性现在默认设置为 'false
'。- 添加了一个新的改进示例项目。在功能上,“改进示例”与“高级示例”相同,但是用于淡入淡出 Adorner 的动画代码现在已集成到
AdornedControl
类本身中,这使得将此功能迁移到新项目变得轻而易举。新的方法 'FadeInAdorner
' 和 'FadeOutAdorner
' 可以被调用来淡入淡出 Adorner。或者,'FadeIn
' 和 'FadeOut
' 命令可以触发此行为。新的属性 'IsMouseOverShowEnabled
' 可以设置为 'true
',以便在鼠标光标悬停在被 Adorn 的控件上时自动淡入并显示 Adorner。
- 2010 年 9 月 25 日:文章更新
- 现在通过设置
IsAdornerVisible
属性来设置 Adorner 的动画状态(感谢 Tri Q Tran 的指点)。 - 进行了一些更改,以确保 Adorner 的动画状态始终正确。
- 现在是对 Adorner 的不透明度进行动画处理,而不是对 Adorner 内容进行动画处理。
- 现在通过设置
- 2010 年 10 月 11 日:文章更新
- 向
AdornedControl
添加了一个新的依赖属性。AdornedTemplatePartName
用于指定视觉树中要被 Adorn 的元素的零件名称。默认情况下,该属性设置为null
,这会导致AdornedControl
本身成为被 Adorn 的 UI 元素(原始行为)。当该属性设置为有效的零件名称时,AdornedControl
会搜索其下方的视觉树,以查找要被 Adorn 的命名 UI 元素。例如,当AdornerContent
是一个可编辑的ComboBox
时,将AdornedTemplatePartName
设置为PART_EditableTextBox
将导致ComboBox
的TextBox
部分被 Adorn。
此功能由 Richard Deeming 提出。
- 向
- 2011 年 3 月 15 日:文章更新
- 修复了 ImprovedAdornedControlSample 中的一个问题。此问题由 Member 2477019 报告(详情请参阅文章消息),修复由 Louis-Philippe Lauzier 提出。
我移除了 HideAdornerInternal
中设置 IsAdornerVisible
为 false
的代码。这行代码似乎干扰了数据绑定,并且经过分析,这行代码似乎完全不必要,因此删除它应该不会造成任何问题。