WPF实现DiagramChart

1、文件架构

2、FlowChartStencils.xaml

csharp 复制代码
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:s="clr-namespace:DiagramDesigner"
                    xmlns:c="clr-namespace:DiagramDesigner.Controls">
    <!--Stroke 设置-->
    <Brush x:Key="ItemStroke">#FFD69436</Brush>
    <!--LinearGradientBrush 设置-->
    <LinearGradientBrush x:Key="ItemBrush" StartPoint="0,0" EndPoint="0,1">
        <LinearGradientBrush.GradientStops>
            <GradientStop Color="#FAFBE9" Offset="0" />
            <GradientStop Color="Orange" Offset="1" />
        </LinearGradientBrush.GradientStops>
    </LinearGradientBrush>
    <!--FlowChartItem 设置-->
    <Style x:Key="FlowChartItemStyle" TargetType="Path">
        <Setter Property="Fill" Value="{StaticResource ItemBrush}"/>
        <Setter Property="Stroke" Value="{StaticResource ItemStroke}"/>
        <Setter Property="StrokeThickness" Value="2"/>
        <Setter Property="StrokeLineJoin" Value="Round"/>
        <Setter Property="Stretch" Value="Fill"/>
        <Setter Property="IsHitTestVisible" Value="False"/>
        <!--SnapsToDevicePixels 此元素的呈现是否应使用特定于设备的像素设置-->
        <Setter Property="SnapsToDevicePixels" Value="True"/>
        <Setter Property="BitmapEffect">
            <Setter.Value>
                <DropShadowBitmapEffect Color="#AAA" Direction="315" ShadowDepth="10"
                                Softness="0.5" Opacity="0.6"/>
            </Setter.Value>
        </Setter>
    </Style>

    <!-- Process(矩形) -->
    <Style x:Key="Process" TargetType="Path" BasedOn="{StaticResource FlowChartItemStyle}">
        <Setter Property="Data" Value="M 0,0 H 60 V40 H 0 Z"/>
    </Style>
    <!-- Process(矩形属性) -->
    <Style x:Key="Process_DragThumb" TargetType="Path" BasedOn="{StaticResource Process}">
        <Setter Property="IsHitTestVisible" Value="true"/>
        <Setter Property="Fill" Value="Transparent"/>
        <Setter Property="Stroke" Value="Transparent"/>
    </Style>

    <!-- Decision(菱形) -->
    <Style x:Key="Decision" TargetType="Path" BasedOn="{StaticResource FlowChartItemStyle}">
        <Setter Property="Data" Value="M 0,20 L 30 0 L 60,20 L 30,40 Z"/>
    </Style>
    <!-- Decision(菱形属性) -->
    <Style x:Key="Decision_DragThumb" TargetType="Path" BasedOn="{StaticResource Decision}">
        <Setter Property="IsHitTestVisible" Value="true"/>
        <Setter Property="Fill" Value="Transparent"/>
        <Setter Property="Stroke" Value="Transparent"/>
    </Style>

    <!-- Document(文件框) -->
    <Style x:Key="Document" TargetType="Path" BasedOn="{StaticResource FlowChartItemStyle}">
        <Setter Property="Data" Value="M 0,0 H 60 V 40 C 30,30 30,50 0,40 Z"/>
    </Style>
    <!-- Document(文件框属性) -->
    <Style x:Key="Document_DragThumb" TargetType="Path" BasedOn="{StaticResource Document}">
        <Setter Property="IsHitTestVisible" Value="true"/>
        <Setter Property="Fill" Value="Transparent"/>
        <Setter Property="Stroke" Value="Transparent"/>
    </Style>

    <!-- Data(平行四边形) -->
    <Style x:Key="Data" TargetType="Path" BasedOn="{StaticResource FlowChartItemStyle}">
        <Setter Property="Data" Value="M 10,0 L 60 0 L 50,40 L 0,40 Z"/>
    </Style>
    <!-- Data(平行四边形属性) -->
    <Style x:Key="Data_DragThumb" TargetType="Path" BasedOn="{StaticResource Data}">
        <Setter Property="IsHitTestVisible" Value="true"/>
        <Setter Property="Fill" Value="Transparent"/>
        <Setter Property="Stroke" Value="Transparent"/>
    </Style>

    <!-- Start -->
    <Style x:Key="Start" TargetType="Path" BasedOn="{StaticResource FlowChartItemStyle}">
        <Setter Property="Data" Value="M 10,20 A 20,20 0 1 1 50,20 A 20,20 0 1 1 10,20"/>
    </Style>

    <Style x:Key="Start_DragThumb" TargetType="Path" BasedOn="{StaticResource Start}">
        <Setter Property="IsHitTestVisible" Value="true"/>
        <Setter Property="Fill" Value="Transparent"/>
        <Setter Property="Stroke" Value="Transparent"/>
    </Style>


    <!-- Predefined(带双竖线矩形) -->
    <Style x:Key="Predefined" TargetType="Path" BasedOn="{StaticResource FlowChartItemStyle}">
        <Setter Property="Data" Value="M 50,0 V 40 M 10,0 V 40 M 0 0 H 60 V 40 H 0 Z"/>
    </Style>

    <Style x:Key="Predefined_DragThumb" TargetType="Path" BasedOn="{StaticResource Predefined}">
        <Setter Property="IsHitTestVisible" Value="true"/>
        <Setter Property="Fill" Value="Transparent"/>
        <Setter Property="Stroke" Value="Transparent"/>
    </Style>


    <!-- StoredData(两边圆弧) -->
    <Style x:Key="StoredData" TargetType="Path" BasedOn="{StaticResource FlowChartItemStyle}">
        <Setter Property="Data" Value="M 5,0 H 60 A 40,40 0 0 0 60,40 H 5 A 40,40 0 0 1 5,0 Z"/>
    </Style>

    <Style x:Key="StoredData_DragThumb" TargetType="Path" BasedOn="{StaticResource StoredData}">
        <Setter Property="IsHitTestVisible" Value="true"/>
        <Setter Property="Fill" Value="Transparent"/>
        <Setter Property="Stroke" Value="Transparent"/>
    </Style>


    <!-- InternalStorage(十字矩形) -->
    <Style x:Key="InternalStorage" TargetType="Path" BasedOn="{StaticResource FlowChartItemStyle}">
        <Setter Property="Data" Value="M 0,10 H 60 M 10,0 V 40 M 0,0 H 60 V 40 H 0 Z"/>
    </Style>

    <Style x:Key="InternalStorage_DragThumb" TargetType="Path" BasedOn="{StaticResource InternalStorage}">
        <Setter Property="IsHitTestVisible" Value="true"/>
        <Setter Property="Fill" Value="Transparent"/>
        <Setter Property="Stroke" Value="Transparent"/>
    </Style>

    <!-- SequentialData -->
    <Style x:Key="SequentialData" TargetType="Path" BasedOn="{StaticResource FlowChartItemStyle}">
        <Setter Property="Data" Value="M 30,40 A 20,20 0 1 1 30,0 A 20,20 0 0 1 43,35 H 50 L 50,40 Z"/>
    </Style>

    <Style x:Key="SequentialData_DragThumb" TargetType="Path" BasedOn="{StaticResource SequentialData}">
        <Setter Property="IsHitTestVisible" Value="true"/>
        <Setter Property="Fill" Value="Transparent"/>
        <Setter Property="Stroke" Value="Transparent"/>
    </Style>

    <!-- DirectData -->
    <Style x:Key="DirectData" TargetType="Path" BasedOn="{StaticResource FlowChartItemStyle}">
        <Setter Property="Data" Value="F 1 M 57,40 H 3 A 4,20 0 1 1 3,0 H 57 A 4,20.1 0 1 1 56,0"/>
    </Style>

    <Style x:Key="DirectData_DragThumb" TargetType="Path" BasedOn="{StaticResource DirectData}">
        <Setter Property="IsHitTestVisible" Value="true"/>
        <Setter Property="Fill" Value="Transparent"/>
        <Setter Property="Stroke" Value="Transparent"/>
    </Style>

    <!-- ManualInput -->
    <Style x:Key="ManualInput" TargetType="Path" BasedOn="{StaticResource FlowChartItemStyle}">
        <Setter Property="Data" Value="M 0 10 L 60,0 V 40 H 0 Z"/>
    </Style>

    <Style x:Key="ManualInput_DragThumb" TargetType="Path" BasedOn="{StaticResource ManualInput}">
        <Setter Property="IsHitTestVisible" Value="true"/>
        <Setter Property="Fill" Value="Transparent"/>
        <Setter Property="Stroke" Value="Transparent"/>
    </Style>

    <!-- Card -->
    <Style x:Key="Card" TargetType="Path" BasedOn="{StaticResource FlowChartItemStyle}">
        <Setter Property="Data" Value="M 0 10 L 10,0 H 60 V 40 H 0 Z"/>
    </Style>

    <Style x:Key="Card_DragThumb" TargetType="Path" BasedOn="{StaticResource Card}">
        <Setter Property="IsHitTestVisible" Value="true"/>
        <Setter Property="Fill" Value="Transparent"/>
        <Setter Property="Stroke" Value="Transparent"/>
    </Style>

    <!-- PaperTape -->
    <Style x:Key="PaperTape" TargetType="Path" BasedOn="{StaticResource FlowChartItemStyle}">
        <Setter Property="Data" Value="M 0,3 C 30,-7 30,13 60,3 V 37 C 30,47 30,27 0,37 Z"/>
    </Style>

    <Style x:Key="PaperTape_DragThumb" TargetType="Path" BasedOn="{StaticResource PaperTape}">
        <Setter Property="IsHitTestVisible" Value="true"/>
        <Setter Property="Fill" Value="Transparent"/>
        <Setter Property="Stroke" Value="Transparent"/>
    </Style>


    <!-- Delay -->
    <Style x:Key="Delay" TargetType="Path" BasedOn="{StaticResource FlowChartItemStyle}">
        <Setter Property="Data" Value="M 0,0 H 40 A 20,20 0 0 1 40,40 H 0 Z"/>
    </Style>

    <Style x:Key="Delay_DragThumb" TargetType="Path" BasedOn="{StaticResource Delay}">
        <Setter Property="IsHitTestVisible" Value="true"/>
        <Setter Property="Fill" Value="Transparent"/>
        <Setter Property="Stroke" Value="Transparent"/>
    </Style>


    <!-- Terminator -->
    <Style x:Key="Terminator" TargetType="Path" BasedOn="{StaticResource FlowChartItemStyle}">
        <Setter Property="Data" Value="M 20,40 A 20,20 0 0 1 20,0 H 40 A 20,20 0 0 1 40,40 Z"/>
    </Style>

    <Style x:Key="Terminator_DragThumb" TargetType="Path" BasedOn="{StaticResource Terminator}">
        <Setter Property="IsHitTestVisible" Value="true"/>
        <Setter Property="Fill" Value="Transparent"/>
        <Setter Property="Stroke" Value="Transparent"/>
    </Style>

    <!-- Display -->
    <Style x:Key="Display" TargetType="Path" BasedOn="{StaticResource FlowChartItemStyle}">
        <Setter Property="Data" Value="M 0,20 A 40,40 0 0 1 15,0 H 55 A 60,60 0 0 1 55,40 H 15 A 40,40, 0 0 1 0,20 Z"/>
    </Style>

    <Style x:Key="Display_DragThumb" TargetType="Path" BasedOn="{StaticResource Display}">
        <Setter Property="IsHitTestVisible" Value="true"/>
        <Setter Property="Fill" Value="Transparent"/>
        <Setter Property="Stroke" Value="Transparent"/>
    </Style>

    <!-- LoopLimit -->
    <Style x:Key="LoopLimit" TargetType="Path" BasedOn="{StaticResource FlowChartItemStyle}">
        <Setter Property="Data" Value="M 0 10 L 10,0 H 50 L 60,10 V 40 H 0 Z"/>
    </Style>

    <Style x:Key="LoopLimit_DragThumb" TargetType="Path" BasedOn="{StaticResource LoopLimit}">
        <Setter Property="IsHitTestVisible" Value="true"/>
        <Setter Property="Fill" Value="Transparent"/>
        <Setter Property="Stroke" Value="Transparent"/>
    </Style>

    <!-- Preparation -->
    <Style x:Key="Preparation" TargetType="Path" BasedOn="{StaticResource FlowChartItemStyle}">
        <Setter Property="Data" Value="M 0,20 L 10,0  H 50 L 60,20 L 50,40 H10 Z"/>
    </Style>

    <Style x:Key="Preparation_DragThumb" TargetType="Path" BasedOn="{StaticResource Preparation}">
        <Setter Property="IsHitTestVisible" Value="true"/>
        <Setter Property="Fill" Value="Transparent"/>
        <Setter Property="Stroke" Value="Transparent"/>
    </Style>

    <!-- ManualOperation -->
    <Style x:Key="ManualOperation" TargetType="Path" BasedOn="{StaticResource FlowChartItemStyle}">
        <Setter Property="Data" Value="M 0 0 H 60 L 50 40 H 10 Z"/>
    </Style>

    <Style x:Key="ManualOperation_DragThumb" TargetType="Path" BasedOn="{StaticResource ManualOperation}">
        <Setter Property="IsHitTestVisible" Value="true"/>
        <Setter Property="Fill" Value="Transparent"/>
        <Setter Property="Stroke" Value="Transparent"/>
    </Style>

    <!-- OffPageReference -->
    <Style x:Key="OffPageReference" TargetType="Path" BasedOn="{StaticResource FlowChartItemStyle}">
        <Setter Property="Data" Value="M 0 0 H 60 V 20 L 30,40 L 0,20 Z"/>
    </Style>

    <Style x:Key="OffPageReference_DragThumb" TargetType="Path" BasedOn="{StaticResource OffPageReference}">
        <Setter Property="IsHitTestVisible" Value="true"/>
        <Setter Property="Fill" Value="Transparent"/>
        <Setter Property="Stroke" Value="Transparent"/>
    </Style>

    <!-- Star -->
    <Style x:Key="Star" TargetType="Path" BasedOn="{StaticResource FlowChartItemStyle}">
        <Setter Property="Data" Value="M 9,2 11,7 17,7 12,10 14,15 9,12 4,15 6,10 1,7 7,7 Z"/>
    </Style>

    <Style x:Key="Star_DragThumb" TargetType="Path" BasedOn="{StaticResource Star}">
        <Setter Property="IsHitTestVisible" Value="true"/>
        <Setter Property="Fill" Value="Transparent"/>
        <Setter Property="Stroke" Value="Transparent"/>
    </Style>

    <s:Toolbox x:Key="FlowChartStencils" ItemSize="60,50" SnapsToDevicePixels="True"
             ScrollViewer.HorizontalScrollBarVisibility="Disabled">
        <ItemsControl.Items>

            <Path Style="{StaticResource Process}" ToolTip="Process">
                <s:DesignerItem.DragThumbTemplate>
                    <ControlTemplate>
                        <Path Style="{StaticResource Process_DragThumb}"/>
                    </ControlTemplate>
                </s:DesignerItem.DragThumbTemplate>
            </Path>

            <Path Style="{StaticResource Decision}" ToolTip="Decision">
                <s:DesignerItem.DragThumbTemplate>
                    <ControlTemplate>
                        <Path Style="{StaticResource Decision_DragThumb}"/>
                    </ControlTemplate>
                </s:DesignerItem.DragThumbTemplate>
            </Path>

            <Path Style="{StaticResource Document}" ToolTip="Document">
                <s:DesignerItem.DragThumbTemplate>
                    <ControlTemplate>
                        <Path Style="{StaticResource Document_DragThumb}"/>
                    </ControlTemplate>
                </s:DesignerItem.DragThumbTemplate>
                <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.5"/>
                            <s:Connector Orientation="Right" c:RelativePositionPanel.RelativePosition="1,0.5"/>
                            <s:Connector Orientation="Bottom" c:RelativePositionPanel.RelativePosition="0.5,0.93"/>
                        </c:RelativePositionPanel>
                    </ControlTemplate>
                </s:DesignerItem.ConnectorDecoratorTemplate>
            </Path>

            <Path Style="{StaticResource Data}" ToolTip="Data">
                <s:DesignerItem.DragThumbTemplate>
                    <ControlTemplate>
                        <Path Style="{StaticResource Data_DragThumb}"/>
                    </ControlTemplate>
                </s:DesignerItem.DragThumbTemplate>
                <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.09,0.5"/>
                            <s:Connector Orientation="Right" c:RelativePositionPanel.RelativePosition="0.91,0.5"/>
                            <s:Connector Orientation="Bottom" c:RelativePositionPanel.RelativePosition="0.5,1"/>
                        </c:RelativePositionPanel>
                    </ControlTemplate>
                </s:DesignerItem.ConnectorDecoratorTemplate>
            </Path>

            <Path Style="{StaticResource Start}" ToolTip="Start">
                <s:DesignerItem.DragThumbTemplate>
                    <ControlTemplate>
                        <Path Style="{StaticResource Start_DragThumb}"/>
                    </ControlTemplate>
                </s:DesignerItem.DragThumbTemplate>
            </Path>

            <Path Style="{StaticResource PaperTape}" ToolTip="Paper Tape">
                <s:DesignerItem.DragThumbTemplate>
                    <ControlTemplate>
                        <Path Style="{StaticResource PaperTape_DragThumb}"/>
                    </ControlTemplate>
                </s:DesignerItem.DragThumbTemplate>
                <s:DesignerItem.ConnectorDecoratorTemplate>
                    <ControlTemplate>
                        <c:RelativePositionPanel Margin="-4">
                            <s:Connector Orientation="Top" c:RelativePositionPanel.RelativePosition="0.5,0.07"/>
                            <s:Connector Orientation="Left" c:RelativePositionPanel.RelativePosition="0,0.5"/>
                            <s:Connector Orientation="Right" c:RelativePositionPanel.RelativePosition="1,0.5"/>
                            <s:Connector Orientation="Bottom" c:RelativePositionPanel.RelativePosition="0.5,0.93"/>
                        </c:RelativePositionPanel>
                    </ControlTemplate>
                </s:DesignerItem.ConnectorDecoratorTemplate>
            </Path>

            <Path Style="{StaticResource Predefined}" ToolTip="Predefined">
                <s:DesignerItem.DragThumbTemplate>
                    <ControlTemplate>
                        <Path Style="{StaticResource Predefined_DragThumb}"/>
                    </ControlTemplate>
                </s:DesignerItem.DragThumbTemplate>
            </Path>

            <Path Style="{StaticResource StoredData}" ToolTip="Stored Data">
                <s:DesignerItem.DragThumbTemplate>
                    <ControlTemplate>
                        <Path Style="{StaticResource StoredData_DragThumb}"/>
                    </ControlTemplate>
                </s:DesignerItem.DragThumbTemplate>
                <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.5"/>
                            <s:Connector Orientation="Right" c:RelativePositionPanel.RelativePosition="0.9,0.5"/>
                            <s:Connector Orientation="Bottom" c:RelativePositionPanel.RelativePosition="0.5,1"/>
                        </c:RelativePositionPanel>
                    </ControlTemplate>
                </s:DesignerItem.ConnectorDecoratorTemplate>
            </Path>

            <Path Style="{StaticResource InternalStorage}" ToolTip="Internal Storage">
                <s:DesignerItem.DragThumbTemplate>
                    <ControlTemplate>
                        <Path Style="{StaticResource InternalStorage_DragThumb}"/>
                    </ControlTemplate>
                </s:DesignerItem.DragThumbTemplate>
            </Path>

            <Path Style="{StaticResource SequentialData}" ToolTip="Sequential Data">
                <s:DesignerItem.DragThumbTemplate>
                    <ControlTemplate>
                        <Path Style="{StaticResource SequentialData_DragThumb}"/>
                    </ControlTemplate>
                </s:DesignerItem.DragThumbTemplate>
            </Path>

            <Path Style="{StaticResource DirectData}" ToolTip="Direct Data">
                <s:DesignerItem.DragThumbTemplate>
                    <ControlTemplate>
                        <Path Style="{StaticResource DirectData_DragThumb}"/>
                    </ControlTemplate>
                </s:DesignerItem.DragThumbTemplate>
            </Path>

            <Path Style="{StaticResource ManualInput}" ToolTip="Manual Input">
                <s:DesignerItem.DragThumbTemplate>
                    <ControlTemplate>
                        <Path Style="{StaticResource ManualInput_DragThumb}"/>
                    </ControlTemplate>
                </s:DesignerItem.DragThumbTemplate>
                <s:DesignerItem.ConnectorDecoratorTemplate>
                    <ControlTemplate>
                        <c:RelativePositionPanel Margin="-4">
                            <s:Connector Orientation="Top" c:RelativePositionPanel.RelativePosition="0.5,0.12"/>
                            <s:Connector Orientation="Left" c:RelativePositionPanel.RelativePosition="0,0.5"/>
                            <s:Connector Orientation="Right" c:RelativePositionPanel.RelativePosition="1,0.5"/>
                            <s:Connector Orientation="Bottom" c:RelativePositionPanel.RelativePosition="0.5,1"/>
                        </c:RelativePositionPanel>
                    </ControlTemplate>
                </s:DesignerItem.ConnectorDecoratorTemplate>
            </Path>

            <Path Style="{StaticResource Card}" ToolTip="Card">
                <s:DesignerItem.DragThumbTemplate>
                    <ControlTemplate>
                        <Path Style="{StaticResource Card_DragThumb}"/>
                    </ControlTemplate>
                </s:DesignerItem.DragThumbTemplate>
            </Path>

            <Path Style="{StaticResource Delay}" ToolTip="Delay">
                <s:DesignerItem.DragThumbTemplate>
                    <ControlTemplate>
                        <Path Style="{StaticResource Delay_DragThumb}"/>
                    </ControlTemplate>
                </s:DesignerItem.DragThumbTemplate>
            </Path>

            <Path Style="{StaticResource Terminator}" ToolTip="Terminator">
                <s:DesignerItem.DragThumbTemplate>
                    <ControlTemplate>
                        <Path Style="{StaticResource Terminator_DragThumb}"/>
                    </ControlTemplate>
                </s:DesignerItem.DragThumbTemplate>
            </Path>

            <Path Style="{StaticResource Display}" ToolTip="Display">
                <s:DesignerItem.DragThumbTemplate>
                    <ControlTemplate>
                        <Path Style="{StaticResource Display_DragThumb}"/>
                    </ControlTemplate>
                </s:DesignerItem.DragThumbTemplate>
            </Path>

            <Path Style="{StaticResource LoopLimit}" ToolTip="Loop Limit">
                <s:DesignerItem.DragThumbTemplate>
                    <ControlTemplate>
                        <Path Style="{StaticResource LoopLimit_DragThumb}"/>
                    </ControlTemplate>
                </s:DesignerItem.DragThumbTemplate>
            </Path>

            <Path Style="{StaticResource Preparation}" ToolTip="Preparation">
                <s:DesignerItem.DragThumbTemplate>
                    <ControlTemplate>
                        <Path Style="{StaticResource Preparation_DragThumb}"/>
                    </ControlTemplate>
                </s:DesignerItem.DragThumbTemplate>
            </Path>

            <Path Style="{StaticResource ManualOperation}" ToolTip="Manual Operation">
                <s:DesignerItem.DragThumbTemplate>
                    <ControlTemplate>
                        <Path Style="{StaticResource ManualOperation_DragThumb}"/>
                    </ControlTemplate>
                </s:DesignerItem.DragThumbTemplate>
                <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.1,0.5"/>
                            <s:Connector Orientation="Right" c:RelativePositionPanel.RelativePosition="0.9,0.5"/>
                            <s:Connector Orientation="Bottom" c:RelativePositionPanel.RelativePosition="0.5,1"/>
                        </c:RelativePositionPanel>
                    </ControlTemplate>
                </s:DesignerItem.ConnectorDecoratorTemplate>
            </Path>

            <Path Style="{StaticResource OffPageReference}" ToolTip="Off Page Reference">
                <s:DesignerItem.DragThumbTemplate>
                    <ControlTemplate>
                        <Path Style="{StaticResource OffPageReference_DragThumb}"/>
                    </ControlTemplate>
                </s:DesignerItem.DragThumbTemplate>
            </Path>

        </ItemsControl.Items>
    </s:Toolbox>
