带有数据绑定支持的 XAML 设计装饰器






4.89/5 (7投票s)
介绍了一些简单的控件,

引言
本文介绍的SmartAdorner
类允许您仅使用XAML在WPF中为任何可视化元素设计和附加adorner。adorner的内容在数据模板中定义,允许数据绑定到被adorner元素的数据上下文。这样,就可以在adorner元素中像在被adorner元素中一样使用和操作绑定模型(或视图模型)的属性。
为了进一步支持adorner的设计,文章代码还包含一个派生自Thumb
的控件,该控件允许仅通过数据绑定来操作坐标,以及一个通用控件来支持角落的调整大小手柄。
背景
WPF中的Adorners是在adorner层上现有元素的装饰,显示在其他可视化元素的上方。Adorners的典型用途是为选定的元素提供拖动/调整大小手柄。如果没有adorner层,这些手柄有时会被隐藏在其他对象后面,使得用户更难甚至无法与它们进行交互。这里提出的SmartAdorner
解决方案是通用的,也支持其他类型的句柄以及动态和交互式装饰。
Adorner始终与一个被adorner元素相关联,该元素必须作为参数传递给Adorner
构造函数。因此,在没有帮助(如这里介绍的SmartAdorner
)的情况下,无法直接在WPF中创建adorners。SmartAdorner
派生自Adorner
,但具有附加属性,允许在数据模板中定义XAML内容,并动态显示和隐藏adorner。
在网上搜索现有解决方案时,我没有找到任何像它一样简单,而且还支持数据绑定的。
Using the Code
添加一个通用Adorner
要为元素的adorner指定内容,只需像这样设置SmartAdorner.Template
附加属性:
<StackPanel Name="test" a:SmartAdorner.Visible="{Binding IsSelected}" >
<Rectangle Width="64" Height="64" Stroke="Black" Fill="White" />
<TextBlock Text="{Binding Text}" />
<a:SmartAdorner.Template>
<DataTemplate DataType="{x:Type local:IconViewModel}" >
<Canvas>
<a:DragThumb Name="IconThumb" Canvas.Left="-6"
Canvas.Top="-6" Width="12"
Height="12" X="{Binding X}" Y="{Binding Y}" />
<TextBox Canvas.Top="64" T
ext="{Binding Text, UpdateSourceTrigger=PropertyChanged}" />
</Canvas>
</DataTemplate>
</a:SmartAdorner.Template>
</StackPanel>
在此示例中,我们使用一个包含thumb控件和文本框的Canvas
来adorner StackPanel
。如所示,可以使用数据绑定。要使adorner可见,必须将SmartAdorner.Visible
设置为true
。在此示例中,当项目在包含的列表框中被选中时,我们使用数据绑定来动态设置此值。
在设计adorner时,了解数据模板上下文在adorner层中的ContentPresenter
中布局,其位置、大小、布局和渲染变换会自动调整以适应被adorner元素的布局。这里,使用Canvas
来允许元素精确放置,即使在被adorner元素的边界之外,但任何元素都可以用在SmartAdorner
的数据模板内部。
对于高级用法,SmartAdorner
还通过SmartAdorner.TemplateSelector
附加属性支持根据数据对象动态选择adorner模板。
添加拖动Thumb
Adorners的常见用途是提供拖动和调整大小的手柄。内置的Thumb
类可以用于此目的,前提是在代码隐藏中编写至少一个DragDelta
事件处理程序。文章代码包含一个派生自Thumb
的版本,名为DragThumb
,它允许在拖动时更改任何一对属性(或仅一个维度)。DragThumb
中的依赖属性X
和Y
允许在XAML中直接绑定到属性,而无需编写任何代码隐藏。在示例项目中,这用于能够操作具有属性X1
、Y1
、X2
和Y2
的线对象。
<a:SmartAdorner.Template>
<DataTemplate DataType="{x:Type local:LineViewModel}" >
<Canvas>
<a:DragThumb Canvas.Left="{Binding X1}"
Canvas.Top="{Binding Y1}" Margin="-6"
X="{Binding X1}"
Y="{Binding Y1}" Width="12"
Height="12" />
<a:DragThumb Canvas.Left="{Binding X2}"
Canvas.Top="{Binding Y2}" Margin="-6"
X="{Binding X2}" Y="{Binding Y2}"
Width="12" Height="12"/>
</Canvas>
</DataTemplate><br /> </a:SmartAdorner.Template>
添加调整大小手柄
由于adorner的常见用法是在角落提供调整大小的手柄,我创建了一个可以简化此操作的控件。只需将ResizingAdorner
放在adorner模板中,并将要操作的属性绑定到X
、Y
、Width
和Height
,如下所示:
<a:SmartAdorner.Template>
<DataTemplate DataType="{x:Type local:RectViewModel}" >
<a:ResizingAdorner X="{Binding X}" Y="{Binding Y}"
Width="{Binding Width}" Height="{Binding Height}"
MinWidth="10" MinHeight="20"
MaxWidth="200" MaxHeight="400" />
</DataTemplate>
</a:SmartAdorner.Template>
如代码片段所示,ResizingAdorner
支持宽度和高度的最小和最大约束。
由于ResizingAdorner
是一个内容控件,可以在其中放置任意内容,如果您愿意,也可以在adorner模板外部使用它。
默认情况下,角落会显示普通的Thumbs
,但由于ResizingAdorner
被设计为一个无外观的自定义控件,它支持通过指定新的控件模板来进行完全的重新样式化。
关注点
SmartAdorner的高效实现
SmartAdorner
类派生自Adorner
,并定义了用于设置模板和使adorner可见的附加属性。它还使用private
附加属性Adorner
来存储创建的adorner的引用,该引用仅在Visible
被设置为true
时在属性更改回调中创建,如下所示:
private static void OnVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement adornedElement = d as FrameworkElement;
if (adornedElement == null) throw new InvalidOperationException("Adorners can only be applied to elements deriving from FrameworkElement");
AdornerLayer layer = AdornerLayer.GetAdornerLayer(adornedElement);
if (layer == null) throw new InvalidOperationException("Cannot show adorner since no adorner layer was found in the visual tree");
SmartAdorner adorner = GetAdorner(adornedElement);
bool isVisible = (bool)e.NewValue;
if (isVisible && adorner == null)
{
adorner = new SmartAdorner(adornedElement);
SetAdorner(adornedElement, adorner);
layer.Add(adorner);
}
else if( !isVisible && adorner != null )
{
layer.Remove(adorner);
SetAdorner(adornedElement, null);
}
}
SmartAdorner
使用一个ContentPresenter
作为其唯一的子元素,其数据上下文和模板在构造函数中初始化。
private ContentPresenter presenter;
public SmartAdorner(FrameworkElement adornedElement)
: base(adornedElement)
{
presenter = new ContentPresenter();
Binding dataContextBinding = new Binding("DataContext");
dataContextBinding.Source = adornedElement;
BindingOperations.SetBinding(presenter, ContentPresenter.ContentProperty, dataContextBinding);
Template = GetTemplate(adornedElement);
TemplateSelector = GetTemplateSelector(adornedElement);
AddVisualChild(presenter);
AddLogicalChild(presenter);
}
public DataTemplate Template
{
get { return presenter.ContentTemplate; }
set { presenter.ContentTemplate = value; }
}
public DataTemplateSelector TemplateSelector
{
get { return presenter.ContentTemplateSelector; }
set { presenter.ContentTemplateSelector = value; }
}
其余的只是一些必须覆盖以布局单个子元素的方法。
protected override int VisualChildrenCount
{
get
{
return 1;
}
}
protected override System.Windows.Media.Visual GetVisualChild(int index)
{
if (index == 0) return presenter;
throw new ArgumentOutOfRangeException("index");
}
protected override Size MeasureOverride(Size constraint)
{
presenter.Measure(constraint);
return presenter.DesiredSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
presenter.Arrange(new Rect(new Point(0,0), finalSize));
return finalSize;
}
结论
在本文中,您学习了一种通用的adorner类的高效实现,该类允许您在XAML中定义新的adorners,并支持数据绑定。该解决方案相当通用且高效。尽管它可能不是构建设计表面的完整解决方案,但我希望您能从中找到好的用途。
历史
- 2014年1月20日 – 初始版本