1 //----------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation. All rights reserved.
3 //----------------------------------------------------------------
5 namespace System.Activities.Presentation.View.TreeView
8 using System.Activities.Presentation.Internal.PropertyEditing;
9 using System.Activities.Presentation.Model;
10 using System.Activities.Presentation.View.OutlineView;
11 using System.Collections.Generic;
12 using System.Collections.ObjectModel;
13 using System.ComponentModel;
14 using System.Globalization;
17 using System.Windows.Media;
19 class TreeViewItemViewModel : INotifyPropertyChanged
21 string nodePrefixText = string.Empty;
22 bool isExpanded = false;
23 bool isHighlighted = false;
24 DrawingBrush icon = null;
25 HashSet<ModelItem> uniqueChildren = null;
26 internal static TreeViewItemViewModel DummyNode = new TreeViewItemViewModel();
27 // the IconCache is a static singleton based on the assumption that the Icon for a particular type is
28 // the same within an AppDomain
29 internal static Dictionary<Type, DrawingBrush> IconCache = new Dictionary<Type, DrawingBrush>();
30 DesignerPerfEventProvider provider = null;
32 public event PropertyChangedEventHandler PropertyChanged;
33 protected TreeViewItemViewModel()
36 InternalChildren = new ObservableCollection<TreeViewItemViewModel>();
37 InternalChildren.CollectionChanged += new Collections.Specialized.NotifyCollectionChangedEventHandler(InternalChildren_CollectionChanged);
38 Children = new ReadOnlyObservableCollection<TreeViewItemViewModel>(InternalChildren);
39 ChildrenValueCache = new HashSet<object>();
40 DuplicatedNodeVisible = true;
42 this.Trackers = new Dictionary<ModelProperty, ChangeNotificationTracker>();
45 internal ObservableCollection<TreeViewItemViewModel> InternalChildren { get; private set; }
47 public ReadOnlyObservableCollection<TreeViewItemViewModel> Children { get; private set; }
49 public string NodePrefixText
53 return nodePrefixText;
57 nodePrefixText = value;
58 this.NotifyPropertyChanged("NodePrefixText");
62 public DrawingBrush Icon
67 if (value != this.icon)
70 this.NotifyPropertyChanged("Icon");
75 internal bool IsAlive { get; private set; }
77 public bool IsExpanded
85 if (value != this.isExpanded)
87 if (this.PerfEventProvider != null)
89 this.PerfEventProvider.DesignerTreeViewExpandStart();
91 this.isExpanded = value;
92 if (this.isExpanded && this.Children.Count == 1 && this.Children[0] == DummyNode)
94 this.InternalChildren.Remove(DummyNode);
97 this.NotifyPropertyChanged("IsExpanded");
98 if (this.PerfEventProvider != null)
100 this.PerfEventProvider.DesignerTreeViewExpandEnd();
106 public bool IsHighlighted
110 return this.isHighlighted;
114 if (this.isHighlighted != value)
116 this.isHighlighted = value;
117 this.NotifyPropertyChanged("IsHighlighted");
122 internal bool DuplicatedNodeVisible { get; set; }
124 internal TreeViewItemState State { get; set; }
126 internal bool HasChildren
130 return (this.State & TreeViewItemState.HasChildren) == TreeViewItemState.HasChildren;
134 internal bool HasSibling
138 return (this.State & TreeViewItemState.HasSibling) == TreeViewItemState.HasSibling;
142 public TreeViewItemViewModel Parent { get; set; }
144 internal ITreeViewItemSelectionHandler TreeViewItem { get; set; }
146 protected HashSet<object> ChildrenValueCache { get; set; }
148 internal Dictionary<ModelProperty, ChangeNotificationTracker> Trackers { get; private set; }
150 protected DesignerPerfEventProvider PerfEventProvider
154 if (provider == null)
156 EditingContext context = this.GetEditingContext();
159 provider = context.Services.GetService<DesignerPerfEventProvider>();
166 protected virtual EditingContext GetEditingContext()
171 internal virtual object GetValue()
176 internal virtual void LoadChildren()
178 foreach (ChangeNotificationTracker t in this.Trackers.Values)
183 foreach (TreeViewItemViewModel child in InternalChildren)
188 this.InternalChildren.Clear();
189 this.ChildrenValueCache.Clear();
192 internal virtual void UpdateChildren(ChangeNotificationTracker tracker, EventArgs e)
194 this.uniqueChildren = null;
197 //if child is null then only add the modelProperty for tracking purpose
198 internal virtual void AddChild(TreeViewItemViewModel child, ModelProperty modelProperty)
200 //check for duplicate first
203 object childValue = child.GetValue();
204 if (!ChildrenValueCache.Contains(childValue))
206 ChildrenValueCache.Add(childValue);
215 ChangeNotificationTracker tracker = GetTracker(modelProperty);
218 // May be adding a node before it's expanded; get rid of the dummy
219 if (this.Children.Count == 1 && this.Children[0] == DummyNode)
221 this.InternalChildren.Remove(DummyNode);
224 int insertIndex = this.FindInsertionIndex(tracker);
225 this.InternalChildren.Insert(insertIndex, child);
226 tracker.ChildViewModels.Add(child);
227 if (child.HasSibling)
229 //loading children rather than just add the sibling of the children
230 //if this turn out to be a big performance impact then we'll need to optimise this
231 child.LoadChildren();
236 internal static TreeViewItemViewModel CreateViewModel(TreeViewItemViewModel parent, object value)
238 TreeViewItemViewModel viewModel = null;
239 if (typeof(ModelItem).IsAssignableFrom(value.GetType()))
241 viewModel = new TreeViewItemModelItemViewModel(parent, value as ModelItem);
243 else if (typeof(ModelProperty).IsAssignableFrom(value.GetType()))
245 viewModel = new TreeViewItemModelPropertyViewModel(parent, value as ModelProperty);
247 else if (typeof(KeyValuePair<ModelItem, ModelItem>).IsAssignableFrom(value.GetType()))
249 viewModel = new TreeViewItemKeyValuePairModelItemViewModel(parent, (KeyValuePair<ModelItem, ModelItem>)value);
254 internal static void AddModelItem(TreeViewItemViewModel parent, ModelItem item, ModelProperty trackingProperty)
258 bool updateTrackingProperty = trackingProperty == null;
260 foreach (ModelProperty property in item.Properties)
262 if (updateTrackingProperty)
264 trackingProperty = property;
266 AddModelProperty(parent, item, trackingProperty, property);
271 internal static void AddModelItemCollection(TreeViewItemViewModel parent, ModelItemCollection collection, ModelProperty trackingProperty)
273 parent.GetTracker(trackingProperty).AddCollection(collection);
275 bool duplicatedNodeVisible = true;
276 string childNodePrefix = string.Empty;
277 ShowPropertyInOutlineViewAttribute viewChild = ExtensibilityAccessor.GetAttribute<ShowPropertyInOutlineViewAttribute>(trackingProperty);
278 if (viewChild != null)
280 duplicatedNodeVisible = viewChild.DuplicatedChildNodesVisible;
281 childNodePrefix = viewChild.ChildNodePrefix;
284 foreach (ModelItem item in collection)
286 AddChild(parent, item, item, duplicatedNodeVisible, childNodePrefix, trackingProperty);
290 internal static void AddModelItemDictionary(TreeViewItemViewModel parent, ModelItemDictionary dictionary, ModelProperty trackingProperty)
292 parent.GetTracker(trackingProperty).AddCollection(dictionary);
294 bool duplicatedNodeVisible = true;
295 string childNodePrefix = string.Empty;
296 ShowPropertyInOutlineViewAttribute viewChild = ExtensibilityAccessor.GetAttribute<ShowPropertyInOutlineViewAttribute>(trackingProperty);
297 if (viewChild != null)
299 duplicatedNodeVisible = viewChild.DuplicatedChildNodesVisible;
300 childNodePrefix = viewChild.ChildNodePrefix;
303 foreach (var pair in dictionary)
305 ModelItem item = null;
306 //AddChild(parent, pair.Value, pair, duplicatedNodeVisible, trackingProperty);
307 AddChild(parent, item, pair, duplicatedNodeVisible, childNodePrefix, trackingProperty);
311 internal static void AddModelProperty(TreeViewItemViewModel parent, ModelItem item, ModelProperty trackingProperty, ModelProperty property)
313 //in the case of multiple attributes, they go in this order
314 //HidePropertyInOutlineViewAttribute
315 //[item.ShowInOutlineViewAttribute.PromotedProperty = property.Name]. Set VisualValue by property and ignore itself. Usage ActivityDelegate, FlowStep.
316 //ShowPropertyInOutlineViewAttribute
317 //ShowPropertyInOutlineViewAsSiblingAttribute
318 //ShowInOutlineViewAttribute
319 ShowPropertyInOutlineViewAttribute viewChild = ExtensibilityAccessor.GetAttribute<ShowPropertyInOutlineViewAttribute>(property);
320 if (ExtensibilityAccessor.GetAttribute<HidePropertyInOutlineViewAttribute>(property) != null)
325 else if (IsPromotedProperty(item, property))
327 if (property.IsCollection)
329 ModelItemCollection mc = property.Value as ModelItemCollection;
330 AddModelItemCollection(parent, mc, trackingProperty);
332 else if (property.IsDictionary)
334 ModelItemDictionary dictionary = property.Dictionary;
335 AddModelItemDictionary(parent, dictionary, trackingProperty);
339 parent.GetTracker(trackingProperty).Add(item, property);
341 //if property.Value is null, then this would not add any node
342 // Use promoted ModelItem's property to track, so pass null to AddModelItem method.
343 AddModelItem(parent, property.Value, null);
346 else if (viewChild != null)
348 if (viewChild.CurrentPropertyVisible) //property node visible
350 if (property.Value != null)
352 TreeViewItemViewModel childModel = TreeViewItemViewModel.CreateViewModel(parent, property);
353 childModel.DuplicatedNodeVisible = viewChild.DuplicatedChildNodesVisible;
354 parent.AddChild(childModel, trackingProperty);
358 //just add the notification tracker without adding the empty child
359 parent.GetTracker(trackingProperty, true).Add(item, trackingProperty);
364 if (property.IsCollection)
366 ModelItemCollection mc = property.Value as ModelItemCollection;
367 AddModelItemCollection(parent, mc, trackingProperty);
369 else if (property.IsDictionary)
371 ModelItemDictionary dictionary = property.Dictionary;
372 AddModelItemDictionary(parent, dictionary, trackingProperty);
376 if (property.Value != null)
378 TreeViewItemViewModel childModel = TreeViewItemViewModel.CreateViewModel(parent, property.Value);
379 childModel.DuplicatedNodeVisible = viewChild.DuplicatedChildNodesVisible;
380 parent.AddChild(childModel, trackingProperty);
384 parent.GetTracker(trackingProperty).Add(item, property);
390 else if (ExtensibilityAccessor.GetAttribute<ShowPropertyInOutlineViewAsSiblingAttribute>(property) != null)
392 //add notification to the tracker that is responsible for this node
393 ChangeNotificationTracker tracker = parent.Parent.GetTracker(parent);
394 tracker.Add(item, property);
395 TreeViewItemViewModel siblingNode = null;
396 if (property.Value != null)
398 siblingNode = TreeViewItemViewModel.CreateViewModel(parent.Parent, property.Value);
400 parent.Parent.AddChild(siblingNode, tracker.ParentProperty);
402 else if (ExtensibilityAccessor.GetAttribute<ShowInOutlineViewAttribute>(property) != null)
404 if (property.Value != null)
406 ShowInOutlineViewAttribute outlineView = ExtensibilityAccessor.GetAttribute<ShowInOutlineViewAttribute>(property);
407 if (string.IsNullOrWhiteSpace(outlineView.PromotedProperty))
409 parent.AddChild(TreeViewItemViewModel.CreateViewModel(parent, property), trackingProperty);
413 ModelProperty promotedProperty = property.Value.Properties.Find(outlineView.PromotedProperty);
414 if (promotedProperty == null)
416 throw FxTrace.Exception.AsError(new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, SR.PromotedPropertyNotFound, outlineView.PromotedProperty, property.Value.Name)));
419 // Add promoted ModelItem and property into tracker. So when property got changed, the grandparent of promoted ModelItem will be notified.
420 ChangeNotificationTracker tracker = parent.GetTracker(trackingProperty, true);
421 tracker.Add(property.Value, promotedProperty);
423 if (promotedProperty.Value == null)
425 tracker.Add(item, property);
429 parent.AddChild(TreeViewItemViewModel.CreateViewModel(parent, property), trackingProperty);
435 parent.GetTracker(trackingProperty, true).Add(item, property);
439 //if the values in the dictionary is viewvisible, note this only works with generic dictionary
440 else if (property.IsDictionary && property.PropertyType.IsGenericType)
442 Type[] arguments = property.PropertyType.GetGenericArguments();
443 if (ExtensibilityAccessor.GetAttribute<ShowInOutlineViewAttribute>(arguments[1]) != null)
445 if (property.Value != null)
447 parent.AddChild(TreeViewItemViewModel.CreateViewModel(parent, property), trackingProperty);
451 parent.GetTracker(trackingProperty, true).Add(item, property);
458 internal static bool IsPromotedProperty(ModelItem modelItem, ModelProperty property)
460 return IsPromotedProperty(modelItem, property.Name);
463 internal static bool IsPromotedProperty(ModelItem modelItem, string propertyName)
465 ShowInOutlineViewAttribute attr = ExtensibilityAccessor.GetAttribute<ShowInOutlineViewAttribute>(modelItem);
466 if (attr != null && !string.IsNullOrEmpty(attr.PromotedProperty))
468 return string.Equals(propertyName, attr.PromotedProperty, StringComparison.Ordinal);
473 internal static void AddChild(TreeViewItemViewModel parent, ModelItem item, object value, bool duplicatedNodeVisible, string childNodePrefix, ModelProperty trackingProperty)
475 // If necessary, evaluate uniqueness of given item
476 bool isUnique = false;
477 if (!duplicatedNodeVisible)
479 // Note: These evaluations expect item to be an immediate child of trackingProperty.Value
480 // Intermediate nodes would for example undermine simple check just below
482 // Caveat 1: Aim is to greatly reduce, not to eliminate, display of nodes visible elsewhere
483 // Caveat 1a: Nodes reachable from other isolated nodes are included in the collection
484 // Caveat 1b: Nodes that are not isolated may be reachable from isolated nodes and thus
485 // displayed together with the isolated ones; ShowPropertyInOutlineViewAsSiblingAttribute may make this seem normal
486 // (If complete duplicate elimination were the aim, would likely need a "never expand"
487 // display mode for duplicateNodeVisible=false children)
488 // Caveat 2: Use of single uniqueChildren field may cause all children of a second
489 // duplcatedNodeVisible=false property to be ignored if (fortunately only if) neither
490 // property uses ShowPropertyInOutlineViewAttribute(true) -- that attribute's default
491 // Caveat 3-n: Please see caveats described at top of UniqueModelItemHelper
492 if (1 >= item.Parents.Count())
498 // Avoided a thorough evaluation as long as we can
499 if (null == parent.uniqueChildren)
501 parent.uniqueChildren = UniqueModelItemHelper.FindUniqueChildren(trackingProperty);
504 isUnique = parent.uniqueChildren.Contains(item);
508 // If displayable now, create the view model node
509 if (duplicatedNodeVisible || isUnique)
511 TreeViewItemViewModel child = TreeViewItemViewModel.CreateViewModel(parent, value);
512 child.NodePrefixText = childNodePrefix;
513 parent.AddChild(child, trackingProperty);
515 // Track for potential addition or removal of parents even if not presently visible
516 if (!duplicatedNodeVisible)
518 ModelItemImpl itemImpl = item as ModelItemImpl;
519 if (null != itemImpl)
521 ChangeNotificationTracker tracker = parent.GetTracker(trackingProperty);
522 tracker.AddCollection(itemImpl.InternalParents);
523 tracker.AddCollection(itemImpl.InternalSources);
528 internal ChangeNotificationTracker GetTracker(ModelProperty modelProperty)
530 return GetTracker(modelProperty, true);
533 internal virtual ChangeNotificationTracker GetTracker(ModelProperty modelProperty, bool createNew)
535 ChangeNotificationTracker tracker = null;
536 if (!this.Trackers.TryGetValue(modelProperty, out tracker) && createNew)
538 tracker = new ChangeNotificationTracker(this, modelProperty);
539 Trackers.Add(modelProperty, tracker);
544 internal ChangeNotificationTracker GetTracker(TreeViewItemViewModel child)
546 ChangeNotificationTracker trackerForChild = null;
547 foreach (ChangeNotificationTracker tracker in this.Trackers.Values)
549 if (tracker.ChildViewModels.Contains(child))
551 trackerForChild = tracker;
555 Fx.Assert(trackerForChild != null, "Tracker should not be null");
556 return trackerForChild;
559 internal virtual int FindInsertionIndex(ChangeNotificationTracker tracker)
562 if (tracker != null && tracker.ChildViewModels != null && tracker.ChildViewModels.Count > 0)
564 //assume the childViewModels are in order
565 insertIndex = this.InternalChildren.IndexOf(tracker.ChildViewModels.Last()) + 1;
570 internal ModelProperty GetTrackingModelPropertyForChild(TreeViewItemViewModel child)
572 ModelProperty property = null;
573 foreach (ChangeNotificationTracker tracker in this.Trackers.Values)
575 if (tracker.ChildViewModels.Contains(child))
577 property = tracker.ParentProperty;
584 internal virtual void UpdateState()
588 void InternalChildren_CollectionChanged(object sender, Collections.Specialized.NotifyCollectionChangedEventArgs e)
590 if (e.Action == Collections.Specialized.NotifyCollectionChangedAction.Remove)
592 foreach (TreeViewItemViewModel item in e.OldItems)
594 this.ChildrenValueCache.Remove(item.GetValue());
599 protected void NotifyPropertyChanged(String propertyName)
601 if (PropertyChanged != null)
603 PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
608 internal enum TreeViewItemState
616 // Disposes this editing context.
618 public void CleanUp()
623 this.IsAlive = false;
627 protected virtual void CleanUpCore()
629 foreach (ChangeNotificationTracker t in this.Trackers.Values)
634 foreach (TreeViewItemViewModel child in InternalChildren)
639 this.InternalChildren.CollectionChanged -= InternalChildren_CollectionChanged;
640 this.Trackers = null;
641 this.InternalChildren = null;
642 this.ChildrenValueCache = null;
643 this.Children = null;
645 this.TreeViewItem = null;
650 internal class TreeViewItemViewModel<T> : TreeViewItemViewModel
652 private T visualValue;
653 //this is the value the UI tree bind to
654 public virtual T VisualValue
662 if (!Equals(visualValue, value))
665 NotifyPropertyChanged("VisualValue");
669 //this is for the view model processing
670 public T Value { get; protected set; }
672 public TreeViewItemViewModel(TreeViewItemViewModel parent)
674 this.Parent = parent;
677 public override string ToString()
681 return Value.ToString();
684 return base.ToString();
687 internal override object GetValue()
692 protected override void CleanUpCore()
694 visualValue = default(T);