|
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);
//Sync 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.DockAlignment = 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
}
|