1、文件架构
2、FlowChartStencils.xaml
<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
<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
<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
<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
<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
<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
<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
<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
<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
<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
<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
<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
<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
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
{
///
/// 自定义Thumb控件(表示可以由用户拖动的控件)
///
public class DragThumb : Thumb
{
public DragThumb()
{
base.DragDelta += new DragDeltaEventHandler(DragThumb_DragDelta);
}
///
/// 焦点和鼠标捕获处理
///
///
///
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
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
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
<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
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
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
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
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
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
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
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
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
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
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
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
<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
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
{
///
/// MainWindow.xaml 的交互逻辑
///
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}