1 //------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation. All rights reserved.
3 //------------------------------------------------------------
5 namespace System.Activities.Presentation.FreeFormEditing
8 using System.Collections.Generic;
10 using System.Windows.Controls;
11 using System.Windows.Documents;
12 using System.Windows.Input;
13 using System.Windows.Media;
14 using System.Windows.Threading;
15 using System.Activities.Presentation;
16 using System.Activities.Presentation.View;
17 using System.Activities.Presentation.Internal.PropertyEditing;
20 using System.Activities.Presentation.Model;
22 internal class FreeFormPanel : Panel
24 public static readonly DependencyProperty ChildSizeProperty = DependencyProperty.RegisterAttached("ChildSize", typeof(Size), typeof(FreeFormPanel), new FrameworkPropertyMetadata());
25 public static readonly DependencyProperty LocationProperty = DependencyProperty.RegisterAttached("Location", typeof(Point), typeof(FreeFormPanel), new FrameworkPropertyMetadata(new Point(-1, -1)));
26 public static readonly DependencyProperty RequiredWidthProperty = DependencyProperty.Register("RequiredWidth", typeof(Double), typeof(FreeFormPanel), new FrameworkPropertyMetadata(double.NaN));
27 public static readonly DependencyProperty RequiredHeightProperty = DependencyProperty.Register("RequiredHeight", typeof(Double), typeof(FreeFormPanel), new FrameworkPropertyMetadata(double.NaN));
28 public static readonly DependencyProperty DestinationConnectionPointProperty = DependencyProperty.RegisterAttached("DestinationConnectionPoint", typeof(ConnectionPoint), typeof(FreeFormPanel), new FrameworkPropertyMetadata());
29 public static readonly DependencyProperty SourceConnectionPointProperty = DependencyProperty.RegisterAttached("SourceConnectionPoint", typeof(ConnectionPoint), typeof(FreeFormPanel), new FrameworkPropertyMetadata());
30 public static readonly DependencyProperty DisabledProperty = DependencyProperty.Register("Disabled", typeof(bool), typeof(FreeFormPanel), new UIPropertyMetadata(false));
31 public static readonly DependencyProperty AutoConnectContainerProperty = DependencyProperty.Register("AutoConnectContainer", typeof(IAutoConnectContainer), typeof(FreeFormPanel), new UIPropertyMetadata(null));
33 public const double ConnectorEditorOpacity = 1.0;
34 public const double ConnectorEditorThickness = 1.5;
35 public const double LeftStackingMargin = 50;
36 public const double TopStackingMargin = 80;
37 public const double VerticalStackingDistance = 50;
38 public const double GridSize = 10;
39 public ConnectorEditor connectorEditor;
41 bool measureConnectors = false;
42 bool measureConnectorsPosted = false;
43 AutoConnectHelper autoConnectHelper = null;
44 DesignerConfigurationService designerConfigurationService = null;
46 public FreeFormPanel()
48 connectorEditor = null;
49 this.autoConnectHelper = new AutoConnectHelper(this);
50 lastYPosition = FreeFormPanel.TopStackingMargin;
52 this.Unloaded += (sender, e) =>
54 this.RemoveConnectorEditor();
58 public event LocationChangedEventHandler LocationChanged;
59 public event ConnectorMovedEventHandler ConnectorMoved;
60 public event RequiredSizeChangedEventHandler RequiredSizeChanged;
62 public static Size GetChildSize(DependencyObject obj)
64 return (Size)obj.GetValue(FreeFormPanel.ChildSizeProperty);
67 public static void SetChildSize(DependencyObject obj, Size size)
69 obj.SetValue(FreeFormPanel.ChildSizeProperty, size);
72 public double RequiredHeight
74 get { return (double)GetValue(FreeFormPanel.RequiredHeightProperty); }
75 private set { SetValue(FreeFormPanel.RequiredHeightProperty, value); }
78 public double RequiredWidth
80 get { return (double)GetValue(FreeFormPanel.RequiredWidthProperty); }
81 private set { SetValue(FreeFormPanel.RequiredWidthProperty, value); }
86 get { return (bool)GetValue(DisabledProperty); }
87 set { SetValue(DisabledProperty, value); }
90 public IAutoConnectContainer AutoConnectContainer
92 get { return (IAutoConnectContainer)GetValue(AutoConnectContainerProperty); }
93 set { SetValue(AutoConnectContainerProperty, value); }
96 public static Vector CalculateMovement(Key key, bool isRightToLeft)
102 moveDir = new Vector(0, FreeFormPanel.GridSize);
105 moveDir = new Vector(0, -FreeFormPanel.GridSize);
108 moveDir = new Vector(FreeFormPanel.GridSize, 0);
111 moveDir = new Vector(-FreeFormPanel.GridSize, 0);
114 Fx.Assert(false, "Invalid case");
115 moveDir = new Vector(0, 0);
121 moveDir.X = -moveDir.X;
127 public static double ZeroIfNegative(double val)
129 return val.IsNoGreaterThan(0) ? 0 : val;
132 internal UIElement CurrentAutoConnectTarget
136 return this.autoConnectHelper.CurrentTarget;
140 internal Connector CurrentAutoSplitTarget
146 bool AutoConnectEnabled
150 if (this.designerConfigurationService == null)
152 DesignerView view = VisualTreeUtils.FindVisualAncestor<DesignerView>(this);
155 this.designerConfigurationService = view.Context.Services.GetService<DesignerConfigurationService>();
156 return this.designerConfigurationService.AutoConnectEnabled;
165 return this.designerConfigurationService.AutoConnectEnabled;
170 public static ConnectionPoint GetDestinationConnectionPoint(DependencyObject obj)
172 return (ConnectionPoint)obj.GetValue(FreeFormPanel.DestinationConnectionPointProperty);
175 public static void SetDestinationConnectionPoint(DependencyObject obj, ConnectionPoint connectionPoint)
177 obj.SetValue(FreeFormPanel.DestinationConnectionPointProperty, connectionPoint);
180 public static ConnectionPoint GetSourceConnectionPoint(DependencyObject obj)
182 return (ConnectionPoint)obj.GetValue(FreeFormPanel.SourceConnectionPointProperty);
185 public static void SetSourceConnectionPoint(DependencyObject obj, ConnectionPoint connectionPoint)
187 obj.SetValue(FreeFormPanel.SourceConnectionPointProperty, connectionPoint);
190 public static Point GetLocation(DependencyObject obj)
192 return (Point)obj.GetValue(FreeFormPanel.LocationProperty);
195 public static void SetLocation(DependencyObject obj, Point point)
197 obj.SetValue(FreeFormPanel.LocationProperty, point);
200 protected override void OnInitialized(EventArgs e)
202 base.OnInitialized(e);
203 this.SnapsToDevicePixels = true;
204 this.AllowDrop = true;
207 internal void RemoveAutoConnectAdorner()
209 this.autoConnectHelper.RemoveDropTargets();
212 internal List<DependencyObject> GetChildShapes(DependencyObject excluded)
214 List<DependencyObject> children = new List<DependencyObject>();
215 foreach (UIElement element in this.Children)
217 if (element is Connector)
221 if (object.Equals(element, excluded))
225 else if (element is VirtualizedContainerService.VirtualizingContainer)
227 if (object.Equals(excluded, ((VirtualizedContainerService.VirtualizingContainer)element).Child))
232 children.Add(element);
237 protected override void OnPreviewDragOver(DragEventArgs e)
239 if (this.IsOutmostPanel())
241 if (this.AutoConnectEnabled && DragDropHelper.GetDraggedObjectCount(e) == 1)
243 this.autoConnectHelper.OnPreviewDragOverPanel(e);
246 base.OnPreviewDragOver(e);
249 public void UpdateConnectorPoints(Connector connector, List<Point> points)
251 PointCollection pointCollection = new PointCollection();
252 foreach (Point point in points)
254 pointCollection.Add(new Point(point.X < 0 ? 0 : point.X, point.Y < 0 ? 0 : point.Y));
256 connector.Points = pointCollection;
257 OnLocationChanged(connector, null);
260 static public List<Point> GetEdgeRelativeToOutmostPanel(ConnectionPoint connectionPoint)
262 return connectionPoint.Edge;
265 static public Point GetLocationRelativeToOutmostPanel(ConnectionPoint connectionPoint)
267 return connectionPoint.Location;
270 public Point GetLocationRelativeToOutmostPanel(Point location)
272 return this.TranslatePoint(location, this.GetOutmostPanel());
275 FreeFormPanel GetOutmostPanel()
277 DependencyObject obj = this;
280 obj = VisualTreeHelper.GetParent(obj);
282 while (obj != null && !typeof(INestedFreeFormPanelContainer).IsAssignableFrom(obj.GetType()));
286 INestedFreeFormPanelContainer container = (INestedFreeFormPanelContainer)obj;
287 if (container.GetChildFreeFormPanel() == this)
289 return container.GetOutmostFreeFormPanel();
295 internal bool IsOutmostPanel()
297 return this == this.GetOutmostPanel();
300 internal static ConnectionPoint ConnectionPointHitTest(Point hitPoint, ConnectionPointsAdorner adorner)
302 FreeFormPanel panel = VisualTreeUtils.FindVisualAncestor<FreeFormPanel>(adorner.AdornedElement);
303 return ConnectionPointHitTest(hitPoint, adorner.ConnectionPoints, panel);
306 internal static ConnectionPoint ConnectionPointHitTest(Point hitPoint, List<ConnectionPoint> connectionPoints, FreeFormPanel panel)
308 ConnectionPoint hitConnectionPoint = null;
309 FreeFormPanel outmost = panel.GetOutmostPanel();
310 foreach (ConnectionPoint connPoint in connectionPoints)
312 if (connPoint != null && connPoint.IsEnabled)
314 if (new Rect(panel.TranslatePoint(connPoint.Location, outmost) + connPoint.HitTestOffset, connPoint.HitTestSize).Contains(hitPoint))
316 hitConnectionPoint = connPoint;
321 return hitConnectionPoint;
324 protected override Size ArrangeOverride(Size finalSize)
328 for (int i = 0; i < Children.Count; i++)
330 Point pt = new Point(0, 0);
331 Size size = Children[i].DesiredSize;
332 if (Children[i].GetType() == typeof(Connector))
334 ((UIElement)Children[i]).Arrange(new Rect(pt, size));
338 pt = FreeFormPanel.GetLocation(Children[i]);
339 ((UIElement)Children[i]).Arrange(new Rect(pt, size));
341 if (width < (size.Width + pt.X))
343 width = size.Width + pt.X;
345 if (height < (size.Height + pt.Y))
347 height = size.Height + pt.Y;
350 width = (width < this.MinWidth) ? this.MinWidth : width;
351 width = (width < this.Width) ? (this.Width < Double.MaxValue ? this.Width : width) : width;
353 height = (height < this.MinHeight) ? this.MinHeight : height;
354 height = (height < this.Height) ? (this.Height < Double.MaxValue ? this.Height : height) : height;
356 return new Size(width, height);
359 protected override Size MeasureOverride(Size availableSize)
361 bool isOutmostPanel = this.IsOutmostPanel();
362 base.MeasureOverride(availableSize);
365 this.MeasureChildren(out height, out width);
366 if (this.RequiredSizeChanged != null)
368 this.RequiredSizeChanged(this, new RequiredSizeChangedEventArgs(new Size(width, height)));
370 this.RequiredWidth = width;
371 this.RequiredHeight = height;
375 Action MeasureConnectors = () =>
377 //This action will execute at Input priority.
378 //Enabling measuring on Connectors and forcing a MeasureOverride by calling InvalidateMeasure.
379 this.measureConnectors = true;
380 this.InvalidateMeasure();
382 if (!measureConnectorsPosted)
384 this.Dispatcher.BeginInvoke(DispatcherPriority.Input, MeasureConnectors);
385 measureConnectorsPosted = true;
387 if (measureConnectors)
389 measureConnectors = false;
390 measureConnectorsPosted = false;
393 width = (width < this.Width) ? (this.Width < Double.MaxValue ? this.Width : width) : width;
394 height = (height < this.Height) ? (this.Height < Double.MaxValue ? this.Height : height) : height;
395 return new Size(width, height);
398 private void MeasureChildren(out double height, out double width)
402 Point pt = new Point(0, 0);
403 bool isOutmostPanel = this.IsOutmostPanel();
404 foreach (UIElement child in Children)
406 Connector connectorChild = child as Connector;
407 if (connectorChild != null && isOutmostPanel)
409 pt = new Point(0, 0);
411 if (measureConnectors)
413 Point srcPoint = FreeFormPanel.GetLocationRelativeToOutmostPanel(FreeFormPanel.GetSourceConnectionPoint(connectorChild));
414 Point destPoint = FreeFormPanel.GetLocationRelativeToOutmostPanel(FreeFormPanel.GetDestinationConnectionPoint(connectorChild));
415 if (connectorChild.Points.Count == 0 || !this.Disabled &&
416 ((DesignerGeometryHelper.ManhattanDistanceBetweenPoints(connectorChild.Points[0], srcPoint) > ConnectorRouter.EndPointTolerance)
417 || (DesignerGeometryHelper.ManhattanDistanceBetweenPoints(connectorChild.Points[connectorChild.Points.Count - 1], destPoint) > ConnectorRouter.EndPointTolerance)))
419 connectorChild.Points = new PointCollection();
420 RoutePolyLine(connectorChild);
422 connectorChild.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
429 else //Measure non-connector elements.
431 child.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
432 if (!child.DesiredSize.Equals(((Size)FreeFormPanel.GetChildSize(child))))
434 FreeFormPanel.SetChildSize(child, child.DesiredSize);
436 pt = FreeFormPanel.GetLocation(child);
437 if (!IsLocationValid(pt))
439 pt = new Point(LeftStackingMargin, lastYPosition);
440 OnLocationChanged(child, new LocationChangedEventArgs(pt));
441 FreeFormPanel.SetLocation(child, pt);
442 lastYPosition += child.DesiredSize.Height + VerticalStackingDistance;
445 if (height < child.DesiredSize.Height + pt.Y)
447 height = child.DesiredSize.Height + pt.Y;
449 if (width < child.DesiredSize.Width + pt.X)
451 width = child.DesiredSize.Width + pt.X;
455 width = (width < this.MinWidth) ? this.MinWidth : width;
456 height = (height < this.MinHeight) ? this.MinHeight : height;
459 static bool IsLocationValid(Point location)
461 return location.X >= 0 && location.Y >= 0;
464 void OnLocationChanged(Object sender, LocationChangedEventArgs e)
466 if (LocationChanged != null)
468 LocationChanged(sender, e);
472 protected override void OnMouseLeave(MouseEventArgs e)
474 if (e != null && !this.Disabled && this.IsOutmostPanel())
476 if (connectorEditor != null && connectorEditor.BeingEdited
477 && Mouse.DirectlyOver != null
478 && !(Mouse.DirectlyOver is ConnectionPointsAdorner))
480 SaveConnectorEditor(e.GetPosition(this));
483 base.OnMouseLeave(e);
486 protected override void OnMouseMove(System.Windows.Input.MouseEventArgs e)
488 if (e != null && !this.Disabled && this.IsOutmostPanel())
490 if (this.AutoConnectEnabled)
492 this.RemoveAutoConnectAdorner();
494 if (e.LeftButton == MouseButtonState.Pressed)
496 if (connectorEditor != null && connectorEditor.BeingEdited)
498 AutoScrollHelper.AutoScroll(e, this, 1);
499 connectorEditor.Update(e.GetPosition(this));
507 protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
509 if (e != null && !this.Disabled && this.IsOutmostPanel())
511 if (connectorEditor != null && connectorEditor.BeingEdited)
513 SaveConnectorEditor(e.GetPosition(this));
516 base.OnMouseLeftButtonUp(e);
519 public void RemoveConnectorEditor()
521 if (connectorEditor != null)
523 connectorEditor.Remove();
524 connectorEditor = null;
529 protected override void OnKeyDown(KeyEventArgs e)
531 if (e != null && !this.Disabled && this.IsOutmostPanel())
533 if (connectorEditor != null && connectorEditor.BeingEdited)
535 if (e.Key == Key.Escape)
537 //If escape key is hit while dragging a connector, end dragging.
539 Connector affectedConnector = connectorEditor.Connector;
540 RemoveConnectorEditor();
541 this.connectorEditor = new ConnectorEditor(this, affectedConnector);
544 // Ignore all other Keyboard input when rerouting connector
552 static bool ShouldCreateNewConnectorEditor(MouseButtonEventArgs e)
554 Connector connector = e.Source as Connector;
555 // Don't create new connector editor when clicking on the start dot.
556 if (connector == null || (connector.StartDot != null && connector.StartDot.IsAncestorOf(e.MouseDevice.DirectlyOver as DependencyObject)))
563 protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
565 if (e != null && !this.Disabled && this.IsOutmostPanel() && e.ClickCount == 1)
567 //If one of the edit points is clicked, update the connector editor.
568 if ((connectorEditor != null) && connectorEditor.EditPointsHitTest(e.GetPosition(this)))
570 connectorEditor.Update(e.GetPosition(this));
573 else if (ShouldCreateNewConnectorEditor(e))
575 CreateNewConnectorEditor(e);
578 base.OnPreviewMouseLeftButtonDown(e);
581 void CreateNewConnectorEditor(MouseButtonEventArgs e)
583 if (connectorEditor == null || !e.Source.Equals(connectorEditor.Connector))
585 //If user clicks anywhere other than the connector editor, destroy it.
586 RemoveConnectorEditor();
587 if (typeof(Connector).IsAssignableFrom(e.Source.GetType()))
589 this.connectorEditor = new ConnectorEditor(this, e.Source as Connector);
594 //Calls the Line routing algorithm and populates the points collection of the connector.
595 void RoutePolyLine(Connector connector)
597 Point[] pts = ConnectorRouter.Route(this, FreeFormPanel.GetSourceConnectionPoint(connector), FreeFormPanel.GetDestinationConnectionPoint(connector));
598 List<Point> points = new List<Point>(pts);
601 UpdateConnectorPoints(connector, points);
606 //Connector editing is complete, save the final connectorEditor state into the connector.
607 void SaveConnectorEditor(Point pt)
609 bool isConnectionEndPointMoved = !connectorEditor.Persist(pt);
611 if (this.ConnectorMoved != null)
613 Connector connector = this.connectorEditor.Connector;
614 List<Point> points = this.connectorEditor.ConnectorEditorLocation;
615 ConnectorMoved(connector, new ConnectorMovedEventArgs(points));
618 if (isConnectionEndPointMoved)
620 //Persist will return false, when the ConnectionEndPoint has been moved.
621 RemoveConnectorEditor();
625 this.InvalidateMeasure();