</ResourceDictionary>

3、ShapeStencils.xaml

csharp 复制代码
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:s="clr-namespace:DiagramDesigner"
                    xmlns:c="clr-namespace:DiagramDesigner.Controls">
    <RadialGradientBrush x:Key="RadialBrushOrange" Center="0.2, 0.2" GradientOrigin="0.2, 0.2" RadiusX="0.8" RadiusY="0.8">
        <GradientStop Color="White" Offset="0"/>
        <GradientStop Color="Orange" Offset="0.9"/>
    </RadialGradientBrush>

    <RadialGradientBrush x:Key="RadialBrushGreen" Center="0.2, 0.2" GradientOrigin="0.2, 0.2" RadiusX="0.8" RadiusY="0.8">
        <GradientStop Color="White" Offset="0"/>
        <GradientStop Color="Green" Offset="0.9"/>
    </RadialGradientBrush>

    <RadialGradientBrush x:Key="RadialBrushBlue" Center="0.2, 0.2" GradientOrigin="0.2, 0.2" RadiusX="0.8" RadiusY="0.8">
        <GradientStop Color="White" Offset="0"/>
        <GradientStop Color="Blue" Offset="0.9"/>
    </RadialGradientBrush>

    <LinearGradientBrush x:Key="Brush6" StartPoint="0,0" EndPoint="0,1" Opacity="1">
        <LinearGradientBrush.GradientStops>
            <GradientStop Color="#FAFBE9" Offset="0.1" />
            <GradientStop Color="Orange" Offset="1" />
        </LinearGradientBrush.GradientStops>
    </LinearGradientBrush>

    <s:Toolbox x:Key="ShapeStencils" ItemSize="60,60">
        <ItemsControl.Items>

            <Ellipse Fill="{StaticResource RadialBrushOrange}" ToolTip="Ellipse" IsHitTestVisible="false">
                <Ellipse.BitmapEffect>
                    <DropShadowBitmapEffect Color="Gray" Direction="315" ShadowDepth="15" Softness="0.8" Opacity="0.4"/>
                </Ellipse.BitmapEffect>
            </Ellipse>

            <Ellipse Fill="{StaticResource RadialBrushBlue}" ToolTip="Ellipse" IsHitTestVisible="false">
                <Ellipse.BitmapEffect>
                    <DropShadowBitmapEffect Color="Gray" Direction="315" ShadowDepth="15" Softness="0.8" Opacity="0.4"/>
                </Ellipse.BitmapEffect>
            </Ellipse>

            <Ellipse Fill="{StaticResource RadialBrushGreen}" ToolTip="Ellipse" IsHitTestVisible="false">
                <Ellipse.BitmapEffect>
                    <DropShadowBitmapEffect Color="Gray" Direction="315" ShadowDepth="15" Softness="0.8" Opacity="0.4"/>
                </Ellipse.BitmapEffect>
            </Ellipse>

            <Path Stretch="Fill" IsHitTestVisible="false"
            StrokeLineJoin="Round"
            Fill="{StaticResource Brush6}"
            Stroke="#AAFF8C00"
            StrokeThickness="3"
            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 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>
                <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>

        </ItemsControl.Items>
    </s:Toolbox>
</ResourceDictionary>

4、SymbolStencils.xaml

csharp 复制代码
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:s="clr-namespace:DiagramDesigner"
                    xmlns:c="clr-namespace:DiagramDesigner.Controls">
    <s:Toolbox x:Key="SymbolStencils" ItemSize="60,60">
        <ItemsControl.Items>
            <!--设置IsHitTestVisble属性值为false,可以使界面元素不响应鼠标,鼠标事件也不会被触发-->
            <Image IsHitTestVisible="False" Stretch="Fill" Source="../Images/attention.png"/>
            <Image IsHitTestVisible="False" Stretch="Fill" Source="../Images/info.png"/>
            <Image IsHitTestVisible="False" Stretch="Fill" Source="../Images/arrow.png"/>
            <Image IsHitTestVisible="False" Stretch="Fill" Source="../Images/plus.png"/>
            <Image IsHitTestVisible="False" Stretch="Fill" Source="../Images/chart3.png"/>
            <Image IsHitTestVisible="False" Stretch="Fill" Source="../Images/printer.png"/>
            <Image IsHitTestVisible="False" Stretch="Fill" Source="../Images/hexagon.png"/>
            <Image IsHitTestVisible="False" Stretch="Fill" Source="../Images/chart1.png"/>
            <Image IsHitTestVisible="False" Stretch="Fill" Source="../Images/chart2.png"/>
            <Image IsHitTestVisible="False" Stretch="Fill" Source="../Images/world.png"/>
            <Image IsHitTestVisible="False" Stretch="Fill" Source="../Images/software.png"/>
            <Image IsHitTestVisible="False" Stretch="Fill" Source="../Images/walk.png"/>
            <Image IsHitTestVisible="False" Stretch="Fill" Source="../Images/nuclear.png"/>
            <Image IsHitTestVisible="False" Stretch="Fill" Source="../Images/ring.png"/>
            <Image IsHitTestVisible="False" Stretch="Fill" Source="../Images/mail.png"/>
            <Image IsHitTestVisible="False" Stretch="Fill" Source="../Images/cross.png"/>
        </ItemsControl.Items>
    </s:Toolbox>
</ResourceDictionary>

5、Expander.xaml

csharp 复制代码
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="Shared.xaml" />
    </ResourceDictionary.MergedDictionaries>

    <!-- SimpleStyles: Expander(设计风格) -->

    <ControlTemplate x:Key="ExpanderToggleButton" TargetType="ToggleButton">
        <Border
      Name="Border" 
      CornerRadius="2,0,0,0"
      Background="Transparent"
      BorderBrush="{StaticResource NormalBorderBrush}"
      BorderThickness="0,0,0,0">
            <Path 
        Name="Arrow"
        Fill="{StaticResource GlyphBrush}"
        HorizontalAlignment="Center"
        VerticalAlignment="Center"
        Data="M 0 0 L 4 4 L 8 0 Z"/>
        </Border>
        <ControlTemplate.Triggers>
            <Trigger Property="ToggleButton.IsMouseOver" Value="true">
                <Setter TargetName="Border" Property="Background"
                Value="{StaticResource DarkBrush}" />
            </Trigger>
            <Trigger Property="IsPressed" Value="true">
                <Setter TargetName="Border" Property="Background"
                Value="{StaticResource PressedBrush}" />
            </Trigger>
            <Trigger Property="IsChecked" Value="true">
                <Setter TargetName="Arrow" Property="Data"
                Value="M 0 4 L 4 0 L 8 4 Z" />
            </Trigger>
            <Trigger Property="IsEnabled" Value="False">
                <Setter TargetName="Border" Property="Background"
                Value="{StaticResource DisabledBackgroundBrush}" />
                <Setter TargetName="Border" Property="BorderBrush"
                Value="{StaticResource DisabledBorderBrush}" />
                <Setter Property="Foreground"
                Value="{StaticResource DisabledForegroundBrush}"/>
                <Setter TargetName="Arrow" Property="Fill"
                Value="{StaticResource DisabledForegroundBrush}" />
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>

    <Style TargetType="Expander">
        <Setter Property="FontFamily" Value="SegoeUI"/>
        <Setter Property="FontSize" Value="12"/>
        <Setter Property="Foreground" Value="#4C4C4C"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Expander">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Name="ContentRow" Height="0"/>
                        </Grid.RowDefinitions>
                        <Border 
              Name="Border" 
              Grid.Row="0" 
              Background="{StaticResource LightBrush}"
              BorderBrush="{StaticResource NormalBorderBrush}"
              BorderThickness="1" 
              CornerRadius="2,2,0,0" >
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="20" />
                                    <ColumnDefinition Width="*" />
                                </Grid.ColumnDefinitions>
                                <ToggleButton
                  IsChecked="{Binding Path=IsExpanded,Mode=TwoWay,
                                      RelativeSource={RelativeSource TemplatedParent}}"
                  OverridesDefaultStyle="True" 
                  Template="{StaticResource ExpanderToggleButton}" 
                  Background="{StaticResource NormalBrush}" />
                                <ContentPresenter 
                  Grid.Column="1"
                  Margin="4" 
                  ContentSource="Header" 
                  RecognizesAccessKey="True" />
                            </Grid>
                        </Border>
                        <Border 
              Name="Content" 
              Grid.Row="1" 
              Background="{StaticResource WindowBackgroundBrush}"
              BorderBrush="{StaticResource SolidBorderBrush}" 
              BorderThickness="1,0,1,1" 
              CornerRadius="0,0,2,2" >
                            <ContentPresenter Margin="4" />
                        </Border>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsExpanded" Value="True">
                            <Setter TargetName="ContentRow" Property="Height"
                      Value="{Binding ElementName=Content,Path=DesiredHeight}" />
                        </Trigger>
                        <Trigger Property="IsEnabled" Value="False">
                            <Setter TargetName="Border" Property="Background"
                      Value="{StaticResource DisabledBackgroundBrush}" />
                            <Setter TargetName="Border" Property="BorderBrush"
                      Value="{StaticResource DisabledBorderBrush}" />
                            <Setter Property="Foreground"
                      Value="{StaticResource DisabledForegroundBrush}"/>
                        </Trigger>

                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

