Files
aistudio-wpf-diagram/AIStudio.Wpf.DiagramDesigner/Controls/DesignerCanvas.cs
2023-04-29 15:29:22 +08:00

632 lines
23 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 List<Connector> connectorsHit = new List<Connector>();
private Point? rubberbandSelectionStartPoint = null;
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
private Connector sourceConnector;
public Connector SourceConnector
{
get
{
return sourceConnector;
}
set
{
if (sourceConnector != value)
{
sourceConnector = value;
connectorsHit.Add(sourceConnector);
FullyCreatedConnectorInfo 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));
partialConnection = new ConnectionViewModel(_viewModel, sourceDataItem, new PartCreatedConnectorInfo(point.X, point.Y), DrawMode, RouterMode);
_viewModel.Add(partialConnection);
}
}
}
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
rubberbandSelectionStartPoint = e.GetPosition(this);
if (!(Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)))
{
_viewModel.ClearSelectedItems();
}
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<DesignerItemViewModelBase>().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);
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 && _service.DrawModeViewModel.GetDrawMode() != DrawMode.DirectLine)
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<bool>("DoneDrawingMessage", true);
if (sourceConnector != null)
{
FullyCreatedConnectorInfo sourceDataItem = sourceConnector.Info;
if (connectorsHit.Count() == 2)
{
Connector sinkConnector = connectorsHit.Last();
FullyCreatedConnectorInfo sinkDataItem = sinkConnector.Info;
int indexOfLastTempConnection = sinkDataItem.DataItem.Root.Items.Count - 1;
sinkDataItem.DataItem.Root.Remove(
sinkDataItem.DataItem.Root.Items[indexOfLastTempConnection]);
sinkDataItem.DataItem.Root.AddItemCommand.Execute(new ConnectionViewModel(_viewModel, sourceDataItem, sinkDataItem, DrawMode, RouterMode));
}
else if (partialConnection.IsFullConnection)//自动连接模式
{
partialConnection.RaiseFullConnection();
}
else
{
//Need to remove last item as we did not finish drawing the path
int indexOfLastTempConnection = sourceDataItem.DataItem.Root.Items.Count - 1;
sourceDataItem.DataItem.Root.Remove(
sourceDataItem.DataItem.Root.Items[indexOfLastTempConnection]);
}
}
connectorsHit = new List<Connector>();
sourceConnector = 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 void HitTesting(Point hitPoint)
{
DependencyObject hitObject = this.InputHitTest(hitPoint) as DependencyObject;
while (hitObject != null &&
hitObject.GetType() != typeof(DesignerCanvas))
{
if (hitObject is Connector connector)
{
if (!connectorsHit.Contains(hitObject as Connector))
connectorsHit.Add(hitObject as Connector);
}
hitObject = VisualTreeHelper.GetParent(hitObject);
}
}
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<DesignerItemViewModelBase>().Min(p => p.Left);
var mintop = designerItems.OfType<DesignerItemViewModelBase>().Min(p => p.Top);
var maxright = designerItems.OfType<DesignerItemViewModelBase>().Max(p => p.Left + p.ItemWidth);
var maxbottom = designerItems.OfType<DesignerItemViewModelBase>().Max(p => p.Top + p.ItemHeight);
var itemswidth = maxright - minleft;
var itemsheight = maxbottom - mintop;
foreach (var item in designerItems.OfType<DesignerItemViewModelBase>())
{
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<DesignerItemViewModelBase>().ToList().SelectMany(n => n.Connectors))
{
if (partialConnection.OnGoingPosition.DistanceTo(port.Position) < SnappingRadius &&
partialConnection.SourceConnectorInfo.CanAttachTo(port))
return port;
}
return null;
}
#endregion
}
}