using System; using System.Collections.Generic; using System.IO; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Markup; using System.Windows.Media; using System.Xml; using System.Linq; using System.Windows.Shapes; using System.Windows.Resources; using System.Runtime.InteropServices; using Newtonsoft.Json; using AIStudio.Wpf.DiagramDesigner.Models; using AIStudio.Wpf.DiagramDesigner.ViewModels; using AIStudio.Wpf.DiagramDesigner.ViewModels.BaseViewModel; using System.Diagnostics; namespace AIStudio.Wpf.DiagramDesigner { public class DesignerCanvas : Canvas { #region 属性 private IDiagramViewModel _viewModel { get { return DataContext as IDiagramViewModel; } } private IDiagramServiceProvider _service { get { return DiagramServicesProvider.Instance.Provider; } } private ConnectionViewModel partialConnection; private Point? rubberbandSelectionStartPoint = null; private Connector sourceConnector; public Connector SourceConnector { get { return sourceConnector; } set { if (sourceConnector != value) { sourceConnector = value; ConnectorInfoBase sourceDataItem = sourceConnector.Info; //Rect rectangleBounds = sourceConnector.TransformToVisual(this).TransformBounds(new Rect(sourceConnector.RenderSize)); //Point point = new Point(rectangleBounds.Left + (rectangleBounds.Width / 2), // rectangleBounds.Bottom + (rectangleBounds.Height / 2)); Point point = sourceDataItem.MiddlePosition; partialConnection = new ConnectionViewModel(_viewModel, sourceDataItem, new PartCreatedConnectorInfo(point.X, point.Y), DrawMode, RouterMode); _viewModel.Add(partialConnection); partialConnection.ZIndex = -1; } } } private Connector sinkConnector; private DrawMode DrawMode { get { if (_viewModel.DrawModeViewModel != null) { return _viewModel.DrawModeViewModel.LineDrawMode; } else { return _service.DrawModeViewModel.LineDrawMode; } } } private RouterMode RouterMode { get { if (_viewModel.DrawModeViewModel != null) { return _viewModel.DrawModeViewModel.LineRouterMode; } else { return _service.DrawModeViewModel.LineRouterMode; } } } private bool EnableSnapping { get { if (_viewModel.DrawModeViewModel != null) { return _viewModel.DrawModeViewModel.EnableSnapping; } else { return _service.DrawModeViewModel.EnableSnapping; } } } private double SnappingRadius { get { if (_viewModel.DrawModeViewModel != null) { return _viewModel.DrawModeViewModel.SnappingRadius; } else { return _service.DrawModeViewModel.SnappingRadius; } } } #region GridCellSize public static readonly DependencyProperty GridCellSizeProperty = DependencyProperty.Register(nameof(GridCellSize), typeof(Size), typeof(DesignerCanvas), new FrameworkPropertyMetadata(new Size(50, 50), FrameworkPropertyMetadataOptions.AffectsRender)); public Size GridCellSize { get { return (Size)GetValue(GridCellSizeProperty); } set { SetValue(GridCellSizeProperty, value); } } #endregion #region ShowGrid public static readonly DependencyProperty ShowGridProperty = DependencyProperty.Register(nameof(ShowGrid), typeof(bool), typeof(DesignerCanvas), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender)); public bool ShowGrid { get { return (bool)GetValue(ShowGridProperty); } set { SetValue(ShowGridProperty, value); } } #endregion #region GridColor public static readonly DependencyProperty GridColorProperty = DependencyProperty.Register(nameof(GridColor), typeof(Color), typeof(DesignerCanvas), new FrameworkPropertyMetadata(Colors.LightGray, FrameworkPropertyMetadataOptions.AffectsRender)); public Color GridColor { get { return (Color)GetValue(GridColorProperty); } set { SetValue(GridColorProperty, value); } } #endregion #region GridMarginSize 单位mm public static readonly DependencyProperty GridMarginSizeProperty = DependencyProperty.Register(nameof(GridMarginSize), typeof(Size), typeof(DesignerCanvas), new FrameworkPropertyMetadata(new Size(28, 28), FrameworkPropertyMetadataOptions.AffectsRender)); public Size GridMarginSize { get { return (Size)GetValue(GridMarginSizeProperty); } set { SetValue(GridMarginSizeProperty, value); } } #endregion #endregion #region 初始化 public DesignerCanvas() { this.Focusable = true; Mediator.Instance.Register(this); _service.PropertyChanged += _service_PropertyChanged; this.Loaded += DesignerCanvas_Loaded; this.IsVisibleChanged += DesignerCanvas_IsVisibleChanged; } private void DesignerCanvas_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) { if (IsVisible) { this.Focus(); } } private void DesignerCanvas_Loaded(object sender, RoutedEventArgs e) { this.Focus(); } protected override void OnRender(DrawingContext dc) { var rect = new Rect(0, 0, RenderSize.Width, RenderSize.Height); dc.DrawRectangle(Background, null, rect); if (ShowGrid && GridCellSize.Width > 0 && GridCellSize.Height > 0) DrawGrid(dc, rect); } protected virtual void DrawGrid(DrawingContext dc, Rect rect) { //using .5 forces wpf to draw a single pixel line for (var i = GridMarginSize.Height + 0.5; i < rect.Height - GridMarginSize.Height; i += GridCellSize.Height) dc.DrawLine(new Pen(new SolidColorBrush(GridColor), 1), new Point(GridMarginSize.Width, i), new Point(rect.Width - GridMarginSize.Width, i)); dc.DrawLine(new Pen(new SolidColorBrush(GridColor), 1), new Point(GridMarginSize.Width, rect.Height - GridMarginSize.Height), new Point(rect.Width - GridMarginSize.Width, rect.Height - GridMarginSize.Height)); for (var i = GridMarginSize.Width + 0.5; i < rect.Width - GridMarginSize.Width; i += GridCellSize.Width) dc.DrawLine(new Pen(new SolidColorBrush(GridColor), 1), new Point(i, GridMarginSize.Height), new Point(i, rect.Height - GridMarginSize.Height)); dc.DrawLine(new Pen(new SolidColorBrush(GridColor), 1), new Point(rect.Width - GridMarginSize.Width, GridMarginSize.Height), new Point(rect.Width - GridMarginSize.Width, rect.Height - GridMarginSize.Height)); } #endregion #region Format/Move private void _service_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) { if (sender is IDrawModeViewModel) { if (e.PropertyName == nameof(CursorMode)) { if (_service.DrawModeViewModel.CursorMode == CursorMode.Format) { EnterFormat(); } else if (_service.DrawModeViewModel.CursorMode == CursorMode.Move) { EnterMove(); } } } } private void EnterFormat() { StreamResourceInfo sri = Application.GetResourceStream(new Uri("pack://application:,,,/AIStudio.Wpf.DiagramDesigner;component/Images/FormatPainter.cur", UriKind.RelativeOrAbsolute)); this.Cursor = new Cursor(sri.Stream); foreach (SelectableDesignerItemViewModelBase item in _viewModel.Items) { item.IsHitTestVisible = false; } } private void EnterMove() { this.Cursor = Cursors.SizeAll; foreach (SelectableDesignerItemViewModelBase item in _viewModel.Items) { item.IsHitTestVisible = false; } } private void ExitCursor() { this.Cursor = Cursors.Arrow; foreach (SelectableDesignerItemViewModelBase item in _viewModel.Items) { item.IsHitTestVisible = true; } _service.DrawModeViewModel.CursorMode = CursorMode.Normal; } private void Format(SelectableDesignerItemViewModelBase source, SelectableDesignerItemViewModelBase target) { CopyHelper.CopyPropertyValue(source.ColorViewModel, target.ColorViewModel); CopyHelper.CopyPropertyValue(source.FontViewModel, target.FontViewModel); CopyHelper.CopyPropertyValue(source.ShapeViewModel, target.ShapeViewModel); CopyHelper.CopyPropertyValue(source.AnimationViewModel, target.AnimationViewModel); } #endregion protected override void OnMouseDown(MouseButtonEventArgs e) { base.OnMouseDown(e); if (_viewModel.IsReadOnly) return; if (_service.DrawModeViewModel.CursorMode == CursorMode.Format) { var element = (e.OriginalSource as FrameworkElement); if (element.DataContext is SelectableDesignerItemViewModelBase target) { Format(_viewModel.SelectedItems.FirstOrDefault(), target); return; } ExitCursor(); } else if (_service.DrawModeViewModel.CursorMode == CursorMode.Move) { ExitCursor(); return; } if (e.LeftButton == MouseButtonState.Pressed) { //if we are source of event, we are rubberband selecting if (e.Source == this) { // in case that this click is the start for a // drag operation we cache the start point Point currentPoint = e.GetPosition(this); rubberbandSelectionStartPoint = currentPoint; if (!(Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))) { _viewModel.ClearSelectedItems(); } if (_service.DrawModeViewModel.LineDrawModeSelected)//画线模式,可以不命中实体 { if (SourceConnector == null) { //新建一个Part连接点 SourceConnector = new Connector() { Content = new PartCreatedConnectorInfo(currentPoint.X, currentPoint.Y) }; } } e.Handled = true; } } } protected override void OnMouseMove(MouseEventArgs e) { //var focusedElement = Keyboard.FocusedElement; //Debug.WriteLine("focusedElement:" + focusedElement?.ToString()); base.OnMouseMove(e); if (_viewModel.IsReadOnly) return; Point currentPoint = e.GetPosition(this); _viewModel.CurrentPoint = new Point(ScreenHelper.WidthToMm(currentPoint.X), ScreenHelper.WidthToMm(currentPoint.Y)); var point = CursorPointManager.GetCursorPosition(); _viewModel.CurrentColor = ColorPickerManager.GetColor(point.X, point.Y); //移动 if (_service.DrawModeViewModel.CursorMode == CursorMode.Move) { _viewModel.SelectedItems.OfType().ToList().ForEach(p => { p.Left = currentPoint.X; p.Top = currentPoint.Y; }); return; } if (SourceConnector != null) { if (e.LeftButton == MouseButtonState.Pressed) { partialConnection.SinkConnectorInfo = new PartCreatedConnectorInfo(currentPoint.X, currentPoint.Y); sinkConnector = HitTesting(currentPoint); if (EnableSnapping) { var nearPort = FindNearPortToAttachTo(); if (nearPort != null || partialConnection.SinkConnectorInfoFully != null) { partialConnection.SinkConnectorInfo = nearPort; } } } } else { // if mouse button is not pressed we have no drag operation, ... if (e.LeftButton != MouseButtonState.Pressed) 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 OnMouseUp(MouseButtonEventArgs e) { base.OnMouseUp(e); if (_viewModel.IsReadOnly) return; Mediator.Instance.NotifyColleagues("DoneDrawingMessage", true); if (sourceConnector != null) { ConnectorInfoBase sourceDataItem = sourceConnector.Info; if (sinkConnector != null) { ConnectorInfoBase sinkDataItem = sinkConnector.Info; _viewModel.Remove(partialConnection); _viewModel.AddItemCommand.Execute(new ConnectionViewModel(_viewModel, sourceDataItem, sinkDataItem, DrawMode, RouterMode)); } else if (partialConnection.IsFullConnection)//自动连接模式 { partialConnection.RaiseFullConnection(); } else if (_service.DrawModeViewModel.LineDrawModeSelected) { Point currentPoint = e.GetPosition(this); ConnectorInfoBase sinkDataItem = new PartCreatedConnectorInfo(currentPoint.X, currentPoint.Y); _viewModel.Remove(partialConnection); _viewModel.AddItemCommand.Execute(new ConnectionViewModel(_viewModel, sourceDataItem, sinkDataItem, DrawMode, RouterMode)); } else { //Need to remove last item as we did not finish drawing the path _viewModel.Remove(partialConnection); } } sourceConnector = null; sinkConnector = null; partialConnection = null; if (_service.DrawModeViewModel.GetDrawMode() != DrawMode.DirectLine) { _service.DrawModeViewModel.ResetDrawMode(); } } protected override void OnPreviewKeyDown(KeyEventArgs e) { base.OnPreviewKeyDown(e); if (_viewModel.IsReadOnly) return; e.Handled = _viewModel.ExecuteShortcut(e); } protected override void OnPreviewMouseWheel(MouseWheelEventArgs e) { base.OnPreviewMouseWheel(e); if (Keyboard.IsKeyDown(Key.LeftCtrl) == false && Keyboard.IsKeyDown(Key.RightCtrl) == false) { return; } var newZoomValue = _viewModel.ZoomValue + (e.Delta > 0 ? 0.1 : -0.1); _viewModel.ZoomValue = Math.Max(Math.Min(newZoomValue, _viewModel.MaximumZoomValue), _viewModel.MinimumZoomValue); e.Handled = true; } protected override Size MeasureOverride(Size constraint) { Size size = new Size(); foreach (UIElement element in this.InternalChildren) { 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; } private Connector HitTesting(Point hitPoint) { DependencyObject hitObject = this.InputHitTest(hitPoint) as DependencyObject; while (hitObject != null && hitObject.GetType() != typeof(DesignerCanvas)) { if (hitObject is Connector connector) { return connector; } hitObject = VisualTreeHelper.GetParent(hitObject); } return null; } protected override void OnDrop(DragEventArgs e) { base.OnDrop(e); if (_viewModel.IsReadOnly) return; DragObject dragObject = e.Data.GetData(typeof(DragObject)) as DragObject; if (dragObject != null) { _viewModel.ClearSelectedItems(); Point position = e.GetPosition(this); if (dragObject.DesignerItem is SerializableObject serializableObject) { var designerItems = serializableObject.ToObject(); var minleft = designerItems.OfType().Min(p => p.Left); var mintop = designerItems.OfType().Min(p => p.Top); var maxright = designerItems.OfType().Max(p => p.Left + p.ItemWidth); var maxbottom = designerItems.OfType().Max(p => p.Top + p.ItemHeight); var itemswidth = maxright - minleft; var itemsheight = maxbottom - mintop; foreach (var item in designerItems.OfType()) { item.Left += position.X - itemswidth / 2; item.Top += position.Y - itemsheight / 2; } _viewModel.AddItemCommand.Execute(designerItems); } else { DesignerItemViewModelBase itemBase = null; if (dragObject.DesignerItem is DesignerItemBase) { itemBase = Activator.CreateInstance(dragObject.ContentType, _viewModel, dragObject.DesignerItem) as DesignerItemViewModelBase; } else { itemBase = Activator.CreateInstance(dragObject.ContentType) as DesignerItemViewModelBase; itemBase.Icon = dragObject.Icon; itemBase.ColorViewModel = CopyHelper.Mapper(dragObject.ColorViewModel); if (dragObject.DesiredSize != null) { itemBase.ItemWidth = dragObject.DesiredSize.Value.Width; itemBase.ItemHeight = dragObject.DesiredSize.Value.Height; } } itemBase.Left = Math.Max(0, position.X - itemBase.ItemWidth / 2); itemBase.Top = Math.Max(0, position.Y - itemBase.ItemHeight / 2); _viewModel.AddItemCommand.Execute(itemBase); } } var dragFile = e.Data.GetData(DataFormats.FileDrop); if (dragFile != null && dragFile is string[] files) { foreach (var file in files) { _viewModel.ClearSelectedItems(); Point position = e.GetPosition(this); ImageItemViewModel itemBase = new ImageItemViewModel(); itemBase.Icon = file; itemBase.Suffix = System.IO.Path.GetExtension(itemBase.Icon).ToLower(); itemBase.InitWidthAndHeight(); itemBase.AutoSize(); itemBase.Left = Math.Max(0, position.X - itemBase.ItemWidth / 2); itemBase.Top = Math.Max(0, position.Y - itemBase.ItemHeight / 2); _viewModel.AddItemCommand.Execute(itemBase); } } e.Handled = true; this.Focus(); } #region 自动依附节点 private FullyCreatedConnectorInfo FindNearPortToAttachTo() { foreach (var port in _viewModel.Items.OfType().ToList().SelectMany(n => n.Connectors)) { if (partialConnection.OnGoingPosition.DistanceTo(port.Position) < SnappingRadius && partialConnection.SourceConnectorInfoFully?.CanAttachTo(port) == true) return port; } return null; } #endregion } }