6、GroupBox.xaml

csharp 复制代码
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="Shared.xaml" />
    </ResourceDictionary.MergedDictionaries>

    <!-- SimpleStyles: GroupBox(设计风格) -->
    <Style TargetType="GroupBox">
        <Setter Property="FontFamily" Value="SegoeUI"/>
        <Setter Property="FontSize" Value="12"/>
        <Setter Property="Foreground" Value="#4C4C4C"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="GroupBox">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="*"/>
                        </Grid.RowDefinitions>
                        <Border 
              Grid.Row="0" 
              Background="{StaticResource LightBrush}"
              BorderBrush="{StaticResource NormalBorderBrush}"
              BorderThickness="1" 
              CornerRadius="2,2,0,0" >
                            <ContentPresenter 
                Margin="4" 
                ContentSource="Header" 
                RecognizesAccessKey="True" />
                        </Border>
                        <Border 
              Grid.Row="1" 
              Background="{TemplateBinding Background}"
              BorderBrush="{StaticResource SolidBorderBrush}" 
              BorderThickness="1,0,1,1" 
              CornerRadius="0,0,2,2" >
                            <ContentPresenter 
                Margin="4" />
                        </Border>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

7、ScrollBar.xaml

csharp 复制代码
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <!--ScrollBarLineButton风格设计-->
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="Shared.xaml" />
    </ResourceDictionary.MergedDictionaries>

    <Style x:Key="ScrollBarLineButton" TargetType="{x:Type RepeatButton}">
        <Setter Property="SnapsToDevicePixels" Value="True"/>
        <Setter Property="OverridesDefaultStyle" Value="true"/>
        <Setter Property="Focusable" Value="false"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type RepeatButton}">
                    <Border 
            Name="Border"
            Margin="1" 
            CornerRadius="2" 
            Background="{StaticResource NormalBrush}"
            BorderBrush="{StaticResource NormalBorderBrush}"
            BorderThickness="1">
                        <Path 
              HorizontalAlignment="Center"
              VerticalAlignment="Center"
              Fill="{StaticResource GlyphBrush}"
              Data="{Binding Path=Content,RelativeSource={RelativeSource TemplatedParent}}" />
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsPressed" Value="true">
                            <Setter TargetName="Border" Property="Background" Value="{StaticResource PressedBrush}" />
                        </Trigger>
                        <Trigger Property="IsEnabled" Value="false">
                            <Setter Property="Foreground" Value="{StaticResource DisabledForegroundBrush}"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    <Style x:Key="ScrollBarPageButton" TargetType="{x:Type RepeatButton}">
        <Setter Property="SnapsToDevicePixels" Value="True"/>
        <Setter Property="OverridesDefaultStyle" Value="true"/>
        <Setter Property="IsTabStop" Value="false"/>
        <Setter Property="Focusable" Value="false"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type RepeatButton}">
                    <Border Background="Transparent" />
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    <Style x:Key="ScrollBarThumb" TargetType="{x:Type Thumb}">
        <Setter Property="SnapsToDevicePixels" Value="True"/>
        <Setter Property="OverridesDefaultStyle" Value="true"/>
        <Setter Property="IsTabStop" Value="false"/>
        <Setter Property="Focusable" Value="false"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Thumb}">
                    <Border 
            CornerRadius="2" 
            Background="{TemplateBinding Background}"
            BorderBrush="{TemplateBinding BorderBrush}"
            BorderThickness="1" />
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    <ControlTemplate x:Key="VerticalScrollBar" TargetType="{x:Type ScrollBar}">
        <Grid >
            <Grid.RowDefinitions>
                <RowDefinition MaxHeight="18"/>
                <RowDefinition Height="0.00001*"/>
                <RowDefinition MaxHeight="18"/>
            </Grid.RowDefinitions>
            <Border
        Grid.RowSpan="3"
        CornerRadius="2" 
        Background="#F0F0F0" />
            <RepeatButton 
        Grid.Row="0"                           
        Style="{StaticResource ScrollBarLineButton}"
        Height="18"
        Command="ScrollBar.LineUpCommand"
        Content="M 0 4 L 8 4 L 4 0 Z" />
            <Track 
        Name="PART_Track"
        Grid.Row="1"
        IsDirectionReversed="true">
                <Track.DecreaseRepeatButton>
                    <RepeatButton 
            Style="{StaticResource ScrollBarPageButton}"
            Command="ScrollBar.PageUpCommand" />
                </Track.DecreaseRepeatButton>
                <Track.Thumb>
                    <Thumb 
            Style="{StaticResource ScrollBarThumb}" 
            Margin="1,0,1,0"  
            Background="{StaticResource HorizontalNormalBrush}"
            BorderBrush="{StaticResource HorizontalNormalBorderBrush}" />
                </Track.Thumb>
                <Track.IncreaseRepeatButton>
                    <RepeatButton 
            Style="{StaticResource ScrollBarPageButton}"
            Command="ScrollBar.PageDownCommand" />
                </Track.IncreaseRepeatButton>
            </Track>
            <RepeatButton 
        Grid.Row="3" 
        Style="{StaticResource ScrollBarLineButton}"
        Height="18"
        Command="ScrollBar.LineDownCommand"
        Content="M 0 0 L 4 4 L 8 0 Z"/>
        </Grid>
    </ControlTemplate>

    <ControlTemplate x:Key="HorizontalScrollBar" TargetType="{x:Type ScrollBar}">
        <Grid >
            <Grid.ColumnDefinitions>
                <ColumnDefinition MaxWidth="18"/>
                <ColumnDefinition Width="0.00001*"/>
                <ColumnDefinition MaxWidth="18"/>
            </Grid.ColumnDefinitions>
            <Border
        Grid.ColumnSpan="3"
        CornerRadius="2" 
        Background="#F0F0F0" />
            <RepeatButton 
        Grid.Column="0"                           
        Style="{StaticResource ScrollBarLineButton}"
        Width="18"
        Command="ScrollBar.LineLeftCommand"
        Content="M 4 0 L 4 8 L 0 4 Z" />
            <Track 
        Name="PART_Track"
        Grid.Column="1"
        IsDirectionReversed="False">
                <Track.DecreaseRepeatButton>
                    <RepeatButton 
            Style="{StaticResource ScrollBarPageButton}"
            Command="ScrollBar.PageLeftCommand" />
                </Track.DecreaseRepeatButton>
                <Track.Thumb>
                    <Thumb 
            Style="{StaticResource ScrollBarThumb}" 
            Margin="0,1,0,1"  
            Background="{StaticResource NormalBrush}"
            BorderBrush="{StaticResource NormalBorderBrush}" />
                </Track.Thumb>
                <Track.IncreaseRepeatButton>
                    <RepeatButton 
            Style="{StaticResource ScrollBarPageButton}"
            Command="ScrollBar.PageRightCommand" />
                </Track.IncreaseRepeatButton>
            </Track>
            <RepeatButton 
        Grid.Column="3" 
        Style="{StaticResource ScrollBarLineButton}"
        Width="18"
        Command="ScrollBar.LineRightCommand"
        Content="M 0 0 L 4 4 L 0 8 Z"/>
        </Grid>
    </ControlTemplate>

    <Style x:Key="{x:Type ScrollBar}" TargetType="{x:Type ScrollBar}">
        <Setter Property="SnapsToDevicePixels" Value="True"/>
        <Setter Property="OverridesDefaultStyle" Value="true"/>
        <Style.Triggers>
            <Trigger Property="Orientation" Value="Horizontal">
                <Setter Property="Width" Value="Auto"/>
                <Setter Property="Height" Value="18" />
                <Setter Property="Template" Value="{StaticResource HorizontalScrollBar}" />
            </Trigger>
            <Trigger Property="Orientation" Value="Vertical">
                <Setter Property="Width" Value="18"/>
                <Setter Property="Height" Value="Auto" />
                <Setter Property="Template" Value="{StaticResource VerticalScrollBar}" />
            </Trigger>
        </Style.Triggers>
    </Style>
</ResourceDictionary>

8、ScrollViewer.xaml

csharp 复制代码
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <!-- SimpleStyles: ScrollViewer风格设计(?) -->

    <Style x:Key="LeftScrollViewer" TargetType="{x:Type ScrollViewer}">
        <Setter Property="OverridesDefaultStyle" Value="True"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ScrollViewer}">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto"/>
                            <ColumnDefinition/>
                        </Grid.ColumnDefinitions>
                        <Grid.RowDefinitions>
                            <RowDefinition/>
                            <RowDefinition Height="Auto"/>
                        </Grid.RowDefinitions>

                        <ScrollContentPresenter Grid.Column="1"/>

                        <ScrollBar Name="PART_VerticalScrollBar"
              Value="{TemplateBinding VerticalOffset}"
              Maximum="{TemplateBinding ScrollableHeight}"
              ViewportSize="{TemplateBinding ViewportHeight}"
              Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"/>
                        <ScrollBar Name="PART_HorizontalScrollBar"
              Orientation="Horizontal"
              Grid.Row="1"
              Grid.Column="1"
              Value="{TemplateBinding HorizontalOffset}"
              Maximum="{TemplateBinding ScrollableWidth}"
              ViewportSize="{TemplateBinding ViewportWidth}"
              Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"/>

                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

9、Shared.xaml

csharp 复制代码
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <!-- Fill Brushes(画刷定义) -->

    <LinearGradientBrush x:Key="NormalBrush" StartPoint="0,0" EndPoint="0,1">
        <GradientBrush.GradientStops>
            <GradientStopCollection>
                <GradientStop Color="#FFF" Offset="0.0"/>
                <GradientStop Color="#CCC" Offset="1.0"/>
            </GradientStopCollection>
        </GradientBrush.GradientStops>
    </LinearGradientBrush>

    <LinearGradientBrush x:Key="HorizontalNormalBrush" StartPoint="0,0" EndPoint="1,0">
        <GradientBrush.GradientStops>
            <GradientStopCollection>
                <GradientStop Color="#FFF" Offset="0.0"/>
                <GradientStop Color="#CCC" Offset="1.0"/>
            </GradientStopCollection>
        </GradientBrush.GradientStops>
    </LinearGradientBrush>

    <LinearGradientBrush x:Key="LightBrush" StartPoint="0,0" EndPoint="0,1">
        <GradientBrush.GradientStops>
            <GradientStopCollection>
                <GradientStop Color="#FFF" Offset="0.0"/>
                <GradientStop Color="#EEE" Offset="1.0"/>
            </GradientStopCollection>
        </GradientBrush.GradientStops>
    </LinearGradientBrush>

    <LinearGradientBrush x:Key="HorizontalLightBrush" StartPoint="0,0" EndPoint="1,0">
        <GradientBrush.GradientStops>
            <GradientStopCollection>
                <GradientStop Color="#FFF" Offset="0.0"/>
                <GradientStop Color="#EEE" Offset="1.0"/>
            </GradientStopCollection>
        </GradientBrush.GradientStops>
    </LinearGradientBrush>

    <LinearGradientBrush x:Key="DarkBrush" StartPoint="0,0" EndPoint="0,1">
        <GradientBrush.GradientStops>
            <GradientStopCollection>
                <GradientStop Color="#FFF" Offset="0.0"/>
                <GradientStop Color="#AAA" Offset="1.0"/>
            </GradientStopCollection>
        </GradientBrush.GradientStops>
    </LinearGradientBrush>

    <LinearGradientBrush x:Key="PressedBrush" StartPoint="0,0" EndPoint="0,1">
        <GradientBrush.GradientStops>
            <GradientStopCollection>
                <GradientStop Color="#BBB" Offset="0.0"/>
                <GradientStop Color="#EEE" Offset="0.1"/>
                <GradientStop Color="#EEE" Offset="0.9"/>
                <GradientStop Color="#FFF" Offset="1.0"/>
            </GradientStopCollection>
        </GradientBrush.GradientStops>
    </LinearGradientBrush>

    <SolidColorBrush x:Key="DisabledForegroundBrush" Color="#888" />

    <SolidColorBrush x:Key="DisabledBackgroundBrush" Color="#EEE" />

    <SolidColorBrush x:Key="WindowBackgroundBrush" Color="#FFF" />

    <SolidColorBrush x:Key="SelectedBackgroundBrush" Color="#DDD" />

    <!-- Border Brushes -->

    <LinearGradientBrush x:Key="NormalBorderBrush" StartPoint="0,0" EndPoint="0,1">
        <GradientBrush.GradientStops>
            <GradientStopCollection>
                <GradientStop Color="#CCC" Offset="0.0"/>
                <GradientStop Color="#444" Offset="1.0"/>
            </GradientStopCollection>
        </GradientBrush.GradientStops>
    </LinearGradientBrush>

    <LinearGradientBrush x:Key="HorizontalNormalBorderBrush" StartPoint="0,0" EndPoint="1,0">
        <GradientBrush.GradientStops>
            <GradientStopCollection>
                <GradientStop Color="#CCC" Offset="0.0"/>
                <GradientStop Color="#444" Offset="1.0"/>
            </GradientStopCollection>
        </GradientBrush.GradientStops>
    </LinearGradientBrush>

    <LinearGradientBrush x:Key="DefaultedBorderBrush" StartPoint="0,0" EndPoint="0,1">
        <GradientBrush.GradientStops>
            <GradientStopCollection>
                <GradientStop Color="#777" Offset="0.0"/>
                <GradientStop Color="#000" Offset="1.0"/>
            </GradientStopCollection>
        </GradientBrush.GradientStops>
    </LinearGradientBrush>

    <LinearGradientBrush x:Key="PressedBorderBrush" StartPoint="0,0" EndPoint="0,1">
        <GradientBrush.GradientStops>
            <GradientStopCollection>
                <GradientStop Color="#444" Offset="0.0"/>
                <GradientStop Color="#888" Offset="1.0"/>
            </GradientStopCollection>
        </GradientBrush.GradientStops>
    </LinearGradientBrush>

    <SolidColorBrush x:Key="DisabledBorderBrush" Color="#AAA" />

    <SolidColorBrush x:Key="SolidBorderBrush" Color="#888" />

    <SolidColorBrush x:Key="LightBorderBrush" Color="#AAA" />

    <SolidColorBrush x:Key="GlyphBrush" Color="#444" />

    <SolidColorBrush x:Key="LightColorBrush" Color="#DDD" />

</ResourceDictionary>

10、ToolTip.xaml

csharp 复制代码
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="Shared.xaml" />
    </ResourceDictionary.MergedDictionaries>

    <!-- SimpleStyles: ToolTip(风格设计) -->
    <Style x:Key="{x:Type ToolTip}" TargetType="ToolTip">
        <Setter Property="OverridesDefaultStyle" Value="true"/>
        <Setter Property="HasDropShadow" Value="True"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ToolTip">
                    <Border Name="Border"
            Background="{StaticResource LightBrush}"
            BorderBrush="{StaticResource SolidBorderBrush}"
            BorderThickness="1"
            Width="{TemplateBinding Width}"
            Height="{TemplateBinding Height}">
                        <ContentPresenter
              Margin="4" 
              HorizontalAlignment="Left"
              VerticalAlignment="Top" />
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="HasDropShadow" Value="true">
                            <Setter TargetName="Border" Property="CornerRadius" Value="4"/>
                            <Setter TargetName="Border" Property="SnapsToDevicePixels" Value="true"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

11、Connection.xaml

