1 //----------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation. All rights reserved.
3 //----------------------------------------------------------------
4 namespace System.Activities.Presentation.Internal.PropertyEditing
7 using System.Collections.Generic;
8 using System.ComponentModel;
9 using System.Diagnostics;
10 using System.Diagnostics.CodeAnalysis;
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;
19 using System.Activities.Presentation;
20 using System.Activities.Presentation.Model;
21 using View = System.Activities.Presentation.View;
22 using System.Activities.Presentation.PropertyEditing;
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;
33 using Microsoft.Activities.Presentation;
36 // The main control that acts as the PropertyInspector
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
44 private static readonly Size DesiredIconSize = new Size(40, 40);
46 private View.Selection _displayedSelection;
47 private View.Selection _lastNotifiedSelection;
48 private ModelItem _lastParent;
50 private bool _ignoreSelectionNameChanges;
52 private List<ModelEditingScope> _pendingTransactions = new List<ModelEditingScope>();
53 private PropertyValueEditorCommandHandler _defaultCommandHandler;
54 private IStateContainer _sessionStateContainer;
56 private SelectionPath _lastSelectionPath;
57 private bool _objectSelectionInitialized;
59 private bool _disposed;
60 private bool _isReadOnly;
62 private string propertyPathToSelect;
64 private ContextItemManager designerContextItemManager;
65 private DesignerPerfEventProvider designerPerfEventProvider;
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>();
73 // FxCop complains this.DataContext, which is somewhat bogus
74 [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
75 public PropertyInspector()
77 this.DataContext = this;
81 this.InitializeComponent();
83 //Handle the commit and cancel keys within the property inspector
84 ValueEditorUtils.SetHandlesCommitKeys(this, true);
86 _propertyToolBar.CurrentViewManagerChanged += new EventHandler(OnCurrentViewManagerChanged);
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.
94 public event EventHandler RootViewModified;
96 public event PropertyChangedEventHandler PropertyChanged;
98 [SuppressMessage("Microsoft.Design", "CA1044:PropertiesShouldNotBeWriteOnly", Justification = "No need for a Setter")]
99 public ContextItemManager DesignerContextItemManager
103 this.designerContextItemManager = value;
104 this.designerContextItemManager.Subscribe<View.Selection>(this.OnSelectionChanged);
109 // Gets a value indicating whether the selected object Name should be read-only
111 public bool IsInfoBarNameReadOnly
115 return _displayedSelection == null || _displayedSelection.SelectionCount != 1;
120 // Gets the selection name to display
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
128 if (_displayedSelection == null || _displayedSelection.SelectionCount == 0)
133 if (_displayedSelection.SelectionCount == 1)
135 return _displayedSelection.PrimarySelection.Name;
138 return System.Activities.Presentation.Internal.Properties.Resources.PropertyEditing_MultipleObjectsSelected;
147 if (CanSetSelectionName(_displayedSelection))
149 ModelItem selection = _displayedSelection.PrimarySelection;
150 Fx.Assert(selection != null, "PrimarySelection should not be null");
154 _ignoreSelectionNameChanges = true;
156 using (ModelEditingScope change = selection.BeginEdit(System.Activities.Presentation.Internal.Properties.Resources.PropertyEditing_NameChangeUndoText))
158 if (string.IsNullOrEmpty(value))
160 // Null with cause ClearValue to be called in the base implementation on the NameProperty
161 selection.Name = null;
165 selection.Name = value;
174 Debug.WriteLine(e.ToString());
176 ErrorReporting.ShowErrorMessage(e.Message);
180 _ignoreSelectionNameChanges = false;
183 OnPropertyChanged("SelectionName");
187 Debug.Fail("Shouldn't be able to set a selection name if no or more than one object is selected.");
193 // Gets the icon for the selection
195 public object SelectionIcon
199 if (_displayedSelection == null || _displayedSelection.SelectionCount == 0)
204 if (_displayedSelection.SelectionCount == 1 || AreHomogenous(_displayedSelection.SelectedObjects))
207 if (_displayedSelection.SelectionCount == 1)
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()))
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;
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)
230 ScaleTransform mirrorTransform = new ScaleTransform(-1, 1);
231 mirrorTransform.CenterX = rect.Width / 2;
232 mirrorTransform.CenterY = rect.Height / 2;
233 controlBrush.Transform = mirrorTransform;
235 rect.Fill = controlBrush;
240 // The selected object is not a visual, so show a non-designable object icon
241 return GetEmbeddedImage("NonDesignableSelection.png");
245 // Show mutliple-selection of the same type icon
246 return GetEmbeddedImage("MultiSelectionSameType.png");
249 // Show multiple-selection of different types icon
250 return GetEmbeddedImage("MultiSelectionDifferentType.png");
255 // Gets the Type name for the current selection
257 public string SelectionTypeName
261 if (_displayedSelection == null || _displayedSelection.SelectionCount == 0)
266 if (_displayedSelection.SelectionCount == 1 || AreHomogenous(_displayedSelection.SelectedObjects))
268 return GetStringRepresentation(_displayedSelection.PrimarySelection.ItemType);
271 return System.Activities.Presentation.Internal.Properties.Resources.PropertyEditing_MultipleTypesSelected;
275 static string GetStringRepresentation(Type type)
277 return TypeNameHelper.GetDisplayName(type, true);
283 // Gets the state that should be persisted while the host is
284 // running, but discarded when the host shuts down.
286 public object SessionState
290 // Don't instantiate the SessionStateContainer until
291 // CategoryList has been instantiated. Otherwise, we would
292 // get an invalid container.
293 if (_categoryList == null)
298 return SessionStateContainer.RetrieveState();
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)
310 SessionStateContainer.RestoreState(value);
312 _objectSelectionInitialized = false;
316 public bool IsReadOnly
318 get { return this._isReadOnly; }
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");
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.
333 public bool IsInAlphaView
335 get { return _propertyToolBar.IsAlphaViewSelected; }
336 set { _propertyToolBar.IsAlphaViewSelected = value; }
339 private void SelectPropertyByPathOnIdle()
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)
347 Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new MethodInvoker(SelectPropertyByPathOnIdle));
351 internal void SelectPropertyByPath(string path)
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));
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; } }
373 internal EditingContext EditingContext { get; set; }
375 private DesignerPerfEventProvider DesignerPerfEventProvider
379 if (this.designerPerfEventProvider == null && this.EditingContext != null)
381 this.designerPerfEventProvider = this.EditingContext.Services.GetService<DesignerPerfEventProvider>();
383 return this.designerPerfEventProvider;
387 private SelectionPath LastSelectionPath
389 get { return _lastSelectionPath; }
390 set { _lastSelectionPath = value; }
393 private IStateContainer SessionStateContainer
397 if (_categoryList == null)
402 if (_sessionStateContainer == null)
404 _sessionStateContainer = new AggregateStateContainer(
405 PropertyStateContainer.Instance,
407 new SelectionPathStateContainer(this),
408 PropertyActiveEditModeStateContainer.Instance,
409 PropertyViewManagerStateContainer.Instance);
412 return _sessionStateContainer;
416 // IPropertyInspectorState
418 internal void Dispose()
421 DisassociateAllProperties();
422 UpdateSelectionPropertyChangedEventHooks(_displayedSelection, null);
423 _displayedSelection = null;
424 _defaultCommandHandler.Dispose();
425 _defaultCommandHandler = null;
428 private void HookIntoCommands()
430 // Use a helper classes to handle all the standard PI commands
431 _defaultCommandHandler = new PropertyValueEditorCommandHandler(this);
435 // Marks all shown properties as disassociated which disables all modifications
436 // done to them through the PI model objects.
438 private void DisassociateAllProperties()
440 if (_categoryList != null && _categoryList.IsLoaded)
442 foreach (ModelCategoryEntry category in _categoryList)
444 category.MarkAllPropertiesDisassociated();
451 private void OnCurrentViewManagerChanged(object sender, EventArgs e)
453 this.RefreshPropertyList(false);
455 // Isolate the current view of the root PropertyInspector into
456 // its own separate flag and event to appease the VS ----s
458 if (this.RootViewModified != null)
460 RootViewModified(null, EventArgs.Empty);
464 private void RefreshPropertyList(bool attachedOnly)
466 UpdateCategories(_lastNotifiedSelection, attachedOnly);
467 UpdateCategoryEditors(_lastNotifiedSelection);
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.
474 if (_objectSelectionInitialized)
476 LastSelectionPath = _categoryList.SelectionPath;
479 _objectSelectionInitialized = true;
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.
485 Dispatcher.BeginInvoke(DispatcherPriority.Render, new UpdateSelectedPropertyInvoker(UpdateSelectedProperty), _lastNotifiedSelection);
491 // SelectionPathStateContainer
494 // Called externally whenever selection changes
496 // <param name="selection">New selection</param>
497 public void OnSelectionChanged(View.Selection selection)
499 _lastNotifiedSelection = selection;
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.
508 public void RefreshSelection()
510 Dispatcher.BeginInvoke(DispatcherPriority.Background, new MethodInvoker(OnSelectionChangedIdle));
513 // Updates PI when the application becomes Idle (perf optimization)
514 private void OnSelectionChangedIdle()
516 if (DesignerPerfEventProvider != null)
518 DesignerPerfEventProvider.PropertyInspectorUpdatePropertyListStart();
521 if (AreSelectionsEquivalent(_lastNotifiedSelection, _displayedSelection))
526 if (!VisualTreeUtils.IsVisible(this))
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)
539 FrameworkElement selectedElement = _lastNotifiedSelection.PrimarySelection.View as FrameworkElement;
540 if (selectedElement != null)
542 commmonFD = selectedElement.FlowDirection;
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)
550 foreach (ModelItem item in _lastNotifiedSelection.SelectedObjects)
552 FrameworkElement curElm = item.View as FrameworkElement;
553 if (curElm != null && curElm.FlowDirection != commmonFD)
555 //reset to LTR (since the FD's are different within multiselect)
556 commmonFD = FlowDirection.LeftToRight;
563 PropertyInspectorResources.GetResources()["SelectedControlFlowDirectionRTL"] = commmonFD;
565 RefreshPropertyList(false);
567 UpdateSelectionPropertyChangedEventHooks(_displayedSelection, _lastNotifiedSelection);
568 _displayedSelection = _lastNotifiedSelection;
569 _lastParent = GetCommonParent(_lastNotifiedSelection);
571 // Handle dangling transactions
572 _defaultCommandHandler.CommitOpenTransactions();
574 OnPropertyChanged("IsInfoBarNameReadOnly");
575 OnPropertyChanged("SelectionName");
576 OnPropertyChanged("SelectionIcon");
577 OnPropertyChanged("SelectionTypeName");
580 // Removes / adds a PropertyChanged listener from / to the previous / current selection
581 private void UpdateSelectionPropertyChangedEventHooks(View.Selection previousSelection, View.Selection currentSelection)
583 if (previousSelection != null && previousSelection.PrimarySelection != null)
585 previousSelection.PrimarySelection.PropertyChanged -= OnSelectedItemPropertyChanged;
588 if (currentSelection != null && currentSelection.PrimarySelection != null)
590 currentSelection.PrimarySelection.PropertyChanged += OnSelectedItemPropertyChanged;
594 private void OnSelectedItemPropertyChanged(object sender, PropertyChangedEventArgs e)
596 if (_ignoreSelectionNameChanges)
601 // PS 40699 - Name is not a special property for WF
602 //if ("Name".Equals(e.PropertyName))
604 // OnSelectedItemNameChanged();
607 if ("Parent".Equals(e.PropertyName))
613 // Called when the name changes
614 private void OnSelectedItemNameChanged()
616 OnPropertyChanged("SelectionName");
619 // Called when the parent of the current selection changes
620 private void OnParentChanged()
622 Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new MethodInvoker(OnParentChangedIdle));
625 private void OnParentChangedIdle()
627 if (_displayedSelection == null || _displayedSelection.SelectionCount < 1)
632 ModelItem newParent = GetCommonParent(_displayedSelection);
634 if (_lastParent != newParent)
636 RefreshPropertyList(true);
637 _lastParent = newParent;
641 // Looks for common parent ModelItem among all the items in the selection
642 private static ModelItem GetCommonParent(View.Selection selection)
644 if (selection == null || selection.SelectionCount < 1)
649 ModelItem parent = null;
650 foreach (ModelItem item in selection.SelectedObjects)
654 parent = item.Parent;
656 else if (parent != item.Parent)
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)
669 return selection != null && selection.SelectionCount == 1;
672 private static bool AreSelectionsEquivalent(View.Selection a, View.Selection b)
674 if (a == null && b == null)
678 if (a == null || b == null)
682 if (a.SelectionCount != b.SelectionCount)
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();
691 while (ea.MoveNext() && eb.MoveNext())
693 if (!object.Equals(ea.Current, eb.Current))
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)
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.
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)
722 category.MarkAttachedPropertiesDisassociated();
726 category.MarkAllPropertiesDisassociated();
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
734 foreach (IEnumerable<ModelProperty> propertySet in
735 ModelPropertyMerger.GetMergedProperties(
736 selection == null ? null : selection.SelectedObjects,
737 selection == null ? 0 : selection.SelectionCount))
740 string propertyName = GetPropertyName(propertySet);
742 // Specifically filter out the Name property
743 // PS 40699 - Name is not a special property for WF
744 //if ("Name".Equals(propertyName))
749 if (attachedOnly && propertyName.IndexOf('.') < 0)
754 ModelPropertyEntry wrappedProperty = _propertyToolBar.CurrentViewManager.AddProperty(propertySet, propertyName, _categoryList);
756 // Make sure no valid properties get culled out
757 wrappedProperty.Disassociated = false;
760 // Third, we walk the properties and categories, and we cull out all of the
761 // marked properties. Empty categories are removed.
763 for (int i = _categoryList.Count - 1; i >= 0; i--)
765 ModelCategoryEntry category = (ModelCategoryEntry)_categoryList[i];
766 category.CullDisassociatedProperties();
767 if (category.IsEmpty)
769 _categoryList.RemoveAt(i);
773 _categoryList.RefreshFilter();
776 // Helper method that adjusts the visible set of CategoryEditors based on the specified selection
777 private void UpdateCategoryEditors(View.Selection selection)
780 // Figure out which category editors to show
781 Dictionary<Type, object> newCategoryEditorTypes = _propertyToolBar.CurrentViewManager.GetCategoryEditors(
782 FindCommonType(selection == null ? null : selection.SelectedObjects),
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)
789 if (!newCategoryEditorTypes.ContainsKey(item.Key) || !IsCategoryShown(item.Key))
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)
797 editorTypesToRemove = new List<Type>();
800 editorTypesToRemove.Add(item.Key);
804 // This category editor already exists, so don't re-add it
805 newCategoryEditorTypes.Remove(item.Key);
809 if (editorTypesToRemove != null)
811 foreach (Type editorTypeToRemove in editorTypesToRemove)
813 ModelCategoryEntry affectedCategory = _categoryList.FindCategory(_activeCategoryEditors[editorTypeToRemove]) as ModelCategoryEntry;
814 if (affectedCategory != null)
816 affectedCategory.RemoveCategoryEditor(editorTypeToRemove);
819 _activeCategoryEditors.Remove(editorTypeToRemove);
823 // Figure out which CategoryEditors are now required and add them
824 foreach (Type editorTypeToAdd in newCategoryEditorTypes.Keys)
826 CategoryEditor editor = (CategoryEditor)ExtensibilityAccessor.SafeCreateInstance(editorTypeToAdd);
832 ModelCategoryEntry affectedCategory = _categoryList.FindCategory(editor.TargetCategory) as ModelCategoryEntry;
833 if (affectedCategory == null)
838 affectedCategory.AddCategoryEditor(editor);
839 _activeCategoryEditors[editorTypeToAdd] = editor.TargetCategory;
843 // Check if the category is shown for the current category editor type
844 private bool IsCategoryShown(Type categoryEditorType)
847 CategoryEditor editorToRemove = (CategoryEditor)ExtensibilityAccessor.SafeCreateInstance(categoryEditorType);
848 if (editorToRemove != null)
850 ModelCategoryEntry affectedCategory = _categoryList.FindCategory(editorToRemove.TargetCategory) as ModelCategoryEntry;
851 if (affectedCategory == null)
863 // Tries to figure out what property to select and selects is
864 private void UpdateSelectedProperty(View.Selection selection)
867 // If we are not loaded, skip any and all selection magic
873 if (selection != null)
876 // See what the view would like us to select if we run out of things
877 // we can think of selecting
879 SelectionPath fallbackSelection = null;
880 if (_propertyToolBar.CurrentViewManager != null)
882 fallbackSelection = _propertyToolBar.CurrentViewManager.GetDefaultSelectionPath(_categoryList);
885 // Select the first thing we request that exists, using the following
888 // * LastSelectionPath
890 // * Whatever the view wants to show (first category, first property, ...)
892 _categoryList.UpdateSelectedProperty(
893 this.LastSelectionPath,
894 ModelPropertyMerger.GetMergedDefaultProperty(selection.SelectedObjects),
898 if (DesignerPerfEventProvider != null)
900 DesignerPerfEventProvider.PropertyInspectorUpdatePropertyListEnd();
904 private static Type FindCommonType(IEnumerable<ModelItem> modelItems)
906 Type commonType = null;
908 if (modelItems != null)
910 foreach (ModelItem selectedItem in modelItems)
912 if (commonType == null)
914 commonType = selectedItem.ItemType;
918 commonType = ModelUtilities.GetCommonAncestor(commonType, selectedItem.ItemType);
926 private static bool AreHomogenous(IEnumerable<ModelItem> items)
928 Fx.Assert(items != null, "items parameter is null");
931 foreach (ModelItem item in items)
935 type = item.ItemType;
937 else if (type != item.ItemType)
948 private static string GetPropertyName(IEnumerable<ModelProperty> propertySet)
950 if (propertySet == null)
954 foreach (ModelProperty property in propertySet)
956 return property.Name;
961 private static Image GetEmbeddedImage(string imageName)
963 Image image = new Image();
964 image.Source = new BitmapImage(new Uri(
966 "/System.Activities.Presentation;component/System/Activities/Presentation/Base/Core/Internal/PropertyEditing/Resources/",
968 UriKind.RelativeOrAbsolute));
973 // AutomationPeer Stuff
975 protected override AutomationPeer OnCreateAutomationPeer()
977 return new PropertyInspectorAutomationPeer(this);
981 // Cross-domain State Storage
984 // Clears the FilterString
986 public void ClearFilterString()
988 _categoryList.FilterString = null;
991 // INotifyPropertyChanged Members
993 private void OnPropertyChanged(string propertyName)
995 if (PropertyChanged != null)
997 PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
1001 private delegate void MethodInvoker();
1002 private delegate void UpdateSelectedPropertyInvoker(View.Selection selection);
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.
1010 private class SelectionPathStateContainer : IStateContainer
1012 private PropertyInspector _parent;
1014 public SelectionPathStateContainer(PropertyInspector parent)
1018 throw FxTrace.Exception.ArgumentNull("parent");
1024 // Pulls the SelectionPath from the CategoryList, but only if it was Sticky,
1025 // meaning we should preserve it
1027 public object RetrieveState()
1029 if (_parent.CategoryList != null)
1031 SelectionPath path = _parent._objectSelectionInitialized ? _parent.CategoryList.SelectionPath : _parent.LastSelectionPath;
1032 return path == null ? null : path.State;
1039 // Pulls the SelectionPath from the CategoryList, but only if it was Sticky,
1040 // meaning we should preserve it
1042 public void RestoreState(object state)
1046 SelectionPath restoredPath = SelectionPath.FromState(state);
1047 _parent.LastSelectionPath = restoredPath;