WPF中的图表设计器 – 3

  阅读: 37 评论: 0 作者: 麒麟 发表于 2009-12-20 15:06 原文链接

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

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

  发表评论


新闻频道:微软2009年10大失误 接连损失两员大将

推荐链接:Windows 7专题发布

网站导航:博客园首页  个人主页  新闻  社区  博问  闪存  知识库

posted on 2009-12-20 15:06  开始上路  阅读(274)  评论(0编辑  收藏  举报