csharp 复制代码
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:s="clr-namespace:DiagramDesigner"
                    xmlns:c="clr-namespace:DiagramDesigner.Controls">
    <Style TargetType="{x:Type s:Connection}">
        <Style.Resources>
            <!-- Style for the ConnectorAdorner thumbs -->
            <Style x:Key="ConnectionAdornerThumbStyle" TargetType="{x:Type Thumb}">
                <Setter Property="Width" Value="12"/>
                <Setter Property="Height" Value="12"/>
                <Setter Property="SnapsToDevicePixels" Value="true"/>
                <Setter Property="RenderTransform">
                    <Setter.Value>
                        <TranslateTransform X="-6" Y="-6"/>
                    </Setter.Value>
                </Setter>
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type Thumb}">
                            <Rectangle Fill="#AADCDCDC" Stroke="DodgerBlue" StrokeThickness="1" RadiusX="0" RadiusY="0"/>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
            <!-- Arrow Grid Style -->
            <Style x:Key="ArrowGridStyle" TargetType="Grid">
                <Setter Property="Width" Value="10"/>
                <Setter Property="Height" Value="10"/>
                <Setter Property="RenderTransform">
                    <Setter.Value>
                        <TranslateTransform X="-5" Y="-5"/>
                    </Setter.Value>
                </Setter>
            </Style>
            <!-- base style for all arrow shapes -->
            <Style x:Key="ArrowSymbolBaseStyle" TargetType="Path">
                <Setter Property="Fill" Value="{StaticResource SolidBorderBrush}"/>
                <Setter Property="Stretch" Value="Fill"/>
            </Style>
            <!-- Arrow -->
            <Style x:Key="Arrow" TargetType="Path" BasedOn="{StaticResource ArrowSymbolBaseStyle}">
                <Setter Property="Data" Value="M0,0 8,4 0,8 Z"/>
            </Style>
            <!-- Diamond  -->
            <Style x:Key="Diamond" TargetType="Path" BasedOn="{StaticResource ArrowSymbolBaseStyle}">
                <Setter Property="Data" Value="M-5,0 0,-5 5,0 0,5 Z"/>
            </Style>
        </Style.Resources>
        <Setter Property="SnapsToDevicePixels" Value="True"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type s:Connection}">
                    <Canvas DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}" >
                        <Path Name="PART_ConnectionPath"
                  StrokeThickness="2"
                  Stroke="{StaticResource SolidBorderBrush}"
                  StrokeStartLineCap="Round"
                  StrokeEndLineCap="Round"
                  StrokeLineJoin="Round"
                  StrokeDashArray="{Binding StrokeDashArray}"
                  SnapsToDevicePixels="True"
                  Data="{Binding PathGeometry}">
                        </Path>
                        <Grid Style="{StaticResource ArrowGridStyle}"
                  Canvas.Left="{Binding AnchorPositionSource.X}"
                  Canvas.Top="{Binding AnchorPositionSource.Y}">
                            <Path Name="PART_SourceAnchorPath"/>
                            <Grid.LayoutTransform>
                                <RotateTransform Angle="{Binding AnchorAngleSource}"/>
                            </Grid.LayoutTransform>
                        </Grid>
                        <Grid Style="{StaticResource ArrowGridStyle}"
                  Canvas.Left="{Binding AnchorPositionSink.X}"
                  Canvas.Top="{Binding AnchorPositionSink.Y}">
                            <Path Name="PART_SinkAnchorPath"/>
                            <Grid.LayoutTransform>
                                <RotateTransform Angle="{Binding AnchorAngleSink}"/>
                            </Grid.LayoutTransform>
                        </Grid>

                        <!--Uncomment this to show default label text-->
                        <!--<TextBlock Width="100" Height="35" Text="Label"                       
                       Canvas.Left="{Binding LabelPosition.X}"
                       Canvas.Top="{Binding LabelPosition.Y}">
              <TextBlock.RenderTransform>
                <TranslateTransform X="5" Y="5"/>
              </TextBlock.RenderTransform>
            </TextBlock>-->

                        <Canvas.BitmapEffect>
                            <DropShadowBitmapEffect Color="Gray" Direction="315" ShadowDepth="10" Softness="0" Opacity="0.1"/>
                        </Canvas.BitmapEffect>
                    </Canvas>
                    <ControlTemplate.Triggers>
                        <DataTrigger Value="Arrow" Binding="{Binding RelativeSource={RelativeSource Self},Path=SourceArrowSymbol}">
                            <Setter TargetName="PART_SourceAnchorPath" Property="Style" Value="{StaticResource Arrow}"/>
                        </DataTrigger>
                        <DataTrigger Value="Diamond" Binding="{Binding RelativeSource={RelativeSource Self},Path=SourceArrowSymbol}">
                            <Setter TargetName="PART_SourceAnchorPath" Property="Style" Value="{StaticResource Diamond}"/>
                        </DataTrigger>
                        <DataTrigger Value="Arrow" Binding="{Binding RelativeSource={RelativeSource Self},Path=SinkArrowSymbol}">
                            <Setter TargetName="PART_SinkAnchorPath" Property="Style" Value="{StaticResource Arrow}"/>
                        </DataTrigger>
                        <DataTrigger Value="Diamond" Binding="{Binding RelativeSource={RelativeSource Self},Path=SinkArrowSymbol}">
                            <Setter TargetName="PART_SinkAnchorPath" Property="Style" Value="{StaticResource Diamond}"/>
                        </DataTrigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

12、DesignerItem.xaml

csharp 复制代码
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:s="clr-namespace:DiagramDesigner"
                    xmlns:c="clr-namespace:DiagramDesigner.Controls">
    <!-- Connector Style -->
    <Style TargetType="{x:Type s:Connector}">
        <Setter Property="Width" Value="8"/>
        <Setter Property="Height" Value="8"/>
        <Setter Property="Cursor" Value="Cross"/>
        <Setter Property="SnapsToDevicePixels" Value="true"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type s:Connector}">
                    <Grid>
                        <!-- transparent extra space makes connector easier to hit -->
                        <Rectangle Fill="Transparent" Margin="-2"/>
                        <Rectangle Fill="Lavender" StrokeThickness="1" Stroke="#AA000080"/>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    <!-- ConnectorDecoratorTemplate Default Template -->
    <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>

    <!-- ResizeDecorator Default Template -->
    <ControlTemplate x:Key="ResizeDecoratorTemplate" TargetType="{x:Type Control}">
        <Grid Opacity="0.7" SnapsToDevicePixels="true">
            <c:ResizeThumb Height="3" Cursor="SizeNS" Margin="0 -4 0 0"
                     VerticalAlignment="Top" HorizontalAlignment="Stretch"/>
            <c:ResizeThumb Width="3" Cursor="SizeWE" Margin="-4 0 0 0"
                     VerticalAlignment="Stretch" HorizontalAlignment="Left"/>
            <c:ResizeThumb Width="3" Cursor="SizeWE" Margin="0 0 -4 0"
                     VerticalAlignment="Stretch" HorizontalAlignment="Right"/>
            <c:ResizeThumb Height="3" Cursor="SizeNS" Margin="0 0 0 -4"
                     VerticalAlignment="Bottom" HorizontalAlignment="Stretch"/>
            <c:ResizeThumb Width="7" Height="7" Cursor="SizeNWSE" Margin="-6 -6 0 0"
                     VerticalAlignment="Top" HorizontalAlignment="Left"/>
            <c:ResizeThumb Width="7" Height="7" Cursor="SizeNESW" Margin="0 -6 -6 0"
                     VerticalAlignment="Top" HorizontalAlignment="Right"/>
            <c:ResizeThumb Width="7" Height="7" Cursor="SizeNESW" Margin="-6 0 0 -6"
                     VerticalAlignment="Bottom" HorizontalAlignment="Left"/>
            <c:ResizeThumb Width="7" Height="7" Cursor="SizeNWSE" Margin="0 0 -6 -6"
                     VerticalAlignment="Bottom" HorizontalAlignment="Right"/>
        </Grid>
    </ControlTemplate>

    <!-- DragThumb Default Template -->
    <Style TargetType="{x:Type c:DragThumb}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type c:DragThumb}">
                    <Rectangle Fill="Transparent"/>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    <!-- DesignerItem Style -->
    <Style TargetType="{x:Type s:DesignerItem}">
        <Setter Property="MinWidth" Value="25"/>
        <Setter Property="MinHeight" Value="25"/>
        <Setter Property="SnapsToDevicePixels" Value="True"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type s:DesignerItem}">
                    <Grid DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}">
                        <!-- PART_DragThumb -->
                        <c:DragThumb x:Name="PART_DragThumb" Cursor="SizeAll"/>
                        <!-- PART_ResizeDecorator -->
                        <Control x:Name="PART_ResizeDecorator"
                     Visibility="Collapsed"
                     Template="{StaticResource ResizeDecoratorTemplate}"/>
                        <!-- PART_ContentPresenter -->
                        <ContentPresenter x:Name="PART_ContentPresenter"
                              HorizontalAlignment="Stretch"
                              VerticalAlignment="Stretch"
                              Content="{TemplateBinding ContentControl.Content}"
                              Margin="{TemplateBinding ContentControl.Padding}"/>
                        <!-- PART_ConnectorDecorator -->
                        <Control x:Name="PART_ConnectorDecorator"
                     Visibility="Hidden"
                     Template="{StaticResource ConnectorDecoratorTemplate}"/>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <DataTrigger Value="True" Binding="{Binding RelativeSource={RelativeSource Self},Path=IsSelected}">
                            <Setter TargetName="PART_ResizeDecorator" Property="Visibility" Value="Visible"/>
                        </DataTrigger>
                        <Trigger Property="IsMouseOver" Value="true">
                            <Setter TargetName="PART_ConnectorDecorator" Property="Visibility" Value="Visible"/>
                        </Trigger>
                        <DataTrigger Value="True" Binding="{Binding RelativeSource={RelativeSource Self},Path=IsDragConnectionOver}">
                            <Setter TargetName="PART_ConnectorDecorator" Property="Visibility" Value="Visible"/>
                        </DataTrigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

</ResourceDictionary>

13、Toolbox.xaml

csharp 复制代码
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:s="clr-namespace:DiagramDesigner">
    <Style TargetType="{x:Type s:Toolbox}">
        <Setter Property="SnapsToDevicePixels" Value="true"/>
        <Setter Property="Focusable" Value="False"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type s:Toolbox}">
                    <Border BorderThickness="{TemplateBinding Border.BorderThickness}"
                  Padding="{TemplateBinding Control.Padding}"
                  BorderBrush="{TemplateBinding Border.BorderBrush}"
                  Background="{TemplateBinding Panel.Background}"
                  SnapsToDevicePixels="True">
                        <ScrollViewer VerticalScrollBarVisibility="Auto">
                            <ItemsPresenter SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
                        </ScrollViewer>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="ItemsPanel">
            <Setter.Value>
                <ItemsPanelTemplate>
                    <WrapPanel Margin="0,5,0,5"
                     ItemHeight="{Binding Path=ItemSize.Height, RelativeSource={RelativeSource AncestorType=s:Toolbox}}"
                     ItemWidth="{Binding Path=ItemSize.Width, RelativeSource={RelativeSource AncestorType=s:Toolbox}}"/>
                </ItemsPanelTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

14、ToolboxItem.xaml

csharp 复制代码
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:s="clr-namespace:DiagramDesigner">
    <Style TargetType="{x:Type s:ToolboxItem}">
        <Setter Property="Control.Padding" Value="10"/>
        <Setter Property="ContentControl.HorizontalContentAlignment" Value="Stretch"/>
        <Setter Property="ContentControl.VerticalContentAlignment" Value="Stretch"/>
        <Setter Property="ToolTip" Value="{Binding ToolTip}"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type s:ToolboxItem}" >
                    <Grid>
                        <Rectangle Name="Border"
                       StrokeThickness="1"
                       StrokeDashArray="2"
                       Fill="Transparent"
                       SnapsToDevicePixels="true"/>
                        <ContentPresenter Content="{TemplateBinding ContentControl.Content}"
                              Margin="{TemplateBinding Padding}"
                              SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="true">
                            <Setter TargetName="Border" Property="Stroke" Value="Gray"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

15、DragThumb.cs

csharp 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls.Primitives;
using System.Windows.Controls;
using System.Windows.Media;

namespace DiagramDesigner.Controls
{
    /// <summary>
    /// 自定义Thumb控件(表示可以由用户拖动的控件)
    /// </summary>
    public class DragThumb : Thumb
    {
        public DragThumb()
        {
            base.DragDelta += new DragDeltaEventHandler(DragThumb_DragDelta);
        }
        /// <summary>
        /// 焦点和鼠标捕获处理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void DragThumb_DragDelta(object sender, DragDeltaEventArgs e)
        {
            DesignerItem designerItem = this.DataContext as DesignerItem;
            DesignerCanvas designer = VisualTreeHelper.GetParent(designerItem) as DesignerCanvas;
            if (designerItem != null && designer != null && designerItem.IsSelected)
            {
                double minLeft = double.MaxValue;
                double minTop = double.MaxValue;

                // we only move DesignerItems
                var designerItems = from item in designer.SelectedItems
                                    where item is DesignerItem
                                    select item;

                foreach (DesignerItem item in designerItems)
                {
                    double left = Canvas.GetLeft(item);
                    double top = Canvas.GetTop(item);

                    minLeft = double.IsNaN(left) ? 0 : Math.Min(left, minLeft);
                    minTop = double.IsNaN(top) ? 0 : Math.Min(top, minTop);
                }

                double deltaHorizontal = Math.Max(-minLeft, e.HorizontalChange);
                double deltaVertical = Math.Max(-minTop, e.VerticalChange);

                foreach (DesignerItem item in designerItems)
                {
                    double left = Canvas.GetLeft(item);
                    double top = Canvas.GetTop(item);

                    if (double.IsNaN(left)) left = 0;
                    if (double.IsNaN(top)) top = 0;

                    Canvas.SetLeft(item, left + deltaHorizontal);
                    Canvas.SetTop(item, top + deltaVertical);
                }

                designer.InvalidateMeasure();
                e.Handled = true;
            }
        }
    }
}

16、RelativePositionPanel.cs

csharp 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows;

namespace DiagramDesigner.Controls
{
    public class RelativePositionPanel : Panel
    {
        public static readonly DependencyProperty RelativePositionProperty =
            DependencyProperty.RegisterAttached("RelativePosition", typeof(Point), typeof(RelativePositionPanel),
            new FrameworkPropertyMetadata(new Point(0, 0),
                                          new PropertyChangedCallback(RelativePositionPanel.OnRelativePositionChanged)));

        public static Point GetRelativePosition(UIElement element)
        {
            if (element == null)
            {
                throw new ArgumentNullException("element");
            }
            return (Point)element.GetValue(RelativePositionProperty);
        }

        public static void SetRelativePosition(UIElement element, Point value)
        {
            if (element == null)
            {
                throw new ArgumentNullException("element");
            }
            element.SetValue(RelativePositionProperty, value);
        }

        private static void OnRelativePositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            UIElement reference = d as UIElement;
            if (reference != null)
            {
                RelativePositionPanel parent = VisualTreeHelper.GetParent(reference) as RelativePositionPanel;
                if (parent != null)
                {
                    parent.InvalidateArrange();
                }
            }
        }

        protected override Size ArrangeOverride(Size arrangeSize)
        {
            foreach (UIElement element in base.InternalChildren)
            {
                if (element != null)
                {
                    Point relPosition = GetRelativePosition(element);
                    double x = (arrangeSize.Width - element.DesiredSize.Width) * relPosition.X;
                    double y = (arrangeSize.Height - element.DesiredSize.Height) * relPosition.Y;

                    if (double.IsNaN(x)) x = 0;
                    if (double.IsNaN(y)) y = 0;

                    element.Arrange(new Rect(new Point(x, y), element.DesiredSize));
                }
            }
            return arrangeSize;
        }

        protected override Size MeasureOverride(Size availableSize)
        {
            Size size = new Size(double.PositiveInfinity, double.PositiveInfinity);

            // SDK docu says about InternalChildren Property: 'Classes that are derived from Panel 
            // should use this property, instead of the Children property, for internal overrides 
            // such as MeasureCore and ArrangeCore.

            foreach (UIElement element in this.InternalChildren)
            {
                if (element != null)
                    element.Measure(size);
            }

            return base.MeasureOverride(availableSize);
        }
    }
}

17、ResizeThumb.cs

csharp 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls.Primitives;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows;

namespace DiagramDesigner.Controls
{
    public class ResizeThumb : Thumb
    {
        public ResizeThumb()
        {
            base.DragDelta += new DragDeltaEventHandler(ResizeThumb_DragDelta);
        }

