天天看点

WPF:从WPF Diagram Designer Part 1学习控件模板、移动、改变大小和旋转

  

这篇文章介绍了通过WPF的控件模板以及Thumb来实现图形设计器的移动Drag、改变大小resize和旋转rotate这三个几本功能,示例代码界面如下:

WPF:从WPF Diagram Designer Part 1学习控件模板、移动、改变大小和旋转

  控件模板由ControlTemplate类来表示,它派生自FrameworkTemplate抽象类,它的重要部分是它的VisualTree内容属性,它包含了定义想要的外观的可视树。通过以下方式可以定义一个控件模板类:

WPF:从WPF Diagram Designer Part 1学习控件模板、移动、改变大小和旋转

<Canvas>

<Canvas.Resources>

<ControlTemplate x:Key="DesignerItemTemplate" TargetType="ContentControl">

<ContentPresenter Content="{TemplateBinding ContentControl.Content}"/>

</ControlTemplate>

</Canvas.Resources>

<ContentControl Name="DesignerItem"

Width="100"

Height="100"

Canvas.Top="100"

Canvas.Left="100"

Template="{StaticResource DesignerItemTemplate}">

<Ellipse Fill="Blue"/>

</ContentControl>

</Canvas>

WPF:从WPF Diagram Designer Part 1学习控件模板、移动、改变大小和旋转

限制目标类型

ControlTemplate和Style一样,也有一个TargetType属性来限制模板可以被应用到的类型上,如果没有一个显示的TargetType,则目标类型将被隐式的设置为Control。由于没有默认的控件模板,所以它与Style是不同的,当使用TargetType时不允许移除模板的x:Key。

模板绑定TemplateBinding

在控件模板中,从目标元素插入属性值的关键是数据绑定,我们可以通过一个简单、轻量级的模板绑定TemplateBinding来处理。TemplateBinding的数据源总是目标元素,而Path则是目标元素的任何一个依赖属性。使用方式如上例的{TemplateBinding ContentControl.Content},如果我们设置了TargetType,可以更简单的使用为{TemplateBinding Content}

TemplateBinding仅仅是一个便捷的设置模板绑定的机制,对于有些可冻结的属性(如Brush的Color属性)时绑定会失败,这时候我们可以使用常规的Binding来达到同样效果,通过使用一个RelativeSource,其值为{Relative Source TemplatedParent}以及一个Path。

ContentPresenter

在控件模板中应该使用轻量级的内容显示元素ContentPresenter,而不是ContentControl。ContentPresenter显示的内容和ContentControl是一样的,但是ContentControl是一个带有控件模板的成熟控件,其内部包含了ContentPresenter。

如果我们在使用ContentPresenter时忘记了将它的Content设置为{TemplateBinding Content}时,它将隐式的假设{TemplateBinding Content}就是我们需要的内容

与触发器交互

在模板内部可以使用触发器,但是在进行绑定时需要注意只能使用Binding,因为触发器位于控件可视树模板外部

  在WPF中有一个Thumb的控件,在MSDN文档中是这么写的: " ...represents a control that lets the user drag and resize controls." 从字面上来看这个是一个用来处理拖放和设置大小的控件,正好应该在图形设计器中来处理移动和改变大小等动作。在以下介绍的Move、Resize和Rotate这三个功能都是使用Thumb来做的。

MoveThumb 是从Thumb继承下来,我们实现了DragDelta事件来处理移动操作,

WPF:从WPF Diagram Designer Part 1学习控件模板、移动、改变大小和旋转
WPF:从WPF Diagram Designer Part 1学习控件模板、移动、改变大小和旋转

代码

public class MoveThumb : Thumb

{

public MoveThumb()

DragDelta += new DragDeltaEventHandler(this.MoveThumb_DragDelta);

}

private void MoveThumb_DragDelta(object sender, DragDeltaEventArgs e)

ContentControl designerItem = DataContext as ContentControl;

if (designerItem != null)

Point dragDelta = new Point(e.HorizontalChange, e.VerticalChange);

RotateTransform rotateTransform = designerItem.RenderTransform as RotateTransform;

if (rotateTransform != null)

dragDelta = rotateTransform.Transform(dragDelta);

Canvas.SetLeft(designerItem, Canvas.GetLeft(designerItem) + dragDelta.X);

Canvas.SetTop(designerItem, Canvas.GetTop(designerItem) + dragDelta.Y);

实现代码中假定DataContext为我们需要操作的图形控件,这个可以在控件模板中看到:

<ControlTemplate x:Key="DesignerItemControlTemplate" TargetType="ContentControl">

<Grid>

<s:DragThumb DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}" Cursor="SizeAll"/>

</Grid>

