1 //----------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation. All rights reserved.
3 //----------------------------------------------------------------
4 namespace System.Activities.Presentation.Internal.PropertyEditing.Editors
7 using System.Collections.Generic;
8 using System.Collections.ObjectModel;
9 using System.ComponentModel;
10 using System.Globalization;
12 using System.Windows.Automation.Peers;
13 using System.Windows.Controls;
14 using System.Windows.Data;
17 using System.Activities.Presentation.Model;
18 using System.Activities.Presentation.PropertyEditing;
20 using System.Activities.Presentation.Internal.PropertyEditing.FromExpression.Framework.PropertyInspector;
22 using System.Activities.Presentation.Internal.PropertyEditing.Automation;
23 using System.Activities.Presentation.Internal.PropertyEditing.Model;
24 using ModelUtilities = System.Activities.Presentation.Internal.PropertyEditing.Model.ModelUtilities;
25 using System.Activities.Presentation.Internal.PropertyEditing.Resources;
26 using System.Activities.Presentation.Internal.PropertyEditing.Selection;
27 using System.Activities.Presentation.Internal.PropertyEditing.State;
30 // We use the SubPropertyEditor to replace the entire property row
31 // when the property exposes its subproperties. This control is _not_
32 // just used within the value-editing portion of a property row.
33 // We cheat because we can.
35 internal class SubPropertyEditor : Control, INotifyPropertyChanged, ISelectionStop
39 // PropertyEntry is used to store the currently displayed PropertyEntry
41 public static readonly DependencyProperty PropertyEntryProperty = DependencyProperty.Register(
43 typeof(PropertyEntry),
44 typeof(SubPropertyEditor),
45 new PropertyMetadata(null, new PropertyChangedCallback(OnPropertyEntryChanged)));
48 // Boolean used to indicate whether the sub-properties are being shown or not. As an optimization,
49 // we don't actually expose the PropertyValue's sub-properties through SelectiveSubProperties until
50 // the sub-property expando-pane has been open at least once.
52 public static readonly DependencyProperty IsExpandedProperty = DependencyProperty.Register(
55 typeof(SubPropertyEditor),
56 new PropertyMetadata(false, new PropertyChangedCallback(OnIsExpandedChanged)));
59 // Exposes the currently selected QuickType in the QuickType drop-down. Essentially,
60 // the value of this DP is plumbed through to reflect the value of _quickTypeView.CurrentItem
62 public static readonly DependencyProperty CurrentQuickTypeProperty = DependencyProperty.Register(
64 typeof(NewItemFactoryTypeModel),
65 typeof(SubPropertyEditor),
66 new PropertyMetadata(null, new PropertyChangedCallback(OnCurrentQuickTypeChanged)));
68 private ICollectionView _quickTypeView;
69 private ObservableCollection<NewItemFactoryTypeModel> _quickTypeCollection;
70 private bool _ignoreInternalChanges;
71 private bool _exposedSubProperties;
73 private ItemsControl _subPropertyListControl;
78 public SubPropertyEditor()
80 _quickTypeCollection = new ObservableCollection<NewItemFactoryTypeModel>();
82 _quickTypeView = CollectionViewSource.GetDefaultView(_quickTypeCollection);
83 _quickTypeView.CurrentChanged += new EventHandler(OnCurrentQuickTypeChanged);
88 public event PropertyChangedEventHandler PropertyChanged;
90 // Internal event we fire for the sake of SubPropertyEditorAutomationPeer that
91 // causes it to refresh its offered set of children
92 internal event EventHandler VisualsChanged;
94 public PropertyEntry PropertyEntry
96 get { return (PropertyEntry)this.GetValue(PropertyEntryProperty); }
97 set { this.SetValue(PropertyEntryProperty, value); }
100 public bool IsExpanded
102 get { return (bool)this.GetValue(IsExpandedProperty); }
103 set { this.SetValue(IsExpandedProperty, value); }
106 public NewItemFactoryTypeModel CurrentQuickType
108 get { return (NewItemFactoryTypeModel)this.GetValue(CurrentQuickTypeProperty); }
109 set { this.SetValue(CurrentQuickTypeProperty, value); }
113 // Gets a flag indicating whether QuickTypes exist
115 public bool HasQuickTypes
118 return _quickTypeCollection.Count > 0;
123 // Returns a list of available QuickTypes (collection of NewItemFactoryTypeModel instances)
125 public ICollectionView QuickTypes
128 return _quickTypeView;
133 // Exposes PropertyValue.SubProperties when the IsExpanded flag first gets set to true
134 // and forever thereafter (or at least until the current PropertyValue changes and we
135 // collapse the sub-properties)
137 public IEnumerable<PropertyEntry> SelectiveSubProperties
140 if (!_exposedSubProperties)
142 if (!this.IsExpanded)
147 _exposedSubProperties = true;
150 PropertyEntry parent = this.PropertyEntry;
156 foreach (ModelPropertyEntry subProperty in parent.PropertyValue.SubProperties)
158 if (subProperty.IsBrowsable)
160 yield return subProperty;
167 // Gets a flag indicating whether the sub-property editor can be expanded or not.
169 public bool IsExpandable
171 get { return this.HasQuickTypes && this.CurrentQuickType != null; }
175 // Gets a SelectionPath to itself.
177 public SelectionPath Path
179 get { return PropertySelectionPathInterpreter.Instance.ConstructSelectionPath(this.PropertyEntry); }
183 // Gets a description of the contained property
184 // to expose through automation
186 public string Description
189 PropertyEntry property = this.PropertyEntry;
190 if (property != null)
192 return string.Format(
193 CultureInfo.CurrentCulture,
194 Properties.Resources.PropertyEditing_SelectionStatus_Property,
195 this.PropertyEntry.PropertyName);
204 // Exposes the ItemsControl used to display the list of sub-properties. UI-specific
206 private ItemsControl SubPropertyListControl
209 if (_subPropertyListControl == null)
211 _subPropertyListControl = VisualTreeUtils.GetNamedChild<ItemsControl>(this, "PART_SubPropertyList");
212 Fx.Assert(_subPropertyListControl != null, "UI for SubPropertyEditor changed. Need to update SubPropertyEditor class logic.");
215 return _subPropertyListControl;
220 // Keyboard Navigation
222 protected override AutomationPeer OnCreateAutomationPeer()
224 return new SubPropertyEditorAutomationPeer(this);
232 // When the displayed PropertyEntry changes, make sure we update the UI and hook into the
233 // new PropertyEntry's notification mechanism
234 private static void OnPropertyEntryChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
236 SubPropertyEditor theThis = obj as SubPropertyEditor;
242 PropertyEntry oldValue = e.OldValue as PropertyEntry;
243 if (oldValue != null)
245 oldValue.PropertyValue.RootValueChanged -= new EventHandler(theThis.OnPropertyValueRootValueChanged);
248 PropertyEntry newValue = e.NewValue as PropertyEntry;
249 if (newValue != null)
251 newValue.PropertyValue.RootValueChanged += new EventHandler(theThis.OnPropertyValueRootValueChanged);
254 theThis.RefreshVisuals();
257 private void OnPropertyValueRootValueChanged(object sender, EventArgs e)
259 if (_ignoreInternalChanges)
270 private static void OnIsExpandedChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
272 SubPropertyEditor theThis = obj as SubPropertyEditor;
278 bool newIsExpanded = (bool)e.NewValue;
279 PropertyEntry containedProperty = theThis.PropertyEntry;
281 // Store the new expansion state
282 if (containedProperty != null)
284 PropertyState state = PropertyStateContainer.Instance.GetPropertyState(
285 ModelUtilities.GetCachedSubPropertyHierarchyPath(containedProperty));
286 state.SubPropertiesExpanded = newIsExpanded;
289 // If we are expanded but we never exposed the sub-properties to anyone before,
290 // fire a signal saying that a list of sub-properties may be now available, so that
291 // UI DataBindings refresh themselves
292 if (newIsExpanded == true &&
293 theThis._exposedSubProperties == false)
295 theThis.FireSubPropertiesListChangedEvents();
299 // CurrentQuickType DP
301 // This method gets called when the DP changes
302 private static void OnCurrentQuickTypeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
304 SubPropertyEditor theThis = obj as SubPropertyEditor;
310 if (theThis._ignoreInternalChanges)
315 theThis._quickTypeView.MoveCurrentTo(e.NewValue);
317 theThis.ExpandSubProperties();
318 theThis.FireSubPropertiesListChangedEvents();
321 // This method gets called when the CurrentItem on _quickTypeView changes
322 private void OnCurrentQuickTypeChanged(object sender, EventArgs e)
324 if (_ignoreInternalChanges)
329 NewItemFactoryTypeModel selectedTypeModel = _quickTypeView.CurrentItem as NewItemFactoryTypeModel;
331 if (selectedTypeModel == null)
336 Fx.Assert(this.PropertyEntry != null, "PropertyEntry should not be null");
337 if (this.PropertyEntry == null)
342 bool previousValue = IgnoreInternalChanges();
345 this.PropertyEntry.PropertyValue.Value = selectedTypeModel.CreateInstance();
349 NoticeInternalChanges(previousValue);
354 protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
356 if (e.Property == PropertyContainer.OwningPropertyContainerProperty)
359 // A quick and dirty way to register this instance as the implementation of
360 // ISelectionBranchPoint that controls the expansion / collapse of this control
362 OnOwningPropertyContainerChanged((PropertyContainer)e.OldValue, (PropertyContainer)e.NewValue);
366 base.OnPropertyChanged(e);
369 private void OnOwningPropertyContainerChanged(PropertyContainer oldValue, PropertyContainer newValue)
371 if (oldValue != null)
373 PropertySelection.ClearSelectionStop(oldValue);
374 PropertySelection.ClearIsSelectionStopDoubleClickTarget(oldValue);
377 if (newValue != null)
379 PropertySelection.SetSelectionStop(newValue, this);
380 PropertySelection.SetIsSelectionStopDoubleClickTarget(newValue, true);
385 // Visual Lookup Helpers
388 // Looks for and returns the specified sub-property
390 // <param name="propertyName">Sub-property to look up</param>
391 // <returns>Corresponding PropertyEntry if found, null otherwise.</returns>
392 internal PropertyEntry FindSubPropertyEntry(string propertyName)
394 if (string.IsNullOrEmpty(propertyName))
399 foreach (PropertyEntry property in SelectiveSubProperties)
401 if (property.PropertyName.Equals(propertyName))
411 // Looks for and returns the PropertyContainer used to display
412 // the specified PropertyEntry
414 // <param name="property">Property to look for</param>
415 // <returns>Corresponding PropertyContainer if found, null otherwise.</returns>
416 internal PropertyContainer FindSubPropertyEntryVisual(PropertyEntry property)
418 if (property == null)
423 ItemsControl subPropertyListControl = this.SubPropertyListControl;
424 if (subPropertyListControl == null)
429 return subPropertyListControl.ItemContainerGenerator.ContainerFromItem(property) as PropertyContainer;
435 private void RefreshVisuals()
438 RestoreIsExpandedState();
439 FireVisualsChangedEvents();
442 private void RefreshQuickTypes()
444 bool previousValue = IgnoreInternalChanges();
447 _quickTypeCollection.Clear();
449 PropertyEntry containedProperty = this.PropertyEntry;
450 if (containedProperty == null)
455 ModelProperty property = ((ModelPropertyEntry)containedProperty).FirstModelProperty;
456 Type containerValueType = ((ModelPropertyEntryBase)containedProperty).CommonValueType;
457 NewItemFactoryTypeModel selectedFactoryModel = null;
458 Type defaultItemType = GetDefaultItemType(property);
460 // Find all elligible NewItemFactoryTypes declared through metadata
461 IEnumerable<NewItemFactoryTypeModel> factoryModels =
462 ExtensibilityAccessor.GetNewItemFactoryTypeModels(
464 ResourceUtilities.GetDesiredTypeIconSize(this));
466 if (factoryModels != null)
468 foreach (NewItemFactoryTypeModel factoryModel in factoryModels)
470 _quickTypeCollection.Add(factoryModel);
472 if (selectedFactoryModel == null)
474 if (object.Equals(containerValueType, factoryModel.Type))
476 selectedFactoryModel = factoryModel;
480 if (defaultItemType != null &&
481 object.Equals(defaultItemType, factoryModel.Type))
483 defaultItemType = null;
488 //add a null value - user should always have an option to clear property value
489 NewItemFactoryTypeModel nullTypeFactoryTypeModel =
490 new NewItemFactoryTypeModel(null, new NullItemFactory());
492 // Add a default item type based on the property type (if it wasn't also added through
494 if (defaultItemType != null)
496 NewItemFactoryTypeModel defaultItemFactoryTypeModel = new NewItemFactoryTypeModel(defaultItemType, new NewItemFactory());
497 _quickTypeCollection.Add(defaultItemFactoryTypeModel);
499 if (selectedFactoryModel == null)
501 if (object.Equals(containerValueType, defaultItemFactoryTypeModel.Type))
503 selectedFactoryModel = defaultItemFactoryTypeModel;
505 else if (containerValueType == null)
507 selectedFactoryModel = nullTypeFactoryTypeModel;
512 _quickTypeCollection.Add(nullTypeFactoryTypeModel);
514 // Make sure the currently selected value on the CollectionView reflects the
515 // actual value of the property
516 _quickTypeView.MoveCurrentTo(selectedFactoryModel);
517 this.CurrentQuickType = selectedFactoryModel;
521 NoticeInternalChanges(previousValue);
525 private static Type GetDefaultItemType(ModelProperty property)
527 if (property == null)
532 Type propertyType = property.PropertyType;
533 if (EditorUtilities.IsConcreteWithDefaultCtor(propertyType))
541 private void RestoreIsExpandedState()
543 bool newIsExpanded = false;
544 PropertyEntry property = this.PropertyEntry;
546 if (property != null)
548 PropertyState state = PropertyStateContainer.Instance.GetPropertyState(
549 ModelUtilities.GetCachedSubPropertyHierarchyPath(property));
550 newIsExpanded = state.SubPropertiesExpanded;
553 this.IsExpanded = newIsExpanded;
554 _exposedSubProperties = false;
557 private void ExpandSubProperties()
559 this.IsExpanded = true;
563 // Change Notification Helpers
565 private bool IgnoreInternalChanges()
567 bool previousValue = _ignoreInternalChanges;
568 _ignoreInternalChanges = true;
569 return previousValue;
572 private void NoticeInternalChanges(bool previousValue)
574 _ignoreInternalChanges = previousValue;
577 private void FireVisualsChangedEvents()
579 // Fire updated events
580 OnPropertyChanged("HasQuickTypes");
581 OnPropertyChanged("QuickTypes");
582 FireSubPropertiesListChangedEvents();
585 private void FireSubPropertiesListChangedEvents()
587 OnPropertyChanged("IsExpandable");
588 OnPropertyChanged("SelectiveSubProperties");
590 if (VisualsChanged != null)
592 VisualsChanged(this, EventArgs.Empty);
597 // INotifyPropertyChanged Members
599 private void OnPropertyChanged(string propertyName)
601 Fx.Assert(!string.IsNullOrEmpty(propertyName), "Can't raise OnPropertyChanged event without a valid property name.");
603 if (PropertyChanged != null)
605 this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
609 // ISelectionStop Members
611 //NullItemFactory - this class is used to provide a null entry in quick types list - it is required to allow user
612 //to clear value of an object.
613 sealed class NullItemFactory : NewItemFactory
615 public override object CreateInstance(Type type)
617 //no input type is allowed - we never create instance of anything
618 Fx.Assert(type == null, "NullItemFactory supports only null as type parameter");
622 public override string GetDisplayName(Type type)
624 //no input type is allowed - we always return (null) string
625 Fx.Assert(type == null, "NullItemFactory supports only null as type parameter");