[原文地址] http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part2.aspx
[原文作者] sukram

[译者]WizRay

  • Download source – 285.4 KB (requires .NET 3.5 SP1)

介绍

在上一篇文章中,我们展示了如何移动、缩放和旋转一个Canvas中的对象。这次,我们要加入一些典型的图表编辑器所必不可少的更深一层的特性:

  • Designer Canvas(可变大小、可缩放)
  • Zoombox
  • 框选
  • 键盘支持(Ctrl + 鼠标左键)
  • Toolbox(可拖动)
  • 可旋转组件

Designer Canvas

在上一篇文章中,你大概已经注意到了,当你把一个元素拖动到DesignerCanvas边框以外时,它就无法再访问到了。通常,用户期望设计器能够提供一个滚动条,使得用户可以将工作区移动到画布可视范围以外的任何区域。为此,我不得不吧DesignerCanvas移动到一个ScrollViewer里面,不过这没有用。很快,我知道了失效的原因,先让我来解释下面这些代码段:

<Canvas Width="200" Height="200" Background="WhiteSmoke"><Rectangle Fill="Blue" Width="100" Height="100" Canvas.Left="300" Canvas.Top="300" />
</Canvas>

我把一个Rectangle放置在一个Canvas中,但是把它放在Canvas的边界以外。这显然不会改变Canvas的尺寸,不管我们把那个Rectangle放在哪儿。

这一位置对于DesignerCanvas,不论我们把一个对象移到离它的边界多么远的地方,他都不会改变尺寸。这样我们就理解了为什么ScrollView在这儿不管用:DesignerCanvas永远不会通知ScrollViewer它的尺寸发生变化,因为它根本就没有变化。

解决方案是我们必须强制设定DesignerCanvas的尺寸随时与移动或缩放的元素保持适应。幸运的是,Canvas提供了名为MeasureOverride的方法,这个方法能够允许DesignerCanvas计算它所需的尺寸,并将结果返回WPF的布局系统。这种计算很简单,就像下面这样:

protected override Size MeasureOverride(Size constraint)
{Size size = new Size();foreach (UIElement element in base.Children){double left = Canvas.GetLeft(element);double top = Canvas.GetTop(element);left = double.IsNaN(left) ? 0 : left;top = double.IsNaN(top) ? 0 : top;//measure desired size for each childelement.Measure(constraint);Size desiredSize = element.DesiredSize;if (!double.IsNaN(desiredSize.Width) && !double.IsNaN(desiredSize.Height)){size.Width = Math.Max(size.Width, left + desiredSize.Width);size.Height = Math.Max(size.Height, top + desiredSize.Height);}}//for aesthetic reasons add extra pointssize.Width += 10;size.Height += 10;return size;
}

DesignerItem

DesignerItem是从ContentControl继承下来的,所以它能够重用上一篇文章中的ControlTemplate。DesignerItem提供了IsSelected属性来表示他是否被选中:

public class DesignerItem : ContentControl
{public bool IsSelected{get { return (bool)GetValue(IsSelectedProperty); }set { SetValue(IsSelectedProperty, value); }}public static readonly DependencyProperty IsSelectedProperty =DependencyProperty.Register("IsSelected", typeof(bool),typeof(DesignerItem),new FrameworkPropertyMetadata(false));

而后我们实现了MouseDown的事件处理来支持多选:

protected override void OnPreviewMouseDown(MouseButtonEventArgs e)
{base.OnPreviewMouseDown(e);DesignerCanvas designer = VisualTreeHelper.GetParent(this) as DesignerCanvas;if (designer != null){if ((Keyboard.Modifiers &(ModifierKeys.Shift | ModifierKeys.Control)) != ModifierKeys.None){this.IsSelected = !this.IsSelected;}else{if (!this.IsSelected){designer.DeselectAll();this.IsSelected = true;}}}e.Handled = false;
}

注意到我们对PreviewMouseDown事件进行监听,并且我们设定该事件没有被处理过。这是因为我们希望即使MouseDown指向了DesignerItem里的一个子级元素,这个DesignerItem也能被选中;就像在Visual Studio的类设计器中,如果我们点击了Expander的ToggleButton,这个项目会被选中,而且Expander会被打开,两个是同时发生的。

最后,我们更新一下DesignerItem的模板,添加一个简单的Trigger,使得缩放的修饰框只在被选择是才可见。

<Style TargetType="{x:Type s:DesignerItem}"><Setter Property="MinHeight" Value="50" /><Setter Property="MinWidth" Value="50" /><Setter Property="SnapsToDevicePixels" Value="true" /><Setter Property="Template"><Setter.Value><ControlTemplate TargetType="{x:Type s:DesignerItem}"><Grid DataContext="{Binding RelativeSource={RelativeSource TemplatedParent},Path=.}"><s:MoveThumb x:Name="PART_MoveThumb" Cursor="SizeAll"Template="{StaticResource MoveThumbTemplate}" /><ContentPresenter x:Name="PART_ContentPresenter"Content="{TemplateBinding ContentControl.Content}"Margin="{TemplateBinding Padding}" /><s:ResizeDecorator x:Name="PART_DesignerItemDecorator" /></Grid><ControlTemplate.Triggers><Trigger Property="IsSelected" Value="True"><Setter TargetName="PART_DesignerItemDecorator"Property="ShowDecorator" Value="True" /></Trigger></ControlTemplate.Triggers></ControlTemplate></Setter.Value></Setter>
</Style>

Toolbox

Toolbox是一种使用ToolboxItem作为默认容器来显示控件的ItemsControl。为此,我们必须重写GetContainerForItemOverride方法和IsItemItsWenContainerOverride方法:

public class Toolbox : ItemsControl
{private Size defaultItemSize = new Size(65, 65);public Size DefaultItemSize{get { return this.defaultItemSize; }set { this.defaultItemSize = value; }}protected override DependencyObject GetContainerForItemOverride(){return new ToolboxItem();}protected override bool IsItemItsOwnContainerOverride(object item){return (item is ToolboxItem);}
}

而且,我们希望Toolbox能够使用WrapPanel作为布局面板。

<Setter Property="ItemsPanel"><Setter.Value><ItemsPanelTemplate><WrapPanel Margin="0,5,0,5" ItemHeight="{Binding Path=DefaultItemSize.Height,RelativeSource={RelativeSource AncestorType=s:Toolbox}}" ItemWidth="{Binding Path=DefaultItemSize.Width,RelativeSource={RelativeSource AncestorType=s:Toolbox}}" /></ItemsPanelTemplate></Setter.Value>
</Setter>

请注意,WrapPanel的ItemHeight和ItemWidth属性是与Toolbox的DefaultItemSize绑定到一起的。

ToolboxItem

如果你希望从Toolbox中拖动一个元素到Canvas中放开的话,ToolboxItem是拖动操作真正开始的地方。拖动和释放本身没有什么问题,但是你需要注意怎么把一个元素从他拖动的起点(Toolbox)复制到释放的位置(DesignerCanvas)。我们使用XamlWriter.Save方法来把ToolboxItem中的元素串行化成XAML,这种串行化有一些已知的限制,在下一节中,我们将使用二进制串行化来代替它。

public class ToolboxItem : ContentControl{private Point? dragStartPoint = null;static ToolboxItem(){FrameworkElement.DefaultStyleKeyProperty.OverrideMetadata(typeof(ToolboxItem),new FrameworkPropertyMetadata(typeof(ToolboxItem)));}protected override void OnPreviewMouseDown(MouseButtonEventArgs e){base.OnPreviewMouseDown(e);this.dragStartPoint = new Point?(e.GetPosition(this));}protected override void OnMouseMove(MouseEventArgs e){base.OnMouseMove(e);if (e.LeftButton != MouseButtonState.Pressed){this.dragStartPoint = null;}if (this.dragStartPoint.HasValue){Point position = e.GetPosition(this);if ((SystemParameters.MinimumHorizontalDragDistance <=Math.Abs((double)(position.X - this.dragStartPoint.Value.X))) ||(SystemParameters.MinimumVerticalDragDistance <=Math.Abs((double)(position.Y - this.dragStartPoint.Value.Y)))){string xamlString = XamlWriter.Save(this.Content);DataObject dataObject = new DataObject("DESIGNER_ITEM", xamlString);if (dataObject != null){DragDrop.DoDragDrop(this, dataObject, DragDropEffects.Copy);}}e.Handled = true;}}}

框选

当用户直接从DesignerCanvas上开始一个拖动动作时,一个RubberbandAdorner对象会被创建:

public class DesignerCanvas : Canvas
{
...protected override void OnMouseMove(MouseEventArgs e)
{base.OnMouseMove(e);if (e.LeftButton != MouseButtonState.Pressed)this.dragStartPoint = null;if (this.dragStartPoint.HasValue){AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(this);if (adornerLayer != null){RubberbandAdorner adorner = new RubberbandAdorner(this, dragStartPoint);if (adorner != null){adornerLayer.Add(adorner);}}e.Handled = true;}
}...
}

一旦RubberbandAdorner被创建,他就开始接管拖动事件、绘制框选的外框、绘制被选中的元素的外框。这些更新在UpdateRubberband()和UpdateSelection()方法内部进行:

public class RubberbandAdorner : Adorner
{
....private Point? startPoint, endPoint;protected override void OnMouseMove(MouseEventArgs e)
{if (e.LeftButton == MouseButtonState.Pressed){if (!this.IsMouseCaptured){this.CaptureMouse();}this.endPoint = e.GetPosition(this);this.UpdateRubberband();this.UpdateSelection();e.Handled = true;}
}...
}

框选的外框实际上是一个Rectangle元素,所以UpdateRubberband()方法只需要更新他的尺寸和位置即可。

private void UpdateRubberband()
{double left = Math.Min(this.startPoint.Value.X, this.endPoint.Value.X);double top = Math.Min(this.startPoint.Value.Y, this.endPoint.Value.Y);double width = Math.Abs(this.startPoint.Value.X - this.endPoint.Value.X);double height = Math.Abs(this.startPoint.Value.Y - this.endPoint.Value.Y);this.rubberband.Width = width;this.rubberband.Height = height;Canvas.SetLeft(this.rubberband, left);Canvas.SetTop(this.rubberband, top);
}

在UpdateSelection()方法中还有一些地方需要处理。我们需要在这儿检查每一个DesignerItem来确定它是否在框选的范围之内。为此,VisualTreeHelper.GetDescendantBounds(item)方法提供了我们每个子级对象的外框范围。我们通过rubberband.Containes(itemBounds)来确定这些元素是否需要被选中。

private void UpdateSelection()
{Rect rubberBand = new Rect(this.startPoint.Value, this.endPoint.Value);foreach (DesignerItem item in this.designerCanvas.Children){Rect itemRect = VisualTreeHelper.GetDescendantBounds(item);Rect itemBounds = item.TransformToAncestor(designerCanvas).TransformBounds(itemRect);if (rubberBand.Contains(itemBounds)){item.IsSelected = true;}else{item.IsSelected = false;}}
}

需要注意的是,在拖动中,无论何时,如果MouseMove事件被触发都会导致上面所说的界面更新方法。它被触发的及其频繁,你也可以换一种方法:在拖动结束时(即MouseUp事件触发是)再判断这些。

自定义DragThumb

DragThumb的默认样式是一个透明的Rectangle,但是如果我们希望调整这个样式,我们可是使用一个叫做DesignerItem.DragThumbTemplate的Attached Property。下面这个示例将解释这种操作,如果DesignerItem的Content是下面这个五角星的话:

<Path Stroke="Red" StrokeThickness="5" Stretch="Fill" IsHitTestVisible="false"Data="M 9,2 11,7 17,7 12,10 14,15 9,12 4,15 6,10 1,7 7,7 Z" />

为了更好地说明,我使用了彩色的DragThumb的模板:

现在添加下面的代码:

<Path Stroke="Red" StrokeThickness="5" Stretch="Fill" IsHitTestVisible="false"Data="M 9,2 11,7 17,7 12,10 14,15 9,12 4,15 6,10 1,7 7,7 Z"><s:DesignerItem.DragThumbTemplate><ControlTemplate><Path Data="M 9,2 11,7 17,7 12,10 14,15 9,12 4,15 6,10 1,7 7,7 Z"Fill="Transparent" Stretch="Fill" /></ControlTemplate></s:DesignerItem.DragThumbTemplate></Path>

现在,DragThumb的样子比刚才合适多了。

(全文完)


以下为广告部分

您部署的HTTPS网站安全吗?

如果您想看下您的网站HTTPS部署的是否安全,花1分钟时间来 myssl.com 检测以下吧。让您的HTTPS网站变得更安全!

SSL检测评估

快速了解HTTPS网站安全情况。

安全评级(A+、A、A-...)、行业合规检测、证书信息查看、证书链信息以及补完、服务器套件信息、证书兼容性检测等。

SSL证书工具

安装部署SSL证书变得更方便。

SSL证书内容查看、SSL证书格式转换、CSR在线生成、SSL私钥加解密、CAA检测等。

SSL漏洞检测

让服务器远离SSL证书漏洞侵扰

TLS ROBOT漏洞检测、心血漏洞检测、FREAK Attack漏洞检测、SSL Poodle漏洞检测、CCS注入漏洞检测。

WPF中的图表设计器 – 2相关推荐

  1. java开发工具MyEclipse 中实体关系设计器介绍

    MyEclipse官方正版下载 本文将介绍在MyEclipse中的实体关系设计器. 使用 ER 图可视化实体关系 MyEclipse 实体关系设计器可帮助您直观地管理关系数据库从设计到实现和维护的生命 ...

  2. 界面控件DevExpress WPF中文指南 - 主题设计器工作区介绍

    DevExpress WPF拥有120+个控件和库,将帮助您交付满足甚至超出企业需求的高性能业务应用程序.通过DevExpress WPF能创建有着强大互动功能的XAML基础应用程序,这些应用程序专注 ...

  3. WF工作流设计器(WPF版)

    转自:http://www.cnblogs.com/foundation/archive/2008/10/28/1321186.html#_Toc212901141 这是一个WPF版的工作流设计器, ...

  4. 纯前端控件集 WijmoJS 2018V2发布,提供可视化设计器,在React、Vue和Angular中的更易用...

    作为一款纯前端控件集,WijmoJS 秉承"快如闪电,触控优先"的设计理念,在提供优质服务和产品的同时,专注于企业应用开发,不断优化产品架构,与时俱进.除在全球率先支持 Angul ...

  5. 用c# 实现设计器 DesignSurface

    DesignSurface 实现设计器问题? http://topic.csdn.net/u/20090419/02/4c0fe387-c019-4159-ac60-71c04495e2b2.html ...

  6. 新一代纯前端控件集 WijmoJS 2018V2发布,提供 Web 设计器,可动态设计页面并生成代码...

    概述 作为一款纯前端控件集,WijmoJS 秉承"快如闪电,触控优先"的设计理念,在提供优质服务和产品的同时,专注于企业应用开发,不断优化产品架构,与时俱进.除在全球率先支持 An ...

  7. mysql 关系图 工具_DbSchema(数据库关系图设计器) V8.1.7 官方版

    DbSchema 是一款专业可靠的数据库关系图设计软件,你可以通过拖放或按外键图标添加表格,并具有多种过滤器,数据排序机制等等.可通过多个数据库管理和同步模式使用可帮助您设计,记录和管理数据库,轻松设 ...

  8. 知识管理系统Data Solution研发日记之六 窗体设计器

    知识管理系统Data Solution已经有五篇文章对它进行介绍,可以通过下面的连接,找到前面的文章 知识管理系统Data Solution研发日记之一 场景设计与需求列出 知识管理系统Data So ...

  9. 通过用 .NET 生成自定义窗体设计器来定制应用程序

    本文讨论: ? 设计时环境基本原理 ? 窗体设计器体系结构 ? Visual Studio .NET 中窗体设计器的实现 ? 为自己的应用程序编写窗体设计器而需要实现的服务 在很多年中,MFC 一直是 ...

最新文章

  1. Python-ORM实战
  2. background-position 详解
  3. flutter从0到1构建大前端应用 pdf_前端骨架屏都是如何生成的
  4. 使用计算机时 开关机顺序会,电脑如何正确开关机
  5. 雪碧图sprity 合并多图使用心得
  6. 无代码绘制基因表达箱线图
  7. Kalman filter—直观理解
  8. Dreammail 下载与安装
  9. linux minerd 进程,linux中了minerd之后的完全清理过程(详解)
  10. HTTP 和 HTTPS 有什么区别?
  11. 云南大学计算机在职硕士,在职硕士
  12. 开源的api管理平台推介
  13. 2.10 lnmp架构_慢查询 MySQL路由器 MHA高可用
  14. p2p终结者在交换机上的机器用P2P终结者
  15. MAC中LateX出字体问题
  16. 控制台Tomcat Locahost log输出No Spring WebApplicationIn
  17. 数据、运营相关试题(二)【牛客网:京东2019春招产品运营类试卷】
  18. 转:钉钉群直播提取视频文件-电脑版
  19. C语言十六进制转八进制
  20. 保研面试/考研复试:英语口语面试必备话题及常用句型句式整理(一)

热门文章

  1. rman 脚本备份全过程
  2. 【转自小峰博客】协调器的启动【自动模式】
  3. 条件运算符和条件表达式
  4. 诗与远方:无题(二十一)
  5. BookKeeper总结
  6. 一般向量空间的基变换_线性代数的本质03 矩阵与线性变换
  7. Github——git本地仓库建立与远程连接(最详细清晰版本!附简化步骤与常见错误)
  8. 计算机专业 职业素养论文,计算机专业本科毕业论文-20210707222739.docx-原创力文档...
  9. FastDFS分布式文件系统
  10. 高仿真机器人助力临床医学发展