RelativeSource

PreviousData 列表的前一个数据项

TemplatedParent 应用模板的元素

Self  元素自身 

FindAncestor 通过父元素链去找

命中测试 IsHitTestVisible

如果我们现在拖动一个圆形,那么界面如下:

WPF:从WPF Diagram Designer Part 1学习控件模板、移动、改变大小和旋转

我们现在拖动时会发现,只能在灰色部分才允许拖动,在圆形区域由于捕获的不是MoveThumb而不能拖动。这时候我们只需要简单的设置<code>IsHitTest为false即可</code>

&lt;Ellipse Fill="Blue" IsHitTestVisible="False"/&gt;

更改大小仍旧使用的是Thumb,我们建立了一个控件模板:

WPF:从WPF Diagram Designer Part 1学习控件模板、移动、改变大小和旋转

&lt;ControlTemplate x:Key="ResizeDecoratorTemplate" TargetType="Control"&gt;

&lt;Thumb Height="3" Cursor="SizeNS" Margin="0 -4 0 0"

VerticalAlignment="Top" HorizontalAlignment="Stretch"/&gt;

&lt;Thumb Width="3" Cursor="SizeWE" Margin="-4 0 0 0"

VerticalAlignment="Stretch" HorizontalAlignment="Left"/&gt;

&lt;Thumb Width="3" Cursor="SizeWE" Margin="0 0 -4 0"

VerticalAlignment="Stretch" HorizontalAlignment="Right"/&gt;

&lt;Thumb Height="3" Cursor="SizeNS" Margin="0 0 0 -4"

VerticalAlignment="Bottom" HorizontalAlignment="Stretch"/&gt;

&lt;Thumb Width="7" Height="7" Cursor="SizeNWSE" Margin="-6 -6 0 0"

VerticalAlignment="Top" HorizontalAlignment="Left"/&gt;

&lt;Thumb Width="7" Height="7" Cursor="SizeNESW" Margin="0 -6 -6 0"

VerticalAlignment="Top" HorizontalAlignment="Right"/&gt;

&lt;Thumb Width="7" Height="7" Cursor="SizeNESW" Margin="-6 0 0 -6"

VerticalAlignment="Bottom" HorizontalAlignment="Left"/&gt;

&lt;Thumb Width="7" Height="7" Cursor="SizeNWSE" Margin="0 0 -6 -6"

VerticalAlignment="Bottom" HorizontalAlignment="Right"/&gt;

WPF:从WPF Diagram Designer Part 1学习控件模板、移动、改变大小和旋转

设置了这个样式的Control界面如下图所示:

WPF:从WPF Diagram Designer Part 1学习控件模板、移动、改变大小和旋转

对于改变大小,我们只要按照MoveThumb一样,从Thumb继承一个ResizeThumb来处理改变大小的动作,对于控件模板,我们只要把上面的Thumb替换成ResizeThumb即可

WPF:从WPF Diagram Designer Part 1学习控件模板、移动、改变大小和旋转
WPF:从WPF Diagram Designer Part 1学习控件模板、移动、改变大小和旋转

public class ResizeThumb : Thumb

public ResizeThumb()

DragDelta += new DragDeltaEventHandler(this.ResizeThumb_DragDelta);

private void ResizeThumb_DragDelta(object sender, DragDeltaEventArgs e)

Control item = this.DataContext as Control;

if (item != null)

double deltaVertical, deltaHorizontal;

switch (VerticalAlignment)

case VerticalAlignment.Bottom:

deltaVertical = Math.Min(-e.VerticalChange,

item.ActualHeight - item.MinHeight);

item.Height -= deltaVertical;

break;

case VerticalAlignment.Top:

