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.Media; using AIStudio.Wpf.DiagramDesigner.Geometrys; using AIStudio.Wpf.DiagramDesigner.Helpers; using SvgPathProperties; namespace AIStudio.Wpf.DiagramDesigner { /// /// DefaultLink /// public class ConnectionViewModel : SelectableDesignerItemViewModelBase { public ConnectionViewModel() { } public ConnectionViewModel(ConnectorInfoBase sourceConnectorInfo, ConnectorInfoBase sinkConnectorInfo, DrawMode drawMode = DrawMode.ConnectingLineSmooth, RouterMode routerMode = AIStudio.Wpf.DiagramDesigner.RouterMode.RouterNormal) : this(null, sourceConnectorInfo, sinkConnectorInfo, drawMode, routerMode) { } public ConnectionViewModel(IDiagramViewModel root, ConnectorInfoBase sourceConnectorInfo, ConnectorInfoBase sinkConnectorInfo, DrawMode drawMode = DrawMode.ConnectingLineSmooth, RouterMode routerMode = AIStudio.Wpf.DiagramDesigner.RouterMode.RouterNormal) : base(root) { _pathMode = drawMode.ToString(); _routerMode = routerMode.ToString(); Init(root, sourceConnectorInfo, sinkConnectorInfo); } public ConnectionViewModel(IDiagramViewModel root, ConnectorInfoBase sourceConnectorInfo, ConnectorInfoBase sinkConnectorInfo, ConnectionItem designer) : base(root, designer) { _pathMode = designer.PathMode; _routerMode = designer.RouterMode; Init(root, sourceConnectorInfo, sinkConnectorInfo); } public override SelectableItemBase GetSerializableObject() { if (IsFullConnection) { ConnectionItem connection = new ConnectionItem(this); return connection; } else//Todo,半连接线也可序列化 { return null; } } protected virtual void Init(IDiagramViewModel root, ConnectorInfoBase sourceConnectorInfo, ConnectorInfoBase sinkConnectorInfo) { IsLoaded = false; this.Root = root ?? sourceConnectorInfo.Root; if (sinkConnectorInfo is FullyCreatedConnectorInfo sink && sink.DataItem.ShowArrow == false) { this.ShapeViewModel.SinkMarker = new SharpPath("", 10, 10, PathStyle.None, SizeStyle.Middle); } var routetype = TypeHelper.GetType(RouterMode); Router = routetype != null ? (System.Activator.CreateInstance(routetype) as IRouter) : new RouterNormal(); var pathGeneratortype = TypeHelper.GetType(PathMode); PathGenerator = pathGeneratortype != null ? (System.Activator.CreateInstance(pathGeneratortype) as IPathGenerator) : new ConnectingLineSmooth(); IsLoaded = true; this.SourceConnectorInfo = sourceConnectorInfo; this.SinkConnectorInfo = sinkConnectorInfo; DeleteConnectionCommand = new SimpleCommand(Command_Enable, DeleteConnection); AddVertexCommand = new SimpleCommand(Command_Enable, AddVertex); AddLabelCommand = new SimpleCommand(Command_Enable, para => AddLabel()); } protected override void LoadDesignerItemViewModel(SelectableItemBase designerbase) { base.LoadDesignerItemViewModel(designerbase); if (designerbase is ConnectionItem designer) { Vertices = new ObservableCollection(designer.Vertices.Select(p => { ConnectorVertexModel temp = new ConnectorVertexModel(this.Root, this, designer) { ConnectorVertexType = p.ConnectorVertexType }; return temp; })); Labels = new ObservableCollection(designer.Labels.Select(p => { ConnectorLabelModel temp = new ConnectorLabelModel(this.Root, this, designer) { Distance = p.Distance, Offset = p.Offset }; return temp; })); } } #region 属性 private string _text; [Browsable(true)] [CanDo] public override string Text { get { var text = _text; if (Labels?.Count > 0) { text = Labels[0].Text; } if (FontViewModel.FontCase == FontCase.Upper) { return text?.ToUpper(); } else if (FontViewModel.FontCase == FontCase.Lower) { return text?.ToLower(); } else { return text; } } set { if (SetProperty(ref _text, value)) { if (!string.IsNullOrEmpty(_text)) { if (Labels?.Count > 0) { Labels[0].Text = _text; } else { AddLabel(_text); } } else { ClearLabel(); } } } } private PointBase _sourceA; public PointBase SourceA { get { return _sourceA; } set { SetProperty(ref _sourceA, value); } } private PointBase _sourceB; public PointBase SourceB { get { return _sourceB; } set { SetProperty(ref _sourceB, value); } } 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 _startRectangle; public RectangleBase StartRectangle { get { return _startRectangle; } private set { SetProperty(ref _startRectangle, value); } } private RectangleBase _endRectangle; public RectangleBase EndRectangle { get { return _endRectangle; } private set { SetProperty(ref _endRectangle, value); } } private Thickness _dragThumbMargin; public Thickness DragThumbMargin { get { return _dragThumbMargin; } private set { SetProperty(ref _dragThumbMargin, value); } } private RectangleBase _area; public RectangleBase Area { get { return _area; } private set { SetProperty(ref _area, value); } } private string _pathMode; public string PathMode { get { return _pathMode; } set { SetProperty(ref _pathMode, value); } } private string _routerMode; public string RouterMode { get { return _routerMode; } set { SetProperty(ref _routerMode, value); } } 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); } } //待完善这两处 private ObservableCollection _vertices = new ObservableCollection(); public ObservableCollection Vertices { get { return _vertices; } private set { SetProperty(ref _vertices, value); } } private ObservableCollection _labels = new ObservableCollection(); public ObservableCollection Labels { get { return _labels; } private set { SetProperty(ref _labels, value); } } public virtual Dictionary PropertiesSetting { get { return new Dictionary() { { "Text","文本" }, }; } } private ConnectorInfoBase _sourceConnectorInfo; public ConnectorInfoBase SourceConnectorInfo { get { return _sourceConnectorInfo; } set { SetProperty(ref _sourceConnectorInfo, value); } } public FullyCreatedConnectorInfo SourceConnectorInfoFully { get { return SourceConnectorInfo as FullyCreatedConnectorInfo; } } public PartCreatedConnectorInfo SourceConnectorInfoPart { get { return SourceConnectorInfo as PartCreatedConnectorInfo; } } private ConnectorInfoBase _sinkConnectorInfo; public ConnectorInfoBase SinkConnectorInfo { get { return _sinkConnectorInfo; } set { SetProperty(ref _sinkConnectorInfo, value); } } public FullyCreatedConnectorInfo SinkConnectorInfoFully { get { return SinkConnectorInfo as FullyCreatedConnectorInfo; } } public PartCreatedConnectorInfo SinkConnectorInfoPart { get { return SinkConnectorInfo as PartCreatedConnectorInfo; } } public PointBase OnGoingPosition { get { return SinkConnectorInfoPart?.MiddlePosition ?? PointBase.Zero; } } public bool IsFullConnection { get { return SourceConnectorInfoFully != null && SinkConnectorInfoFully != null; } } public double SmoothMargin { get; set; } = 125; public double SmoothAutoSlope { get; set; } = 1; public double OrthogonalShapeMargin { get; set; } = 10; public double OrthogonalGlobalBoundsMargin { get; set; } = 50; public bool IsPortless => SourceConnectorInfoFully?.IsPortless == true || SinkConnectorInfoFully?.IsPortless == true; #endregion #region 方法 public ICommand DeleteConnectionCommand { get; set; } public ICommand AddVertexCommand { get; set; } public ICommand AddLabelCommand { get; set; } #endregion protected override void Item_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (IsLoaded == false || IsInternalChanged == true) return; if (sender is ConnectionViewModel) { switch (e.PropertyName) { case nameof(SourceA): case nameof(SourceB): UpdateArea(); break; case nameof(Area): UpdatePathGeneratorResult(); break; case nameof(Vertices): foreach (var vertice in Vertices) { vertice.PropertyChanged += new WeakINPCEventHandler(Item_PropertyChanged).Handler; } break; case nameof(Labels): foreach (var label in Labels) { label.PropertyChanged += new WeakINPCEventHandler(Item_PropertyChanged).Handler; } break; case nameof(SourceConnectorInfo): if (SourceConnectorInfo != null) { SourceA = SourceConnectorInfo.Position; if (SourceConnectorInfoFully != null) { SourceConnectorInfoFully.DataItem.PropertyChanged += new WeakINPCEventHandler(Item_PropertyChanged).Handler; } } break; case nameof(SinkConnectorInfo): if (SinkConnectorInfo != null) { SourceB = SinkConnectorInfo.Position; if (SinkConnectorInfoFully != null) { SinkConnectorInfoFully.DataItem.PropertyChanged += new WeakINPCEventHandler(Item_PropertyChanged).Handler; } } break; case nameof(IsSelected): if (IsSelected == false) { Labels.FirstOrDefault()?.AddToSelection(false, true); } break; case nameof(RouterMode): var routetype = TypeHelper.GetType(RouterMode); Router = routetype != null ? (System.Activator.CreateInstance(routetype) as IRouter) : new RouterNormal(); UpdatePathGeneratorResult(); break; case nameof(PathMode): var pathGeneratortype = TypeHelper.GetType(PathMode); PathGenerator = pathGeneratortype != null ? (System.Activator.CreateInstance(pathGeneratortype) as IPathGenerator) : new ConnectingLineSmooth(); UpdatePathGeneratorResult(); break; } } else if (sender is ShapeViewModel) { if (e.PropertyName == nameof(ShapeViewModel.SourceMarker) || e.PropertyName == nameof(ShapeViewModel.SinkMarker)) { UpdatePathGeneratorResult(); } } else if (sender is DesignerItemViewModelBase) { switch (e.PropertyName) { case nameof(DesignerItemViewModelBase.ItemHeight): case nameof(DesignerItemViewModelBase.ItemWidth): case nameof(DesignerItemViewModelBase.Left): case nameof(DesignerItemViewModelBase.Top): if (IsFullConnection) { //减少触发一次画线 SourceA = PointHelper.GetPointForConnector(this.SourceConnectorInfoFully); SourceB = PointHelper.GetPointForConnector(this.SinkConnectorInfoFully); } else { SourceA = SourceConnectorInfo.MiddlePosition; } break; } } else if (sender is ConnectorVertexModel connectorVertexModel) { switch (e.PropertyName) { case nameof(ConnectorPointModel.X): case nameof(ConnectorPointModel.Y): if (connectorVertexModel.ConnectorVertexType == ConnectorVertexType.None) { UpdatePathGeneratorResult(); } else if (connectorVertexModel.ConnectorVertexType == ConnectorVertexType.Start) { var nearPort = Root.FindNearPortToAttachTo(this, ConnectorVertexType.Start); SetSourcePort(new PartCreatedConnectorInfo(connectorVertexModel.Position.X, connectorVertexModel.Position.Y)); } else if (connectorVertexModel.ConnectorVertexType == ConnectorVertexType.End) { var nearPort = Root.FindNearPortToAttachTo(this, ConnectorVertexType.End); SetSinkPort(new PartCreatedConnectorInfo(connectorVertexModel.Position.X, connectorVertexModel.Position.Y)); } break; case nameof(ConnectorPointModel.DragStart): if (connectorVertexModel.DragStart == false) { if (connectorVertexModel.ConnectorVertexType == ConnectorVertexType.Start) { var nearPort = Root?.FindNearPortToAttachTo(this, ConnectorVertexType.Start); if (nearPort != null) { SetSourcePort(nearPort); } } else if (connectorVertexModel.ConnectorVertexType == ConnectorVertexType.End) { var nearPort = Root?.FindNearPortToAttachTo(this, ConnectorVertexType.End); if (nearPort != null) { SetSinkPort(nearPort); } } Root?.ClearAttachTo(); } break; } } else if (sender is ConnectorLabelModel linkLabelModel) { switch (e.PropertyName) { case nameof(ConnectorPointModel.X): { if (e is ValuePropertyChangedEventArgs valuePropertyChangedEventArgs) { linkLabelModel.UpdateOffsetX((double)valuePropertyChangedEventArgs.OldValue, (double)valuePropertyChangedEventArgs.NewValue); } break; } case nameof(ConnectorPointModel.Y): { if (e is ValuePropertyChangedEventArgs valuePropertyChangedEventArgs) { linkLabelModel.UpdateOffsetY((double)valuePropertyChangedEventArgs.OldValue, (double)valuePropertyChangedEventArgs.NewValue); } break; } } } else if (sender is ColorViewModel) { } } public override bool Verify() { return IsFullConnection == false || SourceConnectorInfo?.CanAttachTo(SinkConnectorInfo) == true; } private void UpdateArea() { Area = new RectangleBase(SourceA, SourceB); } private void UpdatePathGeneratorResult() { if (SourceConnectorInfo == null || SinkConnectorInfo == null) return; var route = Router.Get(Root, this); (var source, var target) = FindConnectionPoints(route); if (source == null || target == null) return; PathGeneratorResult = PathGenerator.Get(Root, this, route, source.Value, target.Value); var startMiddle = new RectangleBase ( (PathGeneratorResult.SourceMarkerPosition.X + (source.Value.X - Area.Left)) / 2, (PathGeneratorResult.SourceMarkerPosition.Y + (source.Value.Y - Area.Top)) / 2, 0, 0 ); var endMiddle = new RectangleBase ( (PathGeneratorResult.TargetMarkerPosition.X + (target.Value.X - Area.Left)) / 2, (PathGeneratorResult.TargetMarkerPosition.Y + (target.Value.Y - Area.Top)) / 2, 0, 0 ); StartRectangle = startMiddle.InflateRectangle(GetSourceMarkerWidth() / 2, GetSourceMarkerHeight() / 2); EndRectangle = endMiddle.InflateRectangle(GetSinkMarkerWidth() / 2, GetSinkMarkerHeight() / 2); StartAngle = PathGeneratorResult.SourceMarkerAngle; EndAngle = PathGeneratorResult.TargetMarkerAngle; StartPoint = new PointBase(source.Value.X - Area.Left, source.Value.Y - Area.Top); EndPoint = new PointBase(target.Value.X - Area.Left, target.Value.Y - Area.Top); var startVertice = Vertices.FirstOrDefault(p => p.ConnectorVertexType == ConnectorVertexType.Start); if (startVertice == null) { startVertice = AddEndsVertex(StartPoint, ConnectorVertexType.Start); } else { IsInternalChanged = true; startVertice.SetPosition(StartPoint); IsInternalChanged = false; } startVertice.ColorViewModel.FillColor.Color = SourceConnectorInfoFully != null ? Colors.DarkRed : Colors.Blue; startVertice.ColorViewModel.LineColor.Color = SourceConnectorInfoFully != null ? Colors.DarkRed : Colors.Blue; var endVertice = Vertices.FirstOrDefault(p => p.ConnectorVertexType == ConnectorVertexType.End); if (endVertice == null) { endVertice = AddEndsVertex(EndPoint, ConnectorVertexType.End); } else { IsInternalChanged = true; endVertice.SetPosition(EndPoint); IsInternalChanged = false; } endVertice.ColorViewModel.FillColor.Color = SinkConnectorInfoFully != null ? Colors.DarkRed : Colors.Blue; endVertice.ColorViewModel.LineColor.Color = SinkConnectorInfoFully != null ? Colors.DarkRed : Colors.Blue; var paths = Labels.Count > 0 ? PathGeneratorResult.Paths.Select(p => new SvgPath(p)).ToArray() : Array.Empty(); foreach (var label in Labels) { label.UpdatePosition(paths); } double marginwidth = 0; double marginheight = 0; if (Area.Width > SourceConnectorInfo.ConnectorWidth + SinkConnectorInfo.ConnectorWidth) { marginwidth = (SourceConnectorInfo.ConnectorWidth + SinkConnectorInfo.ConnectorWidth) / 2; } if (Area.Height > SourceConnectorInfo.ConnectorHeight + SinkConnectorInfo.ConnectorHeight) { marginheight = (SourceConnectorInfo.ConnectorHeight + SinkConnectorInfo.ConnectorHeight) / 2; } DragThumbMargin = new Thickness(marginwidth, marginheight, marginwidth, marginheight); } private void DeleteConnection(object args) { if (this.Root is IDiagramViewModel) { var diagramVM = this.Root as IDiagramViewModel; diagramVM.DeleteCommand.Execute(this); } } private (PointBase? source, PointBase? target) FindConnectionPoints(PointBase[] route) { if (IsPortless) // Portless { if (SourceConnectorInfoFully?.DataItem == null || SinkConnectorInfoFully?.DataItem == null) return (null, null); var sourceCenter = SourceConnectorInfoFully.IsPortless ? SourceConnectorInfoFully.DataItem.MiddlePosition : SourceConnectorInfoFully.MiddlePosition; var targetCenter = SinkConnectorInfoFully?.IsPortless == true ? SinkConnectorInfoFully?.DataItem?.MiddlePosition ?? OnGoingPosition : SinkConnectorInfoFully?.MiddlePosition ?? 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 = SourceConnectorInfoFully.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(SourceConnectorInfoFully, ColorViewModel.LeftArrowSizeStyle); var target = SinkConnectorInfo.MiddlePosition;// GetPortPositionBasedOnAlignment(SinkConnectorInfoFully, ColorViewModel.RightArrowSizeStyle); return (source, target); } } private PointBase? GetPortPositionBasedOnAlignment(ConnectorInfoBase port, SizeStyle marker) { if (port == null) return null; if (marker == 0) 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 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; } public void SetSourcePort(ConnectorInfoBase port) { SourceConnectorInfo = port; } public void SetSinkPort(ConnectorInfoBase port) { SinkConnectorInfo = port; } public void SetPartPostion(PointBase? sourcePoint, PointBase? sinkPoint) { if (sourcePoint != null) { SourceConnectorInfoPart.Position = sourcePoint.Value; _sourceA = SourceConnectorInfoPart.Position; } if (sinkPoint != null) { SinkConnectorInfoPart.Position = sinkPoint.Value; _sourceB = SinkConnectorInfoPart.Position; } UpdateArea(); } public void UpdateConnectionMode(FullyCreatedConnectorInfo source, FullyCreatedConnectorInfo sink, string pathmode, string routermode) { //先置空,避免更新 SourceConnectorInfo = null; SinkConnectorInfo = null; PathMode = pathmode; RouterMode = routermode; SourceConnectorInfo = source; SinkConnectorInfo = sink; } public void SetPathGeneratorParameter(double? smoothMargin, double? smoothAutoSlope, double? orthogonalShapeMargin, double? orthogonalGlobalBoundsMargin) { bool hasChanged = false; if (smoothMargin != null && SmoothMargin != smoothMargin) { hasChanged = true; SmoothMargin = smoothMargin.Value; } if (smoothAutoSlope != null && SmoothAutoSlope != smoothAutoSlope) { hasChanged = true; SmoothAutoSlope = smoothAutoSlope.Value; } if (orthogonalShapeMargin != null && OrthogonalShapeMargin != orthogonalShapeMargin) { hasChanged = true; OrthogonalShapeMargin = orthogonalShapeMargin.Value; } if (orthogonalGlobalBoundsMargin != null && OrthogonalGlobalBoundsMargin != orthogonalGlobalBoundsMargin) { hasChanged = true; OrthogonalGlobalBoundsMargin = orthogonalGlobalBoundsMargin.Value; } if (hasChanged) { UpdatePathGeneratorResult(); } } public void SetVisible(bool visible) { Visible = visible; } public double GetSourceMarkerWidth() { if (string.IsNullOrEmpty(ShapeViewModel.SourceMarker.Path)) { return 0; } return ShapeViewModel.SourceMarker.Width; } public double GetSourceMarkerHeight() { if (string.IsNullOrEmpty(ShapeViewModel.SourceMarker.Path)) { return 0; } return ShapeViewModel.SourceMarker.Height; } public double GetSinkMarkerWidth() { if (string.IsNullOrEmpty(ShapeViewModel.SinkMarker.Path)) { return 0; } return ShapeViewModel.SinkMarker.Width; } public double GetSinkMarkerHeight() { if (string.IsNullOrEmpty(ShapeViewModel.SinkMarker.Path)) { return 0; } return ShapeViewModel.SinkMarker.Height; } #region 双击添加 private void AddVertex(object parameter) { MouseButtonEventArgs mosueArg = ((EventToCommandArgs)parameter).EventArgs as MouseButtonEventArgs; var position = mosueArg.GetPosition(((EventToCommandArgs)parameter).Sender as IInputElement); AddVertex(position, false); if (!((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)) { ShouldInsertAnchor = false; } } public void AddVertex(PointBase pointBase, bool absolute = true) { if (absolute) { pointBase = new PointBase(pointBase.X - Area.Left, pointBase.Y - Area.Top); } var vertice = new ConnectorVertexModel(this, pointBase); vertice.ColorViewModel.LineColor.Color = Colors.Blue; vertice.PropertyChanged += new WeakINPCEventHandler(Item_PropertyChanged).Handler; Vertices.Add(vertice); UpdatePathGeneratorResult(); } public ConnectorVertexModel AddEndsVertex(PointBase pointBase, ConnectorVertexType connectorVertexType) { var vertice = new ConnectorVertexModel(this, pointBase); vertice.ConnectorVertexType = connectorVertexType; vertice.PropertyChanged += new WeakINPCEventHandler(Item_PropertyChanged).Handler; Vertices.Add(vertice); return vertice; } public void RemoveVertex(ConnectorVertexModel vertice) { Vertices.Remove(vertice); UpdatePathGeneratorResult(); } protected override void ExecuteEditCommand(object param) { AddLabel(); } public void AddLabel(string text = null, double? distance = null, PointBase? offset = null) { var label = new ConnectorLabelModel(this, text?.ToString(), distance, offset); label.PropertyChanged += new WeakINPCEventHandler(Item_PropertyChanged).Handler; label.IsSelected = true; Labels.Add(label); var paths = Labels.Count > 0 ? PathGeneratorResult?.Paths.Select(p => new SvgPath(p)).ToArray() : Array.Empty(); label.UpdatePosition(paths); } public void RemoveLabel(ConnectorLabelModel label) { Labels.Remove(label); } public void ClearLabel() { Labels?.Clear(); } #endregion } }