65.9K
CodeProject 正在变化。 阅读更多。
Home

在 XAML 中定义 WPF Adorners

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.97/5 (84投票s)

2010年1月25日

CPOL

7分钟阅读

viewsIcon

435455

downloadIcon

24920

介绍一个允许在 XAML 中定义 Adorners 的自定义类。

引言

本文介绍了我用来在 XAML 中定义 WPF Adorners 的一种技术。该技术需要两个自定义类。

这些类用于定义

  • 要被 Adorn 的控件;以及
  • 构成 Adorner 的 UI 元素

主类是 AdornedControl,它继承自 ContentControlAdornedControl 的内容会以正常方式显示在 UI 的视觉树中。Adorner 使用 AdornedControlAdornerContent 属性进行定义。此属性在 XAML 中的内容定义了将附加到 AdornedControl 的 Adorner 中显示的 UI 元素。

我附带了两个示例项目来配合本文。简单示例展示了本文所述的 AdornedControl 的一个非常简单的用法。高级示例展示了一个更高级的用法,满足了下面“背景”部分所述的需求。

假设知识

假设您已经了解 C#,并对 WPF 和 XAML 的使用有基本了解。了解 WPF 的视觉树和逻辑树也会有所帮助。

背景

最近,我发现我需要一种方法,在用户将鼠标悬停在特定控件上时显示辅助控件。

该特定控件是嵌入在 Canvas 中的一个流程图节点,看起来是这样的

adornedcontrol1.jpg

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

带有辅助控件显示的节点是这样的

adornedcontrol2.jpg

通常,辅助控件不会显示。它们只在鼠标悬停在节点上时可见。当鼠标移开节点并且经过一段时间后,它们会保持可见。我决定 Adorners 很适合这种情况,因为它们是在节点视觉树之外定义的,因此不会干扰节点的正常布局和视觉效果。

创建 Adorners 并将它们添加到 Adorner 层通常是通过过程代码完成的。我真正想要的是在 XAML 中设计被 Adorn 的控件及其 Adorners。我创建了两个协同工作的自定义类来实现这一点:AdornedControlFrameworkElementAdorner

但首先,让我们回顾一下使用 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>

现在我们使用 AdornedControlHorizontalAdornerPlacement 结合 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() 函数以及 ShowHide 命令。在高级示例中,我使用动画在显示和隐藏 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 元素的左上角外部放置的问题。
  • 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 将导致 ComboBoxTextBox 部分被 Adorn。
      此功能由 Richard Deeming 提出。
  • 2011 年 3 月 15 日:文章更新
    • 修复了 ImprovedAdornedControlSample 中的一个问题。此问题由 Member 2477019 报告(详情请参阅文章消息),修复由 Louis-Philippe Lauzier 提出。
    • 我移除了 HideAdornerInternal 中设置 IsAdornerVisiblefalse 的代码。这行代码似乎干扰了数据绑定,并且经过分析,这行代码似乎完全不必要,因此删除它应该不会造成任何问题。

© . All rights reserved.