//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------
namespace System.Activities.Core.Presentation
{
using System.Activities;
using System.Activities.Presentation;
using System.Activities.Presentation.Hosting;
using System.Activities.Presentation.Internal.PropertyEditing;
using System.Activities.Presentation.Metadata;
using System.Activities.Presentation.Model;
using System.Activities.Presentation.View;
using System.Activities.Statements;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
[ActivityDesignerOptions(AlwaysCollapseChildren = true)]
partial class FlowchartDesigner
{
public static readonly DependencyProperty ConnectionPointsProperty = DependencyProperty.RegisterAttached("ConnectionPoints", typeof(List), typeof(FlowchartDesigner), new FrameworkPropertyMetadata());
public static readonly DependencyProperty LinkModelItemProperty = DependencyProperty.RegisterAttached("LinkModelItem", typeof(ModelItem), typeof(FlowchartDesigner), new FrameworkPropertyMetadata());
public static readonly DependencyProperty FlowElementModelItemProperty = DependencyProperty.RegisterAttached("FlowElementModelItem", typeof(ModelItem), typeof(FlowchartDesigner), new FrameworkPropertyMetadata());
public static readonly DependencyProperty FlowchartWidthProperty = DependencyProperty.Register("FlowchartWidth", typeof(double), typeof(FlowchartDesigner), new FrameworkPropertyMetadata());
public static readonly DependencyProperty FlowchartHeightProperty = DependencyProperty.Register("FlowchartHeight", typeof(double), typeof(FlowchartDesigner), new FrameworkPropertyMetadata());
public static readonly DependencyProperty TrueConnectionPointProperty = DependencyProperty.RegisterAttached("TrueConnectionPoint", typeof(ConnectionPoint), typeof(FlowchartDesigner), new FrameworkPropertyMetadata());
public static readonly DependencyProperty FalseConnectionPointProperty = DependencyProperty.RegisterAttached("FalseConnectionPoint", typeof(ConnectionPoint), typeof(FlowchartDesigner), new FrameworkPropertyMetadata());
public static readonly DependencyProperty ShowAllConditionsProperty = DependencyProperty.Register("ShowAllConditions", typeof(bool), typeof(FlowchartDesigner));
public static readonly RoutedCommand SetAsStartNodeCommand = new RoutedCommand("SetAsStartNode", typeof(FlowchartDesigner));
//public static readonly RoutedCommand ConnectNodesCommand = new RoutedCommand("ConnectNodes", typeof(FlowchartDesigner));
public static readonly RoutedCommand ShowAllConditionsCommand = new RoutedCommand("ShowAllConditionsCommand", typeof(FlowchartDesigner));
public static readonly RoutedCommand HideAllConditionsCommand = new RoutedCommand("HideAllConditionsCommand", typeof(FlowchartDesigner));
const double flowElementCaptionFontSize = 11;
const double DebugTimeMaxConnectorShapeDist = 10;
static readonly FontFamily flowElementCaptionFontFamily = new FontFamily("Tohoma");
static readonly FontStyle flowElementCaptionFontStyle = new FontStyle();
static readonly Typeface flowElementCaptionTypeface = new Typeface("Tohoma");
internal const double GridSize = 10;
internal Dictionary modelElement;
//Consider FlowStep.Action = SomeActivity. FlowStep modelItem is referred as FlowNodeMI, SomeActivity modelItem is shapeMI and the designer for SomeActivity is the shape on canvas.
//To go from the FlowNodeMI to the shape on canvas, we can use the path: FlowNodeMI(FlowStep.Action)-> shapeMI (modelElement Dictionary)-> Actual UIElement shape
//However this path does not always work. For instance in delete case: FlowStep.Action is set to null to update the ModelItem.Parents property on the shapeMI
//flowNodeToUIElement dictionary is used to solve this problem.
Dictionary flowNodeToUIElement;
const double startSymbolTopMargin = 10.0;
const string shapeLocation = "ShapeLocation";
const string shapeSize = "ShapeSize";
const string TrueConnectorViewStateKey = "TrueConnector";
const string FalseConnectorViewStateKey = "FalseConnector";
const string CaseViewStateKeyAppendString = "Connector";
const string FlowSwitchDefaultViewStateKey = "Default";
const string ConnectorViewStateKey = "ConnectorLocation";
static Color ConnectionPointColor = Colors.LightGray;
UIElement lastConnectionPointMouseUpElement = null;
//shapeLocations is useful to avoid pasting on existing shapes.
//This is populated in 2 cases 1. When the shape with existing Viewstate is added 2. On ViewState changed.
HashSet shapeLocations = null;
//selectedConnector is a placeholder for the last connector selected.
//This removes the need for a dictionary mapping modelitem to connector for deletion.
//This will change if in future we plan to support multi-select + delete.
Connector selectedConnector;
//srcConnectionPoint is required for link addition gesture to store the source of the link.
ConnectionPoint srcConnectionPoint;
bool internalViewStateChange = false;
bool startNodeAdded = false;
internal FreeFormPanel panel = null;
AdornerLayer adornerLayer;
MenuItem setAsStartNode;
public FlowchartDesigner()
{
InitializeComponent();
this.modelElement = new Dictionary();
this.flowNodeToUIElement = new Dictionary();
this.shapeLocations = new HashSet();
this.selectedConnector = null;
ConstructSetAsStartNodeMenuItem();
this.Loaded += (s, e) =>
{
if (this.ShowExpanded)
{
((ICompositeViewEvents)this).RegisterDefaultCompositeView(this);
}
DesignerView designerView = this.Context.Services.GetService() as DesignerView;
if (!designerView.ContextMenu.Items.Contains(setAsStartNode))
{
designerView.ContextMenu.Items.Add(setAsStartNode);
}
WorkflowCommandExtensionItem item = this.Context.Items.GetValue();
if (item != null)
{
if (item.CommandExtensionCallback is DefaultCommandExtensionCallback)
{
this.InputBindings.Add(new KeyBinding(FlowchartDesignerCommands.ConnectNodesCommand, new DefaultCommandExtensionCallback.ChordKeyGesture(Key.E, Key.F)));
}
}
};
this.Unloaded += (s, e) =>
{
if (object.Equals(this.DefaultCompositeView, this))
{
((ICompositeViewEvents)this).UnregisterDefaultCompositeView(this);
}
DesignerView designerView = this.Context.Services.GetService() as DesignerView;
designerView.ContextMenu.Items.Remove(setAsStartNode);
};
}
public static double FlowNodeCaptionFontSize
{
get { return flowElementCaptionFontSize; }
}
public static FontFamily FlowNodeCaptionFontFamily
{
get { return flowElementCaptionFontFamily; }
}
public static FontStyle FlowNodeCaptionFontStyle
{
get { return flowElementCaptionFontStyle; }
}
public static Typeface FlowElementCaptionTypeface
{
get { return flowElementCaptionTypeface; }
}
void OnSetAsStartNodeCommandCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
//The condition is necessary so that the child flowchart inside a flowchart doesn't try to handle the event.
if (!object.Equals(e.Source, this))
{
e.CanExecute = !this.IsReadOnly;
e.Handled = true;
}
}
void OnSetAsStartNodeCommandExecuted(object sender, ExecutedRoutedEventArgs e)
{
ModelItem selection = this.Context.Items.GetValue().PrimarySelection;
Fx.Assert(this.modelElement.ContainsKey(selection), "Selection is not contained in this container");
this.ModelItem.Properties["StartNode"].SetValue(this.GetFlowElementMI(selection));
e.Handled = true;
}
void ConstructSetAsStartNodeMenuItem()
{
setAsStartNode = new MenuItem();
setAsStartNode.Command = FlowchartDesigner.SetAsStartNodeCommand;
setAsStartNode.Header = this.SetAsStartNodeMenuItemHeader;
setAsStartNode.Visibility = Visibility.Collapsed;
setAsStartNode.Loaded += new RoutedEventHandler(OnSetAsStartNodeLoaded);
//AutomationProperties
setAsStartNode.SetValue(System.Windows.Automation.AutomationProperties.AutomationIdProperty, "SetAsStartNodeMenuItem");
}
string SetAsStartNodeMenuItemHeader
{
get { return (string)this.FindResource("SetAsStartNodeMenuItemHeader"); }
}
void OnSetAsStartNodeLoaded(object sender, RoutedEventArgs e)
{
MenuItem setAsStartNodeMenuItem = sender as MenuItem;
setAsStartNodeMenuItem.Visibility = Visibility.Collapsed;
Selection selection = this.Context.Items.GetValue();
if (selection.SelectionCount == 1 && this.modelElement.ContainsKey(selection.PrimarySelection))
{
setAsStartNodeMenuItem.Visibility = Visibility.Visible;
}
e.Handled = true;
}
public static void RegisterMetadata(AttributeTableBuilder builder)
{
Type type = typeof(Flowchart);
builder.AddCustomAttributes(type, new DesignerAttribute(typeof(FlowchartDesigner)));
builder.AddCustomAttributes(type, type.GetProperty("StartNode"), BrowsableAttribute.No);
builder.AddCustomAttributes(type, type.GetProperty("Nodes"), BrowsableAttribute.No);
builder.AddCustomAttributes(type, type.GetProperty("Variables"), BrowsableAttribute.No);
builder.AddCustomAttributes(type, new FeatureAttribute(typeof(FlowchartSizeFeature)));
type = typeof(FlowStep);
builder.AddCustomAttributes(type, type.GetProperty("Action"), BrowsableAttribute.No);
builder.AddCustomAttributes(type, type.GetProperty("Next"), BrowsableAttribute.No);
CutCopyPasteHelper.AddDisallowedTypeForCopy(typeof(FlowStart));
}
//Unregister all events. Reset startNodeAdded to enable reuse of the designer.
void CleanupFlowchart()
{
this.startNodeAdded = false;
this.panel.Children.Clear();
this.flowNodeToUIElement.Clear();
// Cleaning up the designers as they might be re-used.
foreach (UIElement element in this.modelElement.Values)
{
element.MouseEnter -= new MouseEventHandler(ChildElement_MouseEnter);
element.MouseLeave -= new MouseEventHandler(ChildElement_MouseLeave);
}
this.panel.LocationChanged -= new LocationChangedEventHandler(OnFreeFormPanelLocationChanged);
this.panel.ConnectorMoved -= new ConnectorMovedEventHandler(OnFreeFormPanelConnectorMoved);
this.panel.LayoutUpdated -= new EventHandler(OnFreeFormPanelLayoutUpdated);
this.panel.RequiredSizeChanged -= new RequiredSizeChangedEventHandler(OnFreeFormPanelRequiredSizeChanged);
this.panel = null;
ModelTreeManager modelTreeManager = (this.ModelItem as IModelTreeItem).ModelTreeManager;
modelTreeManager.EditingScopeCompleted -= new EventHandler(ModelTreeManager_EditingScopeCompleted);
this.ViewStateService.ViewStateChanged -= new ViewStateChangedEventHandler(OnViewStateChanged);
}
void OnFreeFormPanelLoaded(object sender, RoutedEventArgs eventArgs)
{
//Adding the following check because of 137896: Inside tab control multiple Loaded events happen without an Unloaded event.
if(this.panel != null)
{
CleanupFlowchart();
}
this.panel = (FreeFormPanel)sender;
if (this.ShowExpanded)
{
PopulateFlowchartChildren();
}
}
void OnFreeFormPanelUnLoaded(object sender, RoutedEventArgs eventArgs)
{
Fx.Assert(object.Equals(sender, this.panel), "Unknown panel unloaded");
CleanupFlowchart();
}
void PopulateFlowchartChildren()
{
Fx.Assert(this.ShowExpanded, "This method should be called only when the flowchart designer is shown expanded.");
Fx.Assert(this.panel != null, "panel cannot be null");
this.panel.LocationChanged += new LocationChangedEventHandler(OnFreeFormPanelLocationChanged);
this.panel.ConnectorMoved += new ConnectorMovedEventHandler(OnFreeFormPanelConnectorMoved);
this.panel.LayoutUpdated += new EventHandler(OnFreeFormPanelLayoutUpdated);
this.panel.RequiredSizeChanged += new RequiredSizeChangedEventHandler(OnFreeFormPanelRequiredSizeChanged);
DesignerPerfEventProvider perfEventProvider = this.Context.Services.GetService();
perfEventProvider.FlowchartDesignerLoadStart();
ModelTreeManager modelTreeManager = (this.ModelItem as IModelTreeItem).ModelTreeManager;
modelTreeManager.EditingScopeCompleted += new EventHandler(ModelTreeManager_EditingScopeCompleted);
this.ViewStateService.ViewStateChanged += new ViewStateChangedEventHandler(OnViewStateChanged);
this.startNodeAdded = false;
panel.Children.Clear();
this.modelElement.Clear();
this.flowNodeToUIElement.Clear();
this.shapeLocations.Clear();
this.FlowchartWidth = (double)TypeDescriptor.GetProperties(this.ModelItem)[FlowchartSizeFeature.WidthPropertyName].GetValue(this.ModelItem);
this.FlowchartHeight = (double)TypeDescriptor.GetProperties(this.ModelItem)[FlowchartSizeFeature.HeightPropertyName].GetValue(this.ModelItem);
CreateStartSymbol();
AddFlowElementsToDesigner(this.ModelItem.Properties["Nodes"].Collection);
perfEventProvider.FlowchartDesignerLoadEnd();
}
//This is to keep this.selectedConnector upto date.
//Eg. cases included 1. create a link, select it and undo, 2. Move a link from one shape to another.
void OnFreeFormPanelLayoutUpdated(object sender, EventArgs e)
{
if (!this.panel.Children.Contains(this.selectedConnector))
{
this.selectedConnector = null;
}
}
public UIElement StartSymbol { get; set; }
internal static List GetConnectionPoints(DependencyObject obj)
{
return (List)obj.GetValue(FlowchartDesigner.ConnectionPointsProperty);
}
internal static ConnectionPoint GetFalseConnectionPoint(DependencyObject obj)
{
return (ConnectionPoint)obj.GetValue(FlowchartDesigner.FalseConnectionPointProperty);
}
internal static ModelItem GetLinkModelItem(DependencyObject obj)
{
return (ModelItem)obj.GetValue(FlowchartDesigner.LinkModelItemProperty);
}
internal static ModelItem GetFlowElementModelItem(DependencyObject obj)
{
return (ModelItem)obj.GetValue(FlowchartDesigner.FlowElementModelItemProperty);
}
internal static ConnectionPoint GetTrueConnectionPoint(DependencyObject obj)
{
return (ConnectionPoint)obj.GetValue(FlowchartDesigner.TrueConnectionPointProperty);
}
public double FlowchartWidth
{
get { return (double)this.GetValue(FlowchartDesigner.FlowchartWidthProperty); }
set { this.SetValue(FlowchartDesigner.FlowchartWidthProperty, value); }
}
public double FlowchartHeight
{
get { return (double)this.GetValue(FlowchartDesigner.FlowchartHeightProperty); }
set { this.SetValue(FlowchartDesigner.FlowchartHeightProperty, value); }
}
public bool ShowAllConditions
{
get { return (bool)GetValue(ShowAllConditionsProperty); }
set { SetValue(ShowAllConditionsProperty, value); }
}
ModelItem GetFlowElementMI(ModelItem shapeModelItem)
{
Fx.Assert(this.modelElement.ContainsKey(shapeModelItem), "The ModelItem does not exist.");
UIElement element = this.modelElement[shapeModelItem];
ModelItem flowElementMI = FlowchartDesigner.GetFlowElementModelItem(element);
Fx.Assert(flowElementMI != null, "FlowNode dependency property not set.");
return flowElementMI;
}
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
}
//Returns actual link destination - Activity ModelItem in case of a FlowStep.
ModelItem GetCorrespondingElementOnCanvas(ModelItem model)
{
ModelItem destModelItem = model;
if (typeof(FlowStep).IsAssignableFrom(model.ItemType)
&& model.Properties["Action"].Value != null)
{
destModelItem = model.Properties["Action"].Value;
}
if (typeof(Flowchart) == model.ItemType)
{
destModelItem = flowStart;
}
return destModelItem;
}
private void OnFlowchartGridMouseLeave(object sender, MouseEventArgs e)
{
bool endLinkCreation = !IsVisualHit(sender as UIElement, sender as UIElement, e.GetPosition(sender as IInputElement));
if (endLinkCreation)
{
RemoveAdorner(this.panel, typeof(LinkCreationAdorner));
this.srcConnectionPoint = null;
}
}
private void OnFlowchartGridMouseMove(object sender, MouseEventArgs e)
{
if (this.srcConnectionPoint != null)
{
AutoScrollHelper.AutoScroll(e, this);
Point[] points = ConnectorRouter.Route(this.panel, this.srcConnectionPoint, e.GetPosition(this.panel));
List segments = new List(points);
//Remove the previous adorner.
RemoveAdorner(this.panel, typeof(LinkCreationAdorner));
//Add new adorner.
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(this.srcConnectionPoint.ParentDesigner);
Fx.Assert(adornerLayer != null, "Adorner Layer does not exist");
LinkCreationAdorner newAdorner = new LinkCreationAdorner(this.panel, segments);
adornerLayer.Add(newAdorner);
e.Handled = true;
}
}
private void OnFlowchartGridMouseUp(object sender, MouseButtonEventArgs e)
{
if (this.srcConnectionPoint != null)
{
RemoveAdorner(this.panel, typeof(LinkCreationAdorner));
this.srcConnectionPoint = null;
}
}
static void SetConnectionPoints(DependencyObject obj, List connectionPoints)
{
obj.SetValue(FlowchartDesigner.ConnectionPointsProperty, connectionPoints);
}
static void SetFalseConnectionPoint(DependencyObject obj, ConnectionPoint connectionPoint)
{
obj.SetValue(FlowchartDesigner.FalseConnectionPointProperty, connectionPoint);
}
static void SetLinkModelItem(DependencyObject obj, ModelItem modelItem)
{
obj.SetValue(FlowchartDesigner.LinkModelItemProperty, modelItem);
}
static void SetFlowElementModelItem(DependencyObject obj, ModelItem modelItem)
{
obj.SetValue(FlowchartDesigner.FlowElementModelItemProperty, modelItem);
}
static void SetTrueConnectionPoint(DependencyObject obj, ConnectionPoint connectionPoint)
{
obj.SetValue(FlowchartDesigner.TrueConnectionPointProperty, connectionPoint);
}
void ChildElement_MouseEnter(object sender, MouseEventArgs e)
{
Fx.Assert(this.panel != null, "This code should not be hit if panel is null");
VirtualizedContainerService.VirtualizingContainer senderElement = sender as VirtualizedContainerService.VirtualizingContainer;
if ((senderElement != null || sender is FlowchartStart) && !this.IsReadOnly)
{
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer((Visual)sender);
Fx.Assert(adornerLayer != null, "Cannot get AdornerLayer.");
ConnectionPointsAdorner adorner = null;
if (sender is FlowchartStart)
{
adorner = new ConnectionPointsAdorner((UIElement)sender, ConnectionPointsToShow((UIElement)sender, this.ModelItem), false);
}
else
{
bool isSenderElementSelected = (((Selection)this.Context.Items.GetValue()).SelectedObjects as ICollection).Contains(senderElement.ModelItem);
adorner = new ConnectionPointsAdorner(senderElement, ConnectionPointsToShow(senderElement, senderElement.ModelItem), isSenderElementSelected);
}
adornerLayer.Add(adorner);
adorner.MouseDown += new MouseButtonEventHandler(ConnectionPoint_MouseDown);
adorner.MouseUp += new MouseButtonEventHandler(ConnectionPoint_MouseUp);
adorner.MouseLeave += new MouseEventHandler(ConnectionPoint_MouseLeave);
}
}
//This method returns which connection points should be shown on hover of a shape.
List ConnectionPointsToShow(UIElement element, ModelItem model)
{
bool isInComingConnection = false;
//This condition checks if it is an incoming connection.
if (this.srcConnectionPoint != null || (this.panel.connectorEditor != null && this.panel.connectorEditor.IsConnectorEndBeingMoved))
{
isInComingConnection = true;
}
List connectionPointsToShow = new List();
if (GenericFlowSwitchHelper.IsGenericFlowSwitch(model.ItemType))
{
connectionPointsToShow.AddRange(FlowchartDesigner.GetConnectionPoints(element));
}
else if (typeof(FlowDecision).IsAssignableFrom(model.ItemType))
{
if (isInComingConnection)
{
connectionPointsToShow.AddRange(FlowchartDesigner.GetConnectionPoints(element));
}
else
{
connectionPointsToShow.Add(FlowchartDesigner.GetTrueConnectionPoint(element));
connectionPointsToShow.Add(FlowchartDesigner.GetFalseConnectionPoint(element));
List outGoingConnectors = GetOutGoingConnectors(element);
if (this.panel.connectorEditor != null && this.panel.connectorEditor.IsConnectorStartBeingMoved)
{
//If the start of an outgoing connector is moved, its not an outgoing connector any more.
outGoingConnectors.Remove(this.panel.connectorEditor.Connector);
}
//Do not show True/False connection point if a link already exists.
foreach (Connector connector in outGoingConnectors)
{
connectionPointsToShow.Remove(FreeFormPanel.GetSourceConnectionPoint(connector));
}
}
}
else// Case where only one out going connector is allowed - Start and FlowStep.
{
ConnectionPointType allowedType = ConnectionPointType.Default;
bool isConnectionAllowed = false;
if (isInComingConnection)
{
allowedType = ConnectionPointType.Incoming;
isConnectionAllowed = true;
}
else
{
List outGoingConnectors = GetOutGoingConnectors(element);
if (this.panel.connectorEditor != null && this.panel.connectorEditor.IsConnectorStartBeingMoved)
{
outGoingConnectors.Remove(this.panel.connectorEditor.Connector);
}
//Outgoing Connection is allowed only if there are no outgoing connectors already.
if (outGoingConnectors.Count == 0)
{
allowedType = ConnectionPointType.Outgoing;
isConnectionAllowed = true;
}
}
if (isConnectionAllowed)
{
foreach (ConnectionPoint connPoint in FlowchartDesigner.GetConnectionPoints(element))
{
if (connPoint.PointType == allowedType || connPoint.PointType == ConnectionPointType.Default)
{
connectionPointsToShow.Add(connPoint);
}
}
}
}
//Do not show the connection points of a selected connector.
if (this.selectedConnector != null)
{
connectionPointsToShow.Remove(FreeFormPanel.GetSourceConnectionPoint(this.selectedConnector));
connectionPointsToShow.Remove(FreeFormPanel.GetDestinationConnectionPoint(this.selectedConnector));
}
return connectionPointsToShow;
}
void ChildElement_MouseLeave(object sender, MouseEventArgs e)
{
Fx.Assert(this.panel != null, "This code should not be hit if panel is null");
bool removeConnectionPointsAdorner = true;
if (Mouse.DirectlyOver != null)
{
removeConnectionPointsAdorner = !typeof(ConnectionPointsAdorner).IsAssignableFrom(Mouse.DirectlyOver.GetType());
}
if (removeConnectionPointsAdorner)
{
RemoveAdorner(sender as UIElement, typeof(ConnectionPointsAdorner));
}
}
void ChildSizeChanged(object sender, SizeChangedEventArgs e)
{
Fx.Assert(this.panel != null, "This code should not be hit if panel is null");
VirtualizedContainerService.VirtualizingContainer container = sender as VirtualizedContainerService.VirtualizingContainer;
if (container != null || sender is FlowchartStart)
{
this.internalViewStateChange = true;
//Initializing storageModelItem for the case of FlowchartStartNode.
ModelItem storageModelItem = this.ModelItem;
if (container != null)
{
storageModelItem = GetFlowElementMI(container.ModelItem);
}
this.ViewStateService.StoreViewState(storageModelItem, shapeSize, ((UIElement)sender).DesiredSize);
this.internalViewStateChange = false;
}
}
void ConnectionPoint_MouseDown(object sender, MouseButtonEventArgs e)
{
UIElement srcElement = ((Adorner)sender).AdornedElement as UIElement;
this.srcConnectionPoint = ConnectionPointHitTest(srcElement, e.GetPosition(this.panel));
e.Handled = true;
}
void ConnectionPoint_MouseLeave(object sender, MouseEventArgs e)
{
Fx.Assert(this.panel != null, "This code should not be hit if panel is null");
UIElement adornedElement = ((Adorner)sender).AdornedElement as UIElement;
RemoveAdorner(adornedElement, typeof(ConnectionPointsAdorner));
}
void ConnectionPoint_MouseUp(object sender, MouseButtonEventArgs e)
{
Fx.Assert(this.panel != null, "This code should not be hit if panel is null");
UIElement dest = ((Adorner)sender).AdornedElement as UIElement;
Fx.Assert(dest != null, "Adorned element is not a UIElement");
if (this.srcConnectionPoint != null)
{
ConnectionPoint destConnectionPoint = ConnectionPointHitTest(dest, e.GetPosition(this.panel));
if (destConnectionPoint != null && !this.srcConnectionPoint.Equals(destConnectionPoint))
{
string errorMessage = string.Empty;
if (!CreateLinkGesture(this.srcConnectionPoint, destConnectionPoint, out errorMessage, null) && !errorMessage.Equals(string.Empty))
{
ErrorReporting.ShowErrorMessage(errorMessage);
}
}
this.srcConnectionPoint = null;
RemoveAdorner(this.panel, typeof(LinkCreationAdorner));
}
else
{
//This will cause the FreeFormPanel to handle the event and is useful while moving connection end points of a connector.
lastConnectionPointMouseUpElement = dest;
dest.RaiseEvent(e);
}
}
void OnFreeFormPanelRequiredSizeChanged(object sender, RequiredSizeChangedEventArgs e)
{
Dispatcher.BeginInvoke(() =>
{
if (e.NewRequiredSize.Width > this.FlowchartWidth)
{
this.ViewStateService.StoreViewState(this.ModelItem, FlowchartSizeFeature.WidthPropertyName, e.NewRequiredSize.Width);
}
if (e.NewRequiredSize.Height > this.FlowchartHeight)
{
this.ViewStateService.StoreViewState(this.ModelItem, FlowchartSizeFeature.HeightPropertyName, e.NewRequiredSize.Height);
}
});
}
void OnFreeFormPanelLocationChanged(object sender, LocationChangedEventArgs e)
{
Fx.Assert(this.panel != null, "This code should not be hit if panel is null");
Fx.Assert(sender is UIElement, "Sender should be of type UIElement");
Connector movedConnector = sender as Connector;
if (movedConnector != null)
{
//ViewState is undoable only when a user gesture moves a connector. If the freeformpanel routes a connector,
//the change is not undoable.
bool isUndoableViewState = false;
ModelItem linkModelItem = FlowchartDesigner.GetLinkModelItem(movedConnector);
ConnectionPoint source = FreeFormPanel.GetSourceConnectionPoint(movedConnector);
string viewStateKey = GetConnectorViewStateKey(linkModelItem, source);
ModelItem storageModelItem = GetConnectorViewStateStorageModelItem(linkModelItem);
PointCollection existingVS = this.ViewStateService.RetrieveViewState(storageModelItem, viewStateKey) as PointCollection;
if (existingVS != null && existingVS.Count > 0 && movedConnector.Points.Count > 0
&& existingVS[0].Equals(movedConnector.Points[0]) && existingVS[existingVS.Count - 1].Equals(movedConnector.Points[movedConnector.Points.Count - 1]))
{
isUndoableViewState = true;
}
StoreConnectorViewState(movedConnector, isUndoableViewState);
}
else
{
//Save the location property of each shape on the CFx object for serialization and viewstate maintenance.
//This is called only when a shape without viewstate is autolayed out by the freeform panel.
VirtualizedContainerService.VirtualizingContainer container = sender as VirtualizedContainerService.VirtualizingContainer;
if (container != null)
{
StoreShapeViewState(container, e.NewLocation);
}
}
}
void UpdateFlowchartOnLinkVisualMoved(ConnectionPoint knownConnectionPoint, Point newPoint, Connector movedConnector, bool isSourceKnown)
{
Fx.Assert(this.panel != null, "This code should not be hit if panel is null");
HitTestResult hitTestResult = VisualTreeHelper.HitTest(this.panel, newPoint);
if (hitTestResult == null)
{
return;
}
//Test if the last connectionPoint hit, is the new location for the connector.
UIElement newViewElement = null;
ConnectionPoint newConnectionPoint = null;
//The case where the link is dropped on a connectionpoint.
if (this.lastConnectionPointMouseUpElement != null)
{
newConnectionPoint = this.ConnectionPointHitTest(this.lastConnectionPointMouseUpElement, newPoint);
if (newConnectionPoint != null)
{
newViewElement = this.lastConnectionPointMouseUpElement;
}
}
//The case where the link is dropped on a shape.
if (newViewElement == null)
{
newViewElement = VisualTreeUtils.FindVisualAncestor(hitTestResult.VisualHit);
if (newViewElement == null)
{
newViewElement = VisualTreeUtils.FindVisualAncestor(hitTestResult.VisualHit);
}
}
if (newViewElement != null)
{
if (this.panel.Children.Contains(newViewElement))
{
using (EditingScope es = (EditingScope)this.ModelItem.BeginEdit(SR.FCLinkMove))
{
//Delete the existing link and keep the caseKey
IFlowSwitchLink oldCaseKey = this.DeleteLink(movedConnector);
//Create new link
bool linkCreated = false;
string errorMessage = string.Empty;
if (isSourceKnown)
{
if (newConnectionPoint == null)
{
linkCreated = CreateLinkGesture(knownConnectionPoint, newViewElement, out errorMessage, true, oldCaseKey);
}
else
{
linkCreated = CreateLinkGesture(knownConnectionPoint, newConnectionPoint, out errorMessage, true, oldCaseKey);
}
}
else
{
//If the Link source is dropped onto itself, we need to set the isLinkValidDueToLinkMove flag.
bool isLinkValidDueToLinkMove = FreeFormPanel.GetSourceConnectionPoint(movedConnector).ParentDesigner.Equals(newViewElement);
if (newConnectionPoint == null)
{
linkCreated = CreateLinkGesture(newViewElement, knownConnectionPoint, out errorMessage, isLinkValidDueToLinkMove, oldCaseKey);
}
else
{
linkCreated = CreateLinkGesture(newConnectionPoint, knownConnectionPoint, out errorMessage, isLinkValidDueToLinkMove, oldCaseKey);
}
}
if (!linkCreated)
{
if (!errorMessage.Equals(string.Empty))
{
ErrorReporting.ShowErrorMessage(errorMessage);
}
es.Revert();
}
else
{
es.Complete();
}
}
}
}
}
void OnFreeFormPanelConnectorMoved(object sender, ConnectorMovedEventArgs e)
{
Fx.Assert(this.panel != null, "This code should not be hit if panel is null");
Connector movedConnector = sender as Connector;
if (movedConnector != null)
{
Fx.Assert(e.NewConnectorLocation.Count > 0, "Invalid connector editor");
if (e.NewConnectorLocation[0].Equals(movedConnector.Points[0]))
{
//DestMoved
ConnectionPoint srcConnPoint = FreeFormPanel.GetSourceConnectionPoint(movedConnector);
Point destPoint = e.NewConnectorLocation[e.NewConnectorLocation.Count - 1];
UpdateFlowchartOnLinkVisualMoved(srcConnPoint, destPoint, movedConnector, true);
}
else
{
//srcMoved
ConnectionPoint destConnPoint = FreeFormPanel.GetDestinationConnectionPoint(movedConnector);
UpdateFlowchartOnLinkVisualMoved(destConnPoint, e.NewConnectorLocation[0], movedConnector, false);
}
}
}
MultiBinding GetConnectionPointBinding(FrameworkElement element, double widthFraction, double heightFraction)
{
Fx.Assert(element != null, "FrameworkElement is null.");
MultiBinding bindings = new MultiBinding();
Binding sizeBinding = new Binding();
sizeBinding.Source = element;
sizeBinding.Path = new PropertyPath(FreeFormPanel.ChildSizeProperty);
Binding locationBinding = new Binding();
locationBinding.Source = element;
locationBinding.Path = new PropertyPath(FreeFormPanel.LocationProperty);
bindings.Bindings.Add(sizeBinding);
bindings.Bindings.Add(locationBinding);
bindings.Converter = new ConnectionPointConverter();
bindings.ConverterParameter = new List