        void ResizeThumb_DragDelta(object sender, DragDeltaEventArgs e)
        {
            DesignerItem designerItem = this.DataContext as DesignerItem;
            DesignerCanvas designer = VisualTreeHelper.GetParent(designerItem) as DesignerCanvas;

            if (designerItem != null && designer != null && designerItem.IsSelected)
            {
                double minLeft, minTop, minDeltaHorizontal, minDeltaVertical;
                double dragDeltaVertical, dragDeltaHorizontal;

                // only resize DesignerItems
                var selectedDesignerItems = from item in designer.SelectedItems
                                            where item is DesignerItem
                                            select item;

                CalculateDragLimits(selectedDesignerItems, out minLeft, out minTop,
                                    out minDeltaHorizontal, out minDeltaVertical);

                foreach (DesignerItem item in selectedDesignerItems)
                {
                    if (item != null)
                    {
                        switch (base.VerticalAlignment)
                        {
                            case VerticalAlignment.Bottom:
                                dragDeltaVertical = Math.Min(-e.VerticalChange, minDeltaVertical);
                                item.Height = item.ActualHeight - dragDeltaVertical;
                                break;
                            case VerticalAlignment.Top:
                                double top = Canvas.GetTop(item);
                                dragDeltaVertical = Math.Min(Math.Max(-minTop, e.VerticalChange), minDeltaVertical);
                                Canvas.SetTop(item, top + dragDeltaVertical);
                                item.Height = item.ActualHeight - dragDeltaVertical;
                                break;
                            default:
                                break;
                        }

                        switch (base.HorizontalAlignment)
                        {
                            case HorizontalAlignment.Left:
                                double left = Canvas.GetLeft(item);
                                dragDeltaHorizontal = Math.Min(Math.Max(-minLeft, e.HorizontalChange), minDeltaHorizontal);
                                Canvas.SetLeft(item, left + dragDeltaHorizontal);
                                item.Width = item.ActualWidth - dragDeltaHorizontal;
                                break;
                            case HorizontalAlignment.Right:
                                dragDeltaHorizontal = Math.Min(-e.HorizontalChange, minDeltaHorizontal);
                                item.Width = item.ActualWidth - dragDeltaHorizontal;
                                break;
                            default:
                                break;
                        }
                    }
                }
                e.Handled = true;
            }
        }

        private static void CalculateDragLimits(IEnumerable<ISelectable> selectedDesignerItems, out double minLeft, out double minTop, out double minDeltaHorizontal, out double minDeltaVertical)
        {
            minLeft = double.MaxValue;
            minTop = double.MaxValue;
            minDeltaHorizontal = double.MaxValue;
            minDeltaVertical = double.MaxValue;

            // drag limits are set by these parameters: canvas top, canvas left, minHeight, minWidth
            // calculate min value for each parameter for each item
            foreach (DesignerItem item in selectedDesignerItems)
            {
                double left = Canvas.GetLeft(item);
                double top = Canvas.GetTop(item);

                minLeft = double.IsNaN(left) ? 0 : Math.Min(left, minLeft);
                minTop = double.IsNaN(top) ? 0 : Math.Min(top, minTop);

                minDeltaVertical = Math.Min(minDeltaVertical, item.ActualHeight - item.MinHeight);
                minDeltaHorizontal = Math.Min(minDeltaHorizontal, item.ActualWidth - item.MinWidth);
            }
        }
    }
}

18、App.xaml

csharp 复制代码
<Application x:Class="DiagramDesigner.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:DiagramDesigner"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Assets\Styles\Shared.xaml"/>
                <ResourceDictionary Source="Assets\Styles\ScrollBar.xaml"/>
                <ResourceDictionary Source="Assets\Styles\Expander.xaml"/>
                <ResourceDictionary Source="Assets\Styles\GroupBox.xaml"/>
                <ResourceDictionary Source="Assets\Styles\ToolTip.xaml"/>
                <ResourceDictionary Source="Assets\Styles\ScrollViewer.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

19、Connection.cs

csharp 复制代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows;

namespace DiagramDesigner
{
    public class Connection : Control, ISelectable, INotifyPropertyChanged
    {
        private Adorner connectionAdorner;

        #region Properties

        // source connector
        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();
                }
            }
        }

        // sink connector
        private Connector sink;
        public Connector Sink
        {
            get { return sink; }
            set
            {
                if (sink != value)
                {
                    if (sink != null)
                    {
                        sink.PropertyChanged -= new PropertyChangedEventHandler(OnConnectorPositionChanged);
                        sink.Connections.Remove(this);
                    }

                    sink = value;

                    if (sink != null)
                    {
                        sink.Connections.Add(this);
                        sink.PropertyChanged += new PropertyChangedEventHandler(OnConnectorPositionChanged);
                    }
                    UpdatePathGeometry();
                }
            }
        }

        // connection path geometry
        private PathGeometry pathGeometry;
        public PathGeometry PathGeometry
        {
            get { return pathGeometry; }
            set
            {
                if (pathGeometry != value)
                {
                    pathGeometry = value;
                    UpdateAnchorPosition();
                    OnPropertyChanged("PathGeometry");
                }
            }
        }

        // between source connector position and the beginning 
        // of the path geometry we leave some space for visual reasons; 
        // so the anchor position source really marks the beginning 
        // of the path geometry on the source side
        private Point anchorPositionSource;
        public Point AnchorPositionSource
        {
            get { return anchorPositionSource; }
            set
            {
                if (anchorPositionSource != value)
                {
                    anchorPositionSource = value;
                    OnPropertyChanged("AnchorPositionSource");
                }
            }
        }

        // slope of the path at the anchor position
        // needed for the rotation angle of the arrow
        private double anchorAngleSource = 0;
        public double AnchorAngleSource
        {
            get { return anchorAngleSource; }
            set
            {
                if (anchorAngleSource != value)
                {
                    anchorAngleSource = value;
                    OnPropertyChanged("AnchorAngleSource");
                }
            }
        }

        // analogue to source side
        private Point anchorPositionSink;
        public Point AnchorPositionSink
        {
            get { return anchorPositionSink; }
            set
            {
                if (anchorPositionSink != value)
                {
                    anchorPositionSink = value;
                    OnPropertyChanged("AnchorPositionSink");
                }
            }
        }
        // analogue to source side
        private double anchorAngleSink = 0;
        public double AnchorAngleSink
        {
            get { return anchorAngleSink; }
            set
            {
                if (anchorAngleSink != value)
                {
                    anchorAngleSink = value;
                    OnPropertyChanged("AnchorAngleSink");
                }
            }
        }

        private ArrowSymbol sourceArrowSymbol = ArrowSymbol.None;
        public ArrowSymbol SourceArrowSymbol
        {
            get { return sourceArrowSymbol; }
            set
            {
                if (sourceArrowSymbol != value)
                {
                    sourceArrowSymbol = value;
                    OnPropertyChanged("SourceArrowSymbol");
                }
            }
        }

        public ArrowSymbol sinkArrowSymbol = ArrowSymbol.Arrow;
        public ArrowSymbol SinkArrowSymbol
        {
            get { return sinkArrowSymbol; }
            set
            {
                if (sinkArrowSymbol != value)
                {
                    sinkArrowSymbol = value;
                    OnPropertyChanged("SinkArrowSymbol");
                }
            }
        }

        // specifies a point at half path length
        private Point labelPosition;
        public Point LabelPosition
        {
            get { return labelPosition; }
            set
            {
                if (labelPosition != value)
                {
                    labelPosition = value;
                    OnPropertyChanged("LabelPosition");
                }
            }
        }

        // pattern of dashes and gaps that is used to outline the connection path
        private DoubleCollection strokeDashArray;
        public DoubleCollection StrokeDashArray
        {
            get
            {
                return strokeDashArray;
            }
            set
            {
                if (strokeDashArray != value)
                {
                    strokeDashArray = value;
                    OnPropertyChanged("StrokeDashArray");
                }
            }
        }
        // if connected, the ConnectionAdorner becomes visible
        private bool isSelected;
        public bool IsSelected
        {
            get { return isSelected; }
            set
            {
                if (isSelected != value)
                {
                    isSelected = value;
                    OnPropertyChanged("IsSelected");
                    if (isSelected)
                        ShowAdorner();
                    else
                        HideAdorner();
                }
            }
        }

        #endregion

        public Connection(Connector source, Connector sink)
        {
            this.Source = source;
            this.Sink = sink;
            base.Unloaded += new RoutedEventHandler(Connection_Unloaded);
        }

        protected override void OnMouseDown(System.Windows.Input.MouseButtonEventArgs e)
        {
            base.OnMouseDown(e);

            // usual selection business
            DesignerCanvas designer = VisualTreeHelper.GetParent(this) as DesignerCanvas;
            if (designer != null)
                if ((Keyboard.Modifiers & (ModifierKeys.Shift | ModifierKeys.Control)) != ModifierKeys.None)
                    if (this.IsSelected)
                    {
                        this.IsSelected = false;
                        designer.SelectedItems.Remove(this);
                    }
                    else
                    {
                        this.IsSelected = true;
                        designer.SelectedItems.Add(this);
                    }
                else if (!this.IsSelected)
                {
                    foreach (ISelectable item in designer.SelectedItems)
                        item.IsSelected = false;

                    designer.SelectedItems.Clear();
                    this.IsSelected = true;
                    designer.SelectedItems.Add(this);
                }
            e.Handled = false;
        }

        void OnConnectorPositionChanged(object sender, PropertyChangedEventArgs e)
        {
            // whenever the 'Position' property of the source or sink Connector 
            // changes we must update the connection path geometry
            if (e.PropertyName.Equals("Position"))
            {
                UpdatePathGeometry();
            }
        }

        private void UpdatePathGeometry()
        {
            if (Source != null && Sink != null)
            {
                PathGeometry geometry = new PathGeometry();
                List<Point> linePoints = PathFinder.GetConnectionLine(Source.GetInfo(), Sink.GetInfo(), true);
                if (linePoints.Count > 0)
                {
                    PathFigure figure = new PathFigure();
                    figure.StartPoint = linePoints[0];
                    linePoints.Remove(linePoints[0]);
                    figure.Segments.Add(new PolyLineSegment(linePoints, true));
                    geometry.Figures.Add(figure);

                    this.PathGeometry = geometry;
                }
            }
        }

        private void UpdateAnchorPosition()
        {
            Point pathStartPoint, pathTangentAtStartPoint;
            Point pathEndPoint, pathTangentAtEndPoint;
            Point pathMidPoint, pathTangentAtMidPoint;

            // the PathGeometry.GetPointAtFractionLength method gets the point and a tangent vector 
            // on PathGeometry at the specified fraction of its length
            this.PathGeometry.GetPointAtFractionLength(0, out pathStartPoint, out pathTangentAtStartPoint);
            this.PathGeometry.GetPointAtFractionLength(1, out pathEndPoint, out pathTangentAtEndPoint);
            this.PathGeometry.GetPointAtFractionLength(0.5, out pathMidPoint, out pathTangentAtMidPoint);

            // get angle from tangent vector
            this.AnchorAngleSource = Math.Atan2(-pathTangentAtStartPoint.Y, -pathTangentAtStartPoint.X) * (180 / Math.PI);
            this.AnchorAngleSink = Math.Atan2(pathTangentAtEndPoint.Y, pathTangentAtEndPoint.X) * (180 / Math.PI);

            // add some margin on source and sink side for visual reasons only
            pathStartPoint.Offset(-pathTangentAtStartPoint.X * 5, -pathTangentAtStartPoint.Y * 5);
            pathEndPoint.Offset(pathTangentAtEndPoint.X * 5, pathTangentAtEndPoint.Y * 5);

            this.AnchorPositionSource = pathStartPoint;
            this.AnchorPositionSink = pathEndPoint;
            this.LabelPosition = pathMidPoint;
        }

        private void ShowAdorner()
        {
            // the ConnectionAdorner is created once for each Connection
            if (this.connectionAdorner == null)
            {
                DesignerCanvas designer = VisualTreeHelper.GetParent(this) as DesignerCanvas;

                AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(designer);
                if (adornerLayer != null)
                {
                    this.connectionAdorner = new ConnectionAdorner(designer, this);
                    adornerLayer.Add(this.connectionAdorner);
                }
            }
            this.connectionAdorner.Visibility = Visibility.Visible;
        }

        internal void HideAdorner()
        {
            if (this.connectionAdorner != null)
                this.connectionAdorner.Visibility = Visibility.Collapsed;
        }

        void Connection_Unloaded(object sender, RoutedEventArgs e)
        {
            // do some housekeeping when Connection is unloaded

            // remove event handler
            source.PropertyChanged -= new PropertyChangedEventHandler(OnConnectorPositionChanged);
            sink.PropertyChanged -= new PropertyChangedEventHandler(OnConnectorPositionChanged);

            // remove adorner
            if (this.connectionAdorner != null)
            {
                DesignerCanvas designer = VisualTreeHelper.GetParent(this) as DesignerCanvas;

                AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(designer);
                if (adornerLayer != null)
                {
                    adornerLayer.Remove(this.connectionAdorner);
                    this.connectionAdorner = null;
                }
            }
        }

        #region INotifyPropertyChanged Members

        // we could use DependencyProperties as well to inform others of property changes
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string name)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(name));
            }
        }

        #endregion
    }

    public enum ArrowSymbol
    {
        None,
        Arrow,
        Diamond
    }
}

20、ConnectionAdorner.cs

csharp 复制代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls.Primitives;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows;

namespace DiagramDesigner
{
    public class ConnectionAdorner : Adorner
    {
        private DesignerCanvas designerCanvas;
        private Canvas adornerCanvas;
        private Connection connection;
        private PathGeometry pathGeometry;
        private Connector fixConnector, dragConnector;
        private Thumb sourceDragThumb, sinkDragThumb;
        private Pen drawingPen;

        private DesignerItem hitDesignerItem;
        private DesignerItem HitDesignerItem
        {
            get { return hitDesignerItem; }
            set
            {
                if (hitDesignerItem != value)
                {
                    if (hitDesignerItem != null)
                        hitDesignerItem.IsDragConnectionOver = false;

                    hitDesignerItem = value;

                    if (hitDesignerItem != null)
                        hitDesignerItem.IsDragConnectionOver = true;
                }
            }
        }

        private Connector hitConnector;
        private Connector HitConnector
        {
            get { return hitConnector; }
            set
            {
                if (hitConnector != value)
                {
                    hitConnector = value;
                }
            }
        }

        private VisualCollection visualChildren;
        protected override int VisualChildrenCount
        {
            get
            {
                return this.visualChildren.Count;
            }
        }

        protected override Visual GetVisualChild(int index)
        {
            return this.visualChildren[index];
        }

        public ConnectionAdorner(DesignerCanvas designer, Connection connection)
            : base(designer)
        {
            this.designerCanvas = designer;
            adornerCanvas = new Canvas();
            this.visualChildren = new VisualCollection(this);
            this.visualChildren.Add(adornerCanvas);

            this.connection = connection;
            this.connection.PropertyChanged += new PropertyChangedEventHandler(AnchorPositionChanged);

            InitializeDragThumbs();

            drawingPen = new Pen(Brushes.LightSlateGray, 1);
            drawingPen.LineJoin = PenLineJoin.Round;
        }

        private void InitializeDragThumbs()
        {
            Style dragThumbStyle = connection.FindResource("ConnectionAdornerThumbStyle") as Style;

            //source drag thumb
            sourceDragThumb = new Thumb();
            Canvas.SetLeft(sourceDragThumb, connection.AnchorPositionSource.X);
            Canvas.SetTop(sourceDragThumb, connection.AnchorPositionSource.Y);
            this.adornerCanvas.Children.Add(sourceDragThumb);
            if (dragThumbStyle != null)
                sourceDragThumb.Style = dragThumbStyle;

            sourceDragThumb.DragDelta += new DragDeltaEventHandler(thumbDragThumb_DragDelta);
            sourceDragThumb.DragStarted += new DragStartedEventHandler(thumbDragThumb_DragStarted);
            sourceDragThumb.DragCompleted += new DragCompletedEventHandler(thumbDragThumb_DragCompleted);

            // sink drag thumb
            sinkDragThumb = new Thumb();
            Canvas.SetLeft(sinkDragThumb, connection.AnchorPositionSink.X);
            Canvas.SetTop(sinkDragThumb, connection.AnchorPositionSink.Y);
            this.adornerCanvas.Children.Add(sinkDragThumb);
            if (dragThumbStyle != null)
                sinkDragThumb.Style = dragThumbStyle;

            sinkDragThumb.DragDelta += new DragDeltaEventHandler(thumbDragThumb_DragDelta);
            sinkDragThumb.DragStarted += new DragStartedEventHandler(thumbDragThumb_DragStarted);
            sinkDragThumb.DragCompleted += new DragCompletedEventHandler(thumbDragThumb_DragCompleted);
        }
        void AnchorPositionChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName.Equals("AnchorPositionSource"))
            {
                Canvas.SetLeft(sourceDragThumb, connection.AnchorPositionSource.X);
                Canvas.SetTop(sourceDragThumb, connection.AnchorPositionSource.Y);
            }

