1 //----------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation. All rights reserved.
3 //----------------------------------------------------------------
5 namespace System.Activities.Presentation
8 using System.Activities.Debugger;
9 using System.Activities.Debugger.Symbol;
10 using System.Activities.Presentation.Debug;
11 using System.Activities.Presentation.Documents;
12 using System.Activities.Presentation.Hosting;
13 using System.Activities.Presentation.Internal.PropertyEditing;
14 using System.Activities.Presentation.Internal.PropertyEditing.Metadata;
15 using System.Activities.Presentation.Metadata;
16 using System.Activities.Presentation.Model;
17 using System.Activities.Presentation.Services;
18 using System.Activities.Presentation.Sqm;
19 using System.Activities.Presentation.Validation;
20 using System.Activities.Presentation.View;
21 using System.Activities.Presentation.View.TreeView;
22 using System.Activities.Presentation.Xaml;
23 using System.Collections;
24 using System.Collections.Generic;
25 using System.Diagnostics.CodeAnalysis;
26 using System.Globalization;
29 using System.Runtime.Versioning;
30 using System.Security;
31 using System.Security.Policy;
34 using System.Windows.Controls;
35 using System.Windows.Input;
36 using System.Windows.Interop;
37 using System.Windows.Threading;
39 using Microsoft.Activities.Presentation;
40 using Microsoft.Activities.Presentation.Xaml;
42 // This is the workflow designer context class.
43 // it provides two views the primary workflow view in View property and the property browser view in the
44 // propertyInspectorView property.
45 // Load takes a objects instance or Xaml ( in the future) to load the designer from
46 public partial class WorkflowDesigner
48 EditingContext context;
49 ModelTreeManager modelTreeManager;
52 PropertyInspector propertyInspector;
54 ViewStateIdManager idManager;
56 DebuggerService debuggerService;
57 UndoEngine undoEngine;
58 ViewManager viewManager;
59 ValidationService validationService;
60 ObjectReferenceService objectReferenceService;
61 DesignerPerfEventProvider perfEventProvider;
62 bool isLoaded = false;
63 bool isModelChanged = false;
65 IXamlLoadErrorService xamlLoadErrorService;
66 WorkflowDesignerXamlSchemaContext workflowDesignerXamlSchemaContext;
68 public event TextChangedEventHandler TextChanged;
69 public event EventHandler ModelChanged;
70 WorkflowSymbol lastWorkflowSymbol;
71 ObjectToSourceLocationMapping objectToSourceLocationMapping;
73 internal class PreviewLoadEventArgs : EventArgs
76 EditingContext context;
78 public PreviewLoadEventArgs(object instance, EditingContext context)
80 this.instance = instance;
81 this.context = context;
84 public object Instance
86 get { return this.instance; }
89 public EditingContext Context
91 get { return this.context; }
94 internal event EventHandler<PreviewLoadEventArgs> PreviewLoad;
96 [SuppressMessage(FxCop.Category.Performance, FxCop.Rule.InitializeReferenceTypeStaticFieldsInline,
97 Justification = "The static constructor is required to initialize the PropertyInspector metadata.")]
98 static WorkflowDesigner()
100 InitializePropertyInspectorMetadata();
101 DesignerMetadata metaData = new DesignerMetadata();
105 public WorkflowDesigner()
107 // create our perf trace provider first
108 this.perfEventProvider = new DesignerPerfEventProvider();
109 this.idManager = new ViewStateIdManager();
110 this.context = new EditingContext();
111 this.ModelSearchService = new ModelSearchServiceImpl(this);
112 this.context.Items.SetValue(new ReadOnlyState { IsReadOnly = false });
113 this.view = new Grid();
114 this.view.Focusable = false;
116 //add the resource dictionary to application resource so every component could reference it
117 if (Application.Current == null)
119 //create an application if it doesn't exist, make sure it will not shutdown after windows being shut down
120 Application app = new Application();
121 app.ShutdownMode = ShutdownMode.OnExplicitShutdown;
123 Fx.Assert(Application.Current != null, "Application and resources must be there");
124 Application.Current.Resources.MergedDictionaries.Add(WorkflowDesignerColors.FontAndColorResources);
125 Application.Current.Resources.MergedDictionaries.Add(WorkflowDesignerIcons.IconResourceDictionary);
126 AttachedPropertiesService propertiesService = new AttachedPropertiesService();
127 this.context.Services.Publish(typeof(AttachedPropertiesService), propertiesService);
129 undoEngine = new UndoEngine(context);
130 this.context.Services.Publish(typeof(UndoEngine), undoEngine);
131 undoEngine.UndoCompleted += new EventHandler<UndoUnitEventArgs>(OnUndoCompleted);
133 this.context.Services.Publish<ValidationService>(this.ValidationService);
134 this.context.Services.Publish<ObjectReferenceService>(this.ObjectReferenceService);
135 this.context.Services.Publish<DesignerPerfEventProvider>(this.perfEventProvider);
136 this.context.Services.Publish<FeatureManager>(new FeatureManager(this.context));
137 this.context.Services.Publish<DesignerConfigurationService>(new DesignerConfigurationService());
139 this.context.Services.Subscribe<ICommandService>((s) =>
141 const string addinTypeName = "Microsoft.VisualStudio.Activities.AddIn.WorkflowDesignerAddIn";
142 if (s != null && s.GetType().FullName.Equals(addinTypeName))
144 DesignerConfigurationService service = this.context.Services.GetService<DesignerConfigurationService>();
147 service.WorkflowDesignerHostId = WorkflowDesignerHostId.Dev10;
152 this.context.Services.Subscribe<IVSSqmService>((service) =>
154 const string serviceTypeName = "Microsoft.VisualStudio.Activities.AddIn.VSSqmService";
155 if (service != null && service.GetType().FullName.Equals(serviceTypeName))
157 DesignerConfigurationService configurationService = this.context.Services.GetService<DesignerConfigurationService>();
158 if (configurationService != null)
160 configurationService.WorkflowDesignerHostId = WorkflowDesignerHostId.Dev11;
165 this.Context.Items.Subscribe<ErrorItem>(delegate(ErrorItem errorItem)
167 ErrorView errorView = new ErrorView();
168 errorView.Message = errorItem.Message;
169 errorView.Details = errorItem.Details;
170 errorView.Context = this.Context;
173 this.view.Children.Clear();
174 this.view.Children.Add(errorView);
175 if (this.outlineView != null)
177 this.outlineView.Children.Clear();
182 this.context.Items.Subscribe<ReadOnlyState>(new SubscribeContextCallback<ReadOnlyState>(OnReadonlyStateChanged));
184 this.context.Services.Subscribe<IXamlLoadErrorService>(s => this.xamlLoadErrorService = s);
186 this.PreviewLoad += NamespaceSettingsHandler.PreviewLoadRoot;
187 this.view.Loaded += (s, e) =>
189 //when view is loaded, check if user did provide his own WindowHelperService - if not, provide a default one
190 if (!this.context.Services.Contains<WindowHelperService>())
192 IntPtr hWND = IntPtr.Zero;
193 Window ownerWindow = Window.GetWindow(this.view);
194 if (null != ownerWindow)
196 WindowInteropHelper helper = new WindowInteropHelper(ownerWindow);
197 hWND = helper.Handle;
199 this.Context.Services.Publish<WindowHelperService>(new WindowHelperService(hWND));
201 WindowHelperService whs = this.context.Services.GetService<WindowHelperService>();
202 whs.View = this.view;
204 //check if workflow command extension item is available - if not, provide default one
205 if (!this.context.Items.Contains<WorkflowCommandExtensionItem>())
207 WorkflowCommandExtensionItem item = new WorkflowCommandExtensionItem(new DefaultCommandExtensionCallback());
208 this.context.Items.SetValue(item);
211 ComponentDispatcher.EnterThreadModal += new EventHandler(ComponentDispatcher_EnterThreadModal);
212 ComponentDispatcher.LeaveThreadModal += new EventHandler(ComponentDispatcher_LeaveThreadModal);
215 this.view.Unloaded += (s, e) =>
217 ComponentDispatcher.EnterThreadModal -= new EventHandler(ComponentDispatcher_EnterThreadModal);
218 ComponentDispatcher.LeaveThreadModal -= new EventHandler(ComponentDispatcher_LeaveThreadModal);
221 this.view.IsKeyboardFocusWithinChanged += (s, e) =>
223 // The ModelTreeManager is null when there is an active ErrorItem.
224 // We have nothing to write to text in this case.
225 if (this.modelTreeManager != null && (bool)e.NewValue == false)
227 if ((FocusManager.GetFocusedElement(this.view) as TextBox) != null)
229 FocusManager.SetFocusedElement(this.view, null);
230 this.NotifyModelChanged();
236 internal ValidationService ValidationService
240 if (this.validationService == null)
242 this.validationService = new ValidationService(this.context);
243 this.validationService.ErrorsMarked += ActivityArgumentHelper.UpdateInvalidArgumentsIfNecessary;
246 return this.validationService;
250 internal ObjectReferenceService ObjectReferenceService
254 if (this.objectReferenceService == null)
256 this.objectReferenceService = new ObjectReferenceService(this.context);
259 return this.objectReferenceService;
263 public UIElement View
271 public UIElement PropertyInspectorView
275 if (this.propertyInspector == null)
277 // We change WorkflowDesigner.PropertyInspectorView to be lazy load because the propertyinspector hosted in
278 // Winform elementhost will not get the resource change notification from Application level resource dictionary.
279 // So we have to have all colors be ready before propertyinspector gets initialized.
280 this.propertyInspector = new PropertyInspector();
281 this.propertyInspector.DesignerContextItemManager = this.context.Items;
282 this.propertyInspector.EditingContext = this.context;
283 this.InitializePropertyInspectorResources();
284 this.InitializePropertyInspectorCommandHandling();
287 return this.propertyInspector;
291 public UIElement OutlineView
295 if (this.outlineView == null)
297 this.outlineView = new Grid();
298 this.outlineView.Focusable = false;
302 return this.outlineView;
306 void AddOutlineView()
308 DesignerTreeView treeView = new DesignerTreeView();
309 treeView.Initialize(context);
310 this.context.Services.Subscribe<ModelService>(delegate(ModelService modelService)
312 if (modelService.Root != null)
314 treeView.SetRootDesigner(modelService.Root);
317 treeView.RestoreDesignerStates();
319 this.outlineView.Children.Add(treeView);
323 public EditingContext Context
331 public ContextMenu ContextMenu
335 if (null != this.context)
337 DesignerView designerView = this.context.Services.GetService<DesignerView>();
338 if (null != designerView)
340 return designerView.ContextMenu;
349 get { return this.text; }
350 set { this.text = value; }
353 [SuppressMessage(FxCop.Category.Design, "CA1044:PropertiesShouldNotBeWriteOnly",
354 Justification = "The host just sets this property for the designer to know which colors to display.")]
355 public string PropertyInspectorFontAndColorData
359 StringReader stringReader = new StringReader(value);
360 XmlReader xmlReader = XmlReader.Create(stringReader);
361 Hashtable fontAndColorDictionary = (Hashtable)System.Windows.Markup.XamlReader.Load(xmlReader);
362 foreach (string key in fontAndColorDictionary.Keys)
364 WorkflowDesignerColors.FontAndColorResources[key] = fontAndColorDictionary[key];
370 public bool IsInErrorState()
372 ErrorItem errorItem = this.context.Items.GetValue<ErrorItem>();
373 return errorItem.Message != null && errorItem.Details != null ? true : false;
377 [SuppressMessage(FxCop.Category.Design, FxCop.Rule.DoNotCatchGeneralExceptionTypes,
378 Justification = "Deserializer might throw if it fails to deserialize. Catching all exceptions to avoid VS Crash.")]
379 [SuppressMessage("Reliability", "Reliability108",
380 Justification = "Deserializer might throw if it fails to deserialize. Catching all exceptions to avoid VS crash.")]
383 this.perfEventProvider.WorkflowDesignerLoadStart();
384 if (!string.IsNullOrEmpty(this.text))
388 this.perfEventProvider.WorkflowDesignerDeserializeStart();
390 IList<XamlLoadErrorInfo> loadErrors;
391 Dictionary<object, SourceLocation> sourceLocations;
392 object deserializedObject = DeserializeString(this.text, out loadErrors, out sourceLocations);
394 this.perfEventProvider.WorkflowDesignerDeserializeEnd();
396 if (deserializedObject != null)
398 this.Load(deserializedObject);
399 this.ValidationService.ValidateWorkflow(ValidationReason.Load);
403 StringBuilder details = new StringBuilder();
404 foreach (XamlLoadErrorInfo error in loadErrors)
406 details.AppendLine(error.Message);
408 this.Context.Items.SetValue(new ErrorItem() { Message = SR.SeeErrorWindow, Details = details.ToString() });
410 if (loadErrors != null)
412 RaiseLoadErrors(loadErrors);
414 this.isModelChanged = false;
418 this.Context.Items.SetValue(new ErrorItem() { Message = e.Message, Details = e.ToString() });
424 this.Context.Items.SetValue(new ErrorItem() { Message = string.Empty, Details = string.Empty });
426 if (this.IsInErrorState())
428 // Clear workflow symbol in case ErrorState changes during validation
429 this.lastWorkflowSymbol = null;
431 this.perfEventProvider.WorkflowDesignerLoadComplete();
434 public void Load(string fileName)
436 if (string.IsNullOrEmpty(fileName))
438 throw FxTrace.Exception.AsError(new ArgumentNullException("fileName"));
441 DesignerConfigurationService service = this.Context.Services.GetService<DesignerConfigurationService>();
442 service.SetDefaultOfLoadingFromUntrustedSourceEnabled();
443 if (!service.LoadingFromUntrustedSourceEnabled && !IsFromUnrestrictedPath(fileName))
445 throw FxTrace.Exception.AsError(new SecurityException(string.Format(CultureInfo.CurrentUICulture, SR.UntrustedSourceDetected, fileName)));
450 IDocumentPersistenceService documentPersistenceService = this.Context.Services.GetService<IDocumentPersistenceService>();
451 if (documentPersistenceService != null)
453 this.Load(documentPersistenceService.Load(fileName));
457 using (StreamReader fileStream = new StreamReader(fileName))
459 this.loadedFile = fileName;
460 WorkflowFileItem fileItem = new WorkflowFileItem();
461 fileItem.LoadedFile = fileName;
462 this.context.Items.SetValue(fileItem);
463 this.Text = fileStream.ReadToEnd();
476 this.Context.Items.SetValue(new ErrorItem() { Message = e.Message, Details = e.ToString() });
479 if (!this.IsInErrorState())
481 if (this.debuggerService != null)
483 this.debuggerService.InvalidateSourceLocationMapping(fileName);
488 // This supports loading objects instead of xaml into the designer
489 public void Load(object instance)
493 throw FxTrace.Exception.AsError(new InvalidOperationException(SR.WorkflowDesignerLoadShouldBeCalledOnlyOnce));
498 if (instance == null)
500 throw FxTrace.Exception.AsError(new ArgumentNullException("instance"));
503 DesignerConfigurationService configurationService = this.context.Services.GetService<DesignerConfigurationService>();
504 configurationService.ApplyDefaultPreference();
506 // Because we want AutoConnect/AutoSplit to be on even in Dev10 if PU1 is installed.
507 // But we cannot know whether PU1 is installed or not, we decide to enable these 2 features for all Dev10.
508 if (configurationService.WorkflowDesignerHostId == WorkflowDesignerHostId.Dev10)
510 configurationService.AutoConnectEnabled = true;
511 configurationService.AutoSplitEnabled = true;
514 configurationService.IsWorkflowLoaded = true;
515 configurationService.Validate();
517 if (this.PreviewLoad != null)
519 this.PreviewLoad(this, new PreviewLoadEventArgs(instance, this.context));
522 if (configurationService.TargetFrameworkName.IsLessThan45())
524 TargetFrameworkPropertyFilter.FilterOut45Properties();
527 modelTreeManager = new ModelTreeManager(this.context);
528 modelTreeManager.Load(instance);
529 this.context.Services.Publish(typeof(ModelTreeManager), modelTreeManager);
530 viewManager = GetViewManager(this.modelTreeManager.Root);
531 this.context.Services.Publish<ModelSearchService>(this.ModelSearchService);
532 view.Children.Add((UIElement)viewManager.View);
534 modelTreeManager.EditingScopeCompleted += new EventHandler<EditingScopeEventArgs>(OnEditingScopeCompleted);
536 this.view.Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle,
537 new Action(() => { this.perfEventProvider.WorkflowDesignerApplicationIdleAfterLoad(); }));
539 //Subscribe to the ViewStateChanged event of ViewStateService to show document dirty. It would be published in the call to GetViewManager().
540 WorkflowViewStateService wfViewStateService = this.Context.Services.GetService(typeof(ViewStateService)) as WorkflowViewStateService;
541 if (wfViewStateService != null)
543 wfViewStateService.UndoableViewStateChanged += new ViewStateChangedEventHandler(OnViewStateChanged);
545 this.isModelChanged = false;
546 if (!this.IsInErrorState())
548 this.lastWorkflowSymbol = GetAttachedWorkflowSymbol();
552 public void Save(string fileName)
554 this.isModelChanged = true; // ensure flushing any viewstate changes that does not imply model changed.
557 // Cancel pervious validation and suppress validation work.
558 this.ValidationService.DeactivateValidation();
563 this.ValidationService.ActivateValidation();
566 using (StreamWriter fileStreamWriter = new StreamWriter(fileName, false, Encoding.UTF8))
568 fileStreamWriter.Write(this.Text);
569 fileStreamWriter.Flush();
572 if (this.Context.Services.GetService<ModelService>() != null)
574 this.ValidationService.ValidateWorkflow(ValidationReason.Save);
577 if (this.debuggerService != null)
579 this.debuggerService.InvalidateSourceLocationMapping(fileName);
588 private bool IsFromUnrestrictedPath(string fileName)
590 Evidence folderEvidence = new Evidence();
591 folderEvidence.AddHostEvidence(Zone.CreateFromUrl(fileName));
593 PermissionSet standardFolderSandbox = SecurityManager.GetStandardSandbox(folderEvidence);
594 return standardFolderSandbox.IsUnrestricted();
597 void Flush(string fileName)
599 if (this.modelTreeManager == null)
601 // It's possible for modelTreeManager to be null if Load is called but the xaml file being loaded is invalid.
602 // We only want to throw exception if Load hasn't been called yet.
603 if (IsInErrorState() == false)
605 throw FxTrace.Exception.AsError(new InvalidOperationException(SR.WorkflowDesignerLoadShouldBeCalledFirst));
611 IDocumentPersistenceService documentPersistenceService = this.Context.Services.GetService<IDocumentPersistenceService>();
612 if (documentPersistenceService != null)
614 documentPersistenceService.Flush(this.modelTreeManager.Root.GetCurrentValue());
618 this.WriteModelToText(fileName);
625 UIElement oldFocus = null;
626 //check if property grid has keyboard focus within, if yes - get focused control
627 if (null != this.propertyInspector && this.propertyInspector.IsKeyboardFocusWithin)
629 oldFocus = FocusManager.GetFocusedElement(this.propertyInspector) as UIElement;
631 //check if view has keyboard focus within, if yes - get focused control
632 if (null != this.view && this.view.IsKeyboardFocusWithin)
634 oldFocus = FocusManager.GetFocusedElement(this.view) as UIElement;
636 if (null != oldFocus)
638 RoutedCommand cmd = DesignerView.CommitCommand as RoutedCommand;
641 cmd.Execute(null, oldFocus);
645 //commit changes within arguments and variables editor
646 var designerView = this.Context.Services.GetService<DesignerView>();
647 if (null != designerView)
649 if (null != designerView.arguments1)
651 DataGridHelper.CommitPendingEdits(designerView.arguments1.argumentsDataGrid);
653 if (null != designerView.variables1)
655 DataGridHelper.CommitPendingEdits(designerView.variables1.variableDataGrid);
660 static void InitializePropertyInspectorMetadata()
662 PropertyInspectorMetadata.Initialize();
665 static Activity GetRootWorkflowElement(object rootModelObject)
667 return WorkflowDesignerXamlHelper.GetRootWorkflowElement(rootModelObject);