002837b49a84ec0068e19fa674d959d213543fe9
[mono.git] / mcs / class / referencesource / System.Activities.Presentation / System.Activities.Presentation / System / Activities / Presentation / Base / Core / Internal / PropertyEditing / ExtensibilityAccessor.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;
8     using System.Collections.Generic;
9     using System.ComponentModel;
10     using System.Diagnostics;
11     using System.Diagnostics.CodeAnalysis;
12     using System.Windows;
13     using System.Windows.Data;
14     using System.Windows.Markup;
15
16     using System.Activities.Presentation.Model;
17     using System.Activities.Presentation.PropertyEditing;
18
19     using System.Activities.Presentation.Internal.PropertyEditing.FromExpression.Framework;
20     using System.Activities.Presentation.Internal.PropertyEditing.FromExpression.Framework.PropertyInspector;
21
22     using System.Activities.Presentation.Internal.PropertyEditing.Editors;
23     using ModelUtilities = System.Activities.Presentation.Internal.PropertyEditing.Model.ModelUtilities;
24
25     // <summary>
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.
30     // </summary>
31     internal static class ExtensibilityAccessor 
32     {
33
34         // Cache of Types to their respective DefaultProperties
35         private static Dictionary<Type, string> _defaultPropertyCache = new Dictionary<Type, string>();
36
37         // <summary>
38         // Gets the name of the category that the specified ModelProperty belongs to
39         // </summary>
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) 
43         {
44             CategoryAttribute attribute = GetAttribute<CategoryAttribute>(property);
45
46             if (attribute == null || string.IsNullOrEmpty(attribute.Category))
47             {
48                 return CategoryAttribute.Default.Category;
49             }
50             else
51             {
52                 return attribute.Category;
53             }
54         }
55
56         // <summary>
57         // Gets the StandardValues that are exposed by the specified TypeConverter
58         // </summary>
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) 
62         {
63             if (converter == null)
64             {
65                 return null;
66             }
67
68             if (!converter.GetStandardValuesSupported())
69             {
70                 return null;
71             }
72
73             ICollection values = converter.GetStandardValues();
74             if (values == null)
75             {
76                 return null;
77             }
78
79             // unwrap ModelItems if that's what the converter gives us
80             ArrayList convertedValues = new ArrayList(values.Count);
81             foreach (object value in values) 
82             {
83                 ModelItem item = value as ModelItem;
84                 if (item != null)
85                 {
86                     convertedValues.Add(item.GetCurrentValue());
87                 }
88                 else
89                 {
90                     convertedValues.Add(value);
91                 }
92             }
93
94             return convertedValues;
95         }
96
97         // <summary>
98         // Gets a flag indicating if a further call to GetStandardValues will
99         // give back a non-zero collection.
100         // </summary>
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) 
104         {
105             return (converter != null && converter.GetStandardValuesSupported());
106         }
107
108         // <summary>
109         // Look for and return any custom PropertyValueEditor defined for the specified ModelProperty
110         // </summary>
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) 
114         {
115             if (property == null)
116             {
117                 return null;
118             }
119
120             PropertyValueEditor editor = ExtensibilityMetadataHelper.GetValueEditor(property.Attributes, MessageLogger.Instance);
121
122             //if property is a generic type, check for designer defined at generic type definition
123             if (editor == null && property.PropertyType.IsGenericType)
124             {
125                 Type genericType = property.PropertyType.GetGenericTypeDefinition();
126                 editor = ExtensibilityMetadataHelper.GetValueEditor(TypeDescriptor.GetAttributes(genericType), MessageLogger.Instance);
127             }
128
129             return editor;
130         }
131         // <summary>
132         // Returns an instance of SubPropertyEditor if the specified ModelProperty can be edited
133         // using sub-properties, null otherwise.
134         // </summary>
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) 
139         {
140             if (property == null)
141             {
142                 return null;
143             }
144
145             if (property.Converter == null ||
146                 property.Converter.GetPropertiesSupported() == false)
147             {
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)
150                 {
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)
155                     {
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())
159                         {
160                             return SubPropertyViewEditor.Instance;
161                         }
162                     }
163                 }
164
165                 return null;
166             }
167
168             //Dont support Arrays
169             if (typeof(Array).IsAssignableFrom(property.PropertyType))
170             {
171                 return null;
172             }
173
174             return SubPropertyViewEditor.Instance;
175         }
176
177         // <summary>
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
180         // </summary>
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) 
184         {
185
186             List<Type> editorTypes = null;
187
188             foreach (EditorAttribute editorAttribute in GetAttributes<EditorAttribute>(ownerType)) 
189             {
190
191                 // A ---- attempt at using the same extensibility code
192                 Type editorType = ExtensibilityMetadataHelper.GetCategoryEditorType(editorAttribute, MessageLogger.Instance);
193                 if (editorType == null)
194                 {
195                     continue;
196                 }
197
198                 if (editorTypes == null)
199                 {
200                     editorTypes = new List<Type>();
201                 }
202
203                 editorTypes.Add(editorType);
204             }
205
206             return editorTypes;
207         }
208
209         // <summary>
210         // Decides whether the specified ModelProperty should be advanced
211         // </summary>
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) 
215         {
216             EditorBrowsableAttribute browsable = GetAttribute<EditorBrowsableAttribute>(property);
217
218             if (browsable == null)
219             {
220                 return false;
221             }
222
223             return browsable.State == EditorBrowsableState.Advanced;
224         }
225
226         // <summary>
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.
231         // </summary>
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) 
235         {
236             EditorBrowsableAttribute browsable = GetAttribute<EditorBrowsableAttribute>(editor.GetType());
237
238             if (browsable == null)
239             {
240                 return false;
241             }
242
243             return browsable.State == EditorBrowsableState.Advanced;
244         }
245
246         // <summary>
247         // Looks up the DefaultPropertyAttribute on the given type and returns the default property,
248         // if any.
249         // </summary>
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) 
253         {
254             if (type == null)
255             {
256                 return null;
257             }
258
259             string defaultProperty;
260             if (_defaultPropertyCache.TryGetValue(type, out defaultProperty))
261             {
262                 return defaultProperty;
263             }
264
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;
270         }
271
272         // <summary>
273         // Attempts to look up a custom display name from the DisplayNameAttribute.
274         // Returns null if the attribute is not defined.
275         // </summary>
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) 
279         {
280             DisplayNameAttribute displayNameAttribute = GetAttribute<DisplayNameAttribute>(property);
281
282             if (displayNameAttribute == null)
283             {
284                 return null;
285             }
286
287             string displayName = displayNameAttribute.DisplayName;
288             if (string.IsNullOrEmpty(displayName))
289             {
290                 return null;
291             }
292
293             return displayName;
294         }
295
296         // <summary>
297         // Gets the description associated with the specified ModelProperty
298         // </summary>
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) 
302         {
303             DescriptionAttribute description = GetAttribute<DescriptionAttribute>(property);
304
305             if (description == null || string.IsNullOrEmpty(description.Description))
306             {
307                 return DescriptionAttribute.Default.Description;
308             }
309
310             return description.Description;
311         }
312
313         // <summary>
314         // Instantiates a TypeConverter from a potential TypeConverterAttribute, if one exists.
315         // </summary>
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) 
320         {
321             return InstantiateTypeConverter(GetAttribute<TypeConverterAttribute>(item));
322         }
323
324         // <summary>
325         // Gets the TypeConverter associated with the specified ModelProperty, returning
326         // null when no TypeConverter is found.
327         // </summary>
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) 
331         {
332             return property == null ? null : property.Converter;
333         }
334
335         // <summary>
336         // Computes the IsReadOnly flag for the specified set of properties, ORing
337         // results together for sets of properties larger than 1.
338         // </summary>
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) 
345         {
346             if (properties == null || properties.Count == 0) 
347             {
348                 Debug.Fail("ExtensibilityAccessor.IsReadOnly: No properties specified.");
349                 return true;
350             }
351
352             Type propertyType = properties[0].PropertyType;
353
354             // ILists are readonly only if value is null
355             if (typeof(IList).IsAssignableFrom(propertyType)) 
356             {
357
358                 if (OrReadOnlyValues(properties)) 
359                 {
360                     IList list = null;
361                     if (isMixedValueEvaluator != null)
362                     {
363                         list = isMixedValueEvaluator() ? null : (ModelUtilities.GetSafeRawValue(properties[0]) as IList);
364                     }
365                     else
366                     {
367                         Debug.Fail("No delegate to evaluate IsMixedValue specified.");
368                     }
369
370                     if (list == null) 
371                     {
372                         return true;
373                     }
374                 }
375
376                 return false;
377             }
378
379             // Arrays and ICollections are readonly
380             if (typeof(Array).IsAssignableFrom(propertyType) || typeof(ICollection).IsAssignableFrom(propertyType)) 
381             {
382                 return true;
383             }
384
385             // Types that implement ONLY ICollection<> or ONLY IList<> (meaning they
386             // don't also implement ICollection or IList, which we handle above)
387             // are also readonly
388             if (ModelUtilities.ImplementsICollection(propertyType) || ModelUtilities.ImplementsIList(propertyType)) 
389             {
390                 return true;
391             }
392
393             // Otherwise, go off of the IsReadOnly value in ModelProperty
394             return OrReadOnlyValues(properties);
395         }
396
397
398         // <summary>
399         // Looks up and returns the BrowsableAttribute on the specified property.
400         // </summary>
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)
405         {
406             if (property == null)
407             {
408                 return false;
409             }
410
411             // Check if the Browsable(true) attribute is explicitly defined.
412             BrowsableAttribute browsable = GetAttribute<BrowsableAttribute>(property);
413
414             // If explicit browsable then honor that.
415             if (browsable != null)
416             {
417                 return browsable.Browsable;
418             }
419             return null;
420         }
421
422         // <summary>
423         // Gets the PropertyOrder token associated with the given ModelProperty
424         // </summary>
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) 
428         {
429             if (property == null)
430             {
431                 return null;
432             }
433
434             PropertyOrderAttribute attr = GetAttribute<PropertyOrderAttribute>(property);
435             if (attr == null)
436             {
437                 return null;
438             }
439
440             return attr.Order;
441         }
442
443         // <summary>
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.
450         // </summary>
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) 
454         {
455
456             List<NewItemTypesAttribute> newItemTypesList = new List<NewItemTypesAttribute>();
457
458             foreach (NewItemTypesAttribute newItemTypesAttribute in GetAttributes<NewItemTypesAttribute>(property)) 
459             {
460
461                 // if there is no custom ItemFactory defined
462                 if (newItemTypesAttribute.FactoryType == typeof(NewItemFactory)) 
463                 {
464                     foreach (Type type in newItemTypesAttribute.Types) 
465                     {
466                         //Check if the type "IsConcreteWithDefaultCtor"
467                         if (EditorUtilities.IsConcreteWithDefaultCtor(type)) 
468                         {
469                             newItemTypesList.Add(new NewItemTypesAttribute(type));
470                         }
471                     }
472                 }
473                 else 
474                 {
475                     newItemTypesList.Add(newItemTypesAttribute);
476                 }
477             }
478             return newItemTypesList;
479         }
480
481         // <summary>
482         // Examines the specified ModelProperty for NewItemTypesAttributes and, if found, returns
483         // an enumerable of all NewItemFactoryTypeModels specified through them.
484         // </summary>
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) 
489         {
490             List<NewItemTypesAttribute> attributes = GetNewItemTypesAttributes(modelProperty);
491             if (attributes == null)
492             {
493                 yield break;
494             }
495
496             foreach (NewItemTypesAttribute attribute in attributes) 
497             {
498                 NewItemFactory factory = (NewItemFactory)Activator.CreateInstance(attribute.FactoryType);
499
500                 foreach (Type type in attribute.Types) 
501                 {
502
503                     NewItemFactoryTypeModel model = null;
504
505                     if (attribute.FactoryType == typeof(NewItemFactory)) 
506                     {
507                         if (EditorUtilities.IsConcreteWithDefaultCtor(type)) 
508                         {
509                             model = new NewItemFactoryTypeModel(type, factory, MessageLogger.Instance);
510                         }
511                     }
512                     else 
513                     {
514                         model = new NewItemFactoryTypeModel(type, factory, MessageLogger.Instance);
515                     }
516
517                     if (model != null) 
518                     {
519                         model.DesiredSize = desiredIconSize;
520                         yield return model;
521                     }
522                 }
523             }
524         }
525
526         // <summary>
527         // Gets all relevant sub-properties from the given ModelItem
528         // </summary>
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) 
532         {
533             if (item == null)
534             {
535                 return null;
536             }
537
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);
542         }
543
544         // <summary>
545         // Gets all relevant sub-properties from the value of the specified
546         // ModelProperty
547         // </summary>
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) 
551         {
552             if (property.Value == null || ModelUtilities.GetSafeRawValue(property) == null)
553             {
554                 return null;
555             }
556
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);
561         }
562
563         // <summary>
564         // try / catch wrapper artound Activator.CreateInstance()
565         // </summary>
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) 
570         {
571             try 
572             {
573                 return Activator.CreateInstance(type);
574             }
575             catch 
576             {
577                 // ignore errors...
578             }
579
580             return null;
581         }
582
583         // <summary>
584         // Returns the property source based on the following heuristic (in line with
585         // Blend's behavior):
586         //
587         // Xaml                                    Source
588         // -------------------------------------------------------------------
589         // "123"                                   Local
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
599         //
600         // </summary>
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) 
604         {
605             if (property == null)
606             {
607                 return null;
608             }
609
610             ModelItem valueItem = property.Value;
611             PropertyValueSource source = null;
612
613             // Binding or any other known markup extension?
614             if (valueItem != null) 
615             {
616                 Type valueType = valueItem.ItemType;
617
618                 if (IsStaticExtension(valueType))
619                 {
620                     source = DependencyPropertyValueSource.SystemResource;
621                 }
622                 else if (typeof(StaticResourceExtension).IsAssignableFrom(valueType))
623                 {
624                     source = DependencyPropertyValueSource.LocalStaticResource;
625                 }
626                 else if (typeof(DynamicResourceExtension).IsAssignableFrom(valueType))
627                 {
628                     source = DependencyPropertyValueSource.LocalDynamicResource;
629                 }
630                 else if (typeof(TemplateBindingExtension).IsAssignableFrom(valueType))
631                 {
632                     source = DependencyPropertyValueSource.TemplateBinding;
633                 }
634                 else if (typeof(Binding).IsAssignableFrom(valueType))
635                 {
636                     source = DependencyPropertyValueSource.DataBound;
637                 }
638                 else if (typeof(MarkupExtension).IsAssignableFrom(valueType))
639                 {
640                     source = DependencyPropertyValueSource.CustomMarkupExtension;
641                 }
642             }
643
644             // If not, is this a local, inherited, or default value?
645             if (source == null) 
646             {
647
648                 if (property.IsSet)
649                 {
650                     source = DependencyPropertyValueSource.Local;
651                 }
652                 else 
653                 {
654
655                     object value = property.ComputedValue;
656
657                     if (object.Equals(value, property.DefaultValue))
658                     {
659                         source = DependencyPropertyValueSource.DefaultValue;
660                     }
661                     else if (valueItem != null && valueItem.Source != property)
662                     {
663                         source = DependencyPropertyValueSource.Inherited;
664                     }
665                 }
666             }
667
668             return source;
669         }
670
671         // Helper method that ORs the ModelProperty.IsReadOnly values together and 
672         // returns the result
673         private static bool OrReadOnlyValues(List<ModelProperty> properties) 
674         {
675             if (properties == null) 
676             {
677                 return true;
678             }
679
680             for (int i = 0; i < properties.Count; i++) 
681             {
682                 if (properties[i].IsReadOnly)
683                 {
684                     return true;
685                 }
686             }
687
688             return false;
689         }
690        
691         // Helper method to find if the propertyvalueeditor is reusable for the 
692         // given properties collection.
693         public static bool IsEditorReusable(IEnumerable<ModelProperty> properties)
694         {
695             if (properties == null)
696             {
697                 return true;
698             }
699
700             foreach (ModelProperty property in properties)
701             {
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))
705                 {
706                     return false;
707                 }
708             }
709             return true;
710         }
711
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) 
716         {
717             return type != null && (
718                 typeof(StaticExtension).IsAssignableFrom(type) ||
719                 string.Equals("System.Activities.Presentation.Internal.Xaml.Builtins.StaticExtension", type.FullName));
720         }
721
722         // Gets all subProperties from the TypeConverter, if one is explicitely specified
723         private static List<ModelProperty> GetTypeConverterSubProperties(ModelItem item) 
724         {
725             return GetTypeConverterSubPropertiesHelper(item, null);
726         }
727
728         // Gets all subProperties from the TypeConverter, if one is explicitely specified
729         private static List<ModelProperty> GetTypeConverterSubProperties(ModelProperty property) 
730         {
731             TypeConverter propertySpecificConverter = property.Converter;
732             return GetTypeConverterSubPropertiesHelper(property.Value, propertySpecificConverter);
733         }
734
735         private static List<ModelProperty> GetTypeConverterSubPropertiesHelper(ModelItem item, TypeConverter customConverter) 
736         {
737
738             if (item == null)
739             {
740                 return null;
741             }
742
743             List<ModelProperty> subProperties = null;
744
745             TypeConverter converter = customConverter;
746
747             if (converter == null) 
748             {
749                 // See if there is a converter associated with the item type itself
750                 converter = ExtensibilityAccessor.GetTypeConverter(item);
751             }
752
753             if (converter != null) 
754             {
755                 PropertyDescriptorCollection subPropertyDescriptors =
756                     converter.GetProperties(item.GetCurrentValue());
757
758                 if (subPropertyDescriptors != null && subPropertyDescriptors.Count > 0) 
759                 {
760
761                     foreach (PropertyDescriptor subPropertyDescriptor in subPropertyDescriptors) 
762                     {
763
764                         ModelProperty subProperty = item.Properties[subPropertyDescriptor.Name];
765
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) 
769                         {
770
771                             if (subProperties == null)
772                             {
773                                 subProperties = new List<ModelProperty>();
774                             }
775
776                             subProperties.Add(subProperty);
777                         }
778                     }
779                 }
780             }
781             return subProperties;
782         }
783
784         // Gets all subProperties that exist
785         private static List<ModelProperty> GetAllSubProperties(ModelItem item) 
786         {
787
788             if (item == null)
789             {
790                 return null;
791             }
792
793             ModelPropertyCollection subModelProperties = item.Properties;
794             if (subModelProperties == null)
795             {
796                 return null;
797             }
798
799             List<ModelProperty> subProperties = null;
800
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) 
804             {
805
806                 if (subProperties == null)
807                 {
808                     subProperties = new List<ModelProperty>();
809                 }
810
811                 subProperties.Add(subModelProperty);
812             }
813
814             return subProperties;
815         }
816
817         // Gets all subProperties that exist
818         private static List<ModelProperty> GetAllSubProperties(ModelProperty property) 
819         {
820             return GetAllSubProperties(property.Value);
821         }
822
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) 
826         {
827             if (typeConverterAttribute == null)
828             {
829                 return null;
830             }
831
832             try 
833             {
834                 Type typeConverterType = Type.GetType(typeConverterAttribute.ConverterTypeName);
835                 if (typeConverterType != null) 
836                 {
837                     return (TypeConverter)Activator.CreateInstance(typeConverterType);
838                 }
839             }
840             catch (Exception) 
841             {
842                 // Ignore failures.  In the future, log these somewhere for 3rd parties to see and debug.
843             }
844
845             return null;
846         }
847     
848         // GetAttributes() and GetAttribute<T>()
849
850         public static T GetAttribute<T>(ModelProperty property) where T : Attribute 
851         {
852             return GetAttribute<T>(property == null ? null : property.Attributes);
853         }
854
855         public static T GetAttribute<T>(ModelItem item) where T : Attribute 
856         {
857             return GetAttribute<T>(item == null ? null : item.Attributes);
858         }
859
860         public static T GetAttribute<T>(Type type) where T : Attribute 
861         {
862             return GetAttribute<T>(type == null ? null : TypeDescriptor.GetAttributes(type));
863         }
864
865         public static IEnumerable<T> GetAttributes<T>(ModelProperty property) where T : Attribute 
866         {
867             return GetAttributes<T>(property == null ? null : property.Attributes);
868         }
869
870         public static IEnumerable<T> GetAttributes<T>(Type type) where T : Attribute 
871         {
872             return GetAttributes<T>(type == null ? null : TypeDescriptor.GetAttributes(type));
873         }
874
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 
878         {
879             T foundAttribute = null;
880             if (attributes != null) 
881             {
882                 foreach (Attribute attribute in attributes) 
883                 {
884                     if (typeof(T).IsAssignableFrom(attribute.GetType()))
885                     {
886                         foundAttribute = attribute as T;
887                     }
888                 }
889             }
890
891             return foundAttribute;
892         }
893
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 
897         {
898             if (attributes != null) 
899             {
900                 foreach (Attribute attribute in attributes) 
901                 {
902                     if (typeof(T).IsAssignableFrom(attribute.GetType()))
903                     {
904                         yield return (T)attribute;
905                     }
906                 }
907             }
908         }
909
910         // <summary>
911         // Delegate intended to wrap logic that evaluates the IsMixedValue flag of
912         // some property or set of properties.
913         // </summary>
914         // <returns>True if values are mixed, false otherwise</returns>
915         public delegate bool IsMixedValueEvaluator();
916
917         // 
918         private class MessageLogger : IMessageLogger 
919         {
920
921             private static MessageLogger _instance = new MessageLogger();
922
923             public static MessageLogger Instance 
924             { get { return _instance; } }
925
926             public void Clear() 
927             {
928             }
929
930             public void Write(string text) 
931             {
932                 Debug.Write(text);
933             }
934
935             public void WriteLine(string text) 
936             {
937                 Debug.WriteLine(text);
938             }
939         }
940     }
941 }