            if (e.PropertyName.Equals("AnchorPositionSink"))
            {
                Canvas.SetLeft(sinkDragThumb, connection.AnchorPositionSink.X);
                Canvas.SetTop(sinkDragThumb, connection.AnchorPositionSink.Y);
            }
        }

        void thumbDragThumb_DragCompleted(object sender, DragCompletedEventArgs e)
        {
            if (HitConnector != null)
            {
                if (connection != null)
                {
                    if (connection.Source == fixConnector)
                        connection.Sink = this.HitConnector;
                    else
                        connection.Source = this.HitConnector;
                }
            }

            this.HitDesignerItem = null;
            this.HitConnector = null;
            this.pathGeometry = null;
            this.connection.StrokeDashArray = null;
            this.InvalidateVisual();
        }

        void thumbDragThumb_DragStarted(object sender, DragStartedEventArgs e)
        {
            this.HitDesignerItem = null;
            this.HitConnector = null;
            this.pathGeometry = null;
            this.Cursor = Cursors.Cross;
            this.connection.StrokeDashArray = new DoubleCollection(new double[] { 1, 2 });

            if (sender == sourceDragThumb)
            {
                fixConnector = connection.Sink;
                dragConnector = connection.Source;
            }
            else if (sender == sinkDragThumb)
            {
                dragConnector = connection.Sink;
                fixConnector = connection.Source;
            }
        }

        void thumbDragThumb_DragDelta(object sender, DragDeltaEventArgs e)
        {
            Point currentPosition = Mouse.GetPosition(this);
            this.HitTesting(currentPosition);
            this.pathGeometry = UpdatePathGeometry(currentPosition);
            this.InvalidateVisual();
        }

        protected override void OnRender(DrawingContext dc)
        {
            base.OnRender(dc);
            dc.DrawGeometry(null, drawingPen, this.pathGeometry);
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            adornerCanvas.Arrange(new Rect(0, 0, this.designerCanvas.ActualWidth, this.designerCanvas.ActualHeight));
            return finalSize;
        }

        private PathGeometry UpdatePathGeometry(Point position)
        {
            PathGeometry geometry = new PathGeometry();

            ConnectorOrientation targetOrientation;
            if (HitConnector != null)
                targetOrientation = HitConnector.Orientation;
            else
                targetOrientation = dragConnector.Orientation;

            List<Point> linePoints = PathFinder.GetConnectionLine(fixConnector.GetInfo(), position, targetOrientation);

            if (linePoints.Count > 0)
            {
                PathFigure figure = new PathFigure();
                figure.StartPoint = linePoints[0];
                linePoints.Remove(linePoints[0]);
                figure.Segments.Add(new PolyLineSegment(linePoints, true));
                geometry.Figures.Add(figure);
            }

            return geometry;
        }

        private void HitTesting(Point hitPoint)
        {
            bool hitConnectorFlag = false;

            DependencyObject hitObject = designerCanvas.InputHitTest(hitPoint) as DependencyObject;
            while (hitObject != null &&
                   hitObject != fixConnector.ParentDesignerItem &&
                   hitObject.GetType() != typeof(DesignerCanvas))
            {
                if (hitObject is Connector)
                {
                    HitConnector = hitObject as Connector;
                    hitConnectorFlag = true;
                }

                if (hitObject is DesignerItem)
                {
                    HitDesignerItem = hitObject as DesignerItem;
                    if (!hitConnectorFlag)
                        HitConnector = null;
                    return;
                }
                hitObject = VisualTreeHelper.GetParent(hitObject);
            }

            HitConnector = null;
            HitDesignerItem = null;
        }
    }
}

21、Connector.cs

csharp 复制代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows;

namespace DiagramDesigner
{
    public class Connector : Control, INotifyPropertyChanged
    {
        // drag start point, relative to the DesignerCanvas
        private Point? dragStartPoint = null;

        public ConnectorOrientation Orientation { get; set; }

        // center position of this Connector relative to the DesignerCanvas
        private Point position;
        public Point Position
        {
            get { return position; }
            set
            {
                if (position != value)
                {
                    position = value;
                    OnPropertyChanged("Position");
                }
            }
        }

        // the DesignerItem this Connector belongs to;
        // retrieved from DataContext, which is set in the
        // DesignerItem template
        private DesignerItem parentDesignerItem;
        public DesignerItem ParentDesignerItem
        {
            get
            {
                if (parentDesignerItem == null)
                    parentDesignerItem = this.DataContext as DesignerItem;

                return parentDesignerItem;
            }
        }

        // keep track of connections that link to this connector
        private List<Connection> connections;
        public List<Connection> Connections
        {
            get
            {
                if (connections == null)
                    connections = new List<Connection>();
                return connections;
            }
        }

        public Connector()
        {
            // fired when layout changes
            base.LayoutUpdated += new EventHandler(Connector_LayoutUpdated);
        }

        // when the layout changes we update the position property
        void Connector_LayoutUpdated(object sender, EventArgs e)
        {
            DesignerCanvas designer = GetDesignerCanvas(this);
            if (designer != null)
            {
                //get centre position of this Connector relative to the DesignerCanvas
                this.Position = this.TransformToAncestor(designer).Transform(new Point(this.Width / 2, this.Height / 2));
            }
        }

        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            base.OnMouseLeftButtonDown(e);
            DesignerCanvas canvas = GetDesignerCanvas(this);
            if (canvas != null)
            {
                // position relative to DesignerCanvas
                this.dragStartPoint = new Point?(e.GetPosition(canvas));
                e.Handled = true;
            }
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);

            // if mouse button is not pressed we have no drag operation, ...
            if (e.LeftButton != MouseButtonState.Pressed)
                this.dragStartPoint = null;

            // but if mouse button is pressed and start point value is set we do have one
            if (this.dragStartPoint.HasValue)
            {
                // create connection adorner 
                DesignerCanvas canvas = GetDesignerCanvas(this);
                if (canvas != null)
                {
                    AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(canvas);
                    if (adornerLayer != null)
                    {
                        ConnectorAdorner adorner = new ConnectorAdorner(canvas, this);
                        if (adorner != null)
                        {
                            adornerLayer.Add(adorner);
                            e.Handled = true;
                        }
                    }
                }
            }
        }

        internal ConnectorInfo GetInfo()
        {
            ConnectorInfo info = new ConnectorInfo();
            info.DesignerItemLeft = DesignerCanvas.GetLeft(this.ParentDesignerItem);
            info.DesignerItemTop = DesignerCanvas.GetTop(this.ParentDesignerItem);
            info.DesignerItemSize = new Size(this.ParentDesignerItem.ActualWidth, this.ParentDesignerItem.ActualHeight);
            info.Orientation = this.Orientation;
            info.Position = this.Position;
            return info;
        }

        // iterate through visual tree to get parent DesignerCanvas
        private DesignerCanvas GetDesignerCanvas(DependencyObject element)
        {
            while (element != null && !(element is DesignerCanvas))
                element = VisualTreeHelper.GetParent(element);

            return element as DesignerCanvas;
        }

        #region INotifyPropertyChanged Members

        // we could use DependencyProperties as well to inform others of property changes
        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string name)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(name));
            }
        }

        #endregion
    }

    // provides compact info about a connector; used for the 
    // routing algorithm, instead of hand over a full fledged Connector
    internal struct ConnectorInfo
    {
        public double DesignerItemLeft { get; set; }
        public double DesignerItemTop { get; set; }
        public Size DesignerItemSize { get; set; }
        public Point Position { get; set; }
        public ConnectorOrientation Orientation { get; set; }
    }

    public enum ConnectorOrientation
    {
        None,
        Left,
        Top,
        Right,
        Bottom
    }
}

22、ConnectorAdorner.cs

csharp 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows;

namespace DiagramDesigner
{
    public class ConnectorAdorner : Adorner
    {
        private PathGeometry pathGeometry;
        private DesignerCanvas designerCanvas;
        private Connector sourceConnector;
        private Pen drawingPen;

        private DesignerItem hitDesignerItem;
        private DesignerItem HitDesignerItem
        {
            get { return hitDesignerItem; }
            set
            {
                if (hitDesignerItem != value)
                {
                    if (hitDesignerItem != null)
                        hitDesignerItem.IsDragConnectionOver = false;

                    hitDesignerItem = value;

                    if (hitDesignerItem != null)
                        hitDesignerItem.IsDragConnectionOver = true;
                }
            }
        }

        private Connector hitConnector;
        private Connector HitConnector
        {
            get { return hitConnector; }
            set
            {
                if (hitConnector != value)
                {
                    hitConnector = value;
                }
            }
        }

        public ConnectorAdorner(DesignerCanvas designer, Connector sourceConnector)
            : base(designer)
        {
            this.designerCanvas = designer;
            this.sourceConnector = sourceConnector;
            drawingPen = new Pen(Brushes.LightSlateGray, 1);
            drawingPen.LineJoin = PenLineJoin.Round;
            this.Cursor = Cursors.Cross;
        }

        protected override void OnMouseUp(MouseButtonEventArgs e)
        {
            if (HitConnector != null)
            {
                Connector sourceConnector = this.sourceConnector;
                Connector sinkConnector = this.HitConnector;
                Connection newConnection = new Connection(sourceConnector, sinkConnector);

                // connections are added with z-index of zero
                this.designerCanvas.Children.Insert(0, newConnection);
            }
            if (HitDesignerItem != null)
            {
                this.HitDesignerItem.IsDragConnectionOver = false;
            }

            if (this.IsMouseCaptured) this.ReleaseMouseCapture();

            AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(this.designerCanvas);
            if (adornerLayer != null)
            {
                adornerLayer.Remove(this);
            }
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                if (!this.IsMouseCaptured) this.CaptureMouse();
                HitTesting(e.GetPosition(this));
                this.pathGeometry = GetPathGeometry(e.GetPosition(this));
                this.InvalidateVisual();
            }
            else
            {
                if (this.IsMouseCaptured) this.ReleaseMouseCapture();
            }
        }

        protected override void OnRender(DrawingContext dc)
        {
            base.OnRender(dc);
            dc.DrawGeometry(null, drawingPen, this.pathGeometry);

            // without a background the OnMouseMove event would not be fired
            // Alternative: implement a Canvas as a child of this adorner, like
            // the ConnectionAdorner does.
            dc.DrawRectangle(Brushes.Transparent, null, new Rect(RenderSize));
        }

        private PathGeometry GetPathGeometry(Point position)
        {
            PathGeometry geometry = new PathGeometry();

            ConnectorOrientation targetOrientation;
            if (HitConnector != null)
                targetOrientation = HitConnector.Orientation;
            else
                targetOrientation = ConnectorOrientation.None;

            List<Point> pathPoints = PathFinder.GetConnectionLine(sourceConnector.GetInfo(), position, targetOrientation);

            if (pathPoints.Count > 0)
            {
                PathFigure figure = new PathFigure();
                figure.StartPoint = pathPoints[0];
                pathPoints.Remove(pathPoints[0]);
                figure.Segments.Add(new PolyLineSegment(pathPoints, true));
                geometry.Figures.Add(figure);
            }

            return geometry;
        }

        private void HitTesting(Point hitPoint)
        {
            bool hitConnectorFlag = false;

            DependencyObject hitObject = designerCanvas.InputHitTest(hitPoint) as DependencyObject;
            while (hitObject != null &&
                   hitObject != sourceConnector.ParentDesignerItem &&
                   hitObject.GetType() != typeof(DesignerCanvas))
            {
                if (hitObject is Connector)
                {
                    HitConnector = hitObject as Connector;
                    hitConnectorFlag = true;
                }

                if (hitObject is DesignerItem)
                {
                    HitDesignerItem = hitObject as DesignerItem;
                    if (!hitConnectorFlag)
                        HitConnector = null;
                    return;
                }
                hitObject = VisualTreeHelper.GetParent(hitObject);
            }

            HitConnector = null;
            HitDesignerItem = null;
        }
    }
}

23、DesignerCanvas.cs

csharp 复制代码
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows;
using System.Xml;
using System.Windows.Markup;

namespace DiagramDesigner
{
    public class DesignerCanvas : Canvas
    {
        // start point of the rubberband drag operation
        private Point? rubberbandSelectionStartPoint = null;

        // keep track of selected items 
        private List<ISelectable> selectedItems;
        public List<ISelectable> SelectedItems
        {
            get
            {
                if (selectedItems == null)
                    selectedItems = new List<ISelectable>();
                return selectedItems;
            }
            set
            {
                selectedItems = value;
            }
        }

        public DesignerCanvas()
        {
            this.AllowDrop = true;
        }

        protected override void OnMouseDown(MouseButtonEventArgs e)
        {
            base.OnMouseDown(e);
            if (e.Source == this)
            {
                // in case that this click is the start for a 
                // drag operation we cache the start point
                this.rubberbandSelectionStartPoint = new Point?(e.GetPosition(this));

                // if you click directly on the canvas all 
                // selected items are 'de-selected'
                foreach (ISelectable item in SelectedItems)
                    item.IsSelected = false;
                selectedItems.Clear();

                e.Handled = true;
            }
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);

            // if mouse button is not pressed we have no drag operation, ...
            if (e.LeftButton != MouseButtonState.Pressed)
                this.rubberbandSelectionStartPoint = null;

            // ... but if mouse button is pressed and start
            // point value is set we do have one
            if (this.rubberbandSelectionStartPoint.HasValue)
            {
                // create rubberband adorner
                AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(this);
                if (adornerLayer != null)
                {
                    RubberbandAdorner adorner = new RubberbandAdorner(this, rubberbandSelectionStartPoint);
                    if (adorner != null)
                    {
                        adornerLayer.Add(adorner);
                    }
                }
            }
            e.Handled = true;
        }

        protected override void OnDrop(DragEventArgs e)
        {
            base.OnDrop(e);
            DragObject dragObject = e.Data.GetData(typeof(DragObject)) as DragObject;
            if (dragObject != null && !String.IsNullOrEmpty(dragObject.Xaml))
            {
                DesignerItem newItem = null;
                Object content = XamlReader.Load(XmlReader.Create(new StringReader(dragObject.Xaml)));

                if (content != null)
                {
                    newItem = new DesignerItem();
                    newItem.Content = content;

                    Point position = e.GetPosition(this);

                    if (dragObject.DesiredSize.HasValue)
                    {
                        Size desiredSize = dragObject.DesiredSize.Value;
                        newItem.Width = desiredSize.Width;
                        newItem.Height = desiredSize.Height;

                        DesignerCanvas.SetLeft(newItem, Math.Max(0, position.X - newItem.Width / 2));
                        DesignerCanvas.SetTop(newItem, Math.Max(0, position.Y - newItem.Height / 2));
                    }
                    else
                    {
                        DesignerCanvas.SetLeft(newItem, Math.Max(0, position.X));
                        DesignerCanvas.SetTop(newItem, Math.Max(0, position.Y));
                    }

                    this.Children.Add(newItem);

                    //update selection
                    foreach (ISelectable item in this.SelectedItems)
                        item.IsSelected = false;
                    SelectedItems.Clear();
                    newItem.IsSelected = true;
                    this.SelectedItems.Add(newItem);
                }

                e.Handled = true;
            }
        }

        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 child
                element.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);
                }
            }
            // add margin 
            size.Width += 10;
            size.Height += 10;
            return size;
        }
    }
}

24、DesignerItem.cs

csharp 复制代码
using DiagramDesigner.Controls;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows;
using System.Xml.Linq;

