[原文地址] http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part3.aspx
[原文作者] sukram
[译者]WizRay
- Download source – 285.4 KB (requires .NET 3.5 SP1)
简介
在一个典型的图表编辑器中,有很多种技术能够把不同的元素连接起来。有一种实现是在工具栏中提供一种“连接线”元素,用户可以将这种元素拖动到DesignerCanvas上,然后拖动他的两个端点到需要连接的元素上。另一种实现是不同的元素自己提供一些“控点”,用户可以从这些“控点”上拖出一条连接线连接到其他元素上。在本文中,我将实现第二种策略的实现。
用例:怎么连接两个元素
大家肯定都知道怎么在一个设计工具中连接两个元素,但是在这儿我还是要展示一些细节,这会使我们更加清楚的看清每个动作中有哪些类被涉及到。
如果你把鼠标移到一个元素表面,在他的四边会出现四个“Connector”类型的元素。这个默认的布局定义在ConnectorDecoratorTemplat中,有一部分定义也出现在DesignerItem的模板中。现在移动鼠标到其中一个Connector上,这时,鼠标指针会变成一个十字。
现在,如果按下鼠标左键然后开始拖动,刚才的Connector元素会创建出ConnectorAdorner类型的Adorner。这种Adorner能够在拖动起始的元素到当前鼠标位置画一条线。当用户拖动鼠标时,这个Adorner不断的对DesignerCanvas使用Hit-test来检测当前鼠标是否在一个可能作为终点的Connector上。
如果当鼠标在一个Connector上时,释放鼠标左键,ConnectorAdorner会创建一个新的Connection对象并把它加入到DesignerCanvas中。如果在没有Connector的位置释放鼠标左键,则不会创建Connection对象。
和DesignerItem一样,Connection类型实现了ISelectable接口。如果一个实现了Connection接口的对象被选中后,就能看到在一条连接线的两个端点上有两个矩形框。这些是名为ConnectionAdorner类型的Adorner,它能够在Connection对象被选中后自动显示出来。
注意:一个ConnectorAdorner属于一个Connector,一个ConnectionAdorner属于一个Connection。
两个端点上的矩形表示两个Thumb控件,他们是ConnectionAdorner对象的一部分,允许你能够修改已经建立的连接。
E.g. 如果用户拖动连接线的一个端点,在另一个Connector上释放鼠标,用户能够修改已经建立的连接。
注意:ConnectorAdorner和ConnectionAdorner的行为有些类似,但是他们对于Adorner类型的使用方式是不同的。
怎样制作粘性连接
Connector的默认布局定义在ConnectorDecoratorTemplate,这个模板包含在DesignerItem的模板中。
<ControlTemplate x:Key="ConnectorDecoratorTemplate" TargetType="{x:Type Control}"> <Grid Margin="-5"> <s:Connector Orientation="Left" VerticalAlignment="Center" HorizontalAlignment="Left" /> <s:Connector Orientation="Top" VerticalAlignment="Top" HorizontalAlignment="Center" /> <s:Connector Orientation="Right" VerticalAlignment="Center" HorizontalAlignment="Right" /> <s:Connector Orientation="Bottom" VerticalAlignment="Bottom" HorizontalAlignment="Center" /> </Grid> </ControlTemplate>
Connector类型有一个Position属性,指定了Connector的中心到DesignerCanvas的相对位置。由于Connector类实现了INotifyPropertyChanged接口,使得它能够对属性变化通知其他部分。作为WPF布局流水线的一部分,当一个DesignerItem的尺寸、位置发生变化时,Connector的LayoutUpdated事件会被自动触发。这时,它的Position属性被更新,通知事件被触发。
public class Connector : Control, INotifyPropertyChanged { private Point position; public Point Position { get { return position; } set { if (position != value) { position = value; OnPropertyChanged("Position"); } } } public Connector() { // fired when layout changes base.LayoutUpdated += new EventHandler(Connector_LayoutUpdated); } void Connector_LayoutUpdated(object sender, EventArgs e) { DesignerCanvas designer = GetDesignerCanvas(this); if (designer != null) { //get center position of this Connector relative to the DesignerCanvas this.Position = this.TransformToAncestor(designer).Transform (new Point(this.Width / 2, this.Height / 2)); } } ... }
现在,我们可以变换Connection的位置了。Connection类型有一个Source属性和一个Sink属性,均是Connector类型。当一个Source或Sink被设定时,我们立刻注册一个事件处理程序来监听Connector的PropertyChanged事件。
public class Connection : Control, ISelectable, INotifyPropertyChanged { private Connector source; public Connector Source { get { return source; } set { if (source != value) { if (source != null) { source.PropertyChanged -= new PropertyChangedEventHandler(OnConnectorPositionChanged); source.Connections.Remove(this); } source = value; if (source != null) { source.Connections.Add(this); source.PropertyChanged += new PropertyChangedEventHandler(OnConnectorPositionChanged); } UpdatePathGeometry(); } } } void OnConnectorPositionChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName.Equals("Position")) { UpdatePathGeometry(); } } ... }
这是Source部分的代码,Sink的设定非常类似。最终,事件处理程序会更新Connection的外观。
自定义Connector的布局
默认的的样式和Connector的数量可能不是我们所需的。下面这个例子是一个使用自定义DragThumbTemplate模板的三角形。(可以参考上一篇文章来学习如何创建DragThumbTemplate)
<Path IsHitTestVisible="False" Fill="Orange" Stretch="Fill" Data="M 0,10 5,0 10,10 Z"> <s:DesignerItem.DragThumbTemplate> <ControlTemplate> <Path Fill="Transparent" Stretch="Fill" Data="M 0,10 5,0 10,10 Z" /> </ControlTemplate> </s:DesignerItem.DragThumbTemplate> </Path>
问题在于,Connector只有在鼠标悬浮时才可见。如果你尝试使用左侧或右侧的Connector会出现问题。这个问题可以通过使用名为DesignerItem.ConnectorDecoratorTemplate的Attached Property来解决。通过自定义样式,下面的示例能够解决这个问题:
<Path IsHitTestVisible="False" Fill="Orange" Stretch="Fill" Data="M 0,10 5,0 10,10 Z"> <!-- Custom DragThumb Template --> <s:DesignerItem.DragThumbTemplate> <ControlTemplate> <Path Fill="Transparent" Stretch="Fill" Data="M 0,10 5,0 10,10 Z" /> </ControlTemplate> <s:DesignerItem.DragThumbTemplate> <!-- Custom ConnectorDecorator Template --> <s:DesignerItem.ConnectorDecoratorTemplate> <ControlTemplate> <Grid Margin="0"> <s:Connector Orientation="Top" HorizontalAlignment="Center" VerticalAlignment="Top" /> <s:Connector Orientation="Bottom" HorizontalAlignment="Center" VerticalAlignment="Bottom" /> <UniformGrid Columns="2"> <s:Connector Grid.Column="0" Orientation="Left" /> <s:Connector Grid.Column="1" Orientation="Right" /> </UniformGrid> </Grid> </ControlTemplate> </s:DesignerItem.ConnectorDecoratorTemplate> </Path>
这个方案提供了还不错的体验,不过需要复杂的布局,这使得这个方案不是所有情况下都适用。为此,在这儿提供了RelativePositionPanel来允许你将元素定位在面板的边缘。下面的示例通过设定RelativePosition属性将三个按钮置于RelativePositionPanel上,RelativePosition是一个Attached Property。
<c:RelativePositionPanel> <Button Content="TopLeft" c:RelativePositionPanel.RelativePosition="0,0" /> <Button Content="Center" c:RelativePositionPanel.RelativePosition="0.5,0.5" /> <Button Content="BottomRight" c:RelativePositionPanel.RelativePosition="1,1" /> </ControlTemplate>
这个面板在安排Connector的布局时非常好用:
<Path IsHitTestVisible="False" Fill="Orange" Stretch="Fill" Data="M 9,2 11,7 17,7 12,10 14,15 9,12 4,15 6,10 1,7 7,7 Z"> <!-- Custom DragThumb Template --> <s:DesignerItem.DragThumbTemplate> <ControlTemplate> <Path Fill="Transparent" Stretch="Fill" Data="M 9,2 11,7 17,7 12,10 14,15 9,12 4,15 6,10 1,7 7,7 Z" /> </ControlTemplate> </s:DesignerItem.DragThumbTemplate> <!-- Custom ConnectorDecorator Template --> <s:DesignerItem.ConnectorDecoratorTemplate> <ControlTemplate> <c:RelativePositionPanel Margin="-4"> <s:Connector Orientation="Top" c:RelativePositionPanel.RelativePosition="0.5,0" /> <s:Connector Orientation="Left" c:RelativePositionPanel.RelativePosition="0,0.385" /> <s:Connector Orientation="Right" c:RelativePositionPanel.RelativePosition="1,0.385" /> <s:Connector Orientation="Bottom" c:RelativePositionPanel.RelativePosition="0.185,1" /> <s:Connector Orientation="Bottom" c:RelativePositionPanel.RelativePosition="0.815,1" /> </c:RelativePositionPanel> </ControlTemplate> </s:DesignerItem.ConnectorDecoratorTemplate> </Path>
(全文完)
以下为广告部分
您部署的HTTPS网站安全吗?
如果您想看下您的网站HTTPS部署的是否安全,花1分钟时间来 myssl.com 检测以下吧。让您的HTTPS网站变得更安全!
快速了解HTTPS网站安全情况。
安全评级(A+、A、A-...)、行业合规检测、证书信息查看、证书链信息以及补完、服务器套件信息、证书兼容性检测等。
安装部署SSL证书变得更方便。
SSL证书内容查看、SSL证书格式转换、CSR在线生成、SSL私钥加解密、CAA检测等。
让服务器远离SSL证书漏洞侵扰
TLS ROBOT漏洞检测、心血漏洞检测、FREAK Attack漏洞检测、SSL Poodle漏洞检测、CCS注入漏洞检测。