deltaVertical = Math.Min(e.VerticalChange,

Canvas.SetTop(item, Canvas.GetTop(item) + deltaVertical);

default:

switch (HorizontalAlignment)

case HorizontalAlignment.Left:

deltaHorizontal = Math.Min(e.HorizontalChange,

item.ActualWidth - item.MinWidth);

Canvas.SetLeft(item, Canvas.GetLeft(item) + deltaHorizontal);

item.Width -= deltaHorizontal;

case HorizontalAlignment.Right:

deltaHorizontal = Math.Min(-e.HorizontalChange,

e.Handled = true;

加入到DesignerItemTemplate控件模板

WPF:从WPF Diagram Designer Part 1学习控件模板、移动、改变大小和旋转

&lt;Grid DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}"&gt;

&lt;s:MoveThumb Template="{StaticResource MoveThumbTemplate}" Cursor="SizeAll"/&gt;

&lt;Control Template="{StaticResource ResizeDecoratorTemplate}"/&gt;

WPF:从WPF Diagram Designer Part 1学习控件模板、移动、改变大小和旋转
WPF:从WPF Diagram Designer Part 1学习控件模板、移动、改变大小和旋转

我们实现旋转功能,仍旧是通过从Thumb继承下来一个RotateThumb,具体实现代码如下:

WPF:从WPF Diagram Designer Part 1学习控件模板、移动、改变大小和旋转
WPF:从WPF Diagram Designer Part 1学习控件模板、移动、改变大小和旋转

public class RotateThumb : Thumb

private double initialAngle;

private RotateTransform rotateTransform;

private Vector startVector;

private Point centerPoint;

private ContentControl designerItem;

private Canvas canvas;

public RotateThumb()

DragDelta += new DragDeltaEventHandler(this.RotateThumb_DragDelta);

DragStarted += new DragStartedEventHandler(this.RotateThumb_DragStarted);

private void RotateThumb_DragStarted(object sender, DragStartedEventArgs e)

this.designerItem = DataContext as ContentControl;

if (this.designerItem != null)

this.canvas = VisualTreeHelper.GetParent(this.designerItem) as Canvas;

if (this.canvas != null)

this.centerPoint = this.designerItem.TranslatePoint(

new Point(this.designerItem.Width * this.designerItem.RenderTransformOrigin.X,

this.designerItem.Height * this.designerItem.RenderTransformOrigin.Y),

this.canvas);

Point startPoint = Mouse.GetPosition(this.canvas);

this.startVector = Point.Subtract(startPoint, this.centerPoint);

this.rotateTransform = this.designerItem.RenderTransform as RotateTransform;

if (this.rotateTransform == null)

this.designerItem.RenderTransform = new RotateTransform(0);

this.initialAngle = 0;

else

this.initialAngle = this.rotateTransform.Angle;

private void RotateThumb_DragDelta(object sender, DragDeltaEventArgs e)

if (this.designerItem != null &amp;&amp; this.canvas != null)

Point currentPoint = Mouse.GetPosition(this.canvas);

Vector deltaVector = Point.Subtract(currentPoint, this.centerPoint);

double angle = Vector.AngleBetween(this.startVector, deltaVector);

RotateTransform rotateTransform = this.designerItem.RenderTransform as RotateTransform;

rotateTransform.Angle = this.initialAngle + Math.Round(angle, 0);

this.designerItem.InvalidateMeasure();

样式如下:

WPF:从WPF Diagram Designer Part 1学习控件模板、移动、改变大小和旋转
WPF:从WPF Diagram Designer Part 1学习控件模板、移动、改变大小和旋转
WPF:从WPF Diagram Designer Part 1学习控件模板、移动、改变大小和旋转

&lt;!-- RotateThumb Style --&gt;

&lt;Style TargetType="{x:Type s:RotateThumb}"&gt;

&lt;Setter Property="RenderTransformOrigin" Value="0.5,0.5"/&gt;

&lt;Setter Property="Cursor" Value="Hand"/&gt;

&lt;Setter Property="Control.Template"&gt;

&lt;Setter.Value&gt;

&lt;ControlTemplate TargetType="{x:Type s:RotateThumb}"&gt;

&lt;Grid Width="30" Height="30"&gt;

&lt;Path Fill="#AAD0D0DD"

Stretch="Fill"

Data="M 50,100 A 50,50 0 1 1 100,50 H 50 V 100"/&gt;

&lt;/Setter.Value&gt;

&lt;/Setter&gt;

&lt;/Style&gt;

&lt;!-- RotateDecorator Template --&gt;

&lt;ControlTemplate x:Key="RotateDecoratorTemplate" TargetType="{x:Type Control}"&gt;

&lt;s:RotateThumb Margin="-18,-18,0,0" VerticalAlignment="Top" HorizontalAlignment="Left"/&gt;

&lt;s:RotateThumb Margin="0,-18,-18,0" VerticalAlignment="Top" HorizontalAlignment="Right"&gt;

&lt;s:RotateThumb.RenderTransform&gt;

&lt;RotateTransform Angle="90" /&gt;

&lt;/s:RotateThumb.RenderTransform&gt;

&lt;/s:RotateThumb&gt;

&lt;s:RotateThumb Margin="0,0,-18,-18" VerticalAlignment="Bottom" HorizontalAlignment="Right"&gt;

&lt;RotateTransform Angle="180" /&gt;

&lt;s:RotateThumb Margin="-18,0,0,-18" VerticalAlignment="Bottom" HorizontalAlignment="Left"&gt;

&lt;RotateTransform Angle="270" /&gt;

WPF:从WPF Diagram Designer Part 1学习控件模板、移动、改变大小和旋转
WPF:从WPF Diagram Designer Part 1学习控件模板、移动、改变大小和旋转

&lt;Style x:Key="DesignerItemStyle" TargetType="ContentControl"&gt;

&lt;Setter Property="MinHeight" Value="50"/&gt;

&lt;Setter Property="MinWidth" Value="50"/&gt;

&lt;Setter Property="Template"&gt;

&lt;ControlTemplate TargetType="ContentControl"&gt;

&lt;Control x:Name="RotateDecorator"

Template="{StaticResource RotateDecoratorTemplate}"

Visibility="Collapsed"/&gt;

&lt;s:MoveThumb Template="{StaticResource MoveThumbTemplate}"

Cursor="SizeAll"/&gt;

&lt;Control x:Name="ResizeDecorator"

Template="{StaticResource ResizeDecoratorTemplate}"

&lt;ControlTemplate.Triggers&gt;

&lt;Trigger Property="Selector.IsSelected" Value="True"&gt;

&lt;Setter TargetName="ResizeDecorator"

Property="Visibility" Value="Visible"/&gt;

&lt;Setter TargetName="RotateDecorator"

&lt;/Trigger&gt;

&lt;/ControlTemplate.Triggers&gt;

WPF:从WPF Diagram Designer Part 1学习控件模板、移动、改变大小和旋转
WPF:从WPF Diagram Designer Part 1学习控件模板、移动、改变大小和旋转
WPF:从WPF Diagram Designer Part 1学习控件模板、移动、改变大小和旋转

WPF支持Adorner来修饰WPF控件,在改变大小等情况下我们可以根据需要来显示,有些建模工具支持选中控件后显示快捷工具条,这个就可以通过使用Adorner来实现。

本篇通过建立一个装饰类DesignerItemDecorator控件,加入DesignerItemTemplate中,由DesignerItemDecorator控件来控制是否显示以及如何显示装饰部分。

控件模板

WPF:从WPF Diagram Designer Part 1学习控件模板、移动、改变大小和旋转

&lt;s:DesignerItemDecorator x:Name="decorator" ShowDecorator="true"/&gt;

&lt;Setter TargetName="decorator" Property="ShowDecorator" Value="true"/&gt;

WPF:从WPF Diagram Designer Part 1学习控件模板、移动、改变大小和旋转

实现  class DesignerItemDecorator : Control

WPF:从WPF Diagram Designer Part 1学习控件模板、移动、改变大小和旋转
WPF:从WPF Diagram Designer Part 1学习控件模板、移动、改变大小和旋转
WPF:从WPF Diagram Designer Part 1学习控件模板、移动、改变大小和旋转

public class DesignerItemDecorator : Control

private Adorner adorner;

public bool ShowDecorator

get { return (bool)GetValue(ShowDecoratorProperty); }

set { SetValue(ShowDecoratorProperty, value); }

public static readonly DependencyProperty ShowDecoratorProperty =

DependencyProperty.Register

("ShowDecorator", typeof(bool), typeof(DesignerItemDecorator),

new FrameworkPropertyMetadata

(false, new PropertyChangedCallback(ShowDecoratorProperty_Changed)));

private void HideAdorner()

...

private void ShowAdorner()

private static void ShowDecoratorProperty_Changed

(DependencyObject d, DependencyPropertyChangedEventArgs e)

DesignerItemDecorator decorator = (DesignerItemDecorator)d;

bool showDecorator = (bool)e.NewValue;

if (showDecorator)

decorator.ShowAdorner();

decorator.HideAdorner();

WPF:从WPF Diagram Designer Part 1学习控件模板、移动、改变大小和旋转

实现  class DesignerItemAdorner : Adorner

WPF:从WPF Diagram Designer Part 1学习控件模板、移动、改变大小和旋转
WPF:从WPF Diagram Designer Part 1学习控件模板、移动、改变大小和旋转
WPF:从WPF Diagram Designer Part 1学习控件模板、移动、改变大小和旋转

public class DesignerItemAdorner : Adorner

private VisualCollection visuals;

private DesignerItemAdornerChrome chrome;

protected override int VisualChildrenCount

get

return this.visuals.Count;

public DesignerItemAdorner(ContentControl designerItem)

: base(designerItem)

this.chrome = new DesignerItemAdornerChrome();

this.chrome.DataContext = designerItem;

this.visuals = new VisualCollection(this);

protected override Size ArrangeOverride(Size arrangeBounds)

this.chrome.Arrange(new Rect(arrangeBounds));

return arrangeBounds;

protected override Visual GetVisualChild(int index)

return this.visuals[index];

WPF:从WPF Diagram Designer Part 1学习控件模板、移动、改变大小和旋转