namespace DiagramDesigner
{
    //These attributes identify the types of the named parts that are used for templating
    [TemplatePart(Name = "PART_DragThumb", Type = typeof(DragThumb))]
    [TemplatePart(Name = "PART_ResizeDecorator", Type = typeof(Control))]
    [TemplatePart(Name = "PART_ConnectorDecorator", Type = typeof(Control))]
    [TemplatePart(Name = "PART_ContentPresenter", Type = typeof(ContentPresenter))]
    public class DesignerItem : ContentControl, ISelectable
    {
        #region IsSelected Property是否选中

        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));

        #endregion

        #region DragThumbTemplate Property拖拽thumb模板

        // can be used to replace the default template for the DragThumb
        public static readonly DependencyProperty DragThumbTemplateProperty =
            DependencyProperty.RegisterAttached("DragThumbTemplate", typeof(ControlTemplate), typeof(DesignerItem));

        public static ControlTemplate GetDragThumbTemplate(UIElement element)
        {
            return (ControlTemplate)element.GetValue(DragThumbTemplateProperty);
        }

        public static void SetDragThumbTemplate(UIElement element, ControlTemplate value)
        {
            element.SetValue(DragThumbTemplateProperty, value);
        }

        #endregion

        #region ConnectorDecoratorTemplate Property连接器模板

        // can be used to replace the default template for the ConnectorDecorator
        public static readonly DependencyProperty ConnectorDecoratorTemplateProperty =
            DependencyProperty.RegisterAttached("ConnectorDecoratorTemplate", typeof(ControlTemplate), typeof(DesignerItem));

        public static ControlTemplate GetConnectorDecoratorTemplate(UIElement element)
        {
            return (ControlTemplate)element.GetValue(ConnectorDecoratorTemplateProperty);
        }

        public static void SetConnectorDecoratorTemplate(UIElement element, ControlTemplate value)
        {
            element.SetValue(ConnectorDecoratorTemplateProperty, value);
        }

        #endregion

        #region IsDragConnectionOver拖拽链接是否结束

        // while drag connection procedure is ongoing and the mouse moves over 
        // this item this value is true; if true the ConnectorDecorator is triggered
        // to be visible, see template -- 当拖动连接过程正在进行并且鼠标移动到此项目上时,此值为真;
        //如果为true,则会触发ConnectorDecorator使其可见,请参阅模板
        public bool IsDragConnectionOver
        {
            get { return (bool)GetValue(IsDragConnectionOverProperty); }
            set { SetValue(IsDragConnectionOverProperty, value); }
        }
        public static readonly DependencyProperty IsDragConnectionOverProperty =
            DependencyProperty.Register("IsDragConnectionOver",
                                         typeof(bool),
                                         typeof(DesignerItem),
                                         new FrameworkPropertyMetadata(false));

        #endregion

        static DesignerItem()
        {
            // set the key to reference the style for this control--设置键以引用此控件的样式
            FrameworkElement.DefaultStyleKeyProperty.OverrideMetadata(
                typeof(DesignerItem), new FrameworkPropertyMetadata(typeof(DesignerItem)));
        }

        public DesignerItem()
        {
            this.Loaded += new RoutedEventHandler(DesignerItem_Loaded);
        }

        protected override void OnPreviewMouseDown(MouseButtonEventArgs e)
        {
            base.OnPreviewMouseDown(e);
            DesignerCanvas designer = VisualTreeHelper.GetParent(this) as DesignerCanvas;

            // update selection
            if (designer != null)
                if ((Keyboard.Modifiers & (ModifierKeys.Shift | ModifierKeys.Control)) != ModifierKeys.None)
                    if (this.IsSelected)
                    {
                        this.IsSelected = false;
                        designer.SelectedItems.Remove(this);
                    }
                    else
                    {
                        this.IsSelected = true;
                        designer.SelectedItems.Add(this);
                    }
                else if (!this.IsSelected)
                {
                    foreach (ISelectable item in designer.SelectedItems)
                        item.IsSelected = false;

                    designer.SelectedItems.Clear();
                    this.IsSelected = true;
                    designer.SelectedItems.Add(this);
                }
            e.Handled = false;
        }

        void DesignerItem_Loaded(object sender, RoutedEventArgs e)
        {
            // if DragThumbTemplate and ConnectorDecoratorTemplate properties of this class
            // are set these templates are applied; 
            // Note: this method is only executed when the Loaded event is fired, so
            // setting DragThumbTemplate or ConnectorDecoratorTemplate properties after
            // will have no effect.
            if (base.Template != null)
            {
                ContentPresenter contentPresenter =
                    this.Template.FindName("PART_ContentPresenter", this) as ContentPresenter;
                if (contentPresenter != null)
                {
                    UIElement contentVisual = VisualTreeHelper.GetChild(contentPresenter, 0) as UIElement;
                    if (contentVisual != null)
                    {
                        DragThumb thumb = this.Template.FindName("PART_DragThumb", this) as DragThumb;
                        Control connectorDecorator = this.Template.FindName("PART_ConnectorDecorator", this) as Control;

                        if (thumb != null)
                        {
                            ControlTemplate template =
                                DesignerItem.GetDragThumbTemplate(contentVisual) as ControlTemplate;
                            if (template != null)
                                thumb.Template = template;
                        }


                        if (connectorDecorator != null)
                        {
                            ControlTemplate template =
                                DesignerItem.GetConnectorDecoratorTemplate(contentVisual) as ControlTemplate;
                            if (template != null)
                                connectorDecorator.Template = template;
                        }
                    }
                }
            }
        }
    }
}

25、ISelectable.cs

csharp 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DiagramDesigner
{
    // Common interface for items that can be selected
    // on the DesignerCanvas; used by DesignerItem and Connection
    public interface ISelectable
    {
        bool IsSelected { get; set; }
    }
}

26、PathFinder.cs

csharp 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows;

namespace DiagramDesigner
{
    // Note: I couldn't find a useful open source library that does
    // orthogonal routing so started to write something on my own.
    // Categorize this as a quick and dirty short term solution.
    // I will keep on searching.

    // Helper class to provide an orthogonal connection path
    internal class PathFinder
    {
        private const int margin = 20;

        internal static List<Point> GetConnectionLine(ConnectorInfo source, ConnectorInfo sink, bool showLastLine)
        {
            List<Point> linePoints = new List<Point>();

            Rect rectSource = GetRectWithMargin(source, margin);
            Rect rectSink = GetRectWithMargin(sink, margin);

            Point startPoint = GetOffsetPoint(source, rectSource);
            Point endPoint = GetOffsetPoint(sink, rectSink);

            linePoints.Add(startPoint);
            Point currentPoint = startPoint;

            if (!rectSink.Contains(currentPoint) && !rectSource.Contains(endPoint))
            {
                while (true)
                {
                    #region source node

                    if (IsPointVisible(currentPoint, endPoint, new Rect[] { rectSource, rectSink }))
                    {
                        linePoints.Add(endPoint);
                        currentPoint = endPoint;
                        break;
                    }

                    Point neighbour = GetNearestVisibleNeighborSink(currentPoint, endPoint, sink, rectSource, rectSink);
                    if (!double.IsNaN(neighbour.X))
                    {
                        linePoints.Add(neighbour);
                        linePoints.Add(endPoint);
                        currentPoint = endPoint;
                        break;
                    }

                    if (currentPoint == startPoint)
                    {
                        bool flag;
                        Point n = GetNearestNeighborSource(source, endPoint, rectSource, rectSink, out flag);
                        linePoints.Add(n);
                        currentPoint = n;

                        if (!IsRectVisible(currentPoint, rectSink, new Rect[] { rectSource }))
                        {
                            Point n1, n2;
                            GetOppositeCorners(source.Orientation, rectSource, out n1, out n2);
                            if (flag)
                            {
                                linePoints.Add(n1);
                                currentPoint = n1;
                            }
                            else
                            {
                                linePoints.Add(n2);
                                currentPoint = n2;
                            }
                            if (!IsRectVisible(currentPoint, rectSink, new Rect[] { rectSource }))
                            {
                                if (flag)
                                {
                                    linePoints.Add(n2);
                                    currentPoint = n2;
                                }
                                else
                                {
                                    linePoints.Add(n1);
                                    currentPoint = n1;
                                }
                            }
                        }
                    }
                    #endregion

                    #region sink node

                    else // from here on we jump to the sink node
                    {
                        Point n1, n2; // neighbour corner
                        Point s1, s2; // opposite corner
                        GetNeighborCorners(sink.Orientation, rectSink, out s1, out s2);
                        GetOppositeCorners(sink.Orientation, rectSink, out n1, out n2);

                        bool n1Visible = IsPointVisible(currentPoint, n1, new Rect[] { rectSource, rectSink });
                        bool n2Visible = IsPointVisible(currentPoint, n2, new Rect[] { rectSource, rectSink });

                        if (n1Visible && n2Visible)
                        {
                            if (rectSource.Contains(n1))
                            {
                                linePoints.Add(n2);
                                if (rectSource.Contains(s2))
                                {
                                    linePoints.Add(n1);
                                    linePoints.Add(s1);
                                }
                                else
                                    linePoints.Add(s2);

                                linePoints.Add(endPoint);
                                currentPoint = endPoint;
                                break;
                            }

                            if (rectSource.Contains(n2))
                            {
                                linePoints.Add(n1);
                                if (rectSource.Contains(s1))
                                {
                                    linePoints.Add(n2);
                                    linePoints.Add(s2);
                                }
                                else
                                    linePoints.Add(s1);

                                linePoints.Add(endPoint);
                                currentPoint = endPoint;
                                break;
                            }

                            if ((Distance(n1, endPoint) <= Distance(n2, endPoint)))
                            {
                                linePoints.Add(n1);
                                if (rectSource.Contains(s1))
                                {
                                    linePoints.Add(n2);
                                    linePoints.Add(s2);
                                }
                                else
                                    linePoints.Add(s1);
                                linePoints.Add(endPoint);
                                currentPoint = endPoint;
                                break;
                            }
                            else
                            {
                                linePoints.Add(n2);
                                if (rectSource.Contains(s2))
                                {
                                    linePoints.Add(n1);
                                    linePoints.Add(s1);
                                }
                                else
                                    linePoints.Add(s2);
                                linePoints.Add(endPoint);
                                currentPoint = endPoint;
                                break;
                            }
                        }
                        else if (n1Visible)
                        {
                            linePoints.Add(n1);
                            if (rectSource.Contains(s1))
                            {
                                linePoints.Add(n2);
                                linePoints.Add(s2);
                            }
                            else
                                linePoints.Add(s1);
                            linePoints.Add(endPoint);
                            currentPoint = endPoint;
                            break;
                        }
                        else
                        {
                            linePoints.Add(n2);
                            if (rectSource.Contains(s2))
                            {
                                linePoints.Add(n1);
                                linePoints.Add(s1);
                            }
                            else
                                linePoints.Add(s2);
                            linePoints.Add(endPoint);
                            currentPoint = endPoint;
                            break;
                        }
                    }
                    #endregion
                }
            }
            else
            {
                linePoints.Add(endPoint);
            }

            linePoints = OptimizeLinePoints(linePoints, new Rect[] { rectSource, rectSink }, source.Orientation, sink.Orientation);

            CheckPathEnd(source, sink, showLastLine, linePoints);
            return linePoints;
        }

        internal static List<Point> GetConnectionLine(ConnectorInfo source, Point sinkPoint, ConnectorOrientation preferredOrientation)
        {
            List<Point> linePoints = new List<Point>();
            Rect rectSource = GetRectWithMargin(source, 10);
            Point startPoint = GetOffsetPoint(source, rectSource);
            Point endPoint = sinkPoint;

            linePoints.Add(startPoint);
            Point currentPoint = startPoint;

            if (!rectSource.Contains(endPoint))
            {
                while (true)
                {
                    if (IsPointVisible(currentPoint, endPoint, new Rect[] { rectSource }))
                    {
                        linePoints.Add(endPoint);
                        break;
                    }

                    bool sideFlag;
                    Point n = GetNearestNeighborSource(source, endPoint, rectSource, out sideFlag);
                    linePoints.Add(n);
                    currentPoint = n;

                    if (IsPointVisible(currentPoint, endPoint, new Rect[] { rectSource }))
                    {
                        linePoints.Add(endPoint);
                        break;
                    }
                    else
                    {
                        Point n1, n2;
                        GetOppositeCorners(source.Orientation, rectSource, out n1, out n2);
                        if (sideFlag)
                            linePoints.Add(n1);
                        else
                            linePoints.Add(n2);

                        linePoints.Add(endPoint);
                        break;
                    }
                }
            }
            else
            {
                linePoints.Add(endPoint);
            }

            if (preferredOrientation != ConnectorOrientation.None)
                linePoints = OptimizeLinePoints(linePoints, new Rect[] { rectSource }, source.Orientation, preferredOrientation);
            else
                linePoints = OptimizeLinePoints(linePoints, new Rect[] { rectSource }, source.Orientation, GetOpositeOrientation(source.Orientation));

            return linePoints;
        }

        private static List<Point> OptimizeLinePoints(List<Point> linePoints, Rect[] rectangles, ConnectorOrientation sourceOrientation, ConnectorOrientation sinkOrientation)
        {
            List<Point> points = new List<Point>();
            int cut = 0;

            for (int i = 0; i < linePoints.Count; i++)
            {
                if (i >= cut)
                {
                    for (int k = linePoints.Count - 1; k > i; k--)
                    {
                        if (IsPointVisible(linePoints[i], linePoints[k], rectangles))
                        {
                            cut = k;
                            break;
                        }
                    }
                    points.Add(linePoints[i]);
                }
            }

            #region Line
            for (int j = 0; j < points.Count - 1; j++)
            {
                if (points[j].X != points[j + 1].X && points[j].Y != points[j + 1].Y)
                {
                    ConnectorOrientation orientationFrom;
                    ConnectorOrientation orientationTo;

                    // orientation from point
                    if (j == 0)
                        orientationFrom = sourceOrientation;
                    else
                        orientationFrom = GetOrientation(points[j], points[j - 1]);

                    // orientation to pint 
                    if (j == points.Count - 2)
                        orientationTo = sinkOrientation;
                    else
                        orientationTo = GetOrientation(points[j + 1], points[j + 2]);


                    if ((orientationFrom == ConnectorOrientation.Left || orientationFrom == ConnectorOrientation.Right) &&
                        (orientationTo == ConnectorOrientation.Left || orientationTo == ConnectorOrientation.Right))
                    {
                        double centerX = Math.Min(points[j].X, points[j + 1].X) + Math.Abs(points[j].X - points[j + 1].X) / 2;
                        points.Insert(j + 1, new Point(centerX, points[j].Y));
                        points.Insert(j + 2, new Point(centerX, points[j + 2].Y));
                        if (points.Count - 1 > j + 3)
                            points.RemoveAt(j + 3);
                        return points;
                    }

                    if ((orientationFrom == ConnectorOrientation.Top || orientationFrom == ConnectorOrientation.Bottom) &&
                        (orientationTo == ConnectorOrientation.Top || orientationTo == ConnectorOrientation.Bottom))
                    {
                        double centerY = Math.Min(points[j].Y, points[j + 1].Y) + Math.Abs(points[j].Y - points[j + 1].Y) / 2;
                        points.Insert(j + 1, new Point(points[j].X, centerY));
                        points.Insert(j + 2, new Point(points[j + 2].X, centerY));
                        if (points.Count - 1 > j + 3)
                            points.RemoveAt(j + 3);
                        return points;
                    }

                    if ((orientationFrom == ConnectorOrientation.Left || orientationFrom == ConnectorOrientation.Right) &&
                        (orientationTo == ConnectorOrientation.Top || orientationTo == ConnectorOrientation.Bottom))
                    {
                        points.Insert(j + 1, new Point(points[j + 1].X, points[j].Y));
                        return points;
                    }

                    if ((orientationFrom == ConnectorOrientation.Top || orientationFrom == ConnectorOrientation.Bottom) &&
                        (orientationTo == ConnectorOrientation.Left || orientationTo == ConnectorOrientation.Right))
                    {
                        points.Insert(j + 1, new Point(points[j].X, points[j + 1].Y));
                        return points;
                    }
                }
            }
            #endregion

            return points;
        }

        private static ConnectorOrientation GetOrientation(Point p1, Point p2)
        {
            if (p1.X == p2.X)
            {
                if (p1.Y >= p2.Y)
                    return ConnectorOrientation.Bottom;
                else
                    return ConnectorOrientation.Top;
            }
            else if (p1.Y == p2.Y)
            {
                if (p1.X >= p2.X)
                    return ConnectorOrientation.Right;
                else
                    return ConnectorOrientation.Left;
            }
            throw new Exception("Failed to retrieve orientation");
        }

        private static Orientation GetOrientation(ConnectorOrientation sourceOrientation)
        {
            switch (sourceOrientation)
            {
                case ConnectorOrientation.Left:
                    return Orientation.Horizontal;
                case ConnectorOrientation.Top:
                    return Orientation.Vertical;
                case ConnectorOrientation.Right:
                    return Orientation.Horizontal;
                case ConnectorOrientation.Bottom:
                    return Orientation.Vertical;
                default:
                    throw new Exception("Unknown ConnectorOrientation");
            }
        }

