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

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (7投票s)

2014年1月20日

CPOL

4分钟阅读

viewsIcon

41162

downloadIcon

1809

介绍了一些简单的控件,可帮助您仅使用 XAML 设计带有数据绑定支持的装饰器

引言

本文介绍的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中的依赖属性XY允许在XAML中直接绑定到属性,而无需编写任何代码隐藏。在示例项目中,这用于能够操作具有属性X1Y1X2Y2的线对象。

<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模板中,并将要操作的属性绑定到XYWidthHeight,如下所示:

<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日 – 初始版本
© . All rights reserved.