mirror of
https://gitee.com/akwkevin/aistudio.-wpf.-diagram
synced 2026-03-03 00:00:57 +08:00
558 lines
18 KiB
C#
558 lines
18 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.ComponentModel;
|
|
using System.Linq;
|
|
using System.Windows;
|
|
using System.Windows.Input;
|
|
//using System.Windows;
|
|
using System.Windows.Media;
|
|
using AIStudio.Wpf.DiagramDesigner.Geometrys;
|
|
using AIStudio.Wpf.DiagramDesigner.Helpers;
|
|
|
|
namespace AIStudio.Wpf.DiagramDesigner
|
|
{
|
|
public class ConnectorViewModel : SelectableDesignerItemViewModelBase
|
|
{
|
|
public ConnectorViewModel(IDiagramViewModel parent, FullyCreatedConnectorInfo sourceConnectorInfo, ConnectorInfoBase sinkConnectorInfo, DrawMode drawMode, RouterMode routerMode)
|
|
{
|
|
Parent = parent;
|
|
PathMode = drawMode.ToString();
|
|
RouterMode = routerMode.ToString();
|
|
Init(sourceConnectorInfo, sinkConnectorInfo);
|
|
}
|
|
|
|
public ConnectorViewModel(IDiagramViewModel parent, FullyCreatedConnectorInfo sourceConnectorInfo, FullyCreatedConnectorInfo sinkConnectorInfo, ConnectionItem designer) : base(parent, designer)
|
|
{
|
|
PathMode = designer.PathMode;
|
|
RouterMode = designer.RouterMode;
|
|
Init(sourceConnectorInfo, sinkConnectorInfo);
|
|
}
|
|
|
|
public ConnectorViewModel(FullyCreatedConnectorInfo sourceConnectorInfo, ConnectorInfoBase sinkConnectorInfo, DrawMode drawMode, RouterMode routerMode) : this(null, sourceConnectorInfo, sinkConnectorInfo, drawMode, routerMode)
|
|
{
|
|
|
|
}
|
|
|
|
private void Init(FullyCreatedConnectorInfo sourceConnectorInfo, ConnectorInfoBase sinkConnectorInfo)
|
|
{
|
|
this.Parent = sourceConnectorInfo.DataItem.Parent;
|
|
|
|
var routetype = GlobalType.AllTypes.Where(p => typeof(IRouter).IsAssignableFrom(p)).FirstOrDefault(p => p.Name == RouterMode);
|
|
Router = routetype != null ? (System.Activator.CreateInstance(routetype) as IRouter) : new RouterNormal();
|
|
|
|
var pathGeneratortype = GlobalType.AllTypes.Where(p => typeof(IPathGenerator).IsAssignableFrom(p)).FirstOrDefault(p => p.Name == PathMode);
|
|
PathGenerator = pathGeneratortype != null ? (System.Activator.CreateInstance(pathGeneratortype) as IPathGenerator) : new ConnectingLineSmooth();
|
|
|
|
this.SourceConnectorInfo = sourceConnectorInfo;
|
|
this.SinkConnectorInfo = sinkConnectorInfo;
|
|
DeleteConnectionCommand = new SimpleCommand(DeleteConnection);
|
|
AddVertexCommand = new SimpleCommand(AddVertex);
|
|
|
|
if (Parent != null && Parent.ColorViewModel != null)
|
|
{
|
|
this.ColorViewModel = CopyHelper.Mapper(Parent.ColorViewModel);
|
|
}
|
|
if (sinkConnectorInfo is FullyCreatedConnectorInfo sink && sink.DataItem.ShowArrow == false)
|
|
{
|
|
this.ColorViewModel.RightArrowPathStyle = ArrowPathStyle.None;
|
|
}
|
|
}
|
|
|
|
public override SelectableDesignerItemBase ToXmlObject()
|
|
{
|
|
if (IsFullConnection)
|
|
{
|
|
ConnectionItem connection = new ConnectionItem(this);
|
|
|
|
return connection;
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public override Type ToXmlType()
|
|
{
|
|
return typeof(ConnectionItem);
|
|
}
|
|
|
|
private PointBase _sourceA;
|
|
public PointBase SourceA
|
|
{
|
|
get
|
|
{
|
|
return _sourceA;
|
|
}
|
|
set
|
|
{
|
|
if (SetProperty(ref _sourceA, value))
|
|
{
|
|
UpdateArea();
|
|
}
|
|
}
|
|
}
|
|
|
|
private PointBase _sourceB;
|
|
public PointBase SourceB
|
|
{
|
|
get
|
|
{
|
|
return _sourceB;
|
|
}
|
|
set
|
|
{
|
|
if (SetProperty(ref _sourceB, value))
|
|
{
|
|
UpdateArea();
|
|
}
|
|
}
|
|
}
|
|
|
|
private ObservableCollection<ConnectorPoint> _connectionPoints = new ObservableCollection<ConnectorPoint>();
|
|
public ObservableCollection<ConnectorPoint> ConnectionPoints
|
|
{
|
|
get
|
|
{
|
|
return _connectionPoints;
|
|
}
|
|
private set
|
|
{
|
|
if (_connectionPoints != null)
|
|
{
|
|
foreach (var connectionPoint in _connectionPoints)
|
|
{
|
|
connectionPoint.PropertyChanged -= new WeakINPCEventHandler(ConnectionPoint_PropertyChanged).Handler;
|
|
}
|
|
}
|
|
SetProperty(ref _connectionPoints, value);
|
|
if (_connectionPoints != null)
|
|
{
|
|
foreach (var connectionPoint in _connectionPoints)
|
|
{
|
|
connectionPoint.PropertyChanged += new WeakINPCEventHandler(ConnectionPoint_PropertyChanged).Handler;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private PointBase _startPoint;
|
|
public PointBase StartPoint
|
|
{
|
|
get
|
|
{
|
|
return _startPoint;
|
|
}
|
|
private set
|
|
{
|
|
SetProperty(ref _startPoint, value);
|
|
}
|
|
}
|
|
|
|
private PointBase _endPoint;
|
|
public PointBase EndPoint
|
|
{
|
|
get
|
|
{
|
|
return _endPoint;
|
|
}
|
|
private set
|
|
{
|
|
SetProperty(ref _endPoint, value);
|
|
}
|
|
}
|
|
|
|
private double _startAngle;
|
|
public double StartAngle
|
|
{
|
|
get
|
|
{
|
|
return _startAngle;
|
|
}
|
|
private set
|
|
{
|
|
SetProperty(ref _startAngle, value);
|
|
}
|
|
}
|
|
|
|
private double _endAngle;
|
|
public double EndAngle
|
|
{
|
|
get
|
|
{
|
|
return _endAngle;
|
|
}
|
|
private set
|
|
{
|
|
SetProperty(ref _endAngle, value);
|
|
}
|
|
}
|
|
|
|
private RectangleBase _area;
|
|
public RectangleBase Area
|
|
{
|
|
get
|
|
{
|
|
return _area;
|
|
}
|
|
private set
|
|
{
|
|
RectangleBase oldarea = _area;
|
|
if (SetProperty(ref _area, value))
|
|
{
|
|
UpdateConnectionPoints();
|
|
OutTextItemLocation(oldarea, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
public string PathMode
|
|
{
|
|
get; set;
|
|
}
|
|
|
|
public string RouterMode
|
|
{
|
|
get; set;
|
|
}
|
|
|
|
public IRouter Router
|
|
{
|
|
get; set;
|
|
}
|
|
|
|
public IPathGenerator PathGenerator
|
|
{
|
|
get; set;
|
|
}
|
|
|
|
private PathGeneratorResult _pathGeneratorResult;
|
|
public PathGeneratorResult PathGeneratorResult
|
|
{
|
|
get
|
|
{
|
|
return _pathGeneratorResult;
|
|
}
|
|
private set
|
|
{
|
|
SetProperty(ref _pathGeneratorResult, value);
|
|
}
|
|
}
|
|
|
|
private bool _shouldInsertAnchor;
|
|
public bool ShouldInsertAnchor
|
|
{
|
|
get
|
|
{
|
|
return _shouldInsertAnchor;
|
|
}
|
|
set
|
|
{
|
|
SetProperty(ref _shouldInsertAnchor, value);
|
|
}
|
|
}
|
|
|
|
//待完善这两处
|
|
public List<LinkVertexModel> Vertices { get; } = new List<LinkVertexModel>();
|
|
public List<LinkLabelModel> Labels { get; set; } = new List<LinkLabelModel>();
|
|
|
|
public virtual Dictionary<string, string> PropertiesSetting
|
|
{
|
|
get
|
|
{
|
|
return new Dictionary<string, string>()
|
|
{
|
|
{ "Text","文本" },
|
|
};
|
|
}
|
|
}
|
|
|
|
private FullyCreatedConnectorInfo _sourceConnectorInfo;
|
|
public FullyCreatedConnectorInfo SourceConnectorInfo
|
|
{
|
|
get
|
|
{
|
|
return _sourceConnectorInfo;
|
|
}
|
|
set
|
|
{
|
|
if (SetProperty(ref _sourceConnectorInfo, value))
|
|
{
|
|
SourceA = PointHelper.GetPointForConnector(_sourceConnectorInfo);
|
|
(_sourceConnectorInfo.DataItem as INotifyPropertyChanged).PropertyChanged += new WeakINPCEventHandler(ConnectorViewModel_PropertyChanged).Handler;
|
|
}
|
|
}
|
|
}
|
|
|
|
private ConnectorInfoBase _sinkConnectorInfo;
|
|
public ConnectorInfoBase SinkConnectorInfo
|
|
{
|
|
get
|
|
{
|
|
return _sinkConnectorInfo;
|
|
}
|
|
set
|
|
{
|
|
if (SetProperty(ref _sinkConnectorInfo, value))
|
|
{
|
|
SourceB = _sinkConnectorInfo.Position;
|
|
if (_sinkConnectorInfo is FullyCreatedConnectorInfo)
|
|
{
|
|
(((FullyCreatedConnectorInfo)_sinkConnectorInfo).DataItem as INotifyPropertyChanged).PropertyChanged += new WeakINPCEventHandler(ConnectorViewModel_PropertyChanged).Handler;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public FullyCreatedConnectorInfo SinkConnectorInfoFully
|
|
{
|
|
get
|
|
{
|
|
return SinkConnectorInfo as FullyCreatedConnectorInfo;
|
|
}
|
|
}
|
|
|
|
public PartCreatedConnectionInfo SinkConnectorInfoPart
|
|
{
|
|
get
|
|
{
|
|
return SinkConnectorInfo as PartCreatedConnectionInfo;
|
|
}
|
|
}
|
|
|
|
public PointBase OnGoingPosition
|
|
{
|
|
get
|
|
{
|
|
return SinkConnectorInfoPart?.MiddlePosition ?? PointBase.Zero;
|
|
}
|
|
}
|
|
|
|
public bool IsFullConnection
|
|
{
|
|
get
|
|
{
|
|
return SinkConnectorInfoFully != null;
|
|
}
|
|
}
|
|
|
|
public bool IsPortless => SourceConnectorInfo?.DataItem?.Connectors?.Count() == 0;
|
|
|
|
private void UpdateArea()
|
|
{
|
|
Area = new RectangleBase(SourceA, SourceB);
|
|
}
|
|
|
|
private void UpdateConnectionPoints()
|
|
{
|
|
if (SourceConnectorInfo == null || SinkConnectorInfo == null)
|
|
return;
|
|
|
|
var route = Router.Get(Parent, this);
|
|
|
|
(var source, var target) = FindConnectionPoints(route);
|
|
if (source == null || target == null)
|
|
return;
|
|
|
|
PathGeneratorResult = PathGenerator.Get(Parent, this, route, source.Value, target.Value);
|
|
StartPoint = PathGeneratorResult.SourceMarkerPosition;
|
|
EndPoint = PathGeneratorResult.TargetMarkerPosition;
|
|
StartAngle = PathGeneratorResult.SourceMarkerAngle;
|
|
EndAngle = PathGeneratorResult.TargetMarkerAngle;
|
|
}
|
|
|
|
private void ConnectorViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
{
|
|
switch (e.PropertyName)
|
|
{
|
|
case "ItemHeight":
|
|
case "ItemWidth":
|
|
case "Left":
|
|
case "Top":
|
|
SourceA = PointHelper.GetPointForConnector(this.SourceConnectorInfo);
|
|
if (IsFullConnection)
|
|
{
|
|
SourceB = PointHelper.GetPointForConnector(this.SinkConnectorInfoFully);
|
|
}
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
private void ConnectionPoint_PropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
{
|
|
switch (e.PropertyName)
|
|
{
|
|
case "Left":
|
|
case "Top":
|
|
RaisePropertyChanged(nameof(ConnectionPoints));
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
public SimpleCommand DeleteConnectionCommand
|
|
{
|
|
get; set;
|
|
}
|
|
|
|
public SimpleCommand AddVertexCommand
|
|
{
|
|
get; set;
|
|
}
|
|
|
|
private void DeleteConnection(object args)
|
|
{
|
|
if (this.Parent is IDiagramViewModel)
|
|
{
|
|
var diagramVM = this.Parent as IDiagramViewModel;
|
|
diagramVM.RemoveItemCommand.Execute(this);
|
|
}
|
|
}
|
|
|
|
private void AddVertex(object parameter)
|
|
{
|
|
MouseButtonEventArgs mosueArg = ((EventToCommandArgs)parameter).EventArgs as MouseButtonEventArgs;
|
|
var position = mosueArg.GetPosition(((EventToCommandArgs)parameter).Sender as IInputElement);
|
|
|
|
ConnectionPoints.Add(new ConnectorPoint(position.X, position.Y));
|
|
|
|
if (!((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control))
|
|
{
|
|
ShouldInsertAnchor = false;
|
|
}
|
|
}
|
|
|
|
protected override void ExecuteEditCommand(object param)
|
|
{
|
|
if (this.OutTextItem != null) return;
|
|
AddText("");
|
|
}
|
|
|
|
public void AddText(string text)
|
|
{
|
|
if (this.Parent is IDiagramViewModel)
|
|
{
|
|
var diagramVM = this.Parent as IDiagramViewModel;
|
|
|
|
TextDesignerItemViewModel textitem = new TextDesignerItemViewModel();
|
|
textitem.ItemWidth = Double.NaN;
|
|
textitem.ItemHeight = double.NaN;
|
|
//if (this.PathMode == DrawMode.ConnectingLineBoundary.ToString())
|
|
//{
|
|
// var mid = (int)(ConnectionPoints.Count / 2);
|
|
// var p = PathGenerators.SegmentMiddlePoint(ConnectionPoints[mid - 1], ConnectionPoints[mid]);
|
|
// textitem.Left = this.Area.Left + p.X + 2;
|
|
// textitem.Top = this.Area.Top + p.Y - 15;
|
|
//}
|
|
//else
|
|
{
|
|
textitem.Left = this.Area.Left + this.Area.Width / 2 - 16;
|
|
textitem.Top = this.Area.Top + this.Area.Height / 2 - 5;
|
|
}
|
|
textitem.Watermark = null;
|
|
textitem.ZIndex = diagramVM.Items.Count;
|
|
textitem.ParentId = this.Id;
|
|
textitem.ParentItem = this;
|
|
textitem.ColorViewModel.FillColor = new ColorObject() { Color = Colors.White };
|
|
textitem.Text = text;
|
|
|
|
diagramVM.DirectAddItemCommand.Execute(textitem);
|
|
|
|
this.OutTextItem = textitem;
|
|
}
|
|
}
|
|
|
|
public void OutTextItemLocation(RectangleBase oldArea, RectangleBase newArea)
|
|
{
|
|
if (this.OutTextItem is TextDesignerItemViewModel text)
|
|
{
|
|
var oldpoint = new PointBase(oldArea.Left + oldArea.Width / 2, oldArea.Top + oldArea.Height / 2);
|
|
var newpoint = new PointBase(newArea.Left + newArea.Width / 2, newArea.Top + newArea.Height / 2);
|
|
|
|
text.Left = text.Left + newpoint.X - oldpoint.X;
|
|
text.Top = text.Top + newpoint.Y - oldpoint.Y;
|
|
}
|
|
}
|
|
|
|
private (PointBase? source, PointBase? target) FindConnectionPoints(PointBase[] route)
|
|
{
|
|
if (IsPortless) // Portless
|
|
{
|
|
if (SourceConnectorInfo.DataItem == null || (IsFullConnection && SinkConnectorInfoFully.DataItem == null))
|
|
return (null, null);
|
|
|
|
var sourceCenter = SourceConnectorInfo.DataItem.GetBounds().Center;
|
|
var targetCenter = SinkConnectorInfoFully?.DataItem?.GetBounds().Center ?? OnGoingPosition;
|
|
var firstPt = route.Length > 0 ? route[0] : targetCenter;
|
|
var secondPt = route.Length > 0 ? route[0] : sourceCenter;
|
|
var sourceLine = new LineBase(firstPt, sourceCenter);
|
|
var targetLine = new LineBase(secondPt, targetCenter);
|
|
var sourceIntersections = SourceConnectorInfo.DataItem.GetShape().GetIntersectionsWithLine(sourceLine);
|
|
var targetIntersections = SinkConnectorInfoFully.DataItem.GetShape()?.GetIntersectionsWithLine(targetLine) ?? new PointBase[] { OnGoingPosition };
|
|
var sourceIntersection = GetClosestPointTo(sourceIntersections, firstPt);
|
|
var targetIntersection = GetClosestPointTo(targetIntersections, secondPt);
|
|
return (sourceIntersection ?? sourceCenter, targetIntersection ?? targetCenter);
|
|
}
|
|
else
|
|
{
|
|
var source = SourceConnectorInfo.MiddlePosition;//GetPortPositionBasedOnAlignment(SourceConnectorInfo, ColorViewModel.LeftArrowSizeStyle);
|
|
var target = SinkConnectorInfo.MiddlePosition;// GetPortPositionBasedOnAlignment(SinkConnectorInfoFully, ColorViewModel.RightArrowSizeStyle);
|
|
return (source, target);
|
|
}
|
|
}
|
|
|
|
private PointBase? GetPortPositionBasedOnAlignment(ConnectorInfoBase port, ArrowSizeStyle marker)
|
|
{
|
|
if (port == null)
|
|
return null;
|
|
|
|
if (marker == null)
|
|
return port.MiddlePosition;
|
|
|
|
var pt = port.Position;
|
|
switch (port.Orientation)
|
|
{
|
|
case ConnectorOrientation.Top:
|
|
return new PointBase(pt.X + port.ConnectorWidth / 2, pt.Y);
|
|
case ConnectorOrientation.TopRight:
|
|
return new PointBase(pt.X + port.ConnectorWidth, pt.Y);
|
|
case ConnectorOrientation.Right:
|
|
return new PointBase(pt.X + port.ConnectorWidth, pt.Y + port.ConnectorHeight / 2);
|
|
case ConnectorOrientation.BottomRight:
|
|
return new PointBase(pt.X + port.ConnectorWidth, pt.Y + port.ConnectorHeight);
|
|
case ConnectorOrientation.Bottom:
|
|
return new PointBase(pt.X + port.ConnectorWidth / 2, pt.Y + port.ConnectorHeight);
|
|
case ConnectorOrientation.BottomLeft:
|
|
return new PointBase(pt.X, pt.Y + port.ConnectorHeight);
|
|
case ConnectorOrientation.Left:
|
|
return new PointBase(pt.X, pt.Y + port.ConnectorHeight / 2);
|
|
default:
|
|
return pt;
|
|
}
|
|
}
|
|
|
|
private PointBase? GetClosestPointTo(IEnumerable<PointBase> points, PointBase point)
|
|
{
|
|
var minDist = double.MaxValue;
|
|
PointBase? minPoint = null;
|
|
|
|
foreach (var pt in points)
|
|
{
|
|
var dist = pt.DistanceTo(point);
|
|
if (dist < minDist)
|
|
{
|
|
minDist = dist;
|
|
minPoint = pt;
|
|
}
|
|
}
|
|
|
|
return minPoint;
|
|
}
|
|
|
|
|
|
|
|
}
|
|
}
|