        private static Point GetNearestNeighborSource(ConnectorInfo source, Point endPoint, Rect rectSource, Rect rectSink, out bool flag)
        {
            Point n1, n2; // neighbors
            GetNeighborCorners(source.Orientation, rectSource, out n1, out n2);

            if (rectSink.Contains(n1))
            {
                flag = false;
                return n2;
            }

            if (rectSink.Contains(n2))
            {
                flag = true;
                return n1;
            }

            if ((Distance(n1, endPoint) <= Distance(n2, endPoint)))
            {
                flag = true;
                return n1;
            }
            else
            {
                flag = false;
                return n2;
            }
        }

        private static Point GetNearestNeighborSource(ConnectorInfo source, Point endPoint, Rect rectSource, out bool flag)
        {
            Point n1, n2; // neighbors
            GetNeighborCorners(source.Orientation, rectSource, out n1, out n2);

            if ((Distance(n1, endPoint) <= Distance(n2, endPoint)))
            {
                flag = true;
                return n1;
            }
            else
            {
                flag = false;
                return n2;
            }
        }

        private static Point GetNearestVisibleNeighborSink(Point currentPoint, Point endPoint, ConnectorInfo sink, Rect rectSource, Rect rectSink)
        {
            Point s1, s2; // neighbors on sink side
            GetNeighborCorners(sink.Orientation, rectSink, out s1, out s2);

            bool flag1 = IsPointVisible(currentPoint, s1, new Rect[] { rectSource, rectSink });
            bool flag2 = IsPointVisible(currentPoint, s2, new Rect[] { rectSource, rectSink });

            if (flag1) // s1 visible
            {
                if (flag2) // s1 and s2 visible
                {
                    if (rectSink.Contains(s1))
                        return s2;

                    if (rectSink.Contains(s2))
                        return s1;

                    if ((Distance(s1, endPoint) <= Distance(s2, endPoint)))
                        return s1;
                    else
                        return s2;

                }
                else
                {
                    return s1;
                }
            }
            else // s1 not visible
            {
                if (flag2) // only s2 visible
                {
                    return s2;
                }
                else // s1 and s2 not visible
                {
                    return new Point(double.NaN, double.NaN);
                }
            }
        }

        private static bool IsPointVisible(Point fromPoint, Point targetPoint, Rect[] rectangles)
        {
            foreach (Rect rect in rectangles)
            {
                if (RectangleIntersectsLine(rect, fromPoint, targetPoint))
                    return false;
            }
            return true;
        }

        private static bool IsRectVisible(Point fromPoint, Rect targetRect, Rect[] rectangles)
        {
            if (IsPointVisible(fromPoint, targetRect.TopLeft, rectangles))
                return true;

            if (IsPointVisible(fromPoint, targetRect.TopRight, rectangles))
                return true;

            if (IsPointVisible(fromPoint, targetRect.BottomLeft, rectangles))
                return true;

            if (IsPointVisible(fromPoint, targetRect.BottomRight, rectangles))
                return true;

            return false;
        }

        private static bool RectangleIntersectsLine(Rect rect, Point startPoint, Point endPoint)
        {
            rect.Inflate(-1, -1);
            return rect.IntersectsWith(new Rect(startPoint, endPoint));
        }

        private static void GetOppositeCorners(ConnectorOrientation orientation, Rect rect, out Point n1, out Point n2)
        {
            switch (orientation)
            {
                case ConnectorOrientation.Left:
                    n1 = rect.TopRight; n2 = rect.BottomRight;
                    break;
                case ConnectorOrientation.Top:
                    n1 = rect.BottomLeft; n2 = rect.BottomRight;
                    break;
                case ConnectorOrientation.Right:
                    n1 = rect.TopLeft; n2 = rect.BottomLeft;
                    break;
                case ConnectorOrientation.Bottom:
                    n1 = rect.TopLeft; n2 = rect.TopRight;
                    break;
                default:
                    throw new Exception("No opposite corners found!");
            }
        }

        private static void GetNeighborCorners(ConnectorOrientation orientation, Rect rect, out Point n1, out Point n2)
        {
            switch (orientation)
            {
                case ConnectorOrientation.Left:
                    n1 = rect.TopLeft; n2 = rect.BottomLeft;
                    break;
                case ConnectorOrientation.Top:
                    n1 = rect.TopLeft; n2 = rect.TopRight;
                    break;
                case ConnectorOrientation.Right:
                    n1 = rect.TopRight; n2 = rect.BottomRight;
                    break;
                case ConnectorOrientation.Bottom:
                    n1 = rect.BottomLeft; n2 = rect.BottomRight;
                    break;
                default:
                    throw new Exception("No neighour corners found!");
            }
        }

        private static double Distance(Point p1, Point p2)
        {
            return Point.Subtract(p1, p2).Length;
        }

        private static Rect GetRectWithMargin(ConnectorInfo connectorThumb, double margin)
        {
            Rect rect = new Rect(connectorThumb.DesignerItemLeft,
                                 connectorThumb.DesignerItemTop,
                                 connectorThumb.DesignerItemSize.Width,
                                 connectorThumb.DesignerItemSize.Height);

            rect.Inflate(margin, margin);

            return rect;
        }

        private static Point GetOffsetPoint(ConnectorInfo connector, Rect rect)
        {
            Point offsetPoint = new Point();

            switch (connector.Orientation)
            {
                case ConnectorOrientation.Left:
                    offsetPoint = new Point(rect.Left, connector.Position.Y);
                    break;
                case ConnectorOrientation.Top:
                    offsetPoint = new Point(connector.Position.X, rect.Top);
                    break;
                case ConnectorOrientation.Right:
                    offsetPoint = new Point(rect.Right, connector.Position.Y);
                    break;
                case ConnectorOrientation.Bottom:
                    offsetPoint = new Point(connector.Position.X, rect.Bottom);
                    break;
                default:
                    break;
            }

            return offsetPoint;
        }

        private static void CheckPathEnd(ConnectorInfo source, ConnectorInfo sink, bool showLastLine, List<Point> linePoints)
        {
            if (showLastLine)
            {
                Point startPoint = new Point(0, 0);
                Point endPoint = new Point(0, 0);
                double marginPath = 15;
                switch (source.Orientation)
                {
                    case ConnectorOrientation.Left:
                        startPoint = new Point(source.Position.X - marginPath, source.Position.Y);
                        break;
                    case ConnectorOrientation.Top:
                        startPoint = new Point(source.Position.X, source.Position.Y - marginPath);
                        break;
                    case ConnectorOrientation.Right:
                        startPoint = new Point(source.Position.X + marginPath, source.Position.Y);
                        break;
                    case ConnectorOrientation.Bottom:
                        startPoint = new Point(source.Position.X, source.Position.Y + marginPath);
                        break;
                    default:
                        break;
                }

                switch (sink.Orientation)
                {
                    case ConnectorOrientation.Left:
                        endPoint = new Point(sink.Position.X - marginPath, sink.Position.Y);
                        break;
                    case ConnectorOrientation.Top:
                        endPoint = new Point(sink.Position.X, sink.Position.Y - marginPath);
                        break;
                    case ConnectorOrientation.Right:
                        endPoint = new Point(sink.Position.X + marginPath, sink.Position.Y);
                        break;
                    case ConnectorOrientation.Bottom:
                        endPoint = new Point(sink.Position.X, sink.Position.Y + marginPath);
                        break;
                    default:
                        break;
                }
                linePoints.Insert(0, startPoint);
                linePoints.Add(endPoint);
            }
            else
            {
                linePoints.Insert(0, source.Position);
                linePoints.Add(sink.Position);
            }
        }

        private static ConnectorOrientation GetOpositeOrientation(ConnectorOrientation connectorOrientation)
        {
            switch (connectorOrientation)
            {
                case ConnectorOrientation.Left:
                    return ConnectorOrientation.Right;
                case ConnectorOrientation.Top:
                    return ConnectorOrientation.Bottom;
                case ConnectorOrientation.Right:
                    return ConnectorOrientation.Left;
                case ConnectorOrientation.Bottom:
                    return ConnectorOrientation.Top;
                default:
                    return ConnectorOrientation.Top;
            }
        }
    }
}

27、RubberbandAdorner.cs

csharp 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows;

namespace DiagramDesigner
{
    public class RubberbandAdorner : Adorner
    {
        private Point? startPoint;
        private Point? endPoint;
        private Pen rubberbandPen;

        private DesignerCanvas designerCanvas;

        public RubberbandAdorner(DesignerCanvas designerCanvas, Point? dragStartPoint)
            : base(designerCanvas)
        {
            this.designerCanvas = designerCanvas;
            this.startPoint = dragStartPoint;
            rubberbandPen = new Pen(Brushes.LightSlateGray, 1);
            rubberbandPen.DashStyle = new DashStyle(new double[] { 2 }, 1);
        }

        protected override void OnMouseMove(System.Windows.Input.MouseEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                if (!this.IsMouseCaptured)
                    this.CaptureMouse();

                endPoint = e.GetPosition(this);
                UpdateSelection();
                this.InvalidateVisual();
            }
            else
            {
                if (this.IsMouseCaptured) this.ReleaseMouseCapture();
            }

            e.Handled = true;
        }

        protected override void OnMouseUp(System.Windows.Input.MouseButtonEventArgs e)
        {
            // release mouse capture
            if (this.IsMouseCaptured) this.ReleaseMouseCapture();

            // remove this adorner from adorner layer
            AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(this.designerCanvas);
            if (adornerLayer != null)
                adornerLayer.Remove(this);

            e.Handled = true;
        }

        protected override void OnRender(DrawingContext dc)
        {
            base.OnRender(dc);

            // without a background the OnMouseMove event would not be fired !
            // Alternative: implement a Canvas as a child of this adorner, like
            // the ConnectionAdorner does.
            dc.DrawRectangle(Brushes.Transparent, null, new Rect(RenderSize));

            if (this.startPoint.HasValue && this.endPoint.HasValue)
                dc.DrawRectangle(Brushes.Transparent, rubberbandPen, new Rect(this.startPoint.Value, this.endPoint.Value));
        }

        private void UpdateSelection()
        {
            foreach (ISelectable item in designerCanvas.SelectedItems)
                item.IsSelected = false;
            designerCanvas.SelectedItems.Clear();

            Rect rubberBand = new Rect(startPoint.Value, endPoint.Value);
            foreach (Control item in designerCanvas.Children)
            {
                Rect itemRect = VisualTreeHelper.GetDescendantBounds(item);
                Rect itemBounds = item.TransformToAncestor(designerCanvas).TransformBounds(itemRect);

                if (rubberBand.Contains(itemBounds) && item is ISelectable)
                {
                    ISelectable selectableItem = item as ISelectable;
                    selectableItem.IsSelected = true;
                    designerCanvas.SelectedItems.Add(selectableItem);
                }
            }
        }
    }
}

28、Toolbox.cs

csharp 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows;

namespace DiagramDesigner
{
    // Implements ItemsControl for ToolboxItems    
    public class Toolbox : ItemsControl
    {
        // Defines the ItemHeight and ItemWidth properties of
        // the WrapPanel used for this Toolbox
        public Size ItemSize
        {
            get { return itemSize; }
            set { itemSize = value; }
        }
        private Size itemSize = new Size(50, 50);

        // Creates or identifies the element that is used to display the given item.        
        protected override DependencyObject GetContainerForItemOverride()
        {
            return new ToolboxItem();
        }

        // Determines if the specified item is (or is eligible to be) its own container.        
        protected override bool IsItemItsOwnContainerOverride(object item)
        {
            return (item is ToolboxItem);
        }
    }
}

29、ToolboxItem.cs

csharp 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows;
using System.Windows.Markup;

namespace DiagramDesigner
{
    // Represents a selectable item in the Toolbox--表示工具箱中的可选项/>.
    public class ToolboxItem : ContentControl
    {
        // caches the start point of the drag operation--缓存拖动操作的起点
        private Point? dragStartPoint = null;

        static ToolboxItem()
        {
            // set the key to reference the style for this control--设置键以引用此控件的样式
            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)
            {
                // XamlWriter.Save() has limitations in exactly what is serialized,--XamlWriter.Save()在具体序列化内容方面有限制
                // see SDK documentation; short term solution only;--见SDK文档;仅短期解决方案
                string xamlString = XamlWriter.Save(this.Content);
                DragObject dataObject = new DragObject();
                dataObject.Xaml = xamlString;

                WrapPanel panel = VisualTreeHelper.GetParent(this) as WrapPanel;
                if (panel != null)
                {
                    // desired size for DesignerCanvas is the stretched Toolbox item size--DesignerCanvas所需的大小是拉伸工具箱项的大小
                    double scale = 1.3;
                    dataObject.DesiredSize = new Size(panel.ItemWidth * scale, panel.ItemHeight * scale);
                }

                DragDrop.DoDragDrop(this, dataObject, DragDropEffects.Copy);

                e.Handled = true;
            }
        }
    }

    // Wraps info of the dragged object into a --将拖动对象的信息包装到类中
    public class DragObject
    {
        // Xaml string that represents the serialized content--Xaml string that represents the serialized content
        public String Xaml { get; set; }

        // Defines width and height of the DesignerItem--定义设计器项的宽度和高度
        // when this DragObject is dropped on the DesignerCanvas--当这个拖拽对象被放到设计画板上时
        public Size? DesiredSize { get; set; }
    }
}

30、MainWindow.xaml

csharp 复制代码
<Window x:Class="DiagramDesigner.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:s="clr-namespace:DiagramDesigner"
        xmlns:c="clr-namespace:DiagramDesigner.Controls"
        WindowStartupLocation="CenterScreen"
        SnapsToDevicePixels="True"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Assets/DesignerItem.xaml"/>
                <ResourceDictionary Source="Assets/ToolboxItem.xaml"/>
                <ResourceDictionary Source="Assets/Toolbox.xaml"/>
                <ResourceDictionary Source="Assets/Connection.xaml"/>
                <ResourceDictionary Source="Assets/Stencils/FlowChartStencils.xaml"/>
                <ResourceDictionary Source="Assets/Stencils/ShapeStencils.xaml"/>
                <ResourceDictionary Source="Assets/Stencils/SymbolStencils.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>
    <Grid>
        <Grid Margin="10">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="265"/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <!-- Toolbox -->
            <StackPanel Grid.Column="0" Margin="0,0,5,0">
                <Expander Header="Symbols" Content="{StaticResource SymbolStencils}" IsExpanded="True" />
                <Expander Header="Flow Chart" Content="{StaticResource FlowChartStencils}" IsExpanded="True"/>
                <Expander Header="Shapes" Content="{StaticResource ShapeStencils}" IsExpanded="False" />
            </StackPanel>
            <!-- GridSplitter -->
            <GridSplitter Focusable="False" Width="2" Background="{StaticResource LightBorderBrush}"
                    VerticalAlignment="Stretch" HorizontalAlignment="Right"/>
            <!-- Designer -->
            <GroupBox Header="Designer" Grid.Column="1" Margin="3,0,0,0" Background="Transparent">
                <ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
                    <s:DesignerCanvas Background="Transparent" Margin="10"/>
                </ScrollViewer>
            </GroupBox>
        </Grid>
    </Grid>
</Window>

31、MainWindow.xaml.cs

csharp 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace DiagramDesigner
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}
相关推荐
Crazy Struggle15 小时前
.NET 8.0 通用管理平台,支持模块化、WinForms 和 WPF
vue·wpf·winform·.net 8.0·通用权限管理
呆萌很2 天前
HCIP-HarmonyOS Application Developer 习题(二十)
华为·wpf·harmonyos
九鼎科技-Leo2 天前
什么是 WPF 中的转换器?如何自定义一个值转换器?
开发语言·windows·c#·.net·wpf
为风而战2 天前
Deepin 系统中安装Rider和Uno Platform
linux·运维·.net·wpf
△曉風殘月〆2 天前
WPF MVVM入门系列教程(三、数据绑定)
c#·wpf·mvvm
^@^lemon tea^@^2 天前
.NET6中WPF项目添加System.Windows.Forms引用
wpf·.net6·windows.forms引用
九鼎科技-Leo2 天前
在 WPF 中,绑定机制是如何工作的?WPF数据绑定机制解析
windows·c#·.net·wpf
就是有点傻3 天前
WPF自定义翻页控件
wpf
eggcode3 天前
使用ookii-dialogs-wpf在WPF选择文件夹时能输入路径
c#·wpf
code bean3 天前
【wpf】ResourceDictionary 字典资源的用法
windows·c#·wpf