a97aca163a8cd10c637c8da8c63f9eff82b142c5
[mono.git] / mcs / class / referencesource / System.Activities.Presentation / System.Activities.Presentation / System / Activities / Presentation / Base / Core / Internal / PropertyEditing / PropertyInspector.xaml.cs
1 //----------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation.  All rights reserved.
3 //----------------------------------------------------------------
4 namespace System.Activities.Presentation.Internal.PropertyEditing
5 {
6     using System;
7     using System.Collections.Generic;
8     using System.ComponentModel;
9     using System.Diagnostics;
10     using System.Diagnostics.CodeAnalysis;
11     using System.Windows;
12     using System.Windows.Automation.Peers;
13     using System.Windows.Controls;
14     using System.Windows.Media;
15     using System.Windows.Media.Imaging;
16     using System.Windows.Shapes;
17     using System.Windows.Threading;
18
19     using System.Activities.Presentation;
20     using System.Activities.Presentation.Model;
21     using View = System.Activities.Presentation.View;
22     using System.Activities.Presentation.PropertyEditing;
23     using System.Runtime;
24
25     using System.Activities.Presentation.Internal.PropertyEditing.Automation;
26     using System.Activities.Presentation.Internal.PropertyEditing.FromExpression.Framework.ValueEditors;
27     using System.Activities.Presentation.Internal.PropertyEditing.Model;
28     using ModelUtilities = System.Activities.Presentation.Internal.PropertyEditing.Model.ModelUtilities;
29     using System.Activities.Presentation.Internal.PropertyEditing.Resources;
30     using System.Activities.Presentation.Internal.PropertyEditing.Selection;
31     using System.Activities.Presentation.Internal.PropertyEditing.State;
32     using System.Text;
33     using Microsoft.Activities.Presentation;
34
35     // <summary>
36     // The main control that acts as the PropertyInspector
37     // </summary>
38     [SuppressMessage(FxCop.Category.Naming, "CA1724:TypeNamesShouldNotMatchNamespaces",
39         Justification = "Code imported from Cider; keeping changes to a minimum as it impacts xaml files as well")]
40     partial class PropertyInspector :
41         INotifyPropertyChanged
42     {
43
44         private static readonly Size DesiredIconSize = new Size(40, 40);
45
46         private View.Selection _displayedSelection;
47         private View.Selection _lastNotifiedSelection;
48         private ModelItem _lastParent;
49
50         private bool _ignoreSelectionNameChanges;
51
52         private List<ModelEditingScope> _pendingTransactions = new List<ModelEditingScope>();
53         private PropertyValueEditorCommandHandler _defaultCommandHandler;
54         private IStateContainer _sessionStateContainer;
55
56         private SelectionPath _lastSelectionPath;
57         private bool _objectSelectionInitialized;
58
59         private bool _disposed;
60         private bool _isReadOnly;
61
62         private string propertyPathToSelect;
63
64         private ContextItemManager designerContextItemManager;
65         private DesignerPerfEventProvider designerPerfEventProvider;
66
67         // Map between currently displayed category editors and the names of the categories they belong to
68         private Dictionary<Type, string> _activeCategoryEditors = new Dictionary<Type, string>();
69
70         // <summary>
71         // Basic ctor
72         // </summary>
73         // FxCop complains this.DataContext, which is somewhat bogus
74         [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
75         public PropertyInspector()
76         {
77             this.DataContext = this;
78
79             HookIntoCommands();
80
81             this.InitializeComponent();
82
83             //Handle the commit and cancel keys within the property inspector
84             ValueEditorUtils.SetHandlesCommitKeys(this, true);
85
86             _propertyToolBar.CurrentViewManagerChanged += new EventHandler(OnCurrentViewManagerChanged);
87         }
88
89         // <summary>
90         // Event fired when the IsInAlphaView changes as a result of some
91         // user or internal interaction.  When IsInAlphaView is set by the
92         // external host, this event will not and should not be fired.
93         // </summary>
94         public event EventHandler RootViewModified;
95
96         public event PropertyChangedEventHandler PropertyChanged;
97
98         [SuppressMessage("Microsoft.Design", "CA1044:PropertiesShouldNotBeWriteOnly", Justification = "No need for a Setter")]
99         public ContextItemManager DesignerContextItemManager
100         {
101             set
102             {
103                 this.designerContextItemManager = value;
104                 this.designerContextItemManager.Subscribe<View.Selection>(this.OnSelectionChanged);
105             }
106         }
107
108         // <summary>
109         // Gets a value indicating whether the selected object Name should be read-only
110         // </summary>
111         public bool IsInfoBarNameReadOnly
112         {
113             get
114             {
115                 return _displayedSelection == null || _displayedSelection.SelectionCount != 1;
116             }
117         }
118
119         // <summary>
120         // Gets the selection name to display
121         // </summary>
122         [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Propagating the error might cause VS to crash")]
123         [SuppressMessage("Reliability", "Reliability108", Justification = "Propagating the error might cause VS to crash")]
124         public string SelectionName
125         {
126             get
127             {
128                 if (_displayedSelection == null || _displayedSelection.SelectionCount == 0)
129                 {
130                     return null;
131                 }
132
133                 if (_displayedSelection.SelectionCount == 1)
134                 {
135                     return _displayedSelection.PrimarySelection.Name;
136                 }
137
138                 return System.Activities.Presentation.Internal.Properties.Resources.PropertyEditing_MultipleObjectsSelected;
139             }
140             set
141             {
142                 if (_disposed)
143                 {
144                     return;
145                 }
146
147                 if (CanSetSelectionName(_displayedSelection))
148                 {
149                     ModelItem selection = _displayedSelection.PrimarySelection;
150                     Fx.Assert(selection != null, "PrimarySelection should not be null");
151
152                     try
153                     {
154                         _ignoreSelectionNameChanges = true;
155
156                         using (ModelEditingScope change = selection.BeginEdit(System.Activities.Presentation.Internal.Properties.Resources.PropertyEditing_NameChangeUndoText))
157                         {
158                             if (string.IsNullOrEmpty(value))
159                             {
160                                 // Null with cause ClearValue to be called in the base implementation on the NameProperty
161                                 selection.Name = null;
162                             }
163                             else
164                             {
165                                 selection.Name = value;
166                             }
167
168                             if (change != null)
169                                 change.Complete();
170                         }
171                     }
172                     catch (Exception e)
173                     {
174                         Debug.WriteLine(e.ToString());
175
176                         ErrorReporting.ShowErrorMessage(e.Message);
177                     }
178                     finally
179                     {
180                         _ignoreSelectionNameChanges = false;
181                     }
182
183                     OnPropertyChanged("SelectionName");
184                 }
185                 else
186                 {
187                     Debug.Fail("Shouldn't be able to set a selection name if no or more than one object is selected.");
188                 }
189             }
190         }
191
192         // <summary>
193         // Gets the icon for the selection
194         // </summary>
195         public object SelectionIcon
196         {
197             get
198             {
199                 if (_displayedSelection == null || _displayedSelection.SelectionCount == 0)
200                 {
201                     return null;
202                 }
203
204                 if (_displayedSelection.SelectionCount == 1 || AreHomogenous(_displayedSelection.SelectedObjects))
205                 {
206
207                     if (_displayedSelection.SelectionCount == 1)
208                     {
209
210                         Visual selectedVisual = _displayedSelection.PrimarySelection.View as Visual;
211                         // We dont want to show tooltips for elements that derive from "Window" class.  
212                         // But we do want to show it for DesignTimeWindow, hence we check the View, so that modelItem returns the correct value 
213                         // for designtimewindow.
214                         if (selectedVisual != null && !typeof(Window).IsAssignableFrom(_displayedSelection.PrimarySelection.View.GetType()))
215                         {
216                             // Show a small preview of the selected single object
217                             VisualBrush controlBrush = new VisualBrush(selectedVisual);
218                             controlBrush.Stretch = Stretch.Uniform;
219                             Rectangle rect = new Rectangle();
220                             rect.Width = DesiredIconSize.Width;
221                             rect.Height = DesiredIconSize.Height;
222                             rect.DataContext = string.Empty;
223
224                             // If the control's parent is RTLed, then the VisualBrush "mirrors" the text.
225                             // so apply "mirror" transform to "negate" the mirroring.
226                             FrameworkElement curElement = selectedVisual as FrameworkElement;
227                             FrameworkElement parentElement = curElement.Parent as FrameworkElement;
228                             if (parentElement != null && parentElement.FlowDirection == FlowDirection.RightToLeft)
229                             {
230                                 ScaleTransform mirrorTransform = new ScaleTransform(-1, 1);
231                                 mirrorTransform.CenterX = rect.Width / 2;
232                                 mirrorTransform.CenterY = rect.Height / 2;
233                                 controlBrush.Transform = mirrorTransform;
234                             }
235                             rect.Fill = controlBrush;
236                             return rect;
237                         }
238                         else
239                         {
240                             // The selected object is not a visual, so show a non-designable object icon
241                             return GetEmbeddedImage("NonDesignableSelection.png");
242                         }
243                     }
244
245                     // Show mutliple-selection of the same type icon
246                     return GetEmbeddedImage("MultiSelectionSameType.png");
247                 }
248
249                 // Show multiple-selection of different types icon
250                 return GetEmbeddedImage("MultiSelectionDifferentType.png");
251             }
252         }
253
254         // <summary>
255         // Gets the Type name for the current selection
256         // </summary>
257         public string SelectionTypeName
258         {
259             get
260             {
261                 if (_displayedSelection == null || _displayedSelection.SelectionCount == 0)
262                 {
263                     return null;
264                 }
265
266                 if (_displayedSelection.SelectionCount == 1 || AreHomogenous(_displayedSelection.SelectedObjects))
267                 {
268                     return GetStringRepresentation(_displayedSelection.PrimarySelection.ItemType);
269                 }
270
271                 return System.Activities.Presentation.Internal.Properties.Resources.PropertyEditing_MultipleTypesSelected;
272             }
273         }
274
275         static string GetStringRepresentation(Type type)
276         {
277             return TypeNameHelper.GetDisplayName(type, true);
278         }
279
280         // Property View
281
282         // <summary>
283         // Gets the state that should be persisted while the host is
284         // running, but discarded when the host shuts down.
285         // </summary>
286         public object SessionState
287         {
288             get
289             {
290                 // Don't instantiate the SessionStateContainer until
291                 // CategoryList has been instantiated.  Otherwise, we would
292                 // get an invalid container.
293                 if (_categoryList == null)
294                 {
295                     return null;
296                 }
297
298                 return SessionStateContainer.RetrieveState();
299             }
300             set
301             {
302                 // Don't instantiate the SessionStateContainer until
303                 // CategoryList has been instantiated.  Otherwise, we would
304                 // get an invalid container.
305                 if (_categoryList == null || value == null)
306                 {
307                     return;
308                 }
309
310                 SessionStateContainer.RestoreState(value);
311
312                 _objectSelectionInitialized = false;
313             }
314         }
315
316         public bool IsReadOnly
317         {
318             get { return this._isReadOnly; }
319             internal set
320             {
321                 this._isReadOnly = value;
322                 this._categoryList.Opacity = this._isReadOnly ? 0.8 : 1.0;
323                 this._categoryList.ToolTip = this._isReadOnly ? this.FindResource("editingDisabledHint") : null;
324                 this.OnPropertyChanged("IsReadOnly");
325             }
326         }
327
328         // <summary>
329         // Gets or sets a flag indicating whether the root PropertyInspector
330         // control is in alpha-view.  We isolate this state from any other
331         // to make VS integration easier.
332         // </summary>
333         public bool IsInAlphaView
334         {
335             get { return _propertyToolBar.IsAlphaViewSelected; }
336             set { _propertyToolBar.IsAlphaViewSelected = value; }
337         }
338
339         private void SelectPropertyByPathOnIdle()
340         {
341             SelectionPath selectionPath =
342                 new SelectionPath(PropertySelectionPathInterpreter.PropertyPathTypeId, propertyPathToSelect);
343             bool pendingGeneration;
344             bool result = this._categoryList.SetSelectionPath(selectionPath, out pendingGeneration);
345             if (!result && pendingGeneration)
346             {
347                 Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new MethodInvoker(SelectPropertyByPathOnIdle));
348             }
349         }
350
351         internal void SelectPropertyByPath(string path)
352         {
353             this.propertyPathToSelect = path;
354             // must do it in application idle time, otherwise the propertygrid is not popugrated yet.
355             Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new MethodInvoker(SelectPropertyByPathOnIdle));
356         }
357
358         internal TextBlock SelectionTypeLabel
359         { get { return _typeLabel; } }
360         //internal TextBlock SelectionNameLabel 
361         //{ get { return _nameLabel; } }
362         //internal StringEditor SelectionNameEditor 
363         //{ get { return _nameEditor; } }
364         internal PropertyToolBar PropertyToolBar
365         { get { return _propertyToolBar; } }
366         internal TextBlock NoSearchResultsLabel
367         { get { return _noSearchResultsLabel; } }
368         internal TextBlock UninitializedLabel
369         { get { return _uninitializedLabel; } }
370         internal CategoryList CategoryList
371         { get { return _categoryList; } }
372
373         internal EditingContext EditingContext { get; set; }
374
375         private DesignerPerfEventProvider DesignerPerfEventProvider
376         {
377             get
378             {
379                 if (this.designerPerfEventProvider == null && this.EditingContext != null)
380                 {
381                     this.designerPerfEventProvider = this.EditingContext.Services.GetService<DesignerPerfEventProvider>();
382                 }
383                 return this.designerPerfEventProvider;
384             }
385         }
386
387         private SelectionPath LastSelectionPath
388         {
389             get { return _lastSelectionPath; }
390             set { _lastSelectionPath = value; }
391         }
392
393         private IStateContainer SessionStateContainer
394         {
395             get
396             {
397                 if (_categoryList == null)
398                 {
399                     return null;
400                 }
401
402                 if (_sessionStateContainer == null)
403                 {
404                     _sessionStateContainer = new AggregateStateContainer(
405                         PropertyStateContainer.Instance,
406                         _categoryList,
407                         new SelectionPathStateContainer(this),
408                         PropertyActiveEditModeStateContainer.Instance,
409                         PropertyViewManagerStateContainer.Instance);
410                 }
411
412                 return _sessionStateContainer;
413             }
414         }
415
416         // IPropertyInspectorState
417
418         internal void Dispose()
419         {
420             _disposed = true;
421             DisassociateAllProperties();
422             UpdateSelectionPropertyChangedEventHooks(_displayedSelection, null);
423             _displayedSelection = null;
424             _defaultCommandHandler.Dispose();
425             _defaultCommandHandler = null;
426         }
427
428         private void HookIntoCommands()
429         {
430             // Use a helper classes to handle all the standard PI commands
431             _defaultCommandHandler = new PropertyValueEditorCommandHandler(this);
432         }
433
434         // <summary>
435         // Marks all shown properties as disassociated which disables all modifications
436         // done to them through the PI model objects.
437         // </summary>
438         private void DisassociateAllProperties()
439         {
440             if (_categoryList != null && _categoryList.IsLoaded)
441             {
442                 foreach (ModelCategoryEntry category in _categoryList)
443                 {
444                     category.MarkAllPropertiesDisassociated();
445                 }
446             }
447         }
448
449         // Properties
450
451         private void OnCurrentViewManagerChanged(object sender, EventArgs e)
452         {
453             this.RefreshPropertyList(false);
454
455             // Isolate the current view of the root PropertyInspector into
456             // its own separate flag and event to appease the VS ----s
457             //
458             if (this.RootViewModified != null)
459             {
460                 RootViewModified(null, EventArgs.Empty);
461             }
462         }
463
464         private void RefreshPropertyList(bool attachedOnly)
465         {
466             UpdateCategories(_lastNotifiedSelection, attachedOnly);
467             UpdateCategoryEditors(_lastNotifiedSelection);
468
469             //
470             // The first time SelectionChanges, there is nothing selected, so don't store the
471             // current property selected.  It would just overwrite the selection path that we
472             // received from SelectionPathStateContainer, which is not what we want.
473             //
474             if (_objectSelectionInitialized)
475             {
476                 LastSelectionPath = _categoryList.SelectionPath;
477             }
478
479             _objectSelectionInitialized = true;
480
481             //
482             // Call UpdateSelectedProperty() _after_ the UI renders.  We need to set PropertySelection.IsSelected
483             // property on a templated visual objects (CategoryContainer, PropertyContainer) and those may not exist yet. 
484             //
485             Dispatcher.BeginInvoke(DispatcherPriority.Render, new UpdateSelectedPropertyInvoker(UpdateSelectedProperty), _lastNotifiedSelection);
486         }
487
488
489         // Selection Logic
490
491         // SelectionPathStateContainer
492
493         // <summary>
494         // Called externally whenever selection changes
495         // </summary>
496         // <param name="selection">New selection</param>
497         public void OnSelectionChanged(View.Selection selection)
498         {
499             _lastNotifiedSelection = selection;
500             RefreshSelection();
501         }
502
503         // <summary>
504         // Called when visibility of the PropertyBrowserPane changes and the
505         // PropertyInspector may be showing a stale selection.  This method is identical
506         // to OnSelectionChanged() but with no new selection instance introduced.
507         // </summary>
508         public void RefreshSelection()
509         {
510             Dispatcher.BeginInvoke(DispatcherPriority.Background, new MethodInvoker(OnSelectionChangedIdle));
511         }
512
513         // Updates PI when the application becomes Idle (perf optimization)
514         private void OnSelectionChangedIdle()
515         {
516             if (DesignerPerfEventProvider != null)
517             {
518                 DesignerPerfEventProvider.PropertyInspectorUpdatePropertyListStart();
519             }
520
521             if (AreSelectionsEquivalent(_lastNotifiedSelection, _displayedSelection))
522             {
523                 return;
524             }
525
526             if (!VisualTreeUtils.IsVisible(this))
527             {
528                 return;
529             }
530
531             // Change the SelectedControlFlowDirectionRTL resource property
532             // This will allow the 3rd party editors to look at this property
533             // and change to RTL for controls that support RTL. 
534             // We set the resource to the primary selections RTL property. 
535             FlowDirection commmonFD = this.FlowDirection;
536             if (_lastNotifiedSelection != null && _lastNotifiedSelection.PrimarySelection != null)
537             {
538
539                 FrameworkElement selectedElement = _lastNotifiedSelection.PrimarySelection.View as FrameworkElement;
540                 if (selectedElement != null)
541                 {
542                     commmonFD = selectedElement.FlowDirection;
543                 }
544
545                 // In case of mulitislection, 
546                 // if the FlowDirection is different then always set it to LTR.
547                 // else set it to common FD.
548                 if (_lastNotifiedSelection.SelectionCount > 1)
549                 {
550                     foreach (ModelItem item in _lastNotifiedSelection.SelectedObjects)
551                     {
552                         FrameworkElement curElm = item.View as FrameworkElement;
553                         if (curElm != null && curElm.FlowDirection != commmonFD)
554                         {
555                             //reset to LTR (since the FD's are different within multiselect)
556                             commmonFD = FlowDirection.LeftToRight;
557                             break;
558                         }
559                     }
560                 }
561             }
562
563             PropertyInspectorResources.GetResources()["SelectedControlFlowDirectionRTL"] = commmonFD;
564
565             RefreshPropertyList(false);
566
567             UpdateSelectionPropertyChangedEventHooks(_displayedSelection, _lastNotifiedSelection);
568             _displayedSelection = _lastNotifiedSelection;
569             _lastParent = GetCommonParent(_lastNotifiedSelection);
570
571             // Handle dangling transactions
572             _defaultCommandHandler.CommitOpenTransactions();
573
574             OnPropertyChanged("IsInfoBarNameReadOnly");
575             OnPropertyChanged("SelectionName");
576             OnPropertyChanged("SelectionIcon");
577             OnPropertyChanged("SelectionTypeName");
578         }
579
580         // Removes / adds a PropertyChanged listener from / to the previous / current selection
581         private void UpdateSelectionPropertyChangedEventHooks(View.Selection previousSelection, View.Selection currentSelection)
582         {
583             if (previousSelection != null && previousSelection.PrimarySelection != null)
584             {
585                 previousSelection.PrimarySelection.PropertyChanged -= OnSelectedItemPropertyChanged;
586             }
587
588             if (currentSelection != null && currentSelection.PrimarySelection != null)
589             {
590                 currentSelection.PrimarySelection.PropertyChanged += OnSelectedItemPropertyChanged;
591             }
592         }
593
594         private void OnSelectedItemPropertyChanged(object sender, PropertyChangedEventArgs e)
595         {
596             if (_ignoreSelectionNameChanges)
597             {
598                 return;
599             }
600
601             // PS 40699 - Name is not a special property for WF
602             //if ("Name".Equals(e.PropertyName))
603             //{
604             //  OnSelectedItemNameChanged();
605             //}
606
607             if ("Parent".Equals(e.PropertyName))
608             {
609                 OnParentChanged();
610             }
611         }
612
613         // Called when the name changes
614         private void OnSelectedItemNameChanged()
615         {
616             OnPropertyChanged("SelectionName");
617         }
618
619         // Called when the parent of the current selection changes
620         private void OnParentChanged()
621         {
622             Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new MethodInvoker(OnParentChangedIdle));
623         }
624
625         private void OnParentChangedIdle()
626         {
627             if (_displayedSelection == null || _displayedSelection.SelectionCount < 1)
628             {
629                 return;
630             }
631
632             ModelItem newParent = GetCommonParent(_displayedSelection);
633
634             if (_lastParent != newParent)
635             {
636                 RefreshPropertyList(true);
637                 _lastParent = newParent;
638             }
639         }
640
641         // Looks for common parent ModelItem among all the items in the selection
642         private static ModelItem GetCommonParent(View.Selection selection)
643         {
644             if (selection == null || selection.SelectionCount < 1)
645             {
646                 return null;
647             }
648
649             ModelItem parent = null;
650             foreach (ModelItem item in selection.SelectedObjects)
651             {
652                 if (parent == null)
653                 {
654                     parent = item.Parent;
655                 }
656                 else if (parent != item.Parent)
657                 {
658                     return null;
659                 }
660             }
661
662             return parent;
663         }
664
665         // The user can only specify the name for the selected objects iff exactly one
666         // object is selected.
667         private static bool CanSetSelectionName(View.Selection selection)
668         {
669             return selection != null && selection.SelectionCount == 1;
670         }
671
672         private static bool AreSelectionsEquivalent(View.Selection a, View.Selection b)
673         {
674             if (a == null && b == null)
675             {
676                 return true;
677             }
678             if (a == null || b == null)
679             {
680                 return false;
681             }
682             if (a.SelectionCount != b.SelectionCount)
683             {
684                 return false;
685             }
686
687             // POSSIBLE OPTIMIZATION: be smarter about same selection in a different order
688             IEnumerator<ModelItem> ea = a.SelectedObjects.GetEnumerator();
689             IEnumerator<ModelItem> eb = b.SelectedObjects.GetEnumerator();
690
691             while (ea.MoveNext() && eb.MoveNext())
692             {
693                 if (!object.Equals(ea.Current, eb.Current))
694                 {
695                     return false;
696                 }
697             }
698
699             return true;
700         }
701
702         // This is the work-horse that refreshes the list of properties and categories within a PropertyInspector
703         // window, including refreshing of CategoryEditors, based on the specified selection
704         private void UpdateCategories(View.Selection selection, bool attachedOnly)
705         {
706
707             // Optimization stolen from Sparkle:
708             // re-rendering the categories is the number one perf issue. Clearing
709             // the databound collection results in massive Avalon code execution, and
710             // then re-adding everything causes another huge shuffle. What is more, 
711             // even when changing the selection between different objects, most properties
712             // do not change. Therefore we are going to take the new list of properties 
713             // and we are going to merge them into the existing stuff, using an 
714             // approach I call Mark, Match, and Cull.
715             //
716             // First we mark all the properties in the current collection. Those which 
717             // are still marked at the end will be culled out
718             foreach (ModelCategoryEntry category in _categoryList)
719             {
720                 if (attachedOnly)
721                 {
722                     category.MarkAttachedPropertiesDisassociated();
723                 }
724                 else
725                 {
726                     category.MarkAllPropertiesDisassociated();
727                 }
728             }
729
730             // Second we try to match each property in the list of properties for the newly selected objects 
731             // against something that we already have. If we have a match, then we reset the existing
732             // ModelPropertyEntry and clear the mark
733             //
734             foreach (IEnumerable<ModelProperty> propertySet in
735                 ModelPropertyMerger.GetMergedProperties(
736                 selection == null ? null : selection.SelectedObjects,
737                 selection == null ? 0 : selection.SelectionCount))
738             {
739
740                 string propertyName = GetPropertyName(propertySet);
741
742                 // Specifically filter out the Name property
743                 // PS 40699 - Name is not a special property for WF
744                 //if ("Name".Equals(propertyName))
745                 //{
746                 //    continue;
747                 //}
748
749                 if (attachedOnly && propertyName.IndexOf('.') < 0)
750                 {
751                     continue;
752                 }
753
754                 ModelPropertyEntry wrappedProperty = _propertyToolBar.CurrentViewManager.AddProperty(propertySet, propertyName, _categoryList);
755
756                 // Make sure no valid properties get culled out
757                 wrappedProperty.Disassociated = false;
758             }
759
760             // Third, we walk the properties and categories, and we cull out all of the
761             // marked properties. Empty categories are removed.
762             //
763             for (int i = _categoryList.Count - 1; i >= 0; i--)
764             {
765                 ModelCategoryEntry category = (ModelCategoryEntry)_categoryList[i];
766                 category.CullDisassociatedProperties();
767                 if (category.IsEmpty)
768                 {
769                     _categoryList.RemoveAt(i);
770                 }
771             }
772
773             _categoryList.RefreshFilter();
774         }
775
776         // Helper method that adjusts the visible set of CategoryEditors based on the specified selection
777         private void UpdateCategoryEditors(View.Selection selection)
778         {
779
780             // Figure out which category editors to show
781             Dictionary<Type, object> newCategoryEditorTypes = _propertyToolBar.CurrentViewManager.GetCategoryEditors(
782                 FindCommonType(selection == null ? null : selection.SelectedObjects),
783                 _categoryList);
784
785             // Figure out which CategoryEditors are no longer needed and remove them
786             List<Type> editorTypesToRemove = null;
787             foreach (KeyValuePair<Type, string> item in _activeCategoryEditors)
788             {
789                 if (!newCategoryEditorTypes.ContainsKey(item.Key) || !IsCategoryShown(item.Key))
790                 {
791
792                     // New selection does not include this existing category editor 
793                     // or the category that contains this editor
794                     // so remove the editor.
795                     if (editorTypesToRemove == null)
796                     {
797                         editorTypesToRemove = new List<Type>();
798                     }
799
800                     editorTypesToRemove.Add(item.Key);
801                 }
802                 else
803                 {
804                     // This category editor already exists, so don't re-add it
805                     newCategoryEditorTypes.Remove(item.Key);
806                 }
807             }
808
809             if (editorTypesToRemove != null)
810             {
811                 foreach (Type editorTypeToRemove in editorTypesToRemove)
812                 {
813                     ModelCategoryEntry affectedCategory = _categoryList.FindCategory(_activeCategoryEditors[editorTypeToRemove]) as ModelCategoryEntry;
814                     if (affectedCategory != null)
815                     {
816                         affectedCategory.RemoveCategoryEditor(editorTypeToRemove);
817                     }
818
819                     _activeCategoryEditors.Remove(editorTypeToRemove);
820                 }
821             }
822
823             // Figure out which CategoryEditors are now required and add them
824             foreach (Type editorTypeToAdd in newCategoryEditorTypes.Keys)
825             {
826                 CategoryEditor editor = (CategoryEditor)ExtensibilityAccessor.SafeCreateInstance(editorTypeToAdd);
827                 if (editor == null)
828                 {
829                     continue;
830                 }
831
832                 ModelCategoryEntry affectedCategory = _categoryList.FindCategory(editor.TargetCategory) as ModelCategoryEntry;
833                 if (affectedCategory == null)
834                 {
835                     continue;
836                 }
837
838                 affectedCategory.AddCategoryEditor(editor);
839                 _activeCategoryEditors[editorTypeToAdd] = editor.TargetCategory;
840             }
841         }
842
843         // Check if the category is shown for the current category editor type
844         private bool IsCategoryShown(Type categoryEditorType)
845         {
846             bool ret = true;
847             CategoryEditor editorToRemove = (CategoryEditor)ExtensibilityAccessor.SafeCreateInstance(categoryEditorType);
848             if (editorToRemove != null)
849             {
850                 ModelCategoryEntry affectedCategory = _categoryList.FindCategory(editorToRemove.TargetCategory) as ModelCategoryEntry;
851                 if (affectedCategory == null)
852                 {
853                     ret = false;
854                 }
855             }
856             else
857             {
858                 ret = false;
859             }
860             return ret;
861         }
862
863         // Tries to figure out what property to select and selects is
864         private void UpdateSelectedProperty(View.Selection selection)
865         {
866
867             // If we are not loaded, skip any and all selection magic
868             if (!this.IsLoaded)
869             {
870                 return;
871             }
872
873             if (selection != null)
874             {
875
876                 // See what the view would like us to select if we run out of things
877                 // we can think of selecting
878                 //
879                 SelectionPath fallbackSelection = null;
880                 if (_propertyToolBar.CurrentViewManager != null)
881                 {
882                     fallbackSelection = _propertyToolBar.CurrentViewManager.GetDefaultSelectionPath(_categoryList);
883                 }
884
885                 // Select the first thing we request that exists, using the following
886                 // precedence order:
887                 //
888                 //  * LastSelectionPath
889                 //  * DefaultProperty
890                 //  * Whatever the view wants to show (first category, first property, ...)
891                 //
892                 _categoryList.UpdateSelectedProperty(
893                     this.LastSelectionPath,
894                     ModelPropertyMerger.GetMergedDefaultProperty(selection.SelectedObjects),
895                     fallbackSelection);
896             }
897
898             if (DesignerPerfEventProvider != null)
899             {
900                 DesignerPerfEventProvider.PropertyInspectorUpdatePropertyListEnd();
901             }
902         }
903
904         private static Type FindCommonType(IEnumerable<ModelItem> modelItems)
905         {
906             Type commonType = null;
907
908             if (modelItems != null)
909             {
910                 foreach (ModelItem selectedItem in modelItems)
911                 {
912                     if (commonType == null)
913                     {
914                         commonType = selectedItem.ItemType;
915                     }
916                     else
917                     {
918                         commonType = ModelUtilities.GetCommonAncestor(commonType, selectedItem.ItemType);
919                     }
920                 }
921             }
922
923             return commonType;
924         }
925
926         private static bool AreHomogenous(IEnumerable<ModelItem> items)
927         {
928             Fx.Assert(items != null, "items parameter is null");
929
930             Type type = null;
931             foreach (ModelItem item in items)
932             {
933                 if (type == null)
934                 {
935                     type = item.ItemType;
936                 }
937                 else if (type != item.ItemType)
938                 {
939                     return false;
940                 }
941             }
942
943             return true;
944         }
945
946         // Static Helpers
947
948         private static string GetPropertyName(IEnumerable<ModelProperty> propertySet)
949         {
950             if (propertySet == null)
951             {
952                 return null;
953             }
954             foreach (ModelProperty property in propertySet)
955             {
956                 return property.Name;
957             }
958             return null;
959         }
960
961         private static Image GetEmbeddedImage(string imageName)
962         {
963             Image image = new Image();
964             image.Source = new BitmapImage(new Uri(
965                 string.Concat(
966                 "/System.Activities.Presentation;component/System/Activities/Presentation/Base/Core/Internal/PropertyEditing/Resources/",
967                 imageName),
968                 UriKind.RelativeOrAbsolute));
969             return image;
970         }
971
972
973         // AutomationPeer Stuff
974
975         protected override AutomationPeer OnCreateAutomationPeer()
976         {
977             return new PropertyInspectorAutomationPeer(this);
978         }
979
980
981         // Cross-domain State Storage
982
983         // <summary>
984         // Clears the FilterString
985         // </summary>
986         public void ClearFilterString()
987         {
988             _categoryList.FilterString = null;
989         }
990
991         // INotifyPropertyChanged Members
992
993         private void OnPropertyChanged(string propertyName)
994         {
995             if (PropertyChanged != null)
996             {
997                 PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
998             }
999         }
1000
1001         private delegate void MethodInvoker();
1002         private delegate void UpdateSelectedPropertyInvoker(View.Selection selection);
1003
1004         // Container for property-selection state represented by SelectionPath.
1005         // Since we receive a stored SelectionPath on the reload of this control,
1006         // at which point the visuals themselves have not been rendered yet, we
1007         // store the supplied SelectionPath instance and use it to select the 
1008         // correct property only after the UI has been rendered.
1009         //
1010         private class SelectionPathStateContainer : IStateContainer
1011         {
1012             private PropertyInspector _parent;
1013
1014             public SelectionPathStateContainer(PropertyInspector parent)
1015             {
1016                 if (parent == null)
1017                 {
1018                     throw FxTrace.Exception.ArgumentNull("parent");
1019                 }
1020                 _parent = parent;
1021             }
1022
1023             //
1024             // Pulls the SelectionPath from the CategoryList, but only if it was Sticky,
1025             // meaning we should preserve it
1026             //
1027             public object RetrieveState()
1028             {
1029                 if (_parent.CategoryList != null)
1030                 {
1031                     SelectionPath path = _parent._objectSelectionInitialized ? _parent.CategoryList.SelectionPath : _parent.LastSelectionPath;
1032                     return path == null ? null : path.State;
1033                 }
1034
1035                 return null;
1036             }
1037
1038             //
1039             // Pulls the SelectionPath from the CategoryList, but only if it was Sticky,
1040             // meaning we should preserve it
1041             //
1042             public void RestoreState(object state)
1043             {
1044                 if (state != null)
1045                 {
1046                     SelectionPath restoredPath = SelectionPath.FromState(state);
1047                     _parent.LastSelectionPath = restoredPath;
1048                 }
1049             }
1050         }
1051     }
1052 }