3c1f479b9d
Former-commit-id: 806294f5ded97629b74c85c09952f2a74fe182d9
2433 lines
94 KiB
Plaintext
2433 lines
94 KiB
Plaintext
namespace System.Workflow.ComponentModel.Design
|
|
{
|
|
using System;
|
|
using System.IO;
|
|
using System.Data;
|
|
using System.Drawing;
|
|
using System.Security;
|
|
using System.Resources;
|
|
using System.Reflection;
|
|
using System.Diagnostics;
|
|
using System.Collections;
|
|
using System.Windows.Forms;
|
|
using System.ComponentModel;
|
|
using System.Drawing.Design;
|
|
using System.Drawing.Imaging;
|
|
using System.Drawing.Printing;
|
|
using System.Drawing.Drawing2D;
|
|
using System.Workflow.Interop;
|
|
using System.Collections.Generic;
|
|
using System.Windows.Forms.Design;
|
|
using System.Security.Permissions;
|
|
using System.ComponentModel.Design;
|
|
using System.Runtime.InteropServices;
|
|
using System.Collections.ObjectModel;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
/// What did I change in this file
|
|
/// 1. Eliminated the layout manager and introduced classes for WorkflowLayout and PrintPreviewLayout
|
|
/// 2. Eliminated the event syncing of PageSetupData change. We call performlayout on the current designer service whenever the pagesetupdata changes
|
|
|
|
/// Designer Features:
|
|
/// Selection on click and thru drag rectangle
|
|
/// Reconfigurable background
|
|
/// Scrolling
|
|
/// Ensure Visible functionality
|
|
/// Accessibility
|
|
/// Zoom
|
|
/// Rehostable
|
|
/// Extensible
|
|
/// Small memory footprint
|
|
/// Ability to move around objects using drag drop
|
|
/// Ability to drop the objects using drag drop
|
|
/// Printing support
|
|
/// Theme support
|
|
/// Magnifier
|
|
/// AutoScroll
|
|
/// AutoExpand
|
|
/// USE THIS FOR PERFORMANCE TEST: Debug.WriteLine("******Root drawing: " + Convert.ToString((DateTime.Now.Ticks - ticks) / 10000) + "ms");
|
|
///
|
|
/// Here are some details about the coordinate system,
|
|
///
|
|
/// Screen CoOrdinate System: Starts at 0,0 of the screen
|
|
/// Client CoOrdinate System: Starts at 0,0 of the control
|
|
/// Logical CoOrdinate System: The workflowview supports zooming and scroll, we want to hide this
|
|
/// complexity from the activity writter and hence whenever we get a coordinate we translate it based
|
|
/// scroll position, zoom level and layout. This helps us to sheild the activity designers from complexity
|
|
/// of zooming, scaling and layouting. The designer writters deal with one coordinate system which is unscaled and
|
|
/// starts at 0,0
|
|
///
|
|
///
|
|
|
|
[ToolboxItem(false)]
|
|
[ActivityDesignerTheme(typeof(AmbientTheme), Xml = WorkflowView.ThemeXml)]
|
|
[Obsolete("The System.Workflow.* types are deprecated. Instead, please use the new types from System.Activities.*")]
|
|
public class WorkflowView : UserControl, IServiceProvider, IMessageFilter
|
|
{
|
|
#region Theme Initializer XML
|
|
internal const string ThemeXml =
|
|
"<AmbientTheme xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/workflow\"" +
|
|
" ApplyTo=\"System.Workflow.ComponentModel.Design.WorkflowView\"" +
|
|
" ShowConfigErrors=\"True\"" +
|
|
" DrawShadow=\"False\"" +
|
|
" DrawGrayscale=\"False\"" +
|
|
" DropIndicatorColor=\"0xFF006400\"" +
|
|
" SelectionForeColor=\"0xFF0000FF\"" +
|
|
" SelectionPatternColor=\"0xFF606060\"" +
|
|
" ForeColor=\"0xFF808080\"" +
|
|
" BackColor=\"0xFFFFFFFF\"" +
|
|
" ShowGrid=\"False\"" +
|
|
" GridColor=\"0xFFC0C0C0\"" +
|
|
" TextQuality=\"Aliased\"" +
|
|
" DrawRounded=\"True\"" +
|
|
" ShowDesignerBorder=\"True\"" +
|
|
" />";
|
|
#endregion
|
|
|
|
#region Members Variables
|
|
[Obsolete("The System.Workflow.* types are deprecated. Instead, please use the new types from System.Activities.*")]
|
|
private enum TabButtonIds { MultiPage = 1, Zoom, Pan }
|
|
|
|
//Designer Hookup
|
|
private IServiceProvider serviceProvider = null;
|
|
|
|
private ActivityDesigner rootDesigner = null;
|
|
|
|
//Zoom
|
|
private float zoomLevel = 1.0f;
|
|
private int shadowDepth = WorkflowTheme.CurrentTheme.AmbientTheme.ShadowDepth;
|
|
|
|
//MessageFilters
|
|
private List<WorkflowDesignerMessageFilter> stockMessageFilters = new List<WorkflowDesignerMessageFilter>();
|
|
private List<WorkflowDesignerMessageFilter> customMessageFilters = new List<WorkflowDesignerMessageFilter>();
|
|
|
|
//
|
|
|
|
private Bitmap viewPortBitmap = null;
|
|
|
|
//Misc.
|
|
private WorkflowToolTip workflowToolTip = null;
|
|
|
|
private CommandSet commandSet = null;
|
|
private DynamicAction fitAllAction = null;
|
|
|
|
//print
|
|
private int prePreviewZoom = 100;
|
|
private Point prePreviewScroll = Point.Empty;
|
|
private WorkflowPrintDocument printDocument = null;
|
|
|
|
//Active layout
|
|
private WorkflowLayout activeLayout = null;
|
|
private WorkflowLayout defaultLayout = null;
|
|
|
|
//One time callable delegates
|
|
private EventHandler layoutEventHandler = null;
|
|
private EventHandler ensureVisibleEventHandler = null;
|
|
|
|
private Stack<HitTestInfo> messageHitTestContexts = new Stack<HitTestInfo>();
|
|
|
|
private HScrollBar hScrollBar;
|
|
private VScrollBar vScrollBar;
|
|
|
|
private TabControl toolContainer;
|
|
private EventHandler idleEventListeners;
|
|
private EventHandler idleEventHandler;
|
|
|
|
private bool dragDropInProgress;
|
|
#endregion
|
|
|
|
#region Events
|
|
public event EventHandler ZoomChanged;
|
|
public event EventHandler RootDesignerChanged;
|
|
#endregion
|
|
|
|
#region Constructor and Dispose
|
|
public WorkflowView()
|
|
: this(new DesignSurface())
|
|
{
|
|
}
|
|
|
|
public WorkflowView(IServiceProvider serviceProvider)
|
|
{
|
|
Debug.Assert(serviceProvider != null);
|
|
if (serviceProvider == null)
|
|
throw new ArgumentNullException("serviceProvider");
|
|
|
|
SuspendLayout();
|
|
AllowDrop = true;
|
|
AutoScroll = false;
|
|
HScroll = false;
|
|
VScroll = false;
|
|
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint | ControlStyles.Opaque | ControlStyles.AllPaintingInWmPaint | ControlStyles.Selectable | ControlStyles.EnableNotifyMessage, true);
|
|
|
|
this.serviceProvider = serviceProvider;
|
|
|
|
//*****Promote the services which are accessed from other components
|
|
IServiceContainer serviceContainer = GetService(typeof(IServiceContainer)) as IServiceContainer;
|
|
if (serviceContainer != null)
|
|
{
|
|
//Remove any existing designer service if there is any
|
|
serviceContainer.RemoveService(typeof(WorkflowView));
|
|
serviceContainer.AddService(typeof(WorkflowView), this);
|
|
}
|
|
|
|
//set the UI Service to be used by themes
|
|
IUIService uiService = this.serviceProvider.GetService(typeof(IUIService)) as IUIService;
|
|
if (uiService != null)
|
|
WorkflowTheme.UIService = uiService;
|
|
|
|
//Make sure that we add scrollbars
|
|
EnsureScrollBars(new HScrollBar(), new VScrollBar());
|
|
|
|
//Initialize the tooltip shown
|
|
this.workflowToolTip = new WorkflowToolTip(this);
|
|
|
|
//[....] the global theme change event, which is fired by the theme infrastructure for theme change
|
|
WorkflowTheme.ThemeChanged += new EventHandler(OnThemeChange);
|
|
|
|
//Create the core message filters
|
|
PopulateMessageFilters(true);
|
|
|
|
//Set the root designer, note that the dynamic action is dependent on the DynamicActionMessageFilter pushed
|
|
//when the root is set.
|
|
RootDesigner = ActivityDesigner.GetSafeRootDesigner(this);
|
|
this.fitAllAction = CreateDynamicAction();
|
|
|
|
//If the active layout is still null then we will set the default layout as active layout
|
|
if (this.activeLayout == null || this.defaultLayout == null)
|
|
ActiveLayout = DefaultLayout = new WorkflowRootLayout(this.serviceProvider);
|
|
|
|
//Create the local command set and update all the commands once
|
|
IMenuCommandService menuCommandService = GetService(typeof(IMenuCommandService)) as IMenuCommandService;
|
|
if (menuCommandService != null)
|
|
{
|
|
this.commandSet = new CommandSet(this);
|
|
this.commandSet.UpdatePanCommands(true);
|
|
}
|
|
|
|
//Subscribe to selection change
|
|
ISelectionService selectionService = GetService(typeof(ISelectionService)) as ISelectionService;
|
|
if (selectionService != null)
|
|
selectionService.SelectionChanged += new EventHandler(OnSelectionChanged);
|
|
|
|
//In case of non VS case we need to pumpin the Keyboard messages, the user control sets
|
|
//focus to the child controls by default which is a problem so we need to trap the
|
|
//messages by adding application level message filter, in case of VS this is not required and
|
|
//the message filter is never called.
|
|
Application.AddMessageFilter(this);
|
|
|
|
//We make sure that during the construction we dont do perform layouts on idle event
|
|
ResumeLayout(true);
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
//Remove the proffered services
|
|
if (disposing)
|
|
{
|
|
try
|
|
{
|
|
SuspendLayout();
|
|
|
|
Application.RemoveMessageFilter(this);
|
|
|
|
if (this.layoutEventHandler != null)
|
|
{
|
|
Idle -= this.layoutEventHandler;
|
|
this.layoutEventHandler = null;
|
|
}
|
|
|
|
if (this.ensureVisibleEventHandler != null)
|
|
{
|
|
Idle -= this.ensureVisibleEventHandler;
|
|
this.ensureVisibleEventHandler = null;
|
|
}
|
|
|
|
if (this.idleEventHandler != null)
|
|
{
|
|
this.idleEventListeners = null;
|
|
|
|
Form host = TopLevelControl as Form;
|
|
if (!Application.MessageLoop || (host != null && host.Modal))
|
|
WorkflowTimer.Default.Unsubscribe(this.idleEventHandler);
|
|
else
|
|
Application.Idle -= this.idleEventHandler;
|
|
this.idleEventHandler = null;
|
|
}
|
|
|
|
ISelectionService selectionService = GetService(typeof(ISelectionService)) as ISelectionService;
|
|
if (selectionService != null)
|
|
selectionService.SelectionChanged -= new EventHandler(OnSelectionChanged);
|
|
|
|
//Unsubscribe the theme change
|
|
WorkflowTheme.ThemeChanged -= new EventHandler(OnThemeChange);
|
|
|
|
//Remove the dynamic action
|
|
if (this.fitAllAction != null)
|
|
{
|
|
this.fitAllAction.Dispose();
|
|
this.fitAllAction = null;
|
|
}
|
|
|
|
if (this.workflowToolTip != null)
|
|
{
|
|
((IDisposable)this.workflowToolTip).Dispose();
|
|
this.workflowToolTip = null;
|
|
}
|
|
|
|
DisposeMessageFilters(false);
|
|
DisposeMessageFilters(true);
|
|
|
|
//Dispose the layouts
|
|
this.activeLayout = null;
|
|
if (this.defaultLayout != null)
|
|
{
|
|
this.defaultLayout.Dispose();
|
|
this.defaultLayout = null;
|
|
}
|
|
|
|
//Destroy other resources
|
|
if (this.viewPortBitmap != null)
|
|
{
|
|
this.viewPortBitmap.Dispose();
|
|
this.viewPortBitmap = null;
|
|
}
|
|
|
|
if (this.commandSet != null)
|
|
{
|
|
this.commandSet.Dispose();
|
|
this.commandSet = null;
|
|
}
|
|
|
|
HScrollBar.ValueChanged -= new EventHandler(OnScroll);
|
|
VScrollBar.ValueChanged -= new EventHandler(OnScroll);
|
|
|
|
if (this.toolContainer != null)
|
|
{
|
|
Controls.Remove(this.toolContainer);
|
|
this.toolContainer.TabStrip.Tabs.Clear();
|
|
this.toolContainer.Dispose();
|
|
this.toolContainer = null;
|
|
}
|
|
|
|
IServiceContainer serviceContainer = GetService(typeof(IServiceContainer)) as IServiceContainer;
|
|
if (serviceContainer != null)
|
|
{
|
|
serviceContainer.RemoveService(typeof(WorkflowView));
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
ResumeLayout(false);
|
|
}
|
|
}
|
|
|
|
base.Dispose(disposing);
|
|
}
|
|
#endregion
|
|
|
|
#region Public Properties
|
|
public int Zoom
|
|
{
|
|
get
|
|
{
|
|
return Convert.ToInt32(this.zoomLevel * 100);
|
|
}
|
|
|
|
set
|
|
{
|
|
if (Zoom == value)
|
|
return;
|
|
|
|
if (value < AmbientTheme.MinZoom || value > AmbientTheme.MaxZoom)
|
|
throw new NotSupportedException(DR.GetString(DR.ZoomLevelException2, AmbientTheme.MinZoom, AmbientTheme.MaxZoom));
|
|
|
|
ScrollBar hScrollBar = HScrollBar;
|
|
ScrollBar vScrollBar = VScrollBar;
|
|
|
|
if (hScrollBar != null && vScrollBar != null)
|
|
{
|
|
PointF oldRelativeCenter = Point.Empty;
|
|
Point oldCenter = new Point(ScrollPosition.X, ScrollPosition.Y);
|
|
oldRelativeCenter = new PointF((float)oldCenter.X / (float)hScrollBar.Maximum, (float)oldCenter.Y / (float)vScrollBar.Maximum);
|
|
|
|
//recalculate the zoom and scroll range
|
|
this.zoomLevel = (float)value / 100.0f;
|
|
UpdateScrollRange();
|
|
|
|
//center the view again
|
|
Point newCenter = new Point((int)((float)hScrollBar.Maximum * oldRelativeCenter.X), (int)((float)vScrollBar.Maximum * oldRelativeCenter.Y));
|
|
ScrollPosition = new Point(newCenter.X, newCenter.Y);
|
|
|
|
if (this.rootDesigner != null)
|
|
this.rootDesigner.Location = this.activeLayout.RootDesignerAlignment;
|
|
|
|
InvalidateClientRectangle(Rectangle.Empty);
|
|
|
|
//
|
|
|
|
this.activeLayout.Update(null, WorkflowLayout.LayoutUpdateReason.ZoomChanged);
|
|
|
|
//force command refresh
|
|
//this is to workarond VS not refreshing Zoom drop down when doing area zoom-in
|
|
IUIService uis = GetService(typeof(IUIService)) as IUIService;
|
|
if (uis != null)
|
|
uis.SetUIDirty();
|
|
|
|
//We need to update the zoom commands when the zoom is updated
|
|
if (this.commandSet != null)
|
|
this.commandSet.UpdateZoomCommands(true);
|
|
|
|
OnZoomChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
public ActivityDesigner RootDesigner
|
|
{
|
|
get
|
|
{
|
|
return this.rootDesigner;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (this.rootDesigner == value)
|
|
return;
|
|
|
|
DisposeMessageFilters(false);
|
|
|
|
this.rootDesigner = value;
|
|
|
|
if (this.rootDesigner != null)
|
|
{
|
|
PopulateMessageFilters(false);
|
|
ActiveLayout = DefaultLayout = this.rootDesigner.SupportedLayout;
|
|
}
|
|
|
|
OnRootDesignerChanged();
|
|
|
|
base.PerformLayout();
|
|
}
|
|
}
|
|
|
|
public int ShadowDepth
|
|
{
|
|
get
|
|
{
|
|
return this.shadowDepth;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (value < AmbientTheme.MinShadowDepth || value > AmbientTheme.MaxShadowDepth)
|
|
throw new NotSupportedException(DR.GetString(DR.ShadowDepthException, AmbientTheme.MinShadowDepth, AmbientTheme.MaxShadowDepth));
|
|
|
|
if (this.shadowDepth == value)
|
|
return;
|
|
|
|
this.shadowDepth = value;
|
|
InvalidateClientRectangle(Rectangle.Empty);
|
|
}
|
|
}
|
|
|
|
public Rectangle ViewPortRectangle
|
|
{
|
|
get
|
|
{
|
|
return new Rectangle(ScrollPosition, ViewPortSize);
|
|
}
|
|
}
|
|
|
|
public Size ViewPortSize
|
|
{
|
|
get
|
|
{
|
|
Size viewPortSize = ClientSize;
|
|
if (HScrollBar.Visible)
|
|
viewPortSize.Height = Math.Max(0, viewPortSize.Height - HScrollBar.Height);
|
|
if (VScrollBar.Visible)
|
|
viewPortSize.Width = Math.Max(0, viewPortSize.Width - VScrollBar.Width);
|
|
return viewPortSize;
|
|
}
|
|
}
|
|
|
|
public Point ScrollPosition
|
|
{
|
|
get
|
|
{
|
|
return new Point(HScrollBar.Value, VScrollBar.Value);
|
|
}
|
|
|
|
set
|
|
{
|
|
ScrollBar hScrollBar = HScrollBar;
|
|
if (hScrollBar != null)
|
|
{
|
|
value.X = Math.Min(value.X, hScrollBar.Maximum - hScrollBar.LargeChange + 1);
|
|
value.X = Math.Max(value.X, hScrollBar.Minimum);
|
|
hScrollBar.Value = value.X;
|
|
}
|
|
|
|
ScrollBar vScrollBar = VScrollBar;
|
|
if (vScrollBar != null)
|
|
{
|
|
value.Y = Math.Min(value.Y, vScrollBar.Maximum - vScrollBar.LargeChange + 1);
|
|
value.Y = Math.Max(value.Y, vScrollBar.Minimum);
|
|
vScrollBar.Value = value.Y;
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool PrintPreviewMode
|
|
{
|
|
get
|
|
{
|
|
return (this.activeLayout == ((WorkflowPrintDocument)PrintDocument).PrintPreviewLayout);
|
|
}
|
|
|
|
set
|
|
{
|
|
if (PrintPreviewMode == value)
|
|
return;
|
|
|
|
if (value && PrinterSettings.InstalledPrinters.Count == 0)
|
|
{
|
|
DesignerHelpers.ShowError(this, DR.GetString(DR.ThereIsNoPrinterInstalledErrorMessage));
|
|
value = false;
|
|
}
|
|
|
|
ActiveLayout = (value) ? ((WorkflowPrintDocument)PrintDocument).PrintPreviewLayout : DefaultLayout;
|
|
|
|
if (this.commandSet != null)
|
|
this.commandSet.UpdatePageLayoutCommands(true);
|
|
|
|
if (PrintPreviewMode)
|
|
{
|
|
this.prePreviewZoom = Zoom;
|
|
this.prePreviewScroll = ScrollPosition;
|
|
Zoom = 40;
|
|
}
|
|
else
|
|
{
|
|
Zoom = this.prePreviewZoom;
|
|
ScrollPosition = this.prePreviewScroll;
|
|
}
|
|
}
|
|
}
|
|
|
|
public PrintDocument PrintDocument
|
|
{
|
|
get
|
|
{
|
|
if (this.printDocument == null)
|
|
this.printDocument = new WorkflowPrintDocument(this);
|
|
|
|
return this.printDocument;
|
|
}
|
|
}
|
|
|
|
public event EventHandler Idle
|
|
{
|
|
add
|
|
{
|
|
//Add the listener to our list
|
|
this.idleEventListeners += value;
|
|
|
|
if (this.idleEventHandler == null)
|
|
{
|
|
this.idleEventHandler = new EventHandler(OnWorkflowIdle);
|
|
|
|
Form host = TopLevelControl as Form;
|
|
if (!Application.MessageLoop || (host != null && host.Modal))
|
|
WorkflowTimer.Default.Subscribe(100, this.idleEventHandler);
|
|
else
|
|
Application.Idle += this.idleEventHandler;
|
|
}
|
|
}
|
|
|
|
remove
|
|
{
|
|
this.idleEventListeners -= value;
|
|
|
|
if (this.idleEventHandler != null && this.idleEventListeners == null)
|
|
{
|
|
Form host = TopLevelControl as Form;
|
|
if (host != null && host.Modal)
|
|
WorkflowTimer.Default.Unsubscribe(this.idleEventHandler);
|
|
else
|
|
Application.Idle -= this.idleEventHandler;
|
|
|
|
this.idleEventHandler = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
public HScrollBar HScrollBar
|
|
{
|
|
get
|
|
{
|
|
return this.hScrollBar;
|
|
}
|
|
}
|
|
|
|
public VScrollBar VScrollBar
|
|
{
|
|
get
|
|
{
|
|
return this.vScrollBar;
|
|
}
|
|
}
|
|
|
|
public bool EnableFitToScreen
|
|
{
|
|
get
|
|
{
|
|
return (this.fitAllAction != null);
|
|
}
|
|
|
|
set
|
|
{
|
|
if (EnableFitToScreen == value)
|
|
return;
|
|
|
|
if (value)
|
|
{
|
|
if (this.fitAllAction == null)
|
|
this.fitAllAction = CreateDynamicAction();
|
|
}
|
|
else
|
|
{
|
|
if (this.fitAllAction != null)
|
|
{
|
|
this.fitAllAction.Dispose();
|
|
this.fitAllAction = null;
|
|
}
|
|
}
|
|
|
|
InvalidateClientRectangle(Rectangle.Empty);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Protected Properties
|
|
#endregion
|
|
|
|
#region Private Properties
|
|
internal bool DragDropInProgress
|
|
{
|
|
get
|
|
{
|
|
return this.dragDropInProgress;
|
|
}
|
|
}
|
|
|
|
internal bool ShowToolContainer
|
|
{
|
|
get
|
|
{
|
|
return (this.toolContainer != null);
|
|
}
|
|
|
|
set
|
|
{
|
|
if (ShowToolContainer == value)
|
|
return;
|
|
|
|
try
|
|
{
|
|
SuspendLayout();
|
|
|
|
if (value)
|
|
{
|
|
this.toolContainer = new TabControl(DockStyle.Right, AnchorAlignment.Far);
|
|
Controls.Add(this.toolContainer);
|
|
EnsureScrollBars(this.hScrollBar, this.toolContainer.ScrollBar as VScrollBar);
|
|
|
|
string[,] tabButtonInfo = new string[/*Caption Resource ID*/, /*Bitmap Resource ID*/] { { "MultipageLayoutCaption", "MultipageLayout" }, { "ZoomCaption", "Zoom" }, { "PanCaption", "AutoPan" } };
|
|
for (int i = 0; i < tabButtonInfo.GetLength(0); i++)
|
|
{
|
|
Bitmap tabImage = DR.GetImage(tabButtonInfo[i, 1]) as Bitmap;
|
|
string buttonCaption = DR.GetString(tabButtonInfo[i, 0]);
|
|
this.toolContainer.TabStrip.Tabs.Add(new ItemInfo(i + 1, tabImage, buttonCaption));
|
|
}
|
|
|
|
this.toolContainer.TabStrip.TabChange += new SelectionChangeEventHandler<TabSelectionChangeEventArgs>(OnTabChange);
|
|
if (this.commandSet != null)
|
|
{
|
|
this.commandSet.UpdatePageLayoutCommands(true);
|
|
this.commandSet.UpdateZoomCommands(true);
|
|
this.commandSet.UpdatePanCommands(true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
this.toolContainer.TabStrip.TabChange -= new SelectionChangeEventHandler<TabSelectionChangeEventArgs>(OnTabChange);
|
|
this.toolContainer.TabStrip.Tabs.Clear();
|
|
|
|
Controls.Remove(this.toolContainer);
|
|
this.toolContainer.Dispose();
|
|
this.toolContainer = null;
|
|
|
|
EnsureScrollBars(this.hScrollBar, new VScrollBar());
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
ResumeLayout(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal HitTestInfo MessageHitTestContext
|
|
{
|
|
get
|
|
{
|
|
return this.messageHitTestContexts.Peek();
|
|
}
|
|
}
|
|
|
|
internal WorkflowLayout ActiveLayout
|
|
{
|
|
get
|
|
{
|
|
return this.activeLayout;
|
|
}
|
|
|
|
set
|
|
{
|
|
Debug.Assert(value != null);
|
|
if (value == null)
|
|
throw new ArgumentNullException("Layout cannot be null!");
|
|
|
|
Cursor cursor = Cursor.Current;
|
|
try
|
|
{
|
|
Cursor.Current = Cursors.WaitCursor;
|
|
|
|
this.activeLayout = value;
|
|
if (this.activeLayout != ((WorkflowPrintDocument)PrintDocument).PrintPreviewLayout)
|
|
DefaultLayout = this.activeLayout;
|
|
|
|
base.PerformLayout();
|
|
if (this.commandSet != null)
|
|
this.commandSet.UpdatePageLayoutCommands(true);
|
|
}
|
|
finally
|
|
{
|
|
Cursor.Current = cursor;
|
|
}
|
|
}
|
|
}
|
|
|
|
private WorkflowLayout DefaultLayout
|
|
{
|
|
get
|
|
{
|
|
if (this.defaultLayout == null)
|
|
this.defaultLayout = new WorkflowRootLayout(this);
|
|
return this.defaultLayout;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (value == null)
|
|
throw new ArgumentNullException(DR.GetString(DR.Error_WorkflowLayoutNull));
|
|
|
|
if (this.defaultLayout == value)
|
|
return;
|
|
|
|
if (this.defaultLayout != null)
|
|
this.defaultLayout.Dispose();
|
|
|
|
this.defaultLayout = value;
|
|
}
|
|
}
|
|
|
|
private float ScaleZoomFactor
|
|
{
|
|
get
|
|
{
|
|
return (this.zoomLevel * this.activeLayout.Scaling);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Public Methods
|
|
public void AddDesignerMessageFilter(WorkflowDesignerMessageFilter designerMessageFilter)
|
|
{
|
|
if (designerMessageFilter == null)
|
|
throw new ArgumentNullException("designerMessageFilter");
|
|
|
|
if (Capture)
|
|
Capture = false;
|
|
|
|
this.customMessageFilters.Insert(0, designerMessageFilter);
|
|
designerMessageFilter.SetParentView(this);
|
|
}
|
|
|
|
public void RemoveDesignerMessageFilter(WorkflowDesignerMessageFilter designerMessageFilter)
|
|
{
|
|
if (designerMessageFilter == null)
|
|
throw new ArgumentNullException("designerMessageFilter");
|
|
|
|
if (this.customMessageFilters.Contains(designerMessageFilter))
|
|
{
|
|
if (Capture)
|
|
Capture = false;
|
|
|
|
this.customMessageFilters.Remove(designerMessageFilter);
|
|
((IDisposable)designerMessageFilter).Dispose();
|
|
}
|
|
}
|
|
|
|
public void ShowInPlaceToolTip(string toolTipText, Rectangle toolTipRectangle)
|
|
{
|
|
if (toolTipText == null)
|
|
throw new ArgumentNullException("toolTipText");
|
|
|
|
if (toolTipRectangle.IsEmpty)
|
|
throw new ArgumentException(SR.GetString(SR.Error_EmptyToolTipRectangle));
|
|
|
|
this.workflowToolTip.SetText(toolTipText, toolTipRectangle);
|
|
}
|
|
|
|
public void ShowInfoTip(string text)
|
|
{
|
|
if (text == null)
|
|
throw new ArgumentNullException("text");
|
|
|
|
this.workflowToolTip.SetText(String.Empty, text);
|
|
}
|
|
|
|
public void ShowInfoTip(string title, string text)
|
|
{
|
|
if (title == null)
|
|
throw new ArgumentNullException("title");
|
|
|
|
if (text == null)
|
|
throw new ArgumentNullException("text");
|
|
|
|
this.workflowToolTip.SetText(title, text);
|
|
}
|
|
|
|
public void EnsureVisible(object selectableObject)
|
|
{
|
|
if (selectableObject == null)
|
|
throw new ArgumentNullException("selectableObject");
|
|
|
|
// make sure that all the parents are expanded
|
|
Activity activity = selectableObject as Activity;
|
|
while (activity != null)
|
|
{
|
|
ActivityDesigner activityDesigner = ActivityDesigner.GetDesigner(activity);
|
|
CompositeActivityDesigner parentDesigner = activityDesigner.ParentDesigner;
|
|
if (parentDesigner != null)
|
|
{
|
|
if (activityDesigner != null)
|
|
parentDesigner.EnsureVisibleContainedDesigner(activityDesigner);
|
|
activity = parentDesigner.Activity;
|
|
}
|
|
else
|
|
{
|
|
activity = null;
|
|
}
|
|
}
|
|
|
|
//this is to handle the case when we call ensure visible of a scope which currently has
|
|
//activity from the secondary flow selected. instead we should always switch to the main flow
|
|
activity = selectableObject as Activity;
|
|
if (activity != null)
|
|
{
|
|
CompositeActivityDesigner compositeDesigner = ActivityDesigner.GetDesigner(activity) as CompositeActivityDesigner;
|
|
if (compositeDesigner != null)
|
|
compositeDesigner.EnsureVisibleContainedDesigner(compositeDesigner);
|
|
}
|
|
|
|
PerformLayout(false);
|
|
|
|
if (this.ensureVisibleEventHandler == null)
|
|
{
|
|
this.ensureVisibleEventHandler = new EventHandler(OnEnsureVisible);
|
|
Idle += this.ensureVisibleEventHandler;
|
|
}
|
|
}
|
|
|
|
public void PerformLayout(bool immediateUpdate)
|
|
{
|
|
if (immediateUpdate)
|
|
{
|
|
if (this.layoutEventHandler != null)
|
|
{
|
|
Idle -= this.layoutEventHandler;
|
|
this.layoutEventHandler = null;
|
|
}
|
|
base.PerformLayout(); //invalidate rectangle really cares for the this.layoutEventHandler being null
|
|
}
|
|
else if (this.layoutEventHandler == null)
|
|
{
|
|
this.layoutEventHandler = new EventHandler(OnPerformLayout);
|
|
Idle += this.layoutEventHandler;
|
|
}
|
|
}
|
|
|
|
public void SaveViewState(Stream viewState)
|
|
{
|
|
if (viewState == null)
|
|
throw new ArgumentNullException("viewState");
|
|
|
|
IDesignerHost designerHost = (IDesignerHost)GetService(typeof(IDesignerHost));
|
|
if (designerHost == null)
|
|
throw new Exception(SR.GetString(SR.General_MissingService, typeof(IDesignerHost).FullName));
|
|
|
|
BinaryWriter writer = new BinaryWriter(viewState);
|
|
|
|
// write workflow properties
|
|
writer.Write(this.PrintPreviewMode);
|
|
writer.Write(this.Zoom);
|
|
|
|
// write components
|
|
DesignerHelpers.SerializeDesignerStates(designerHost, writer);
|
|
|
|
// write scroll position
|
|
writer.Write(this.ScrollPosition.X);
|
|
writer.Write(this.ScrollPosition.Y);
|
|
}
|
|
|
|
public void LoadViewState(Stream viewState)
|
|
{
|
|
if (viewState == null)
|
|
throw new ArgumentNullException("viewState");
|
|
|
|
bool outdated = false;
|
|
Point scrollPosition = new Point(0, 0);
|
|
|
|
IDesignerHost designerHost = (IDesignerHost)GetService(typeof(IDesignerHost));
|
|
if (designerHost == null)
|
|
throw new Exception(SR.GetString(SR.General_MissingService, typeof(IDesignerHost).FullName));
|
|
|
|
viewState.Position = 0;
|
|
|
|
BinaryReader reader = new BinaryReader(viewState);
|
|
|
|
// read workflow properties
|
|
this.PrintPreviewMode = reader.ReadBoolean();
|
|
this.Zoom = reader.ReadInt32();
|
|
try
|
|
{
|
|
// get activities
|
|
outdated = DesignerHelpers.DeserializeDesignerStates(designerHost, reader);
|
|
|
|
// we will apply the scrolling only if if there is perfect match
|
|
// between the components in the workflow and the persisted data.
|
|
// It might be different if files were updated outside of VS, or if
|
|
// VS crashes.
|
|
if (!outdated)
|
|
{
|
|
scrollPosition.X = reader.ReadInt32();
|
|
scrollPosition.Y = reader.ReadInt32();
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
// flush the layout to apply the new settings, this will set the scrollers extents
|
|
base.PerformLayout();
|
|
this.ScrollPosition = scrollPosition;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Changes zoom level on the design surface such that the entire workflow is displayed in the view
|
|
/// </summary>
|
|
public void FitToScreenSize()
|
|
{
|
|
if (HScrollBar.Maximum > ViewPortSize.Width || VScrollBar.Maximum > ViewPortSize.Height)
|
|
{
|
|
int newZoom = (int)(100.0f / ActiveLayout.Scaling * Math.Min((float)ViewPortSize.Width / (float)ActiveLayout.Extent.Width, (float)ViewPortSize.Height / (float)ActiveLayout.Extent.Height));
|
|
Zoom = Math.Min(Math.Max(newZoom, AmbientTheme.MinZoom), AmbientTheme.MaxZoom);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the zoom level to 100% so that the workflow size is restored to actial workflow size
|
|
/// </summary>
|
|
public void FitToWorkflowSize()
|
|
{
|
|
if (Zoom != 100)
|
|
Zoom = 100;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Saves workflow as image to a file based on the format specified
|
|
/// </summary>
|
|
/// <param name="imageFile">Path to file where to save the image</param>
|
|
/// <param name="imageFormat">Format in which to save the image</param>
|
|
public void SaveWorkflowImage(string imageFile, ImageFormat imageFormat)
|
|
{
|
|
if (imageFile == null)
|
|
throw new ArgumentNullException("imageFile");
|
|
|
|
if (imageFormat == null)
|
|
throw new ArgumentNullException("imageFormat");
|
|
|
|
Bitmap workflowBitmap = TakeWorkflowSnapShot();
|
|
if (workflowBitmap != null)
|
|
{
|
|
workflowBitmap.Save(imageFile, imageFormat);
|
|
workflowBitmap.Dispose();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Saves workflow as image to a stream based on encoding specified
|
|
/// </summary>
|
|
/// <param name="stream">Stream where to save the workflow</param>
|
|
/// <param name="imageFormat">Format in which to save the image</param>
|
|
public void SaveWorkflowImage(Stream stream, ImageFormat imageFormat)
|
|
{
|
|
if (stream == null)
|
|
throw new ArgumentNullException("stream");
|
|
|
|
if (imageFormat == null)
|
|
throw new ArgumentNullException("imageFormat");
|
|
|
|
Bitmap workflowBitmap = TakeWorkflowSnapShot();
|
|
if (workflowBitmap != null)
|
|
{
|
|
workflowBitmap.Save(stream, imageFormat);
|
|
workflowBitmap.Dispose();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stores the workflow image to clipboard.
|
|
/// </summary>
|
|
public void SaveWorkflowImageToClipboard()
|
|
{
|
|
Bitmap workflowBitmap = TakeWorkflowSnapShot();
|
|
if (workflowBitmap != null)
|
|
{
|
|
Clipboard.SetDataObject(workflowBitmap, true);
|
|
workflowBitmap.Dispose();
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Protected Methods
|
|
|
|
#region Overridden Methods handling UI events
|
|
#region Drawing
|
|
protected override void OnPaint(PaintEventArgs e)
|
|
{
|
|
base.OnPaint(e);
|
|
|
|
//We set the highest quality interpolation so that we do not loose the image quality
|
|
GraphicsContainer graphicsState = e.Graphics.BeginContainer();
|
|
e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
|
|
e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
|
e.Graphics.CompositingQuality = CompositingQuality.HighQuality;
|
|
|
|
bool takeWorkflowSnapShot = (this.viewPortBitmap == null || this.viewPortBitmap.Size != ViewPortSize);
|
|
if (takeWorkflowSnapShot)
|
|
{
|
|
if (this.viewPortBitmap != null)
|
|
this.viewPortBitmap.Dispose();
|
|
this.viewPortBitmap = new Bitmap(Math.Max(1, ViewPortSize.Width), Math.Max(1, ViewPortSize.Height), e.Graphics);
|
|
}
|
|
|
|
//Create viewport information and take the workflow snapshot before passing on the information to the active layout
|
|
ViewPortData viewPortData = new ViewPortData();
|
|
viewPortData.LogicalViewPort = ClientRectangleToLogical(new Rectangle(Point.Empty, ViewPortSize));
|
|
viewPortData.MemoryBitmap = this.viewPortBitmap;
|
|
viewPortData.Scaling = new SizeF(ScaleZoomFactor, ScaleZoomFactor);
|
|
viewPortData.Translation = ScrollPosition;
|
|
viewPortData.ShadowDepth = new Size(this.shadowDepth, this.shadowDepth);
|
|
viewPortData.ViewPortSize = ViewPortSize;
|
|
|
|
//capture the workflow onto in-memory bitmap
|
|
if (this.layoutEventHandler == null || takeWorkflowSnapShot)
|
|
WorkflowView.TakeWorkflowSnapShot(this, viewPortData);
|
|
|
|
//copy workflow from the bitmap onto corresponding pages on the screen
|
|
try
|
|
{
|
|
this.activeLayout.OnPaintWorkflow(e, viewPortData);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
//If a layout throws an exception then we will not draw the layout
|
|
//
|
|
Debug.WriteLine(ex);
|
|
}
|
|
|
|
//If any of the message filters throws an exception we continue to draw
|
|
using (WorkflowMessageDispatchData dispatchData = new WorkflowMessageDispatchData(this, EventArgs.Empty))
|
|
{
|
|
foreach (WorkflowDesignerMessageFilter filter in dispatchData.Filters)
|
|
{
|
|
try
|
|
{
|
|
if (((IWorkflowDesignerMessageSink)filter).OnPaintWorkflowAdornments(e, ViewPortRectangle))
|
|
break;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
//Ignore the filter throwing the exception and continue to function
|
|
Debug.WriteLine(ex);
|
|
}
|
|
}
|
|
}
|
|
|
|
e.Graphics.EndContainer(graphicsState);
|
|
|
|
e.Graphics.FillRectangle(SystemBrushes.Control, new Rectangle(Width - SystemInformation.VerticalScrollBarWidth, Height - SystemInformation.HorizontalScrollBarHeight, SystemInformation.VerticalScrollBarWidth, SystemInformation.HorizontalScrollBarHeight));
|
|
}
|
|
|
|
protected virtual void OnZoomChanged()
|
|
{
|
|
if (this.ZoomChanged != null)
|
|
this.ZoomChanged(this, EventArgs.Empty);
|
|
}
|
|
|
|
protected virtual void OnRootDesignerChanged()
|
|
{
|
|
if (this.RootDesignerChanged != null)
|
|
this.RootDesignerChanged(this, EventArgs.Empty);
|
|
}
|
|
#endregion
|
|
|
|
#region Mouse Events
|
|
protected override void OnMouseDown(MouseEventArgs e)
|
|
{
|
|
base.OnMouseDown(e);
|
|
|
|
using (WorkflowMessageDispatchData dispatchData = new WorkflowMessageDispatchData(this, e))
|
|
{
|
|
foreach (WorkflowDesignerMessageFilter filter in dispatchData.Filters)
|
|
{
|
|
if (((IWorkflowDesignerMessageSink)filter).OnMouseDown(e))
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
protected override void OnMouseMove(MouseEventArgs e)
|
|
{
|
|
base.OnMouseMove(e);
|
|
|
|
using (WorkflowMessageDispatchData dispatchData = new WorkflowMessageDispatchData(this, e))
|
|
{
|
|
foreach (WorkflowDesignerMessageFilter filter in dispatchData.Filters)
|
|
{
|
|
if (((IWorkflowDesignerMessageSink)filter).OnMouseMove(e))
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
protected override void OnMouseUp(MouseEventArgs e)
|
|
{
|
|
base.OnMouseUp(e);
|
|
|
|
using (WorkflowMessageDispatchData dispatchData = new WorkflowMessageDispatchData(this, e))
|
|
{
|
|
foreach (WorkflowDesignerMessageFilter filter in dispatchData.Filters)
|
|
{
|
|
if (((IWorkflowDesignerMessageSink)filter).OnMouseUp(e))
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
protected override void OnMouseDoubleClick(MouseEventArgs e)
|
|
{
|
|
base.OnMouseDoubleClick(e);
|
|
|
|
using (WorkflowMessageDispatchData dispatchData = new WorkflowMessageDispatchData(this, e))
|
|
{
|
|
foreach (WorkflowDesignerMessageFilter filter in dispatchData.Filters)
|
|
{
|
|
if (((IWorkflowDesignerMessageSink)filter).OnMouseDoubleClick(e))
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
protected override void OnMouseEnter(EventArgs e)
|
|
{
|
|
base.OnMouseEnter(e);
|
|
|
|
Point clientPoint = PointToClient(Control.MousePosition);
|
|
MouseEventArgs eventArgs = new MouseEventArgs(Control.MouseButtons, 1, clientPoint.X, clientPoint.Y, 0);
|
|
|
|
using (WorkflowMessageDispatchData dispatchData = new WorkflowMessageDispatchData(this, eventArgs))
|
|
{
|
|
foreach (WorkflowDesignerMessageFilter filter in dispatchData.Filters)
|
|
{
|
|
if (((IWorkflowDesignerMessageSink)filter).OnMouseEnter(eventArgs))
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
protected override void OnMouseHover(EventArgs e)
|
|
{
|
|
base.OnMouseHover(e);
|
|
|
|
Point clientPoint = PointToClient(Control.MousePosition);
|
|
MouseEventArgs eventArgs = new MouseEventArgs(Control.MouseButtons, 1, clientPoint.X, clientPoint.Y, 0);
|
|
|
|
using (WorkflowMessageDispatchData dispatchData = new WorkflowMessageDispatchData(this, eventArgs))
|
|
{
|
|
foreach (WorkflowDesignerMessageFilter filter in dispatchData.Filters)
|
|
{
|
|
if (((IWorkflowDesignerMessageSink)filter).OnMouseHover(eventArgs))
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
protected override void OnMouseLeave(EventArgs e)
|
|
{
|
|
base.OnMouseLeave(e);
|
|
|
|
using (WorkflowMessageDispatchData dispatchData = new WorkflowMessageDispatchData(this, EventArgs.Empty))
|
|
{
|
|
foreach (WorkflowDesignerMessageFilter filter in dispatchData.Filters)
|
|
{
|
|
if (((IWorkflowDesignerMessageSink)filter).OnMouseLeave())
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
protected override void OnMouseCaptureChanged(EventArgs e)
|
|
{
|
|
base.OnMouseCaptureChanged(e);
|
|
|
|
using (WorkflowMessageDispatchData dispatchData = new WorkflowMessageDispatchData(this, EventArgs.Empty))
|
|
{
|
|
foreach (WorkflowDesignerMessageFilter filter in dispatchData.Filters)
|
|
{
|
|
if (((IWorkflowDesignerMessageSink)filter).OnMouseCaptureChanged())
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
protected override void OnMouseWheel(MouseEventArgs e)
|
|
{
|
|
base.OnMouseWheel(e);
|
|
|
|
using (WorkflowMessageDispatchData dispatchData = new WorkflowMessageDispatchData(this, e))
|
|
{
|
|
foreach (WorkflowDesignerMessageFilter filter in dispatchData.Filters)
|
|
{
|
|
if (((IWorkflowDesignerMessageSink)filter).OnMouseWheel(e))
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Keyboard Events
|
|
protected override void OnKeyDown(KeyEventArgs e)
|
|
{
|
|
using (WorkflowMessageDispatchData dispatchData = new WorkflowMessageDispatchData(this, e))
|
|
{
|
|
foreach (WorkflowDesignerMessageFilter filter in dispatchData.Filters)
|
|
{
|
|
if (((IWorkflowDesignerMessageSink)filter).OnKeyDown(e))
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!e.Handled)
|
|
base.OnKeyDown(e);
|
|
}
|
|
|
|
protected override void OnKeyUp(KeyEventArgs e)
|
|
{
|
|
using (WorkflowMessageDispatchData dispatchData = new WorkflowMessageDispatchData(this, e))
|
|
{
|
|
foreach (WorkflowDesignerMessageFilter filter in dispatchData.Filters)
|
|
{
|
|
if (((IWorkflowDesignerMessageSink)filter).OnKeyUp(e))
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!e.Handled)
|
|
base.OnKeyUp(e);
|
|
}
|
|
#endregion
|
|
|
|
#region Layouting Events
|
|
protected override void OnLayout(LayoutEventArgs levent)
|
|
{
|
|
base.OnLayout(levent);
|
|
|
|
ScrollBar hScrollBar = HScrollBar;
|
|
ScrollBar vScrollBar = VScrollBar;
|
|
|
|
if (Controls.Contains(hScrollBar))
|
|
hScrollBar.Bounds = new Rectangle(0, Math.Max(0, Height - SystemInformation.HorizontalScrollBarHeight), Math.Max(Width - ((vScrollBar.Visible) ? SystemInformation.VerticalScrollBarWidth : 0), 0), SystemInformation.HorizontalScrollBarHeight);
|
|
|
|
if (Controls.Contains(vScrollBar))
|
|
vScrollBar.Bounds = new Rectangle(Math.Max(0, Width - SystemInformation.VerticalScrollBarWidth), 0, SystemInformation.VerticalScrollBarWidth, Math.Max(Height - ((hScrollBar.Visible) ? SystemInformation.HorizontalScrollBarHeight : 0), 0));
|
|
|
|
if (this.toolContainer != null)
|
|
{
|
|
this.toolContainer.Location = new Point(Width - this.toolContainer.Width, 0);
|
|
this.toolContainer.Height = Height - ((hScrollBar.Visible) ? hScrollBar.Height : 0);
|
|
}
|
|
|
|
using (WorkflowMessageDispatchData dispatchData = new WorkflowMessageDispatchData(this, levent))
|
|
{
|
|
foreach (WorkflowDesignerMessageFilter filter in dispatchData.Filters)
|
|
((IWorkflowDesignerMessageSink)filter).OnLayout(levent);
|
|
}
|
|
|
|
//Layout the designers
|
|
using (Graphics graphics = CreateGraphics())
|
|
{
|
|
this.activeLayout.Update(graphics, WorkflowLayout.LayoutUpdateReason.LayoutChanged);
|
|
|
|
if (this.rootDesigner != null)
|
|
this.rootDesigner.Location = this.activeLayout.RootDesignerAlignment;
|
|
}
|
|
|
|
//Update the scroll range and redraw
|
|
UpdateScrollRange();
|
|
InvalidateClientRectangle(Rectangle.Empty);
|
|
}
|
|
#endregion
|
|
|
|
#region DragDrop Events
|
|
protected override void OnDragEnter(DragEventArgs dragEventArgs)
|
|
{
|
|
base.OnDragEnter(dragEventArgs);
|
|
|
|
this.dragDropInProgress = true;
|
|
|
|
using (WorkflowMessageDispatchData dispatchData = new WorkflowMessageDispatchData(this, dragEventArgs))
|
|
{
|
|
foreach (WorkflowDesignerMessageFilter filter in dispatchData.Filters)
|
|
{
|
|
if (((IWorkflowDesignerMessageSink)filter).OnDragEnter(dragEventArgs))
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
protected override void OnDragOver(DragEventArgs dragEventArgs)
|
|
{
|
|
base.OnDragOver(dragEventArgs);
|
|
|
|
using (WorkflowMessageDispatchData dispatchData = new WorkflowMessageDispatchData(this, dragEventArgs))
|
|
{
|
|
foreach (WorkflowDesignerMessageFilter filter in dispatchData.Filters)
|
|
{
|
|
if (((IWorkflowDesignerMessageSink)filter).OnDragOver(dragEventArgs))
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
protected override void OnDragLeave(EventArgs e)
|
|
{
|
|
base.OnDragLeave(e);
|
|
|
|
using (WorkflowMessageDispatchData dispatchData = new WorkflowMessageDispatchData(this, EventArgs.Empty))
|
|
{
|
|
foreach (WorkflowDesignerMessageFilter filter in dispatchData.Filters)
|
|
{
|
|
if (((IWorkflowDesignerMessageSink)filter).OnDragLeave())
|
|
break;
|
|
}
|
|
}
|
|
|
|
this.dragDropInProgress = false;
|
|
}
|
|
|
|
protected override void OnDragDrop(DragEventArgs dragEventArgs)
|
|
{
|
|
base.OnDragDrop(dragEventArgs);
|
|
|
|
using (WorkflowMessageDispatchData dispatchData = new WorkflowMessageDispatchData(this, dragEventArgs))
|
|
{
|
|
foreach (WorkflowDesignerMessageFilter filter in dispatchData.Filters)
|
|
{
|
|
if (((IWorkflowDesignerMessageSink)filter).OnDragDrop(dragEventArgs))
|
|
break;
|
|
}
|
|
}
|
|
|
|
this.dragDropInProgress = false;
|
|
}
|
|
|
|
protected override void OnGiveFeedback(GiveFeedbackEventArgs gfbevent)
|
|
{
|
|
base.OnGiveFeedback(gfbevent);
|
|
|
|
using (WorkflowMessageDispatchData dispatchData = new WorkflowMessageDispatchData(this, gfbevent))
|
|
{
|
|
foreach (WorkflowDesignerMessageFilter filter in dispatchData.Filters)
|
|
{
|
|
if (((IWorkflowDesignerMessageSink)filter).OnGiveFeedback(gfbevent))
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
protected override void OnQueryContinueDrag(QueryContinueDragEventArgs qcdevent)
|
|
{
|
|
base.OnQueryContinueDrag(qcdevent);
|
|
|
|
using (WorkflowMessageDispatchData dispatchData = new WorkflowMessageDispatchData(this, qcdevent))
|
|
{
|
|
foreach (WorkflowDesignerMessageFilter filter in dispatchData.Filters)
|
|
{
|
|
if (((IWorkflowDesignerMessageSink)filter).OnQueryContinueDrag(qcdevent))
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region General Events
|
|
//Handle context menus here reason being it can come from mouse r button click
|
|
//or shift+F10, or there might be other keys too
|
|
//We need to handle the WndProc and not the OnNotifyMessage because we need to set
|
|
//the m.Result to handled (IntPtr.Zero) and dont let the base class see the message at all
|
|
//see WinOE #787 "The keyboard "key" to launch the context menu launches the menu at 0,0"
|
|
[UIPermission(SecurityAction.Assert, Window = UIPermissionWindow.AllWindows)]
|
|
[SuppressMessage("Microsoft.Security", "CA2106", Justification = "This is SecurityCritical, therefore not callable from partial trust code.")]
|
|
protected override void WndProc(ref Message m)
|
|
{
|
|
using (WorkflowMessageDispatchData dispatchData = new WorkflowMessageDispatchData(this, EventArgs.Empty))
|
|
{
|
|
foreach (WorkflowDesignerMessageFilter filter in dispatchData.Filters)
|
|
{
|
|
if (((IWorkflowDesignerMessageSink)filter).ProcessMessage(m))
|
|
break;
|
|
}
|
|
|
|
const int WM_CONTEXTMENU = 0x007B;
|
|
if (m.Msg == WM_CONTEXTMENU)
|
|
{
|
|
int LParam = (int)m.LParam;
|
|
Point location = (LParam != -1) ? new Point(LParam) : Control.MousePosition;
|
|
|
|
foreach (WorkflowDesignerMessageFilter filter in dispatchData.Filters)
|
|
{
|
|
if (((IWorkflowDesignerMessageSink)filter).OnShowContextMenu(location))
|
|
break;
|
|
}
|
|
|
|
//mark the message handled
|
|
m.Result = IntPtr.Zero;
|
|
//dont pass the message to the base but return immediatly
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (this.workflowToolTip != null && m.Msg == NativeMethods.WM_NOTIFY)
|
|
this.workflowToolTip.RelayParentNotify(ref m);
|
|
|
|
try
|
|
{
|
|
if (m.Result == IntPtr.Zero)
|
|
base.WndProc(ref m);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (e != CheckoutException.Canceled)
|
|
DesignerHelpers.ShowError(this, e);
|
|
}
|
|
}
|
|
|
|
protected override void OnControlAdded(ControlEventArgs e)
|
|
{
|
|
if (e.Control != VScrollBar && e.Control != HScrollBar && e.Control != this.toolContainer)
|
|
throw new InvalidOperationException(SR.GetString(SR.Error_InsertingChildControls));
|
|
}
|
|
|
|
protected override AccessibleObject CreateAccessibilityInstance()
|
|
{
|
|
return new WorkflowViewAccessibleObject(this);
|
|
}
|
|
#endregion
|
|
|
|
#endregion
|
|
|
|
#endregion
|
|
|
|
#region Private Methods
|
|
private void OnWorkflowIdle(object sender, EventArgs e)
|
|
{
|
|
if (this.idleEventListeners != null)
|
|
this.idleEventListeners(this, e);
|
|
}
|
|
|
|
private void UpdateLayout()
|
|
{
|
|
if (this.layoutEventHandler != null)
|
|
{
|
|
PerformLayout(true);
|
|
InvalidateClientRectangle(Rectangle.Empty);
|
|
}
|
|
}
|
|
|
|
internal void OnCommandKey(KeyEventArgs e)
|
|
{
|
|
this.OnKeyDown(e);
|
|
this.OnKeyUp(e);
|
|
}
|
|
|
|
private void OnSelectionChanged(object sender, EventArgs e)
|
|
{
|
|
if (this.commandSet != null)
|
|
this.commandSet.UpdateCommandSet();
|
|
|
|
//Make sure that the ensure visible also works when the component is selected
|
|
//from property browser dropdown
|
|
//Make sure that when there is a selection change using the property browser
|
|
//drop down we make sure that the designer associated with component selected by the user in the dropdown
|
|
//is made visible.
|
|
//To enable this functionality please note that selection change is not a good event as it will get
|
|
//fired in multiple cases, instead we should add a event in extended ui service which will do this and move
|
|
//the following code in the event handler of that event
|
|
//Ref Bug#3925
|
|
|
|
if (RootDesigner != null && RootDesigner.Activity != null)
|
|
{
|
|
ISelectionService selectionService = GetService(typeof(ISelectionService)) as ISelectionService;
|
|
if (selectionService != null && selectionService.GetComponentSelected(RootDesigner.Activity))
|
|
{
|
|
IHelpService helpService = GetService(typeof(IHelpService)) as IHelpService;
|
|
if (helpService != null)
|
|
helpService.AddContextAttribute("Keyword", RootDesigner.Activity.GetType().FullName, HelpKeywordType.F1Keyword);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnPerformLayout(object sender, EventArgs e)
|
|
{
|
|
if (this.layoutEventHandler != null)
|
|
{
|
|
Idle -= this.layoutEventHandler;
|
|
this.layoutEventHandler = null;
|
|
|
|
base.PerformLayout();
|
|
}
|
|
}
|
|
|
|
//Gets the snapshot of the entire workflow
|
|
private Bitmap TakeWorkflowSnapShot()
|
|
{
|
|
Bitmap bitmap = null;
|
|
ActivityDesigner rootDesigner = RootDesigner;
|
|
if (rootDesigner != null)
|
|
{
|
|
using (Graphics graphics = CreateGraphics())
|
|
{
|
|
ViewPortData viewPortData = new ViewPortData();
|
|
viewPortData.LogicalViewPort = new Rectangle(Point.Empty, new Size(rootDesigner.Bounds.Width + 2 * DefaultWorkflowLayout.Separator.Width, rootDesigner.Bounds.Height + 2 * DefaultWorkflowLayout.Separator.Height));
|
|
viewPortData.MemoryBitmap = new Bitmap(viewPortData.LogicalViewPort.Width, viewPortData.LogicalViewPort.Height, graphics);
|
|
viewPortData.Scaling = new SizeF(1, 1);
|
|
viewPortData.Translation = Point.Empty;
|
|
viewPortData.ShadowDepth = new Size(0, 0);
|
|
viewPortData.ViewPortSize = viewPortData.LogicalViewPort.Size;
|
|
TakeWorkflowSnapShot(this, viewPortData);
|
|
bitmap = viewPortData.MemoryBitmap;
|
|
}
|
|
}
|
|
|
|
return bitmap;
|
|
}
|
|
|
|
//This function will give snapshot of what is drawn on the screen at any point of time
|
|
//It will scale and translate the designers and drawing based on the viewport data
|
|
//We need this function in OnPaint and taking snapshot of magnifier bitmap
|
|
//At the end of this function; the ViewPortData.MemoryBitmap will contain the bitmap of the
|
|
//workflow to be drawn as per layout
|
|
internal static void TakeWorkflowSnapShot(WorkflowView workflowView, ViewPortData viewPortData)
|
|
{
|
|
//Get the drawing canvas
|
|
Bitmap memoryBitmap = viewPortData.MemoryBitmap;
|
|
Debug.Assert(memoryBitmap != null);
|
|
|
|
using (Graphics viewPortGraphics = Graphics.FromImage(memoryBitmap))
|
|
{
|
|
//We set the highest quality interpolation so that we do not loose the image quality
|
|
viewPortGraphics.SmoothingMode = SmoothingMode.HighQuality;
|
|
viewPortGraphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
|
|
|
using (PaintEventArgs eventArgs = new PaintEventArgs(viewPortGraphics, viewPortData.LogicalViewPort))
|
|
{
|
|
workflowView.ActiveLayout.OnPaint(eventArgs, viewPortData);
|
|
}
|
|
|
|
//Create the scaling matrix
|
|
Matrix transformationMatrix = new Matrix();
|
|
transformationMatrix.Scale(viewPortData.Scaling.Width, viewPortData.Scaling.Height, MatrixOrder.Prepend);
|
|
|
|
//When we draw on the viewport we draw in scaled and translated.
|
|
//So that we minimize the calls to DrawImage
|
|
//Make sure that we scale down the logical view port origin in order to take care of scaling factor
|
|
//Before we select the transform factor we make sure that logicalviewport origin is scaled down
|
|
Point[] logicalViewPortOrigin = new Point[] { viewPortData.LogicalViewPort.Location };
|
|
transformationMatrix.TransformPoints(logicalViewPortOrigin);
|
|
|
|
//For performance improvement and to eliminate one extra DrawImage...we draw the designers on the viewport
|
|
//bitmap with visual depth consideration
|
|
transformationMatrix.Translate(-logicalViewPortOrigin[0].X + viewPortData.ShadowDepth.Width, -logicalViewPortOrigin[0].Y + viewPortData.ShadowDepth.Height, MatrixOrder.Append);
|
|
|
|
//Select the transform into viewport graphics.
|
|
//Viewport bitmap has the scaled and translated designers which we then map to
|
|
//the actual graphics based on page layout
|
|
viewPortGraphics.Transform = transformationMatrix;
|
|
|
|
//Draw the designers on bitmap
|
|
if (workflowView.RootDesigner != null)
|
|
{
|
|
using (Region clipRegion = new Region())
|
|
using (GraphicsPath designerPath = ActivityDesignerPaint.GetDesignerPath(workflowView.RootDesigner, false))
|
|
{
|
|
Region oldRegion = viewPortGraphics.Clip;
|
|
|
|
//First draw the grid and rectangle with the designer clip region
|
|
clipRegion.MakeEmpty();
|
|
clipRegion.Union(designerPath);
|
|
viewPortGraphics.Clip = clipRegion;
|
|
AmbientTheme ambientTheme = WorkflowTheme.CurrentTheme.AmbientTheme;
|
|
viewPortGraphics.FillRectangle(ambientTheme.BackgroundBrush, workflowView.RootDesigner.Bounds);
|
|
if (ambientTheme.ShowGrid)
|
|
ActivityDesignerPaint.DrawGrid(viewPortGraphics, workflowView.RootDesigner.Bounds);
|
|
viewPortGraphics.Clip = oldRegion;
|
|
|
|
//Then draw the root with clip region extended
|
|
try
|
|
{
|
|
using (PaintEventArgs paintEventArgs = new PaintEventArgs(viewPortGraphics, viewPortData.LogicalViewPort))
|
|
{
|
|
((IWorkflowDesignerMessageSink)workflowView.RootDesigner).OnPaint(paintEventArgs, viewPortData.LogicalViewPort);
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
//Eat the exception thrown in draw
|
|
Debug.WriteLine(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
//Draw all the filters
|
|
|
|
|
|
using (PaintEventArgs paintArgs = new PaintEventArgs(viewPortGraphics, workflowView.RootDesigner.Bounds))
|
|
{
|
|
using (WorkflowMessageDispatchData dispatchData = new WorkflowMessageDispatchData(workflowView, EventArgs.Empty))
|
|
{
|
|
foreach (WorkflowDesignerMessageFilter filter in dispatchData.Filters)
|
|
{
|
|
try
|
|
{
|
|
if (((IWorkflowDesignerMessageSink)filter).OnPaint(paintArgs, viewPortData.LogicalViewPort))
|
|
break;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.WriteLine(e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
viewPortGraphics.Transform = new Matrix();
|
|
|
|
//Now that we have a bitmap which is bit offseted based visual depth we need to take copy of it
|
|
//This is done so as to avoid expensive DrawImage call, what I am assuming here is that time it
|
|
//will take to create a new bitmap from an existing one is less expensive in terms of speed than space
|
|
//As you just need to copy bitmap bits in memory than to perform expesive Image Drawing operation
|
|
if (!viewPortData.ShadowDepth.IsEmpty)
|
|
{
|
|
Bitmap temporaryBitmap = new Bitmap(memoryBitmap);
|
|
|
|
//THEMETODO: WE JUST NEED TO GRAYSCALE THIS, RATHER THAN DRAWING A SHADOW
|
|
//Now that we have taken a copy we will draw over the existing bitmap so that we can make it as shadow bitmap
|
|
using (Brush shadowDepthBrush = new SolidBrush(Color.FromArgb(220, Color.White)))
|
|
viewPortGraphics.FillRectangle(shadowDepthBrush, new Rectangle(Point.Empty, new Size(memoryBitmap.Size.Width - viewPortData.ShadowDepth.Width - 1, memoryBitmap.Size.Height - viewPortData.ShadowDepth.Height - 1)));
|
|
|
|
//Now make sure that we draw the image from the temporary bitmap with white color set as transparent
|
|
//so that we achive the 3D effect
|
|
//Make sure that we take into consideration the transparency key
|
|
ImageAttributes transparentColorKey = new ImageAttributes();
|
|
transparentColorKey.SetColorKey(viewPortData.TransparentColor, viewPortData.TransparentColor, ColorAdjustType.Default);
|
|
transparentColorKey.SetColorKey(viewPortData.TransparentColor, viewPortData.TransparentColor, ColorAdjustType.Bitmap);
|
|
viewPortGraphics.DrawImage(temporaryBitmap, new Rectangle(-viewPortData.ShadowDepth.Width, -viewPortData.ShadowDepth.Height, memoryBitmap.Width, memoryBitmap.Height), 0, 0, memoryBitmap.Width, memoryBitmap.Height, GraphicsUnit.Pixel, transparentColorKey);
|
|
|
|
//Now dispose the temporary bitmap
|
|
temporaryBitmap.Dispose();
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void OnThemeChange(object sender, EventArgs e)
|
|
{
|
|
ShadowDepth = WorkflowTheme.CurrentTheme.AmbientTheme.ShadowDepth;
|
|
|
|
using (WorkflowMessageDispatchData dispatchData = new WorkflowMessageDispatchData(this, EventArgs.Empty))
|
|
{
|
|
foreach (WorkflowDesignerMessageFilter filter in dispatchData.Filters)
|
|
{
|
|
try
|
|
{
|
|
((IWorkflowDesignerMessageSink)filter).OnThemeChange();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine(ex);
|
|
}
|
|
}
|
|
}
|
|
|
|
base.PerformLayout();
|
|
}
|
|
|
|
private void OnEnsureVisible(object sender, EventArgs e)
|
|
{
|
|
if (this.ensureVisibleEventHandler != null)
|
|
{
|
|
Idle -= this.ensureVisibleEventHandler;
|
|
this.ensureVisibleEventHandler = null;
|
|
}
|
|
|
|
ISelectionService selectionService = (ISelectionService)GetService(typeof(ISelectionService));
|
|
if (selectionService != null && selectionService.SelectionCount > 0)
|
|
{
|
|
//We do not want to regenerate a layout event in ensure visible
|
|
ArrayList selectedComponents = new ArrayList(selectionService.GetSelectedComponents());
|
|
for (int i = selectedComponents.Count - 1; i >= 0; i--)
|
|
{
|
|
Rectangle rectangleToMakeVisible = Rectangle.Empty;
|
|
if (selectedComponents[i] is Activity)
|
|
{
|
|
ActivityDesigner activityDesigner = ActivityDesigner.GetDesigner(selectedComponents[i] as Activity);
|
|
if (activityDesigner != null)
|
|
{
|
|
rectangleToMakeVisible = activityDesigner.Bounds;
|
|
rectangleToMakeVisible.Inflate(WorkflowTheme.CurrentTheme.AmbientTheme.SelectionSize);
|
|
rectangleToMakeVisible.Inflate(WorkflowTheme.CurrentTheme.AmbientTheme.SelectionSize);
|
|
}
|
|
}
|
|
else if (selectedComponents[i] is HitTestInfo)
|
|
{
|
|
rectangleToMakeVisible = ((HitTestInfo)selectedComponents[i]).Bounds;
|
|
}
|
|
|
|
if (!rectangleToMakeVisible.IsEmpty)
|
|
EnsureVisible(rectangleToMakeVisible);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void EnsureVisible(Rectangle rect)
|
|
{
|
|
Rectangle clientRectangle = ClientRectangleToLogical(new Rectangle(Point.Empty, ViewPortSize));
|
|
|
|
if (!clientRectangle.Contains(rect.Location) || !clientRectangle.Contains(new Point(rect.Right, rect.Bottom)))
|
|
{
|
|
Size scrollDelta = new Size();
|
|
if (!clientRectangle.Contains(new Point(rect.Left, clientRectangle.Top)) || !clientRectangle.Contains(new Point(rect.Right, clientRectangle.Top)))
|
|
{
|
|
if (rect.Width > clientRectangle.Width)
|
|
scrollDelta.Width = (rect.Left + rect.Width / 2) - (clientRectangle.Left + clientRectangle.Width / 2);
|
|
else if (rect.Left < clientRectangle.Left)
|
|
scrollDelta.Width = (rect.Left - clientRectangle.Left);
|
|
else
|
|
scrollDelta.Width = (rect.Right - clientRectangle.Right);
|
|
}
|
|
|
|
if (!clientRectangle.Contains(new Point(clientRectangle.Left, rect.Top)) || !clientRectangle.Contains(new Point(clientRectangle.Left, rect.Bottom)))
|
|
{
|
|
if ((rect.Top < clientRectangle.Top) || (rect.Height > clientRectangle.Height))
|
|
scrollDelta.Height = (rect.Top - clientRectangle.Top);
|
|
else
|
|
scrollDelta.Height = rect.Bottom - clientRectangle.Bottom;
|
|
}
|
|
|
|
scrollDelta = LogicalSizeToClient(scrollDelta);
|
|
Point scrollPosition = ScrollPosition;
|
|
ScrollPosition = new Point(scrollPosition.X + scrollDelta.Width, scrollPosition.Y + scrollDelta.Height);
|
|
}
|
|
}
|
|
|
|
private void OnScroll(object sender, EventArgs e)
|
|
{
|
|
//Lets speedup the scrolling logic
|
|
InvalidateClientRectangle(Rectangle.Empty);
|
|
|
|
ScrollBar scrollBar = sender as ScrollBar;
|
|
if (scrollBar != null)
|
|
{
|
|
using (WorkflowMessageDispatchData dispatchData = new WorkflowMessageDispatchData(this, e))
|
|
{
|
|
foreach (WorkflowDesignerMessageFilter filter in dispatchData.Filters)
|
|
{
|
|
try
|
|
{
|
|
((IWorkflowDesignerMessageSink)filter).OnScroll(scrollBar, scrollBar.Value);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine(ex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void UpdateScrollRange()
|
|
{
|
|
if (ViewPortSize.Width < 0 || ViewPortSize.Height < 0)
|
|
return;
|
|
|
|
Size currentSize = ViewPortSize;
|
|
Size maximumScrollSize = LogicalSizeToClient(this.activeLayout.Extent);
|
|
Size largeChangeSize = new Size(Math.Min(maximumScrollSize.Width, currentSize.Width), Math.Min(maximumScrollSize.Height, currentSize.Height));
|
|
|
|
if (hScrollBar.Maximum != maximumScrollSize.Width)
|
|
hScrollBar.Maximum = maximumScrollSize.Width;
|
|
if (vScrollBar.Maximum != maximumScrollSize.Height)
|
|
vScrollBar.Maximum = maximumScrollSize.Height;
|
|
|
|
if (hScrollBar.LargeChange != largeChangeSize.Width)
|
|
{
|
|
hScrollBar.SmallChange = largeChangeSize.Width / 15;
|
|
hScrollBar.LargeChange = largeChangeSize.Width + 1;
|
|
}
|
|
if (vScrollBar.LargeChange != largeChangeSize.Height)
|
|
{
|
|
vScrollBar.SmallChange = largeChangeSize.Height / 15;
|
|
vScrollBar.LargeChange = largeChangeSize.Height + 1;
|
|
}
|
|
|
|
int xMaxScrollPos = maximumScrollSize.Width - hScrollBar.LargeChange;
|
|
xMaxScrollPos = (xMaxScrollPos < 0) ? 0 : xMaxScrollPos;
|
|
if (hScrollBar.Value > xMaxScrollPos)
|
|
hScrollBar.Value = xMaxScrollPos;
|
|
|
|
int yMaxScrollPos = maximumScrollSize.Height - vScrollBar.LargeChange;
|
|
yMaxScrollPos = (yMaxScrollPos < 0) ? 0 : yMaxScrollPos;
|
|
if (vScrollBar.Value > yMaxScrollPos)
|
|
vScrollBar.Value = yMaxScrollPos;
|
|
|
|
RefreshDynamicAction();
|
|
|
|
bool hScrollBarVisible = hScrollBar.Visible;
|
|
if (Controls.Contains(hScrollBar))
|
|
hScrollBar.Visible = (hScrollBar.Maximum > currentSize.Width);
|
|
|
|
bool vScrollBarVisible = vScrollBar.Visible;
|
|
if (Controls.Contains(vScrollBar))
|
|
vScrollBar.Visible = (vScrollBar.Maximum > currentSize.Height);
|
|
|
|
if (hScrollBarVisible != hScrollBar.Visible || vScrollBar.Visible != vScrollBarVisible)
|
|
{
|
|
base.PerformLayout();
|
|
Refresh();
|
|
}
|
|
}
|
|
|
|
private DynamicAction CreateDynamicAction()
|
|
{
|
|
DynamicAction fitAllAction = new DynamicAction();
|
|
fitAllAction.ButtonSize = DynamicAction.ButtonSizes.Large;
|
|
fitAllAction.Doc----gnment = DesignerContentAlignment.BottomRight;
|
|
fitAllAction.DockMargin = new Size(5, 5);
|
|
|
|
ActionButton fitallButton = new ActionButton(new Image[] { DR.GetImage(DR.FitToScreen) as Bitmap });
|
|
fitallButton.StateChanged += new EventHandler(OnFitToScreen);
|
|
fitAllAction.Buttons.Add(fitallButton);
|
|
|
|
return fitAllAction;
|
|
}
|
|
|
|
private void RefreshDynamicAction()
|
|
{
|
|
DynamicActionMessageFilter dynamicActionFilter = GetService(typeof(DynamicActionMessageFilter)) as DynamicActionMessageFilter;
|
|
if (dynamicActionFilter == null || this.fitAllAction == null)
|
|
return;
|
|
|
|
if (HScrollBar.Maximum > ViewPortSize.Width || VScrollBar.Maximum > ViewPortSize.Height)
|
|
{
|
|
//This means we need to show the zoomin icon
|
|
this.fitAllAction.Buttons[0].Description = DR.GetString(DR.FitToScreenDescription);
|
|
this.fitAllAction.Buttons[0].StateImages = new Bitmap[] { DR.GetImage(DR.FitToScreen) as Bitmap };
|
|
dynamicActionFilter.AddAction(this.fitAllAction);
|
|
}
|
|
else if (Zoom != 100)
|
|
{
|
|
//We need to show zoomout icon
|
|
this.fitAllAction.Buttons[0].Description = DR.GetString(DR.FitToWorkflowDescription);
|
|
this.fitAllAction.Buttons[0].StateImages = new Bitmap[] { DR.GetImage(DR.FitToWorkflow) as Bitmap };
|
|
dynamicActionFilter.AddAction(this.fitAllAction);
|
|
}
|
|
else
|
|
{
|
|
//In neither case we remove the action
|
|
dynamicActionFilter.RemoveAction(this.fitAllAction);
|
|
this.fitAllAction.Buttons[0].State = ActionButton.States.Normal;
|
|
}
|
|
}
|
|
|
|
private void OnFitToScreen(object sender, EventArgs e)
|
|
{
|
|
ActionButton fitallButton = sender as ActionButton;
|
|
if (fitallButton == null || fitallButton.State != ActionButton.States.Pressed)
|
|
return;
|
|
|
|
if (HScrollBar.Maximum > ViewPortSize.Width || VScrollBar.Maximum > ViewPortSize.Height)
|
|
FitToScreenSize();
|
|
else if (Zoom != 100)
|
|
FitToWorkflowSize();
|
|
}
|
|
|
|
private void OnTabChange(object sender, TabSelectionChangeEventArgs e)
|
|
{
|
|
if (e.CurrentItem.Identifier == (int)TabButtonIds.MultiPage ||
|
|
e.CurrentItem.Identifier == (int)TabButtonIds.Zoom ||
|
|
e.CurrentItem.Identifier == (int)TabButtonIds.Pan)
|
|
{
|
|
Rectangle buttonRect = e.SelectedTabBounds;
|
|
CommandID menuID = null;
|
|
|
|
if (e.CurrentItem.Identifier == (int)TabButtonIds.MultiPage)
|
|
menuID = WorkflowMenuCommands.PageLayoutMenu;
|
|
else if (e.CurrentItem.Identifier == (int)TabButtonIds.Zoom)
|
|
menuID = WorkflowMenuCommands.ZoomMenu;
|
|
else
|
|
menuID = WorkflowMenuCommands.PanMenu;
|
|
|
|
IMenuCommandService menuCommandService = (IMenuCommandService)GetService(typeof(IMenuCommandService));
|
|
if (menuCommandService != null)
|
|
menuCommandService.ShowContextMenu(menuID, buttonRect.Right, buttonRect.Top);
|
|
}
|
|
}
|
|
|
|
private void EnsureScrollBars(HScrollBar newHorizScrollBar, VScrollBar newVertScrollBar)
|
|
{
|
|
try
|
|
{
|
|
SuspendLayout();
|
|
|
|
if (this.hScrollBar != newHorizScrollBar)
|
|
{
|
|
if (this.hScrollBar != null)
|
|
{
|
|
this.hScrollBar.ValueChanged -= new EventHandler(OnScroll);
|
|
if (Controls.Contains(this.hScrollBar))
|
|
Controls.Remove(this.hScrollBar);
|
|
}
|
|
|
|
this.hScrollBar = newHorizScrollBar;
|
|
if (this.hScrollBar.Parent == null)
|
|
{
|
|
this.hScrollBar.TabStop = false;
|
|
Controls.Add(this.hScrollBar);
|
|
}
|
|
}
|
|
|
|
if (this.vScrollBar != newVertScrollBar)
|
|
{
|
|
if (this.vScrollBar != null)
|
|
{
|
|
this.vScrollBar.ValueChanged -= new EventHandler(OnScroll);
|
|
if (Controls.Contains(this.vScrollBar))
|
|
Controls.Remove(this.vScrollBar);
|
|
}
|
|
|
|
this.vScrollBar = newVertScrollBar;
|
|
if (this.vScrollBar.Parent == null)
|
|
{
|
|
this.vScrollBar.TabStop = false;
|
|
Controls.Add(this.vScrollBar);
|
|
}
|
|
}
|
|
|
|
this.hScrollBar.ValueChanged += new EventHandler(OnScroll);
|
|
this.vScrollBar.ValueChanged += new EventHandler(OnScroll);
|
|
}
|
|
finally
|
|
{
|
|
ResumeLayout(true);
|
|
}
|
|
}
|
|
|
|
private void PopulateMessageFilters(bool stockFilters)
|
|
{
|
|
IList<WorkflowDesignerMessageFilter> filters = (stockFilters) ? this.stockMessageFilters : this.customMessageFilters;
|
|
Debug.Assert(filters.Count == 0);
|
|
|
|
if (stockFilters)
|
|
{
|
|
filters.Add(new GlyphManager());
|
|
filters.Add(new WindowManager());
|
|
}
|
|
else
|
|
{
|
|
Debug.Assert(this.rootDesigner != null);
|
|
|
|
if (Capture)
|
|
Capture = false;
|
|
|
|
IList customFilters = ((IWorkflowRootDesigner)this.rootDesigner).MessageFilters;
|
|
foreach (WorkflowDesignerMessageFilter filter in customFilters)
|
|
filters.Add(filter);
|
|
}
|
|
|
|
foreach (WorkflowDesignerMessageFilter filter in filters)
|
|
filter.SetParentView(this);
|
|
}
|
|
|
|
private void DisposeMessageFilters(bool stockFilters)
|
|
{
|
|
List<WorkflowDesignerMessageFilter> filters = (stockFilters) ? this.stockMessageFilters : this.customMessageFilters;
|
|
|
|
//We dispose all the message filters, this is done by copying because some of the
|
|
//message filters might remove other dependent messagefilters
|
|
ArrayList clonedFilterList = new ArrayList(filters.ToArray());
|
|
foreach (WorkflowDesignerMessageFilter filter in clonedFilterList)
|
|
((IDisposable)filter).Dispose();
|
|
filters.Clear();
|
|
}
|
|
#endregion
|
|
|
|
#region Coordinate Transformation Functions
|
|
public void InvalidateClientRectangle(Rectangle clientRectangle)
|
|
{
|
|
if (this.layoutEventHandler == null)
|
|
{
|
|
if (!clientRectangle.IsEmpty)
|
|
{
|
|
//Inflate the invalidated rectangle. When zoom factor is less than 1; there is a loss of precision
|
|
clientRectangle.Inflate(1, 1);
|
|
base.Invalidate(clientRectangle);
|
|
}
|
|
else
|
|
{
|
|
base.Invalidate();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void InvalidateLogicalRectangle(Rectangle logicalRectangle)
|
|
{
|
|
InvalidateClientRectangle(LogicalRectangleToClient(logicalRectangle));
|
|
}
|
|
|
|
public Point LogicalPointToScreen(Point logicalPoint)
|
|
{
|
|
return PointToScreen(LogicalPointToClient(logicalPoint));
|
|
}
|
|
|
|
public Point ScreenPointToLogical(Point screenPoint)
|
|
{
|
|
return ClientPointToLogical(PointToClient(screenPoint));
|
|
}
|
|
|
|
public Point LogicalPointToClient(Point logicalPoint)
|
|
{
|
|
return LogicalPointToClient(logicalPoint, true);
|
|
}
|
|
|
|
public Point ClientPointToLogical(Point clientPoint)
|
|
{
|
|
return ClientPointToLogical(clientPoint, true);
|
|
}
|
|
|
|
public Size LogicalSizeToClient(Size logicalSize)
|
|
{
|
|
Point[] points = new Point[] { new Point(logicalSize) };
|
|
|
|
//Scale the point
|
|
Matrix scalingMatrix = new Matrix();
|
|
scalingMatrix.Scale(ScaleZoomFactor, ScaleZoomFactor);
|
|
scalingMatrix.TransformPoints(points);
|
|
return new Size(points[0]);
|
|
}
|
|
|
|
public Size ClientSizeToLogical(Size clientSize)
|
|
{
|
|
//Scale the size, size scaling does not require translate
|
|
Point[] points = new Point[] { new Point(clientSize) };
|
|
Matrix scalingMatrix = new Matrix();
|
|
scalingMatrix.Scale(ScaleZoomFactor, ScaleZoomFactor);
|
|
scalingMatrix.Invert();
|
|
scalingMatrix.TransformPoints(points);
|
|
scalingMatrix.Invert();
|
|
return new Size(points[0]);
|
|
}
|
|
|
|
public Rectangle LogicalRectangleToClient(Rectangle rectangle)
|
|
{
|
|
Debug.Assert(this.activeLayout != null, "active layout should not be null");
|
|
Rectangle clientViewPort = (this.activeLayout != null) ? this.activeLayout.MapOutRectangleFromLayout(rectangle) : rectangle;
|
|
//
|
|
|
|
|
|
return new Rectangle(LogicalPointToClient(clientViewPort.Location, false), LogicalSizeToClient(clientViewPort.Size));
|
|
}
|
|
|
|
public Rectangle ClientRectangleToLogical(Rectangle rectangle)
|
|
{
|
|
//We translate the client viewport to logical view port.
|
|
//To do this we first get the view port rectangle scale it down
|
|
//then translate it to area of page we would be viewing
|
|
Rectangle scaledLogicalViewPort = new Rectangle(ClientPointToLogical(rectangle.Location, false), ClientSizeToLogical(rectangle.Size));
|
|
return this.activeLayout.MapInRectangleToLayout(scaledLogicalViewPort);
|
|
}
|
|
|
|
internal bool IsClientPointInActiveLayout(Point clientPoint)
|
|
{
|
|
Point logicalPoint = ClientPointToLogical(clientPoint, false);
|
|
return this.activeLayout.IsCoOrdInLayout(logicalPoint);
|
|
}
|
|
|
|
/*
|
|
* Client scale is when we transform the coordinate based on zoom and translate it based on scrolling position and layout
|
|
* Logical scale is when we transform the coordinate and map it to a flat coordinate system which goes from 0,0 to m,n
|
|
* We also consider the ActiveLayout to transform the coordinates.
|
|
*/
|
|
private Point LogicalPointToClient(Point point, bool mapToLayout)
|
|
{
|
|
if (mapToLayout)
|
|
point = this.activeLayout.MapOutCoOrdFromLayout(point);
|
|
|
|
//Scale the point
|
|
Matrix scalingMatrix = new Matrix();
|
|
scalingMatrix.Scale(ScaleZoomFactor, ScaleZoomFactor);
|
|
Point[] points = new Point[] { point };
|
|
scalingMatrix.TransformPoints(points);
|
|
|
|
//Translate the point
|
|
Matrix translateMatrix = new Matrix();
|
|
translateMatrix.Translate(-ScrollPosition.X, -ScrollPosition.Y);
|
|
translateMatrix.TransformPoints(points);
|
|
return points[0];
|
|
}
|
|
|
|
private Point ClientPointToLogical(Point point, bool mapToLayout)
|
|
{
|
|
Point[] points = new Point[] { point };
|
|
|
|
//Translate the point
|
|
Matrix translateMatrix = new Matrix();
|
|
translateMatrix.Translate(ScrollPosition.X, ScrollPosition.Y);
|
|
translateMatrix.TransformPoints(points);
|
|
|
|
//Scale down the point
|
|
Matrix scalingMatrix = new Matrix();
|
|
scalingMatrix.Scale(ScaleZoomFactor, ScaleZoomFactor);
|
|
scalingMatrix.Invert();
|
|
scalingMatrix.TransformPoints(points);
|
|
scalingMatrix.Invert();
|
|
if (!mapToLayout)
|
|
return points[0];
|
|
else
|
|
return this.activeLayout.MapInCoOrdToLayout(points[0]);
|
|
}
|
|
#endregion
|
|
|
|
#region IServiceProvider Implemetation
|
|
object IServiceProvider.GetService(Type serviceType)
|
|
{
|
|
return GetService(serviceType);
|
|
}
|
|
|
|
protected override object GetService(Type serviceType)
|
|
{
|
|
object retVal = null;
|
|
|
|
if (serviceType == typeof(CommandID))
|
|
retVal = new CommandID(new Guid("5f1c3c8d-60f1-4b98-b85b-8679f97e8eac"), 0);
|
|
else
|
|
retVal = this.serviceProvider.GetService(serviceType);
|
|
|
|
return retVal;
|
|
}
|
|
#endregion
|
|
|
|
#region Class WorkflowMessageDispatchData
|
|
private sealed class WorkflowMessageDispatchData : IDisposable
|
|
{
|
|
private WorkflowView workflowView;
|
|
private HitTestInfo messageContext = null;
|
|
|
|
public WorkflowMessageDispatchData(WorkflowView workflowView, EventArgs e)
|
|
{
|
|
this.workflowView = workflowView;
|
|
|
|
if (this.workflowView.RootDesigner != null && this.workflowView.stockMessageFilters.Count > 0)
|
|
{
|
|
Point clientPoint = Point.Empty;
|
|
if (e is MouseEventArgs || e is DragEventArgs)
|
|
{
|
|
if (e is MouseEventArgs)
|
|
{
|
|
clientPoint = new Point(((MouseEventArgs)e).X, ((MouseEventArgs)e).Y);
|
|
}
|
|
else if (e is DragEventArgs)
|
|
{
|
|
clientPoint = this.workflowView.PointToClient(new Point(((DragEventArgs)e).X, ((DragEventArgs)e).Y));
|
|
this.workflowView.UpdateLayout();
|
|
}
|
|
|
|
Point logicalPoint = this.workflowView.ClientPointToLogical(clientPoint);
|
|
HitTestInfo hitTestInfo = this.workflowView.RootDesigner.HitTest(logicalPoint);
|
|
this.messageContext = (hitTestInfo != null) ? hitTestInfo : HitTestInfo.Nowhere;
|
|
this.workflowView.messageHitTestContexts.Push(this.messageContext);
|
|
}
|
|
}
|
|
}
|
|
|
|
void IDisposable.Dispose()
|
|
{
|
|
if (this.workflowView != null && this.messageContext != null)
|
|
{
|
|
HitTestInfo hittestInfo = this.workflowView.messageHitTestContexts.Pop();
|
|
if (hittestInfo != this.messageContext)
|
|
Debug.Assert(false, "WorkflowView poped wrong message context");
|
|
}
|
|
}
|
|
|
|
public ReadOnlyCollection<WorkflowDesignerMessageFilter> Filters
|
|
{
|
|
get
|
|
{
|
|
//We recreate a new list everytime as in some of the messages dispatched, we there can
|
|
//be additional filters which might be added
|
|
List<WorkflowDesignerMessageFilter> mergedFilterList = new List<WorkflowDesignerMessageFilter>();
|
|
mergedFilterList.AddRange(this.workflowView.customMessageFilters);
|
|
mergedFilterList.AddRange(this.workflowView.stockMessageFilters);
|
|
return mergedFilterList.AsReadOnly();
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region IMessageFilter Implementation
|
|
bool IMessageFilter.PreFilterMessage(ref Message m)
|
|
{
|
|
bool handled = false;
|
|
if (m.Msg == NativeMethods.WM_KEYDOWN || m.Msg == NativeMethods.WM_SYSKEYDOWN ||
|
|
m.Msg == NativeMethods.WM_KEYUP || m.Msg == NativeMethods.WM_SYSKEYUP)
|
|
{
|
|
Control control = Control.FromHandle(m.HWnd);
|
|
if (control != null && (control == this || Controls.Contains(control)))
|
|
{
|
|
KeyEventArgs eventArgs = new KeyEventArgs((Keys)(unchecked((int)(long)m.WParam)) | ModifierKeys);
|
|
if (m.Msg == NativeMethods.WM_KEYDOWN || m.Msg == NativeMethods.WM_SYSKEYDOWN)
|
|
OnKeyDown(eventArgs);
|
|
else
|
|
OnKeyUp(eventArgs);
|
|
|
|
handled = eventArgs.Handled;
|
|
}
|
|
}
|
|
|
|
return handled;
|
|
}
|
|
#endregion
|
|
}
|
|
|
|
#region Class WorkflowViewAccessibleObject
|
|
[Obsolete("The System.Workflow.* types are deprecated. Instead, please use the new types from System.Activities.*")]
|
|
public class WorkflowViewAccessibleObject : Control.ControlAccessibleObject
|
|
{
|
|
private WorkflowView workflowView;
|
|
|
|
public WorkflowViewAccessibleObject(WorkflowView workflowView)
|
|
: base(workflowView)
|
|
{
|
|
if (workflowView == null)
|
|
throw new ArgumentNullException("workflowView");
|
|
this.workflowView = workflowView;
|
|
}
|
|
|
|
public override Rectangle Bounds
|
|
{
|
|
get
|
|
{
|
|
return new Rectangle(this.workflowView.PointToScreen(Point.Empty), this.workflowView.ViewPortSize);
|
|
}
|
|
}
|
|
|
|
public override string DefaultAction
|
|
{
|
|
get
|
|
{
|
|
return DR.GetString(DR.AccessibleAction);
|
|
}
|
|
}
|
|
|
|
public override string Description
|
|
{
|
|
get
|
|
{
|
|
return DR.GetString(DR.WorkflowViewAccessibleDescription);
|
|
}
|
|
}
|
|
|
|
public override string Help
|
|
{
|
|
get
|
|
{
|
|
return DR.GetString(DR.WorkflowViewAccessibleHelp);
|
|
}
|
|
}
|
|
|
|
public override string Name
|
|
{
|
|
get
|
|
{
|
|
return DR.GetString(DR.WorkflowViewAccessibleName);
|
|
}
|
|
|
|
set
|
|
{
|
|
}
|
|
}
|
|
|
|
public override AccessibleRole Role
|
|
{
|
|
get
|
|
{
|
|
return AccessibleRole.Diagram;
|
|
}
|
|
}
|
|
|
|
public override AccessibleObject GetChild(int index)
|
|
{
|
|
return (this.workflowView.RootDesigner != null && index == 0) ? this.workflowView.RootDesigner.AccessibilityObject : base.GetChild(index);
|
|
}
|
|
|
|
public override int GetChildCount()
|
|
{
|
|
return (this.workflowView.RootDesigner != null) ? 1 : -1;
|
|
}
|
|
|
|
public override AccessibleObject Navigate(AccessibleNavigation navdir)
|
|
{
|
|
if (navdir == AccessibleNavigation.FirstChild || navdir == AccessibleNavigation.LastChild)
|
|
return GetChild(0);
|
|
else
|
|
return base.Navigate(navdir);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region WorkflowTimer
|
|
internal sealed class WorkflowTimer : IDisposable
|
|
{
|
|
private static WorkflowTimer workflowTimer;
|
|
|
|
private const int TimerInterval = 50;
|
|
private Timer timer = null;
|
|
private List<ElapsedEventUnit> elapsedEvents = new List<ElapsedEventUnit>();
|
|
|
|
internal static WorkflowTimer Default
|
|
{
|
|
get
|
|
{
|
|
if (WorkflowTimer.workflowTimer == null)
|
|
WorkflowTimer.workflowTimer = new WorkflowTimer();
|
|
return WorkflowTimer.workflowTimer;
|
|
}
|
|
}
|
|
|
|
private WorkflowTimer()
|
|
{
|
|
this.timer = new Timer();
|
|
this.timer.Interval = WorkflowTimer.TimerInterval;
|
|
this.timer.Tick += new EventHandler(OnTimer);
|
|
this.timer.Stop();
|
|
}
|
|
|
|
~WorkflowTimer()
|
|
{
|
|
Dispose(false);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
private void Dispose(bool disposing)
|
|
{
|
|
if (this.timer != null)
|
|
{
|
|
if (this.timer.Enabled)
|
|
this.timer.Stop();
|
|
|
|
this.timer.Dispose();
|
|
this.timer = null;
|
|
}
|
|
}
|
|
|
|
internal void Subscribe(int elapsedInterval, EventHandler elapsedEventHandler)
|
|
{
|
|
this.elapsedEvents.Add(new ElapsedEventUnit(elapsedInterval / WorkflowTimer.TimerInterval, elapsedEventHandler));
|
|
if (!this.timer.Enabled)
|
|
this.timer.Start();
|
|
}
|
|
|
|
internal void Unsubscribe(EventHandler elapsedEventHandler)
|
|
{
|
|
List<ElapsedEventUnit> removableElapsedEvents = new List<ElapsedEventUnit>();
|
|
foreach (ElapsedEventUnit elapsedEvent in this.elapsedEvents)
|
|
{
|
|
if (elapsedEvent.elapsedEventHandler == elapsedEventHandler)
|
|
removableElapsedEvents.Add(elapsedEvent);
|
|
}
|
|
|
|
foreach (ElapsedEventUnit elapsedEvent in removableElapsedEvents)
|
|
this.elapsedEvents.Remove(elapsedEvent);
|
|
|
|
if (this.elapsedEvents.Count == 0 && this.timer.Enabled)
|
|
this.timer.Stop();
|
|
}
|
|
|
|
private void OnTimer(object sender, EventArgs e)
|
|
{
|
|
List<ElapsedEventUnit> clonedList = new List<ElapsedEventUnit>(this.elapsedEvents);
|
|
foreach (ElapsedEventUnit elapsedEvent in clonedList)
|
|
{
|
|
elapsedEvent.elapsedTime += 1;
|
|
if (elapsedEvent.elapsedInterval <= elapsedEvent.elapsedTime)
|
|
{
|
|
elapsedEvent.elapsedTime = 0;
|
|
elapsedEvent.elapsedEventHandler(this, EventArgs.Empty);
|
|
}
|
|
}
|
|
}
|
|
|
|
private sealed class ElapsedEventUnit
|
|
{
|
|
internal EventHandler elapsedEventHandler;
|
|
internal int elapsedInterval;
|
|
internal int elapsedTime;
|
|
|
|
internal ElapsedEventUnit(int interval, EventHandler eventHandler)
|
|
{
|
|
this.elapsedInterval = interval;
|
|
this.elapsedEventHandler = eventHandler;
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
}
|