PageRenderTime 36ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/Src/Common/Source/Extensibility/Binding/BindingPropertyDescriptor.cs

http://nupattern.codeplex.com
C# | 231 lines | 145 code | 25 blank | 61 comment | 27 complexity | 9e5378aaaf2785e94feb4d3a56522e07 MD5 | raw file
Possible License(s): Apache-2.0, CC-BY-SA-3.0
  1. using System;
  2. using System.ComponentModel;
  3. using System.Drawing.Design;
  4. using System.Linq;
  5. using Microsoft.VisualStudio.Patterning.Runtime;
  6. using Microsoft.VisualStudio.TeamArchitect.PowerTools.Features;
  7. using Microsoft.VisualStudio.TeamArchitect.PowerTools.Features.Design;
  8. namespace Microsoft.VisualStudio.Patterning.Extensibility.Binding
  9. {
  10. /// <summary>
  11. /// A custom descriptor that automatically serializes a binding to Json, and provides a
  12. /// dropdown picker for the binding type.
  13. /// </summary>
  14. [CLSCompliant(false)]
  15. public class BindingPropertyDescriptor<TValue> : DelegatingPropertyDescriptor
  16. where TValue : class
  17. {
  18. private BindingSettings settings;
  19. /// <summary>
  20. /// Initializes a new instance of the <see cref="BindingPropertyDescriptor{T}"/> class with optional
  21. /// custom attributes to modify the property behavior.
  22. /// </summary>
  23. /// <param name="innerDescriptor">The inner descriptor.</param>
  24. /// <param name="overriddenAttributes">The optional custom attributes.</param>
  25. public BindingPropertyDescriptor(PropertyDescriptor innerDescriptor, params Attribute[] overriddenAttributes)
  26. : base(innerDescriptor, AddStandardValuesEditor(overriddenAttributes))
  27. {
  28. }
  29. /// <summary>
  30. /// Adds the standard values editor to the property.
  31. /// </summary>
  32. /// <param name="attributes">The attributes.</param>
  33. /// <returns></returns>
  34. private static Attribute[] AddStandardValuesEditor(Attribute[] attributes)
  35. {
  36. return attributes
  37. .Where(a => !(a is EditorAttribute))
  38. .Concat(new[]
  39. {
  40. new EditorAttribute(typeof(StandardValuesEditor), typeof(UITypeEditor))
  41. }).ToArray();
  42. }
  43. /// <summary>
  44. /// Overrides the converter and provides the fixed one that sets the bound type value.
  45. /// </summary>
  46. public override TypeConverter Converter
  47. {
  48. get { return new TypeIdConverter(); }
  49. }
  50. /// <summary>
  51. /// When overridden in a derived class, gets the current value of the property on a component.
  52. /// </summary>
  53. public override object GetValue(object component)
  54. {
  55. // We cache the settings to avoid paying the serialization cost too often.
  56. // Also, this allows us to track changes more consistently.
  57. if (this.settings == null)
  58. {
  59. var json = base.GetValue(component) as string;
  60. // We always initialize the value to some non-null object, which
  61. // turns on the dropdown for type selection. If we don't, the
  62. // dropdown never shows up.
  63. if (string.IsNullOrWhiteSpace(json))
  64. {
  65. this.settings = new BindingSettings();
  66. // Save the value right after instantiation, so that
  67. // subsequent GetValue gets the same instance and
  68. // does not re-create from scratch.
  69. SetValue(component, settings);
  70. }
  71. else
  72. {
  73. // Deserialize always to the concrete type, as we the serializer needs to
  74. // know the type of thing to create.
  75. this.settings = BindingSerializer.Deserialize<BindingSettings>(json);
  76. }
  77. // Hookup property changed event, which supports nested changes as well. This
  78. // allows us to automatically save the serialized json on every property change.
  79. this.settings.PropertyChanged += OnSettingsChanged;
  80. }
  81. return this.settings;
  82. }
  83. /// <summary>
  84. /// When overridden in a derived class, sets the value of the component to a different value.
  85. /// </summary>
  86. public override void SetValue(object component, object value)
  87. {
  88. var stringValue = value as string;
  89. var settingsValue = value as BindingSettings;
  90. // The setter will be called with a plain string value whenever the standard values editor is
  91. // used to pick the TypeId. So we need to actually set that value on the binding instead.
  92. if (stringValue != null)
  93. {
  94. // Note that the setting of the property TypeId automatically triggers a property changed
  95. // that will cause the value to be serialized and saved :). Indeed,
  96. // it will be calling the SetValue again, but this time with the entire binding settings
  97. // so it will call the next code branch.
  98. ((BindingSettings)this.GetValue(component)).TypeId = stringValue;
  99. }
  100. else if (settingsValue != null)
  101. {
  102. // Someone is explicitly setting the entire binding value, so we have to serialize it straight.
  103. SaveSettings(component, settings);
  104. // If the previous value was non-null, then we'd be monitoring its property changes
  105. // already, and we'd need to unsubscribe.
  106. if (this.settings != null)
  107. {
  108. this.settings.PropertyChanged -= OnSettingsChanged;
  109. }
  110. // Store for reuse on GetValue, and attach to property changes for auto-save.
  111. this.settings = settingsValue;
  112. this.settings.PropertyChanged += OnSettingsChanged;
  113. }
  114. }
  115. /// <summary>
  116. /// When overridden in a class, determines if the vaue of this property can be reset.
  117. /// </summary>
  118. public override bool CanResetValue(object component)
  119. {
  120. return (this.settings != null && this.settings.IsConfigured());
  121. }
  122. /// <summary>
  123. /// When overridden in a class, resets teh property value.
  124. /// </summary>
  125. public override void ResetValue(object component)
  126. {
  127. this.settings.Reset();
  128. }
  129. private void OnSettingsChanged(object sender, EventArgs args)
  130. {
  131. // Automatically save the settings on change.
  132. SetValue(sender, this.settings);
  133. }
  134. private void SaveSettings(object component, BindingSettings settings)
  135. {
  136. if (!this.settings.IsConfigured())
  137. {
  138. base.SetValue(component, BindingSettings.Empty);
  139. }
  140. else
  141. {
  142. base.SetValue(component, BindingSerializer.Serialize(settings));
  143. }
  144. }
  145. /// <summary>
  146. /// Custom converter that customizes the way the binding is shown as a string.
  147. /// It inherits from the FeatureComponentTypeConverter which already handles
  148. /// the initialization of standard values.
  149. /// </summary>
  150. private class TypeIdConverter : FeatureComponentTypeConverter<TValue>
  151. {
  152. public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
  153. {
  154. var settings = value as IBindingSettings;
  155. if (destinationType == typeof(string) && settings != null)
  156. {
  157. // TODO: Display simplified type name (consistent across all bindings).
  158. //if (settings.TypeId.Contains('.'))
  159. // return ((IBindingSettings)value).TypeId.Split('.').Last();
  160. return settings.IsConfigured() ? settings.TypeId : BindingSettings.Empty;
  161. }
  162. return base.ConvertTo(context, culture, value, destinationType);
  163. }
  164. public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
  165. {
  166. return true;
  167. }
  168. public override bool GetPropertiesSupported(ITypeDescriptorContext context)
  169. {
  170. return true;
  171. }
  172. public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
  173. {
  174. var settings = (IBindingSettings)value;
  175. var properties = new PropertyDescriptorCollection(new PropertyDescriptor[0]);
  176. if (settings.IsConfigured())
  177. {
  178. var extensionType = this.Components.FromFeaturesCatalog()
  179. .Where(binding => binding.Metadata.Id == settings.TypeId)
  180. .Select(binding => binding.Metadata.ExportingType)
  181. .FirstOrDefault();
  182. if (extensionType == null && !string.IsNullOrEmpty(settings.TypeId))
  183. {
  184. extensionType = (from type in this.ProjectTypeProvider.GetTypes<TValue>()
  185. let meta = type.AsProjectFeatureComponent()
  186. where meta != null && meta.Id == settings.TypeId
  187. select type)
  188. .FirstOrDefault();
  189. }
  190. if (extensionType != null)
  191. {
  192. foreach (var descriptor in TypeDescriptor.GetProperties(extensionType).Cast<PropertyDescriptor>().Where(d => d.IsPlatuBindableProperty()))
  193. {
  194. properties.Add(new DesignPropertyDescriptor(
  195. descriptor.Name,
  196. descriptor.PropertyType,
  197. settings.GetType(),
  198. descriptor.Attributes.Cast<Attribute>().ToArray()));
  199. }
  200. }
  201. }
  202. return properties;
  203. }
  204. }
  205. }
  206. }