1 //----------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation. All rights reserved.
3 //----------------------------------------------------------------
4 namespace System.Activities.Presentation.Internal.PropertyEditing
7 using System.Collections;
8 using System.Collections.Generic;
9 using System.ComponentModel;
10 using System.Diagnostics;
11 using System.Diagnostics.CodeAnalysis;
13 using System.Windows.Data;
14 using System.Windows.Markup;
16 using System.Activities.Presentation.Model;
17 using System.Activities.Presentation.PropertyEditing;
19 using System.Activities.Presentation.Internal.PropertyEditing.FromExpression.Framework;
20 using System.Activities.Presentation.Internal.PropertyEditing.FromExpression.Framework.PropertyInspector;
22 using System.Activities.Presentation.Internal.PropertyEditing.Editors;
23 using ModelUtilities = System.Activities.Presentation.Internal.PropertyEditing.Model.ModelUtilities;
26 // Static helper class that contains all extensibility-related code. No other code
27 // under PropertyEditing should be looking up attributes and interpretting them.
28 // In most cases, the methods here delegate to Sparkle's binaries to make sure that
29 // both products behave consistently.
31 internal static class ExtensibilityAccessor
34 // Cache of Types to their respective DefaultProperties
35 private static Dictionary<Type, string> _defaultPropertyCache = new Dictionary<Type, string>();
38 // Gets the name of the category that the specified ModelProperty belongs to
40 // <param name="property">ModelProperty to examine</param>
41 // <returns>Name of the category that the specified ModelProperty belongs to</returns>
42 public static string GetCategoryName(ModelProperty property)
44 CategoryAttribute attribute = GetAttribute<CategoryAttribute>(property);
46 if (attribute == null || string.IsNullOrEmpty(attribute.Category))
48 return CategoryAttribute.Default.Category;
52 return attribute.Category;
57 // Gets the StandardValues that are exposed by the specified TypeConverter
59 // <param name="converter">TypeConverter to examine</param>
60 // <returns>StandardValues that are exposed by the specified TypeConverter</returns>
61 public static ArrayList GetStandardValues(TypeConverter converter)
63 if (converter == null)
68 if (!converter.GetStandardValuesSupported())
73 ICollection values = converter.GetStandardValues();
79 // unwrap ModelItems if that's what the converter gives us
80 ArrayList convertedValues = new ArrayList(values.Count);
81 foreach (object value in values)
83 ModelItem item = value as ModelItem;
86 convertedValues.Add(item.GetCurrentValue());
90 convertedValues.Add(value);
94 return convertedValues;
98 // Gets a flag indicating if a further call to GetStandardValues will
99 // give back a non-zero collection.
101 // <param name="converter">The type converter to check.</param>
102 // <returns>True if the type converter supports standard values.</returns>
103 public static bool GetStandardValuesSupported(TypeConverter converter)
105 return (converter != null && converter.GetStandardValuesSupported());
109 // Look for and return any custom PropertyValueEditor defined for the specified ModelProperty
111 // <param name="property">ModelProperty to examine</param>
112 // <returns>A custom PropertyValueEditor for the specified ModelProperty (may be null)</returns>
113 public static PropertyValueEditor GetCustomPropertyValueEditor(ModelProperty property)
115 if (property == null)
120 PropertyValueEditor editor = ExtensibilityMetadataHelper.GetValueEditor(property.Attributes, MessageLogger.Instance);
122 //if property is a generic type, check for designer defined at generic type definition
123 if (editor == null && property.PropertyType.IsGenericType)
125 Type genericType = property.PropertyType.GetGenericTypeDefinition();
126 editor = ExtensibilityMetadataHelper.GetValueEditor(TypeDescriptor.GetAttributes(genericType), MessageLogger.Instance);
132 // Returns an instance of SubPropertyEditor if the specified ModelProperty can be edited
133 // using sub-properties, null otherwise.
135 // <param name="property">ModelProperty to examine</param>
136 // <returns>An instance of SubPropertyEditor if the specified ModelProperty can be edited
137 // using sub-properties, null otherwise.</returns>
138 public static PropertyValueEditor GetSubPropertyEditor(ModelProperty property)
140 if (property == null)
145 if (property.Converter == null ||
146 property.Converter.GetPropertiesSupported() == false)
148 // if it's a property of a generic type, check for converter defined at the property of generic type definition
149 if (property.Parent.ItemType.IsGenericType)
151 Type genericType = property.Parent.ItemType.GetGenericTypeDefinition();
152 PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(genericType);
153 PropertyDescriptor propertyDescriptor = properties.Find(property.Name, false);
154 if (propertyDescriptor != null)
156 // wrap the converter and check if it supports GetProperties()
157 TypeConverter converter = new ModelTypeConverter(((IModelTreeItem)property.Parent).ModelTreeManager, propertyDescriptor.Converter);
158 if (converter.GetPropertiesSupported())
160 return SubPropertyViewEditor.Instance;
168 //Dont support Arrays
169 if (typeof(Array).IsAssignableFrom(property.PropertyType))
174 return SubPropertyViewEditor.Instance;
178 // ----s open the specified Type and looks for EditorAttributes that represent
179 // CategoryEditors - returns the Types of those editors, if any are found, as a list
181 // <param name="ownerType">Type to ---- open</param>
182 // <returns>List of CategoryEditors associated with the specified type, if any. Null otherwise.</returns>
183 public static IEnumerable<Type> GetCategoryEditorTypes(Type ownerType)
186 List<Type> editorTypes = null;
188 foreach (EditorAttribute editorAttribute in GetAttributes<EditorAttribute>(ownerType))
191 // A ---- attempt at using the same extensibility code
192 Type editorType = ExtensibilityMetadataHelper.GetCategoryEditorType(editorAttribute, MessageLogger.Instance);
193 if (editorType == null)
198 if (editorTypes == null)
200 editorTypes = new List<Type>();
203 editorTypes.Add(editorType);
210 // Decides whether the specified ModelProperty should be advanced
212 // <param name="property">ModelProperty to look up</param>
213 // <returns>True if the property should be advanced, false otherwise</returns>
214 public static bool GetIsAdvanced(ModelProperty property)
216 EditorBrowsableAttribute browsable = GetAttribute<EditorBrowsableAttribute>(property);
218 if (browsable == null)
223 return browsable.State == EditorBrowsableState.Advanced;
227 // Decides whether the specified CategoryEditor should be advanced.
228 // Note: Blend uses custom, baked-in logic to determine whether a given CategoryEditor
229 // is advanced or not. The logic is the same as the one here, but we can't share it
230 // because they don't expose it. In v2, this is definitely something we should share.
232 // <param name="editor">CategoryEditor to look up</param>
233 // <returns>True if the specified editor should be advanced, false otherwise</returns>
234 public static bool GetIsAdvanced(CategoryEditor editor)
236 EditorBrowsableAttribute browsable = GetAttribute<EditorBrowsableAttribute>(editor.GetType());
238 if (browsable == null)
243 return browsable.State == EditorBrowsableState.Advanced;
247 // Looks up the DefaultPropertyAttribute on the given type and returns the default property,
250 // <param name="type">Type to look up</param>
251 // <returns>Default property associated with the specified type, if any.</returns>
252 public static string GetDefaultProperty(Type type)
259 string defaultProperty;
260 if (_defaultPropertyCache.TryGetValue(type, out defaultProperty))
262 return defaultProperty;
265 DefaultPropertyAttribute dpa = GetAttribute<DefaultPropertyAttribute>(type);
266 defaultProperty = dpa == null ? null : dpa.Name;
267 defaultProperty = defaultProperty == null ? null : defaultProperty.Trim();
268 _defaultPropertyCache[type] = defaultProperty;
269 return defaultProperty;
273 // Attempts to look up a custom display name from the DisplayNameAttribute.
274 // Returns null if the attribute is not defined.
276 // <param name="property">ModelProperty to examine</param>
277 // <returns>Custom DisplayName for the property, if any.</returns>
278 public static string GetDisplayName(ModelProperty property)
280 DisplayNameAttribute displayNameAttribute = GetAttribute<DisplayNameAttribute>(property);
282 if (displayNameAttribute == null)
287 string displayName = displayNameAttribute.DisplayName;
288 if (string.IsNullOrEmpty(displayName))
297 // Gets the description associated with the specified ModelProperty
299 // <param name="property">ModelProperty to examine</param>
300 // <returns>The description associated with the specified ModelProperty</returns>
301 public static string GetDescription(ModelProperty property)
303 DescriptionAttribute description = GetAttribute<DescriptionAttribute>(property);
305 if (description == null || string.IsNullOrEmpty(description.Description))
307 return DescriptionAttribute.Default.Description;
310 return description.Description;
314 // Instantiates a TypeConverter from a potential TypeConverterAttribute, if one exists.
316 // <param name="item">ModelItem to examine</param>
317 // <returns>Instantiated TypeConverter from a potential TypeConverterAttribute, if one exists,
318 // null otherwise.</returns>
319 public static TypeConverter GetTypeConverter(ModelItem item)
321 return InstantiateTypeConverter(GetAttribute<TypeConverterAttribute>(item));
325 // Gets the TypeConverter associated with the specified ModelProperty, returning
326 // null when no TypeConverter is found.
328 // <param name="property">property to examine</param>
329 // <returns>Associated TypeConverter if one exists, null otherwise.</returns>
330 public static TypeConverter GetTypeConverter(ModelProperty property)
332 return property == null ? null : property.Converter;
336 // Computes the IsReadOnly flag for the specified set of properties, ORing
337 // results together for sets of properties larger than 1.
339 // <param name="properties">Properties to examine</param>
340 // <param name="isMixedValueDelegate">Delegate that evaluates the IsMixedValue flag for
341 // the passed in property values (added as an optimization, since we don't always require
342 // the value and it may be computationally expensive)</param>
343 // <returns>Flag indicating whether the set of properties is read only or not</returns>
344 public static bool IsReadOnly(List<ModelProperty> properties, IsMixedValueEvaluator isMixedValueEvaluator)
346 if (properties == null || properties.Count == 0)
348 Debug.Fail("ExtensibilityAccessor.IsReadOnly: No properties specified.");
352 Type propertyType = properties[0].PropertyType;
354 // ILists are readonly only if value is null
355 if (typeof(IList).IsAssignableFrom(propertyType))
358 if (OrReadOnlyValues(properties))
361 if (isMixedValueEvaluator != null)
363 list = isMixedValueEvaluator() ? null : (ModelUtilities.GetSafeRawValue(properties[0]) as IList);
367 Debug.Fail("No delegate to evaluate IsMixedValue specified.");
379 // Arrays and ICollections are readonly
380 if (typeof(Array).IsAssignableFrom(propertyType) || typeof(ICollection).IsAssignableFrom(propertyType))
385 // Types that implement ONLY ICollection<> or ONLY IList<> (meaning they
386 // don't also implement ICollection or IList, which we handle above)
388 if (ModelUtilities.ImplementsICollection(propertyType) || ModelUtilities.ImplementsIList(propertyType))
393 // Otherwise, go off of the IsReadOnly value in ModelProperty
394 return OrReadOnlyValues(properties);
399 // Looks up and returns the BrowsableAttribute on the specified property.
401 // <param name="property">ModelProperty to examine</param>
402 // <returns>True, if the property is marked as browsable, false if it is
403 // marked as non-browsable, null if it is unmarked.</returns>
404 public static bool? IsBrowsable(ModelProperty property)
406 if (property == null)
411 // Check if the Browsable(true) attribute is explicitly defined.
412 BrowsableAttribute browsable = GetAttribute<BrowsableAttribute>(property);
414 // If explicit browsable then honor that.
415 if (browsable != null)
417 return browsable.Browsable;
423 // Gets the PropertyOrder token associated with the given ModelProperty
425 // <param name="property">ModelProperty to examine</param>
426 // <returns>Associated PropertyOrder token if one exists, null otherwise.</returns>
427 public static PropertyOrder GetPropertyOrder(ModelProperty property)
429 if (property == null)
434 PropertyOrderAttribute attr = GetAttribute<PropertyOrderAttribute>(property);
444 // Returns the list of NewItemTypesAttributes that are associated with the
445 // specified ModelProperty. Note that we should never be returning attributes
446 // from any of the public methods of ExtensibilityAccessor. The only reason
447 // why we do so here is to pass them to a Blend API that requires it. However,
448 // this is a design flaw and we should not follow suite elsewhere.
449 // This method is guaranteed not to return null.
451 // <param name="modelProperty">ModelProperty instance to look up</param>
452 // <returns>List of NewItemTypesSttributes associated with the given ModelProperty.</returns>
453 public static List<NewItemTypesAttribute> GetNewItemTypesAttributes(ModelProperty property)
456 List<NewItemTypesAttribute> newItemTypesList = new List<NewItemTypesAttribute>();
458 foreach (NewItemTypesAttribute newItemTypesAttribute in GetAttributes<NewItemTypesAttribute>(property))
461 // if there is no custom ItemFactory defined
462 if (newItemTypesAttribute.FactoryType == typeof(NewItemFactory))
464 foreach (Type type in newItemTypesAttribute.Types)
466 //Check if the type "IsConcreteWithDefaultCtor"
467 if (EditorUtilities.IsConcreteWithDefaultCtor(type))
469 newItemTypesList.Add(new NewItemTypesAttribute(type));
475 newItemTypesList.Add(newItemTypesAttribute);
478 return newItemTypesList;
482 // Examines the specified ModelProperty for NewItemTypesAttributes and, if found, returns
483 // an enumerable of all NewItemFactoryTypeModels specified through them.
485 // <param name="modelProperty">ModelProperty instance to look up</param>
486 // <returns>Returns an enumerable of all NewItemFactoryTypeModels specified through custom
487 // NewItemFactoryTypeModels, if any.</returns>
488 public static IEnumerable<NewItemFactoryTypeModel> GetNewItemFactoryTypeModels(ModelProperty modelProperty, Size desiredIconSize)
490 List<NewItemTypesAttribute> attributes = GetNewItemTypesAttributes(modelProperty);
491 if (attributes == null)
496 foreach (NewItemTypesAttribute attribute in attributes)
498 NewItemFactory factory = (NewItemFactory)Activator.CreateInstance(attribute.FactoryType);
500 foreach (Type type in attribute.Types)
503 NewItemFactoryTypeModel model = null;
505 if (attribute.FactoryType == typeof(NewItemFactory))
507 if (EditorUtilities.IsConcreteWithDefaultCtor(type))
509 model = new NewItemFactoryTypeModel(type, factory, MessageLogger.Instance);
514 model = new NewItemFactoryTypeModel(type, factory, MessageLogger.Instance);
519 model.DesiredSize = desiredIconSize;
527 // Gets all relevant sub-properties from the given ModelItem
529 // <param name="item">Item to examine</param>
530 // <returns>Sub-properties exposed by the given ModelItem</returns>
531 public static List<ModelProperty> GetSubProperties(ModelItem item)
538 // First, see if there is a custom TypeConverter. If so, get the subProperties
539 // from there. Otherwise, get all subProperties including those that aren't browsable,
540 // since the Browsability call should be made by the UI, not by the model.
541 return GetTypeConverterSubProperties(item) ?? GetAllSubProperties(item);
545 // Gets all relevant sub-properties from the value of the specified
548 // <param name="property">ModelProperty to examine</param>
549 // <returns>Sub-properties exposed by the value of the specified ModelProperty</returns>
550 public static List<ModelProperty> GetSubProperties(ModelProperty property)
552 if (property.Value == null || ModelUtilities.GetSafeRawValue(property) == null)
557 // First, see if there is a custom TypeConverter. If so, get the subProperties
558 // from there. Otherwise, get all subProperties including those that aren't browsable,
559 // since the Browsability call should be made by the UI, not by the model.
560 return GetTypeConverterSubProperties(property) ?? GetAllSubProperties(property);
564 // try / catch wrapper artound Activator.CreateInstance()
566 // <param name="type">Type to instantiate</param>
567 // <returns>Instantiated object or null on error</returns>
568 [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
569 public static object SafeCreateInstance(Type type)
573 return Activator.CreateInstance(type);
584 // Returns the property source based on the following heuristic (in line with
585 // Blend's behavior):
588 // -------------------------------------------------------------------
590 // "{Binding}" DataBound
591 // not specified (default value) Default
592 // not specified (inherited value) Inherited
593 // not specified (from style) Inherited
594 // "{DynamicResource ...}" LocalDynamicResource
595 // "{StaticResource ...}" LocalStaticResource
596 // "{x:Static ...}" SystemResource
597 // "{TemplateBinding ...}" TemplateBinding
598 // "{CustomMarkup ...}" CustomMarkupExtension
601 // <param name="property">Property to examine</param>
602 // <returns>Source of the specified property, if any</returns>
603 public static PropertyValueSource GetPropertySource(ModelProperty property)
605 if (property == null)
610 ModelItem valueItem = property.Value;
611 PropertyValueSource source = null;
613 // Binding or any other known markup extension?
614 if (valueItem != null)
616 Type valueType = valueItem.ItemType;
618 if (IsStaticExtension(valueType))
620 source = DependencyPropertyValueSource.SystemResource;
622 else if (typeof(StaticResourceExtension).IsAssignableFrom(valueType))
624 source = DependencyPropertyValueSource.LocalStaticResource;
626 else if (typeof(DynamicResourceExtension).IsAssignableFrom(valueType))
628 source = DependencyPropertyValueSource.LocalDynamicResource;
630 else if (typeof(TemplateBindingExtension).IsAssignableFrom(valueType))
632 source = DependencyPropertyValueSource.TemplateBinding;
634 else if (typeof(Binding).IsAssignableFrom(valueType))
636 source = DependencyPropertyValueSource.DataBound;
638 else if (typeof(MarkupExtension).IsAssignableFrom(valueType))
640 source = DependencyPropertyValueSource.CustomMarkupExtension;
644 // If not, is this a local, inherited, or default value?
650 source = DependencyPropertyValueSource.Local;
655 object value = property.ComputedValue;
657 if (object.Equals(value, property.DefaultValue))
659 source = DependencyPropertyValueSource.DefaultValue;
661 else if (valueItem != null && valueItem.Source != property)
663 source = DependencyPropertyValueSource.Inherited;
671 // Helper method that ORs the ModelProperty.IsReadOnly values together and
672 // returns the result
673 private static bool OrReadOnlyValues(List<ModelProperty> properties)
675 if (properties == null)
680 for (int i = 0; i < properties.Count; i++)
682 if (properties[i].IsReadOnly)
691 // Helper method to find if the propertyvalueeditor is reusable for the
692 // given properties collection.
693 public static bool IsEditorReusable(IEnumerable<ModelProperty> properties)
695 if (properties == null)
700 foreach (ModelProperty property in properties)
702 // even if one property says the editor is not reusable, then
703 // the editor is not reusable for this whole list.
704 if (!ExtensibilityMetadataHelper.IsEditorReusable(property.Attributes))
712 // Hack to deal with {x:Static ...} extensions. The Cider Markup code currently
713 // replaces all StaticExtensions with internal versions of the same class.
714 // Once bug 100647 is fixed this code can go away.
715 private static bool IsStaticExtension(Type type)
717 return type != null && (
718 typeof(StaticExtension).IsAssignableFrom(type) ||
719 string.Equals("System.Activities.Presentation.Internal.Xaml.Builtins.StaticExtension", type.FullName));
722 // Gets all subProperties from the TypeConverter, if one is explicitely specified
723 private static List<ModelProperty> GetTypeConverterSubProperties(ModelItem item)
725 return GetTypeConverterSubPropertiesHelper(item, null);
728 // Gets all subProperties from the TypeConverter, if one is explicitely specified
729 private static List<ModelProperty> GetTypeConverterSubProperties(ModelProperty property)
731 TypeConverter propertySpecificConverter = property.Converter;
732 return GetTypeConverterSubPropertiesHelper(property.Value, propertySpecificConverter);
735 private static List<ModelProperty> GetTypeConverterSubPropertiesHelper(ModelItem item, TypeConverter customConverter)
743 List<ModelProperty> subProperties = null;
745 TypeConverter converter = customConverter;
747 if (converter == null)
749 // See if there is a converter associated with the item type itself
750 converter = ExtensibilityAccessor.GetTypeConverter(item);
753 if (converter != null)
755 PropertyDescriptorCollection subPropertyDescriptors =
756 converter.GetProperties(item.GetCurrentValue());
758 if (subPropertyDescriptors != null && subPropertyDescriptors.Count > 0)
761 foreach (PropertyDescriptor subPropertyDescriptor in subPropertyDescriptors)
764 ModelProperty subProperty = item.Properties[subPropertyDescriptor.Name];
766 // We want to expose all properties through the model regardless of whether they
767 // are browsable or not. That distinction should be made by the UI utilizing it
768 if (subProperty != null)
771 if (subProperties == null)
773 subProperties = new List<ModelProperty>();
776 subProperties.Add(subProperty);
781 return subProperties;
784 // Gets all subProperties that exist
785 private static List<ModelProperty> GetAllSubProperties(ModelItem item)
793 ModelPropertyCollection subModelProperties = item.Properties;
794 if (subModelProperties == null)
799 List<ModelProperty> subProperties = null;
801 // We want to expose all properties through the model regardless of whether they
802 // are browsable or not. That distinction should be made by the UI utilizing it
803 foreach (ModelProperty subModelProperty in subModelProperties)
806 if (subProperties == null)
808 subProperties = new List<ModelProperty>();
811 subProperties.Add(subModelProperty);
814 return subProperties;
817 // Gets all subProperties that exist
818 private static List<ModelProperty> GetAllSubProperties(ModelProperty property)
820 return GetAllSubProperties(property.Value);
823 [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Propagating the error might cause VS to crash")]
824 [SuppressMessage("Reliability", "Reliability108", Justification = "Propagating the error might cause VS to crash")]
825 private static TypeConverter InstantiateTypeConverter(TypeConverterAttribute typeConverterAttribute)
827 if (typeConverterAttribute == null)
834 Type typeConverterType = Type.GetType(typeConverterAttribute.ConverterTypeName);
835 if (typeConverterType != null)
837 return (TypeConverter)Activator.CreateInstance(typeConverterType);
842 // Ignore failures. In the future, log these somewhere for 3rd parties to see and debug.
848 // GetAttributes() and GetAttribute<T>()
850 public static T GetAttribute<T>(ModelProperty property) where T : Attribute
852 return GetAttribute<T>(property == null ? null : property.Attributes);
855 public static T GetAttribute<T>(ModelItem item) where T : Attribute
857 return GetAttribute<T>(item == null ? null : item.Attributes);
860 public static T GetAttribute<T>(Type type) where T : Attribute
862 return GetAttribute<T>(type == null ? null : TypeDescriptor.GetAttributes(type));
865 public static IEnumerable<T> GetAttributes<T>(ModelProperty property) where T : Attribute
867 return GetAttributes<T>(property == null ? null : property.Attributes);
870 public static IEnumerable<T> GetAttributes<T>(Type type) where T : Attribute
872 return GetAttributes<T>(type == null ? null : TypeDescriptor.GetAttributes(type));
875 // Note: Calling AttributeCollection[typeof(MyAttribute)] creates a default attribute if
876 // the specified attribute is not found. That's generally not what we want.
877 public static T GetAttribute<T>(AttributeCollection attributes) where T : Attribute
879 T foundAttribute = null;
880 if (attributes != null)
882 foreach (Attribute attribute in attributes)
884 if (typeof(T).IsAssignableFrom(attribute.GetType()))
886 foundAttribute = attribute as T;
891 return foundAttribute;
894 // Note: Calling AttributeCollection[typeof(MyAttribute)] creates a default attribute if
895 // the specified attribute is not found. That's generally not what we want.
896 private static IEnumerable<T> GetAttributes<T>(AttributeCollection attributes) where T : Attribute
898 if (attributes != null)
900 foreach (Attribute attribute in attributes)
902 if (typeof(T).IsAssignableFrom(attribute.GetType()))
904 yield return (T)attribute;
911 // Delegate intended to wrap logic that evaluates the IsMixedValue flag of
912 // some property or set of properties.
914 // <returns>True if values are mixed, false otherwise</returns>
915 public delegate bool IsMixedValueEvaluator();
918 private class MessageLogger : IMessageLogger
921 private static MessageLogger _instance = new MessageLogger();
923 public static MessageLogger Instance
924 { get { return _instance; } }
930 public void Write(string text)
935 public void WriteLine(string text)
937 Debug.WriteLine(text);