WPF中的图表设计器 – 1
[原文地址] http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part1.aspx
[原文作者] sukram
[译者]WizRay
介绍
在这篇文章中,我将介绍在Canvas中移动、缩放或者旋转任何类型的对象。为此,我将提供两个不同的解决方案——一个使用了WPF中Adoner的方案和一个不使用的。
关于代码
附件中的Visual Studio 2008 工程由三个项目组成:
MoveResize: 这个版本解释了如何在不使用WPF中Adorner的情况下移动、缩放对象;
MoveResizeRotate: 另外,这个项目解释了如何在不使用WPF中Adorner的情况下旋转对象。当我们移动、缩放对象时,旋转可能会出现一些小的副作用。我们能够通过比较这个项目和前一个项目来简单的跟踪这些副作用;
MoveResizeRotateWithAdorners: 第三个项目最终解释了如何使用WPF中的Adorner来对WPF中的对象进行移动、缩放、旋转。而且提供了一个示例,解释了如何在缩放操作时使用Adorner来提供对象真实尺寸的可视化回馈。
准备工作
我们从一个简单的图表开始:
<Canvas> <Ellipse Fill="Blue" Width="100" Height="100" Canvas.Top="100" Canvas.Left="100" /> </Canvas>
你可能觉得这个图表很不起眼,但不论怎样,这就是我们的开始。它很好理解,而且具有一个图表所需的所有要素:一个用于绘制图形的Canvas。不过你也许是对的,这个图表没什么大用——他只是静态的。
所以让我们做一些准备工作,把这个圆形保存到ContentControl中:
<Canvas> <ContentControl Width="100" Height="100" Canvas.Top="100" Canvas.Left="100"> <Ellipse Fill="Blue" /> </ContentControl> </Canvas>
你或许说这也没比刚才好多少,我们仍然不能移动这个圆形,那它比好在哪儿呢?OK,这个ContentControl提供了一个我们放置在Canvas中的对象的容器,事实上,这个ContentControl就是我们要移动、缩放和旋转的对象。由于ContentControl的Content可以使任何类型的对象,所以我们将能够移动、缩放和旋转在Canvas中的任何对象!
注意:由于ContentControl的关键作用,我们把他称作DesignerItem。
我们现在为DesignerItem声明一个Control Template。这实际上提供了进一步的抽象,这样,从这儿开始,我们在完全不顾及ContentControl的内容情况下对此进行扩展。
<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>
我们完成了准备工作,我们已经准备好向Canvas中引入一些动态的元素了。
移动
在MSDN中,Thumb元素是这样解释的:“…represents a control that lets the user drag and resize controls.”。这看上去正好是我们需要的元素来做移动,下面我们将使用Thumb来完成我们的功能:
public class MoveThumb : Thumb { public MoveThumb() { DragDelta += MoveThumb_DragDelta; } private void MoveThumb_DragDelta(object sender, DragDeltaEventArgs e) { Control item = DataContext as Control; if (item != null) { double left = Canvas.GetLeft(item); double top = Canvas.GetTop(item); Canvas.SetLeft(item, left + e.HorizontalChange); Canvas.SetTop(item, top + e.VerticalChange); } } }
MoveThumb类型继承自Thumb,提供了DragDelta的事件响应。通过相应DragDelta事件,将DataContext转换为ContentControl类型,而后根据Thumb在水平和垂直方向上拖动的距离更新DataContext的位置。你可能已经猜到了,DataContext中的元素就是刚才的DesignerItem,但是他是从哪儿来的呢?我们通过更新DesignerItme的模板来处理:
<ControlTemplate x:Key="DesignerItemControlTemplate" TargetType="ContentControl"> <Grid> <s:MoveThumbDataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}" Cursor="SizeAll" /> <ContentPresenter Content="{TemplateBinding ContentControl.Content}" /> </Grid> </ControlTemplate>
从这儿我们看到,MoveThumb的DataContext属性绑定到了父级模板对象,也就是DesignerItem。注意我们在这儿加入了一个Grid作为面板来布局,它能够将ContentPresenter和MoveThumb显示在同样的位置,具有同样的尺寸。现在,我们编译、运行这个程序:
运行结果中,我们得到了一个灰色的MoveThumb,表面盖着一个蓝色的圆形。你甚至可以发现,可以通过拖动来改变他们的位置,但是只有在灰色的部分也就是MoveThumb可见的部分才能拖动。这是因为圆形阻碍了鼠标事件,使事件穿过了MoveThumb。我们通过将圆形的IsHitTest属性设为false来修正这个问题。
<Ellipse Fill="Blue" IsHitTestVisible="False" />
MoveThumb继承了Thumb的样式,这在我们的功能中没有用。我们为它创建一个只包括一个透明矩形的新的模板。当然更加通用的方法是为MoveThumb建立默认的样式,但是在这儿,自定义样式就够了。
现在DesignerItem的模板如下:
<ControlTemplate x:Key="MoveThumbTemplate" TargetType="{x:Type s:MoveThumb}"> <Rectangle Fill="Transparent" /> </ControlTemplate> <ControlTemplate x:Key="DesignerItemTemplate" TargetType="Control"> <Grid> <s:MoveThumb Template="{StaticResource MoveThumbTemplate}" DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}" Cursor="SizeAll" /> <ContentPresenter Content="{TemplateBinding ContentControl.Content}" /> </Grid> </ControlTemplate>
到此,我们已经能够在Canvas中移动对象了,下面,我们来让它支持缩放。
缩放
我们记得,MSDN中对于Thumb的介绍里说明了其可以拖动和缩放。所以我们继续使用Thumb来创建ResizeDecoratorTeamplate模板。
<ControlTemplate x:Key="ResizeDecoratorTemplate" TargetType="Control"> <Grid> <Thumb Height="3" Cursor="SizeNS" Margin="0 -4 0 0" VerticalAlignment="Top" HorizontalAlignment="Stretch" /> <Thumb Width="3" Cursor="SizeWE" Margin="-4 0 0 0" VerticalAlignment="Stretch" HorizontalAlignment="Left" /> <Thumb Width="3" Cursor="SizeWE" Margin="0 0 -4 0" VerticalAlignment="Stretch" HorizontalAlignment="Right" /> <Thumb Height="3" Cursor="SizeNS" Margin="0 0 0 -4" VerticalAlignment="Bottom" HorizontalAlignment="Stretch" /> <Thumb Width="7" Height="7" Cursor="SizeNWSE" Margin="-6 -6 0 0" VerticalAlignment="Top" HorizontalAlignment="Left" /> <Thumb Width="7" Height="7" Cursor="SizeNESW" Margin="0 -6 -6 0" VerticalAlignment="Top" HorizontalAlignment="Right" /> <Thumb Width="7" Height="7" Cursor="SizeNESW" Margin="-6 0 0 -6" VerticalAlignment="Bottom" HorizontalAlignment="Left" /> <Thumb Width="7" Height="7" Cursor="SizeNWSE" Margin="0 0 -6 -6" VerticalAlignment="Bottom" HorizontalAlignment="Right" /> </Grid> </ControlTemplate>
在这儿我们看到,这个模板由一组8个Thumb填满的Grid元素组成,这就是我们用来支持缩放的控件。通过设定Thumb的各种属性,我们实现了一个类似于真正的缩放修饰框的布局。
这很令人兴奋,但是现在它只不过是个表象,因为还没有在上面添加任何关于DragDelta事件的处理方法。于是,我们使用ResizeThumb来代替Thumb元素。
public class ResizeThumb : Thumb { public ResizeThumb() { DragDelta += ResizeThumb_DragDelta; } private void ResizeThumb_DragDelta(object sender, DragDeltaEventArgs e) { Control item = 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, item.ActualHeight - item.MinHeight); Canvas.SetTop(item, Canvas.GetTop(item) + deltaVertical); item.Height -= deltaVertical; break; default: break; } switch (HorizontalAlignment) { case HorizontalAlignment.Left: deltaHorizontal = Math.Min(e.HorizontalChange, item.ActualWidth - item.MinWidth); Canvas.SetLeft(item, Canvas.GetLeft(item) + deltaHorizontal); item.Width -= deltaHorizontal; break; case HorizontalAlignment.Right: deltaHorizontal = Math.Min(-e.HorizontalChange, item.ActualWidth - item.MinWidth); item.Width -= deltaHorizontal; break; default: break; } } e.Handled = true; } }
根据ResizeThumb的水平和垂直对齐的设定,这些ResizeThumb能够更新DesignerItem的宽度、高度和(或)位置。现在,通过添加一个使用了ResizeDecoratorTemplate模板的控件,我们把刚刚完成的拖动和缩放两部分集成到一起。
<ControlTemplate x:Key="DesignerItemTemplate" TargetType="ContentControl"> <Grid DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}"> <s:MoveThumb Template="{StaticResource MoveThumbTemplate}" Cursor="SizeAll" /> <Control Template="{StaticResource ResizeDecoratorTemplate}" /> <ContentPresenter Content="{TemplateBinding ContentControl.Content}" /> </Grid> </ControlTemplate>
很好,现在,我们能够对对象进行拖动和缩放了。下面我们让他转起来。
旋转
为了能够让Canvas中的元素能够旋转,我们可以遵循前两张阐述的方法完成,但是这次,我们创建一个名为RotateThumb的继承自Thumb的类,并且在名为RotateDecoratorTemplate的模板中加入了4个RotateThumb来实现。这些和缩放的修饰框集成到一起时,就象这样:
RotateThumb和RotateDecoratorTemplate的代码与之前我们使用的代码非常相像,所以在此,我不列出详细代码。
注意:我最早的解决方案中,使用了WPF中的TranslateTransform,ScaleTransform和RotateTransform。而后发现这是错误的,因为在WPF中,Transform没有改变对象的真实属性(宽度、高度、位置等),Transform只是一种渲染时的修改。所以我在实现拖动、放缩时,没有使用TranslateTransform和ScaleTransform,但是使用RotateTransform实现了旋转功能,因为在WPF中没有其他手段来实现对所有元素的旋转。
DesignerItem的样式
为了方便起见,我们将DesignerItem的模板包装到一个Style中,同时我们也能设定很多其他属性诸如MinWidth、MaxHeight和RenderTransformOrigin等。一个Trigger使得再选中状态时显示缩放和旋转的修饰框,这其中使用了Attached Property,也就是Selector.IsSelected。
注意:在WPF中提供了名为Selector的类,它允许用户在他的子级元素中选择出一个。本文中,我没有使用任何Selector元素,但是我是用了Selector.IsSelected的Attached Property来模拟了选中操作。
<Style x:Key="DesignerItemStyle" TargetType="ContentControl"> <Setter Property="MinHeight" Value="50" /> <Setter Property="MinWidth" Value="50" /> <Setter Property="RenderTransformOrigin" Value="0.5,0.5" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="ContentControl"> <Grid DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}"> <Control x:Name="RotateDecorator" Template="{StaticResource RotateDecoratorTemplate}" Visibility="Collapsed" /> <s:MoveThumb Template="{StaticResource MoveThumbTemplate}" Cursor="SizeAll" /> <Control x:Name="ResizeDecorator" Template="{StaticResource ResizeDecoratorTemplate}" Visibility="Collapsed" /> <ContentPresenter Content="{TemplateBinding ContentControl.Content}" /> </Grid> <ControlTemplate.Triggers> <Trigger Property="Selector.IsSelected" Value="True"> <Setter TargetName="ResizeDecorator" Property="Visibility" Value="Visible" /> <Setter TargetName="RotateDecorator" Property="Visibility" Value="Visible" /> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style>
OK,我们完成了!现在,我们可以拖动、缩放、旋转一个对象。仅仅三个类和数行的XAML代码就使我们完成了这些功能!最妙的是,我们不需要知道需要拖动、缩放、旋转的元素本身的任何信息,所有的行为都被包装到一个模板中!
使用Adorner的方案
在本章,我将演示如何将缩放和旋转的修饰框提取到AdornerLayer中,以便能够在其他元素表面加以渲染。
通过DesignerItem的模板,我们能够解释基于Adorner的方案:
<ControlTemplate x:Key="DesignerItemTemplate" TargetType="ContentControl"> <Grid DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}"> <s:MoveThumb Template="{StaticResource MoveThumbTemplate}" Cursor="SizeAll" /> <ContentPresenter Content="{TemplateBinding ContentControl.Content}" /> <s:DesignerItemDecorator x:Name="decorator" ShowDecorator="true" /> </Grid> <ControlTemplate.Triggers> <Trigger Property="Selector.IsSelected" Value="True"> <Setter TargetName="decorator" Property="ShowDecorator" Value="true" /> </Trigger> </ControlTemplate.Triggers> </ControlTemplate>
这个模板与前面几张我们使用的模板很相像,除了将缩放和旋转的修饰框替换为DesignerItemDecorator的新的类,这个类从Control继承,没有默认Style,替换为在ShowAdorner属性为true时显示的类。
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(); } else { decorator.HideAdorner(); } } }
当DesignerItem被选中时,下面这个Adorner就被显示出来。
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]; } }
我们看到,这个Adorner只有一个子级元素:DesignerItemAdornerChrome,这是实际上在缩放和旋转项目上提供了拖动的事件处理。这个元素具有默认的Style,使得其具有我们前面几章提到的ResizeThumb和RotateThumb的样式,所以我再次不再重复这些代码。
自定义Adorner
显然我们可以将自定义的Adorner加入到DesignerItem中。下面的示例中,我将一个能够显示被缩放元素实际宽高的Adorner加入到其中。详细的代码请参见附件,如果你有任何问题,请与我联系。
(全文完)
以下为广告部分
您部署的HTTPS网站安全吗?
如果您想看下您的网站HTTPS部署的是否安全,花1分钟时间来 myssl.com 检测以下吧。让您的HTTPS网站变得更安全!
快速了解HTTPS网站安全情况。
安全评级(A+、A、A-...)、行业合规检测、证书信息查看、证书链信息以及补完、服务器套件信息、证书兼容性检测等。
安装部署SSL证书变得更方便。
SSL证书内容查看、SSL证书格式转换、CSR在线生成、SSL私钥加解密、CAA检测等。
让服务器远离SSL证书漏洞侵扰
TLS ROBOT漏洞检测、心血漏洞检测、FREAK Attack漏洞检测、SSL Poodle漏洞检测、CCS注入漏洞检测。