WPF中的图表设计器 – 3

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

[译者]WizRay

WPFDiagramDesignerFlowChart

简介

在一个典型的图表编辑器中,有很多种技术能够把不同的元素连接起来。有一种实现是在工具栏中提供一种“连接线”元素,用户可以将这种元素拖动到DesignerCanvas上,然后拖动他的两个端点到需要连接的元素上。另一种实现是不同的元素自己提供一些“控点”,用户可以从这些“控点”上拖出一条连接线连接到其他元素上。在本文中,我将实现第二种策略的实现。

用例:怎么连接两个元素

大家肯定都知道怎么在一个设计工具中连接两个元素,但是在这儿我还是要展示一些细节,这会使我们更加清楚的看清每个动作中有哪些类被涉及到。

WPFDiagramDesigner01

如果你把鼠标移到一个元素表面,在他的四边会出现四个“Connector”类型的元素。这个默认的布局定义在ConnectorDecoratorTemplat中,有一部分定义也出现在DesignerItem的模板中。现在移动鼠标到其中一个Connector上,这时,鼠标指针会变成一个十字。

WPFDiagramDesigner02

现在,如果按下鼠标左键然后开始拖动,刚才的Connector元素会创建出ConnectorAdorner类型的Adorner。这种Adorner能够在拖动起始的元素到当前鼠标位置画一条线。当用户拖动鼠标时,这个Adorner不断的对DesignerCanvas使用Hit-test来检测当前鼠标是否在一个可能作为终点的Connector上。

WPFDiagramDesigner03

如果当鼠标在一个Connector上时,释放鼠标左键,ConnectorAdorner会创建一个新的Connection对象并把它加入到DesignerCanvas中。如果在没有Connector的位置释放鼠标左键,则不会创建Connection对象。

WPFDiagramDesigner04

和DesignerItem一样,Connection类型实现了ISelectable接口。如果一个实现了Connection接口的对象被选中后,就能看到在一条连接线的两个端点上有两个矩形框。这些是名为ConnectionAdorner类型的Adorner,它能够在Connection对象被选中后自动显示出来。

注意:一个ConnectorAdorner属于一个Connector,一个ConnectionAdorner属于一个Connection。

WPFDiagramDesigner05

两个端点上的矩形表示两个Thumb控件,他们是ConnectionAdorner对象的一部分,允许你能够修改已经建立的连接。

WPFDiagramDesigner06

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>

WPFDiagramDesigner07

问题在于,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>

WPFDiagramDesigner08

这个方案提供了还不错的体验,不过需要复杂的布局,这使得这个方案不是所有情况下都适用。为此,在这儿提供了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>


WPFDiagramDesigner09



(全文完)


以下为广告部分

您部署的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注入漏洞检测。

原文地址:https://www.cnblogs.com/zhuqil/p/1628282.html