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.Controls; using AIStudio.Wpf.DiagramDesigner.Geometrys; using AIStudio.Wpf.DiagramDesigner.Helpers; using SvgPathProperties; namespace AIStudio.Wpf.DiagramDesigner { public class ConnectionViewModel : SelectableDesignerItemViewModelBase { public ConnectionViewModel(IDiagramViewModel root, FullyCreatedConnectorInfo sourceConnectorInfo, ConnectorInfoBase sinkConnectorInfo, DrawMode drawMode, RouterMode routerMode) { Root = root; PathMode = drawMode.ToString(); RouterMode = routerMode.ToString(); Init(sourceConnectorInfo, sinkConnectorInfo); } public ConnectionViewModel(IDiagramViewModel root, FullyCreatedConnectorInfo sourceConnectorInfo, FullyCreatedConnectorInfo sinkConnectorInfo, ConnectionItem designer) : base(root, designer) { PathMode = designer.PathMode; RouterMode = designer.RouterMode; Init(sourceConnectorInfo, sinkConnectorInfo); } public ConnectionViewModel(FullyCreatedConnectorInfo sourceConnectorInfo, ConnectorInfoBase sinkConnectorInfo, DrawMode drawMode, RouterMode routerMode) : this(null, sourceConnectorInfo, sinkConnectorInfo, drawMode, routerMode) { } public override SelectableItemBase GetSerializableObject() { if (IsFullConnection) { ConnectionItem connection = new ConnectionItem(this); return connection; } else { return null; } } protected virtual void Init(FullyCreatedConnectorInfo sourceConnectorInfo, ConnectorInfoBase sinkConnectorInfo) { this.Root = sourceConnectorInfo.DataItem.Root; if (Root != null && Root.ColorViewModel != null) { this.ColorViewModel = CopyHelper.Mapper(Root.ColorViewModel); } if (sinkConnectorInfo is FullyCreatedConnectorInfo sink && sink.DataItem.ShowArrow == false) { this.ColorViewModel.RightArrowPathStyle = ArrowPathStyle.None; } ColorViewModel.PropertyChanged += ConnectorViewModel_PropertyChanged; this.PropertyChanged += ConnectorViewModel_PropertyChanged; 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); AddLabelCommand = new SimpleCommand(AddLabel); } protected override void LoadDesignerItemViewModel(IDiagramViewModel root, SelectableItemBase designerbase) { base.LoadDesignerItemViewModel(root, designerbase); if (designerbase is ConnectionItem designer) { Vertices = new ObservableCollection(designer.Vertices.Select(p => new ConnectorVertexModel(this.Root, this, designer))); Labels = new ObservableCollection(designer.Labels.Select(p => new ConnectorLabelModel(this.Root, this, designer))); } } #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); } } } } } 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 _area; public RectangleBase Area { get { return _area; } private set { SetProperty(ref _area, 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); } } //待完善这两处 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); } } private LineAnimation _lineAnimation = LineAnimation.PathAnimation; [Browsable(true)] [CanDo] [StyleName("LineAnimationStyle")] public LineAnimation LineAnimation { get { return _lineAnimation; } set { SetProperty(ref _lineAnimation, value); } } public virtual Dictionary PropertiesSetting { get { return new Dictionary() { { "Text","文本" }, }; } } private FullyCreatedConnectorInfo _sourceConnectorInfo; public FullyCreatedConnectorInfo SourceConnectorInfo { get { return _sourceConnectorInfo; } set { SetProperty(ref _sourceConnectorInfo, value); } } 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 SinkConnectorInfoFully != null; } } public bool IsPortless => SourceConnectorInfo?.DataItem?.Connectors?.Count() == 0; #endregion #region 方法 public SimpleCommand DeleteConnectionCommand { get; set; } public SimpleCommand AddVertexCommand { get; set; } public SimpleCommand AddLabelCommand { get; set; } #endregion private void ConnectorViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e) { 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(ConnectorViewModel_PropertyChanged).Handler; } break; case nameof(Labels): foreach (var label in Labels) { label.PropertyChanged += new WeakINPCEventHandler(ConnectorViewModel_PropertyChanged).Handler; } break; case nameof(SourceConnectorInfo): SourceA = PointHelper.GetPointForConnector(SourceConnectorInfo); (SourceConnectorInfo.DataItem as INotifyPropertyChanged).PropertyChanged += new WeakINPCEventHandler(ConnectorViewModel_PropertyChanged).Handler; break; case nameof(SinkConnectorInfo): SourceB = SinkConnectorInfo.Position; if (SinkConnectorInfo is FullyCreatedConnectorInfo) { (((FullyCreatedConnectorInfo)SinkConnectorInfo).DataItem as INotifyPropertyChanged).PropertyChanged += new WeakINPCEventHandler(ConnectorViewModel_PropertyChanged).Handler; } break; case nameof(IsSelected): if (IsSelected == false) { Labels.FirstOrDefault()?.AddToSelection(false); } break; } } else if (sender is ColorViewModel) { if (e.PropertyName == nameof(ColorViewModel.LeftArrowPathStyle) || e.PropertyName == nameof(ColorViewModel.LeftArrowSizeStyle) || e.PropertyName == nameof(ColorViewModel.RightArrowPathStyle) || e.PropertyName == nameof(ColorViewModel.RightArrowSizeStyle)) { 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): SourceA = PointHelper.GetPointForConnector(this.SourceConnectorInfo); if (IsFullConnection) { SourceB = PointHelper.GetPointForConnector(this.SinkConnectorInfoFully); } break; } } else if (sender is ConnectorVertexModel) { switch (e.PropertyName) { case nameof(ConnectorPointModel.X): case nameof(ConnectorPointModel.Y): UpdatePathGeneratorResult(); 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; } } } } 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); //修正旋转 switch (SourceConnectorInfo.Orientation) { case ConnectorOrientation.Left: { StartPoint = new PointBase(PathGeneratorResult.SourceMarkerPosition.X, PathGeneratorResult.SourceMarkerPosition.Y - ColorViewModel.LeftArrowSize / 2); break; } case ConnectorOrientation.Top: { StartPoint = new PointBase(PathGeneratorResult.SourceMarkerPosition.X - ColorViewModel.LeftArrowSize / 2, PathGeneratorResult.SourceMarkerPosition.Y); break; } case ConnectorOrientation.Right: { StartPoint = new PointBase(PathGeneratorResult.SourceMarkerPosition.X - ColorViewModel.LeftArrowSize, PathGeneratorResult.SourceMarkerPosition.Y - ColorViewModel.LeftArrowSize / 2); break; } case ConnectorOrientation.Bottom: { StartPoint = new PointBase(PathGeneratorResult.SourceMarkerPosition.X - ColorViewModel.LeftArrowSize / 2, PathGeneratorResult.SourceMarkerPosition.Y - ColorViewModel.LeftArrowSize); break; } default: { StartPoint = PathGeneratorResult.SourceMarkerPosition; break; } } //修正旋转 switch (SinkConnectorInfo.Orientation) { case ConnectorOrientation.Left: { EndPoint = new PointBase(PathGeneratorResult.TargetMarkerPosition.X, PathGeneratorResult.TargetMarkerPosition.Y - ColorViewModel.RightArrowSize / 2); break; } case ConnectorOrientation.Top: { EndPoint = new PointBase(PathGeneratorResult.TargetMarkerPosition.X - ColorViewModel.RightArrowSize / 2, PathGeneratorResult.TargetMarkerPosition.Y); break; } case ConnectorOrientation.Right: { EndPoint = new PointBase(PathGeneratorResult.TargetMarkerPosition.X - ColorViewModel.RightArrowSize, PathGeneratorResult.TargetMarkerPosition.Y - ColorViewModel.RightArrowSize / 2); break; } case ConnectorOrientation.Bottom: { EndPoint = new PointBase(PathGeneratorResult.TargetMarkerPosition.X - ColorViewModel.RightArrowSize / 2, PathGeneratorResult.TargetMarkerPosition.Y - ColorViewModel.RightArrowSize); break; } default: { EndPoint = PathGeneratorResult.TargetMarkerPosition; break; } } StartAngle = PathGeneratorResult.SourceMarkerAngle; EndAngle = PathGeneratorResult.TargetMarkerAngle; var paths = Labels.Count > 0 ? PathGeneratorResult.Paths.Select(p => new SvgPath(p)).ToArray() : Array.Empty(); foreach (var label in Labels) { label.UpdatePosition(paths); } } private void DeleteConnection(object args) { if (this.Root is IDiagramViewModel) { var diagramVM = this.Root as IDiagramViewModel; diagramVM.RemoveItemCommand.Execute(this); } } 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 == 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; } #region 双击添加 private void AddVertex(object parameter) { MouseButtonEventArgs mosueArg = ((EventToCommandArgs)parameter).EventArgs as MouseButtonEventArgs; var position = mosueArg.GetPosition(((EventToCommandArgs)parameter).Sender as IInputElement); var vertice = new ConnectorVertexModel(this, new PointBase(position.X, position.Y)); vertice.PropertyChanged += new WeakINPCEventHandler(ConnectorViewModel_PropertyChanged).Handler; Vertices.Add(vertice); UpdatePathGeneratorResult(); if (!((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)) { ShouldInsertAnchor = false; } } protected override void ExecuteEditCommand(object param) { AddLabel(); } public void AddLabel(object text = null) { var label = new ConnectorLabelModel(this, text?.ToString()); label.PropertyChanged += new WeakINPCEventHandler(ConnectorViewModel_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); } #endregion } }