1 //----------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation. All rights reserved.
3 //----------------------------------------------------------------
5 namespace System.Activities.Presentation
7 using System.Activities.Presentation.Internal.Properties;
10 using System.Collections.Generic;
11 using System.ComponentModel;
12 using System.Diagnostics;
13 using System.Globalization;
15 using System.Activities.Presentation;
18 // The EditingContext class contains contextual state about a designer. This includes permanent
19 // state such as list of services running in the designer.
20 // It also includes transient state consisting of context items. Examples of transient
21 // context item state include the set of currently selected objects as well as the editing tool
22 // being used to manipulate objects on the design surface.
24 // The editing context is designed to be a concrete class for ease of use. It does have a protected
25 // API that can be used to replace its implementation.
27 public class EditingContext : IDisposable
30 private ContextItemManager _contextItems;
31 private ServiceManager _services;
34 // Creates a new editing context.
36 public EditingContext()
42 // The Disposing event gets fired just before the context gets disposed.
44 public event EventHandler Disposing;
47 // Returns the local collection of context items offered by this editing context.
50 public ContextItemManager Items
53 if (_contextItems == null)
55 _contextItems = CreateContextItemManager();
56 if (_contextItems == null)
58 throw FxTrace.Exception.AsError(new InvalidOperationException(
59 string.Format(CultureInfo.CurrentCulture, Resources.Error_NullImplementation, "CreateContextItemManager")));
68 // Returns the service manager for this editing context.
71 public ServiceManager Services
74 if (_services == null)
76 _services = CreateServiceManager();
77 if (_services == null)
79 throw FxTrace.Exception.AsError(new InvalidOperationException(
80 string.Format(CultureInfo.CurrentCulture, Resources.Error_NullImplementation, "CreateServiceManager")));
89 // Creates an instance of the context item manager to be returned from
90 // the ContextItems property. The default implementation creates a
91 // ContextItemManager that supports delayed activation of design editor
92 // managers through the declaration of a SubscribeContext attribute on
93 // the design editor manager.
95 // <returns>Returns an implementation of the ContextItemManager class.</returns>
96 protected virtual ContextItemManager CreateContextItemManager()
98 return new DefaultContextItemManager(this);
102 // Creates an instance of the service manager to be returned from the
103 // Services property. The default implementation creates a ServiceManager
104 // that supports delayed activation of design editor managers through the
105 // declaration of a SubscribeService attribute on the design editor manager.
107 // <returns>Returns an implemetation of the ServiceManager class.</returns>
108 protected virtual ServiceManager CreateServiceManager()
110 return new DefaultServiceManager();
114 // Disposes this editing context.
116 public void Dispose()
119 GC.SuppressFinalize(this);
123 // Disposes this editing context.
124 // <param name="disposing">True if this object is being disposed, or false if it is finalizing.</param>
126 protected virtual void Dispose(bool disposing)
130 // Let any interested parties know the context is being disposed
131 if (Disposing != null)
133 Disposing(this, EventArgs.Empty);
136 IDisposable d = _services as IDisposable;
142 d = _contextItems as IDisposable;
151 // This is the default context item manager for our editing context.
153 private sealed class DefaultContextItemManager : ContextItemManager
155 private EditingContext _context;
156 private DefaultContextLayer _currentLayer;
157 private Dictionary<Type, SubscribeContextCallback> _subscriptions;
159 internal DefaultContextItemManager(EditingContext context)
162 _currentLayer = new DefaultContextLayer(null);
166 // This changes a context item to the given value. It is illegal to pass
167 // null here. If you want to set a context item to its empty value create
168 // an instance of the item using a default constructor.
170 // <param name="value"></param>
171 public override void SetValue(ContextItem value)
175 throw FxTrace.Exception.ArgumentNull("value");
178 // The rule for change is that we store the new value,
179 // raise a change on the item, and then raise a change
180 // to everyone else. If changing the item fails, we recover
181 // the previous item.
182 ContextItem existing, existingRawValue;
183 existing = existingRawValue = GetValueNull(value.ItemType);
185 if (existing == null)
187 existing = GetValue(value.ItemType);
190 bool success = false;
194 _currentLayer.Items[value.ItemType] = value;
195 NotifyItemChanged(_context, value, existing);
202 OnItemChanged(value);
206 // The item threw during its transition to
207 // becoming active. Put the old one back.
208 // We must put the old one back by re-activating
209 // it. This could throw a second time, so we
210 // cover this case by removing the value first.
211 // Should it throw again, we won't recurse because
212 // the existing raw value would be null.
214 _currentLayer.Items.Remove(value.ItemType);
215 if (existingRawValue != null)
217 SetValue(existingRawValue);
224 // Returns true if the item manager contains an item of the given type.
225 // This only looks in the current layer.
227 // <param name="itemType"></param>
228 // <returns></returns>
229 public override bool Contains(Type itemType)
231 if (itemType == null)
233 throw FxTrace.Exception.ArgumentNull("itemType");
235 if (!typeof(ContextItem).IsAssignableFrom(itemType))
237 throw FxTrace.Exception.AsError(new ArgumentException(
238 string.Format(CultureInfo.CurrentCulture,
239 Resources.Error_ArgIncorrectType,
240 "itemType", typeof(ContextItem).FullName)));
243 return _currentLayer.Items.ContainsKey(itemType);
247 // Returns an instance of the requested item type. If there is no context
248 // item with the given type, an empty item will be created.
250 // <param name="itemType"></param>
251 // <returns></returns>
252 public override ContextItem GetValue(Type itemType)
255 ContextItem item = GetValueNull(itemType);
260 // Check the default item table and add a new
261 // instance there if we need to
262 if (!_currentLayer.DefaultItems.TryGetValue(itemType, out item))
264 item = (ContextItem)Activator.CreateInstance(itemType);
266 // Verify that the resulting item has the correct item type
267 // If it doesn't, it means that the user provided a derived
269 if (item.ItemType != itemType)
271 throw FxTrace.Exception.AsError(new ArgumentException(string.Format(
272 CultureInfo.CurrentCulture,
273 Resources.Error_DerivedContextItem,
275 item.ItemType.FullName)));
278 // Now push the item in the context so we have
279 // a consistent reference
280 _currentLayer.DefaultItems.Add(item.ItemType, item);
288 // Similar to GetValue, but returns NULL if the item isn't found instead of
289 // creating an empty item.
291 // <param name="itemType"></param>
292 // <returns></returns>
293 private ContextItem GetValueNull(Type itemType)
296 if (itemType == null)
298 throw FxTrace.Exception.ArgumentNull("itemType");
300 if (!typeof(ContextItem).IsAssignableFrom(itemType))
302 throw FxTrace.Exception.AsError(new ArgumentException(
303 string.Format(CultureInfo.CurrentCulture,
304 Resources.Error_ArgIncorrectType,
305 "itemType", typeof(ContextItem).FullName)));
308 ContextItem item = null;
309 DefaultContextLayer layer = _currentLayer;
310 while (layer != null && !layer.Items.TryGetValue(itemType, out item))
312 layer = layer.ParentLayer;
319 // Enumerates the context items in the editing context. This enumeration
320 // includes prior layers unless the enumerator hits an isolated layer.
321 // Enumeration is typically not useful in most scenarios but it is provided so
322 // that developers can search in the context and learn what is placed in it.
324 // <returns></returns>
325 public override IEnumerator<ContextItem> GetEnumerator()
327 return _currentLayer.Items.Values.GetEnumerator();
331 // Called when an item changes value. This happens in one of two ways:
332 // either the user has called Change, or the user has removed a layer.
334 // <param name="item"></param>
335 private void OnItemChanged(ContextItem item)
337 SubscribeContextCallback callback;
339 Fx.Assert(item != null, "You cannot pass a null item here.");
341 if (_subscriptions != null && _subscriptions.TryGetValue(item.ItemType, out callback))
348 // Adds an event callback that will be invoked with a context item of the given item type changes.
350 // <param name="contextItemType"></param>
351 // <param name="callback"></param>
352 public override void Subscribe(Type contextItemType, SubscribeContextCallback callback)
354 if (contextItemType == null)
356 throw FxTrace.Exception.ArgumentNull("contextItemType");
358 if (callback == null)
360 throw FxTrace.Exception.ArgumentNull("callback");
362 if (!typeof(ContextItem).IsAssignableFrom(contextItemType))
364 throw FxTrace.Exception.AsError(new ArgumentException(
365 string.Format(CultureInfo.CurrentCulture,
366 Resources.Error_ArgIncorrectType,
367 "contextItemType", typeof(ContextItem).FullName)));
370 if (_subscriptions == null)
372 _subscriptions = new Dictionary<Type, SubscribeContextCallback>();
375 SubscribeContextCallback existing = null;
377 _subscriptions.TryGetValue(contextItemType, out existing);
379 existing = (SubscribeContextCallback)Delegate.Combine(existing, callback);
380 _subscriptions[contextItemType] = existing;
382 // If the context is already present, invoke the callback.
383 ContextItem item = GetValueNull(contextItemType);
392 // Removes a subscription.
394 public override void Unsubscribe(Type contextItemType, SubscribeContextCallback callback)
397 if (contextItemType == null)
399 throw FxTrace.Exception.ArgumentNull("contextItemType");
401 if (callback == null)
403 throw FxTrace.Exception.ArgumentNull("callback");
405 if (!typeof(ContextItem).IsAssignableFrom(contextItemType))
407 throw FxTrace.Exception.AsError(new ArgumentException(
408 string.Format(CultureInfo.CurrentCulture,
409 Resources.Error_ArgIncorrectType,
410 "contextItemType", typeof(ContextItem).FullName)));
412 if (_subscriptions != null)
414 SubscribeContextCallback existing;
415 if (_subscriptions.TryGetValue(contextItemType, out existing))
417 existing = (SubscribeContextCallback)RemoveCallback(existing, callback);
418 if (existing == null)
420 _subscriptions.Remove(contextItemType);
424 _subscriptions[contextItemType] = existing;
431 // This context layer contains our context items.
433 private class DefaultContextLayer
435 private DefaultContextLayer _parentLayer;
436 private Dictionary<Type, ContextItem> _items;
437 private Dictionary<Type, ContextItem> _defaultItems;
439 internal DefaultContextLayer(DefaultContextLayer parentLayer)
441 _parentLayer = parentLayer; // can be null
444 internal Dictionary<Type, ContextItem> DefaultItems
447 if (_defaultItems == null)
449 _defaultItems = new Dictionary<Type, ContextItem>();
451 return _defaultItems;
455 internal Dictionary<Type, ContextItem> Items
460 _items = new Dictionary<Type, ContextItem>();
466 internal DefaultContextLayer ParentLayer
468 get { return _parentLayer; }
474 // This is the default service manager for our editing context.
476 private sealed class DefaultServiceManager : ServiceManager, IDisposable
478 private static readonly object _recursionSentinel = new object();
480 private Dictionary<Type, object> _services;
481 private Dictionary<Type, SubscribeServiceCallback> _subscriptions;
483 internal DefaultServiceManager()
488 // Returns true if the service manager contains a service of the given type.
490 // <param name="serviceType"></param>
491 // <returns></returns>
492 public override bool Contains(Type serviceType)
494 if (serviceType == null)
496 throw FxTrace.Exception.ArgumentNull("serviceType");
498 return (_services != null && _services.ContainsKey(serviceType));
502 // Retrieves the requested service. This method returns null if the service could not be located.
504 // <param name="serviceType"></param>
505 // <returns></returns>
506 public override object GetService(Type serviceType)
508 object result = this.GetPublishedService(serviceType);
511 if (this.Contains(typeof(IServiceProvider)))
513 result = this.GetRequiredService<IServiceProvider>().GetService(serviceType);
516 this.Publish(serviceType, result);
523 object GetPublishedService(Type serviceType)
525 object service = null;
527 if (serviceType == null)
529 throw FxTrace.Exception.ArgumentNull("serviceType");
532 if (_services != null && _services.TryGetValue(serviceType, out service))
535 // If this service is our recursion sentinel, it means that someone is recursing
536 // while resolving a service callback. Throw to break out of the recursion
538 if (service == _recursionSentinel)
540 throw FxTrace.Exception.AsError(new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resources.Error_RecursionResolvingService, serviceType.FullName)));
543 // See if this service is a callback. If it is, invoke it and store
544 // the resulting service back in the dictionary.
545 PublishServiceCallback callback = service as PublishServiceCallback;
546 if (callback != null)
549 // Store a recursion sentinel in the dictionary so we can easily
550 // tell if someone is recursing
551 _services[serviceType] = _recursionSentinel;
554 service = callback(serviceType);
557 throw FxTrace.Exception.AsError(new InvalidOperationException(
558 string.Format(CultureInfo.CurrentCulture,
559 Resources.Error_NullService,
560 callback.Method.DeclaringType.FullName,
561 serviceType.FullName)));
564 if (!serviceType.IsInstanceOfType(service))
566 throw FxTrace.Exception.AsError(new InvalidOperationException(
567 string.Format(CultureInfo.CurrentCulture,
568 Resources.Error_IncorrectServiceType,
569 callback.Method.DeclaringType.FullName,
570 serviceType.FullName,
571 service.GetType().FullName)));
576 // Note, this puts the callback back in place if it threw.
577 _services[serviceType] = service;
582 // If the service is not found locally, do not walk up the parent chain.
583 // This was a major source of unreliability with the component model
584 // design. For a service to be accessible from the editing context, it
591 // Retrieves an enumerator that can be used to enumerate all of the services that this
592 // service manager publishes.
594 // <returns></returns>
595 public override IEnumerator<Type> GetEnumerator()
597 if (_services == null)
599 _services = new Dictionary<Type, object>();
602 return _services.Keys.GetEnumerator();
606 // Calls back on the provided callback when someone has published the requested service.
607 // If the service was already available, this method invokes the callback immediately.
609 // A generic version of this method is provided for convience, and calls the non-generic
610 // method with appropriate casts.
612 // <param name="serviceType"></param>
613 // <param name="callback"></param>
614 public override void Subscribe(Type serviceType, SubscribeServiceCallback callback)
616 if (serviceType == null)
618 throw FxTrace.Exception.ArgumentNull("serviceType");
620 if (callback == null)
622 throw FxTrace.Exception.ArgumentNull("callback");
625 object service = GetService(serviceType);
629 // If the service is already available, callback immediately
630 callback(serviceType, service);
635 // Otherwise, store this for later
636 if (_subscriptions == null)
638 _subscriptions = new Dictionary<Type, SubscribeServiceCallback>();
640 SubscribeServiceCallback existing = null;
641 _subscriptions.TryGetValue(serviceType, out existing);
642 existing = (SubscribeServiceCallback)Delegate.Combine(existing, callback);
643 _subscriptions[serviceType] = existing;
648 // Calls back on the provided callback when someone has published the requested service.
649 // If the service was already available, this method invokes the callback immediately.
651 // A generic version of this method is provided for convience, and calls the non-generic
652 // method with appropriate casts.
654 // <param name="serviceType"></param>
655 // <param name="callback"></param>
656 public override void Publish(Type serviceType, PublishServiceCallback callback)
658 if (serviceType == null)
660 throw FxTrace.Exception.ArgumentNull("serviceType");
662 if (callback == null)
664 throw FxTrace.Exception.ArgumentNull("callback");
667 Publish(serviceType, (object)callback);
671 // If you already have an instance to a service, you can publish it here.
673 // <param name="serviceType"></param>
674 // <param name="serviceInstance"></param>
675 public override void Publish(Type serviceType, object serviceInstance)
677 if (serviceType == null)
679 throw FxTrace.Exception.ArgumentNull("serviceType");
681 if (serviceInstance == null)
683 throw FxTrace.Exception.ArgumentNull("serviceInstance");
686 if (!(serviceInstance is PublishServiceCallback) && !serviceType.IsInstanceOfType(serviceInstance))
688 throw FxTrace.Exception.AsError(new ArgumentException(
689 string.Format(CultureInfo.CurrentCulture,
690 Resources.Error_IncorrectServiceType,
691 typeof(ServiceManager).Name,
692 serviceType.FullName,
693 serviceInstance.GetType().FullName)));
696 if (_services == null)
698 _services = new Dictionary<Type, object>();
703 _services.Add(serviceType, serviceInstance);
705 catch (ArgumentException e)
707 throw FxTrace.Exception.AsError(new ArgumentException(string.Format(
708 CultureInfo.CurrentCulture,
709 Resources.Error_DuplicateService, serviceType.FullName), e));
712 // Now see if there were any subscriptions that required this service
713 SubscribeServiceCallback subscribeCallback;
714 if (_subscriptions != null && _subscriptions.TryGetValue(serviceType, out subscribeCallback))
716 subscribeCallback(serviceType, GetService(serviceType));
717 _subscriptions.Remove(serviceType);
722 // Removes a subscription.
724 public override void Unsubscribe(Type serviceType, SubscribeServiceCallback callback)
727 if (serviceType == null)
729 throw FxTrace.Exception.ArgumentNull("serviceType");
731 if (callback == null)
733 throw FxTrace.Exception.ArgumentNull("callback");
736 if (_subscriptions != null)
738 SubscribeServiceCallback existing;
739 if (_subscriptions.TryGetValue(serviceType, out existing))
741 existing = (SubscribeServiceCallback)RemoveCallback(existing, callback);
742 if (existing == null)
744 _subscriptions.Remove(serviceType);
748 _subscriptions[serviceType] = existing;
755 // We implement IDisposable so that the editing context can destroy us when it
758 void IDisposable.Dispose()
760 if (_services != null)
762 Dictionary<Type, object> services = _services;
766 foreach (object value in services.Values)
768 IDisposable d = value as IDisposable;