Guest User

Dependency Property Factory 3

a guest
Nov 12th, 2010
405
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1.     /// <summary>
  2.     /// A dependency property factory to simplify creating and managing dependency properties for a certain type.
  3.     /// </summary>
  4.     /// <typeparam name="T">An enum used to identify the dependency properties.</typeparam>
  5.     /// <author>Steven Jeuris</author>
  6.     public class DependencyPropertyFactory<T>
  7.     {
  8.         private readonly Type m_ownerType;
  9.         private readonly bool m_enforceWpfConvention = true;
  10.  
  11.         /// <summary>
  12.         /// A list containing the dependency properties.
  13.         /// TODO: Make this a readonly collection.
  14.         /// </summary>
  15.         public Dictionary<T, global::System.Windows.DependencyProperty> Properties
  16.         {
  17.             get; private set;
  18.         }
  19.  
  20.         private readonly Dictionary<T, DependencyPropertyKey> m_readOnlyProperties = new Dictionary<T, DependencyPropertyKey>();
  21.  
  22.         /// <summary>
  23.         /// Create a new dependency property factory for a specific set of properties.
  24.         /// When naming conventions aren't followed, an exception is thrown.
  25.         /// (http://msdn.microsoft.com/en-us/library/bb613563.aspx)
  26.         /// The type which encloses the type parameter is used as the owner type.
  27.         /// </summary>
  28.         public DependencyPropertyFactory() : this(true)
  29.         {
  30.         }
  31.  
  32.         /// <summary>
  33.         /// Create a new dependency property factory for a specific set of properties.
  34.         /// The type which encloses the type parameter is used as the owner type.
  35.         /// </summary>
  36.         /// <param name="enforceNamingConventions">
  37.         /// Whether or not to throw exceptions when the naming conventions aren't followed.
  38.         /// See http://msdn.microsoft.com/en-us/library/bb613563.aspx.
  39.         /// </param>
  40.         public DependencyPropertyFactory(bool enforceNamingConventions) : this(null, enforceNamingConventions)
  41.         {
  42.         }
  43.  
  44.         /// <summary>
  45.         /// Create a new dependency property factory for a specific set of properties.
  46.         /// When naming conventions aren't followed, an exception is thrown.
  47.         /// (http://msdn.microsoft.com/en-us/library/bb613563.aspx)
  48.         /// </summary>
  49.         /// <param name="ownerType">The owner type of the dependency properties.</param>
  50.         public DependencyPropertyFactory(Type ownerType) : this(ownerType, true)
  51.         {
  52.         }
  53.  
  54.         /// <summary>
  55.         /// Create a new dependency property factory for a specific set of properties.
  56.         /// </summary>
  57.         /// <param name="ownerType">The owner type of the dependency properties.</param>
  58.         /// <param name="enforceNamingConventions">
  59.         /// Whether or not to throw exceptions when the naming conventions aren't followed.
  60.         /// See http://msdn.microsoft.com/en-us/library/bb613563.aspx.
  61.         /// </param>
  62.         public DependencyPropertyFactory(Type ownerType, bool enforceNamingConventions)
  63.         {
  64.             Properties = new Dictionary<T, global::System.Windows.DependencyProperty>();
  65.             m_ownerType = ownerType;
  66.             m_enforceWpfConvention = enforceNamingConventions;
  67.  
  68.             // Check whether the ID type is an enum.
  69.             Type enumType = typeof (T);
  70.             if (!enumType.IsEnum)
  71.             {
  72.                 throw new ArgumentException(
  73.                     "The DependencyPropertyFactory requires an enum specifying all properties " +
  74.                     "which should be dependency properties as template parameter.");
  75.             }
  76.  
  77.             // Check whether the type parameter has an owner type when no owner set.
  78.             if (m_ownerType == null)
  79.             {
  80.                 if (enumType.IsNested)
  81.                 {
  82.                     m_ownerType = enumType.DeclaringType;
  83.                 }
  84.                 else
  85.                 {
  86.                     throw new ArgumentException(
  87.                         "When not specifying an owner type in the constructor, " +
  88.                         "the enum type passed as a template parameter should be nested inside the desired owner.");
  89.                 }
  90.             }
  91.  
  92.             // Check whether the factory itself is defined as a static field.
  93.             FieldInfo[] fields = m_ownerType.GetFields(BindingFlags.FlattenHierarchy | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
  94.             bool validFactory =
  95.                 fields.Where(
  96.                     field => field.FieldType == typeof (DependencyPropertyFactory<T>)).Where(
  97.                         field => field.IsStatic).Any();
  98.             if (!validFactory)
  99.             {
  100.                 throw new ArgumentException(
  101.                     "Incorrect usage of DependencyPropertyFactory in class '" + m_ownerType.Name + "'. " +
  102.                     "A DependencyPropertyFactory needs to be created as a static field inside it's owner class.");
  103.             }
  104.  
  105.             // Get all enum values.
  106.             const BindingFlags ALL_ENUM_VALUES = BindingFlags.Public | BindingFlags.Static;
  107.             MemberInfo[] enumMembers = enumType.GetMembers(ALL_ENUM_VALUES);
  108.             T[] enumValues = new T[enumMembers.Length];
  109.             for (int i = 0; i < enumMembers.Length; i++)
  110.             {
  111.                 enumValues[i] = (T)Enum.Parse(enumType, enumMembers[i].Name);
  112.             }
  113.  
  114.             // Check all properties for a dependency property attribute.
  115.             const BindingFlags ALL_PROPERTIES = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
  116.             var matchingProperties = new Dictionary<PropertyInfo, DependencyPropertyAttribute>();
  117.             foreach ( PropertyInfo property in m_ownerType.GetProperties( ALL_PROPERTIES ) )
  118.             {
  119.                 object[] attribute = property.GetCustomAttributes( typeof( DependencyPropertyAttribute ), false );
  120.                 if ( attribute != null && attribute.Length == 1 )
  121.                 {
  122.                     // A correct attribute was found.
  123.                     DependencyPropertyAttribute dependency = (DependencyPropertyAttribute)attribute[ 0 ];
  124.  
  125.                     // Check whether the ID corresponds to the ID required for this factory.
  126.                     if (dependency.GetId() is T)
  127.                     {
  128.                         matchingProperties.Add(property, dependency);
  129.                     }
  130.                 }
  131.             }
  132.  
  133.             // TODO: Check whether callback attributes are applied to non-static functions.
  134.  
  135.             // Every enum value should have a matching property.
  136.             if (enumValues.Length != matchingProperties.Count)
  137.             {
  138.                 throw new ArgumentException(
  139.                     "Not all enum values of the template parameter have a matching property " +
  140.                     "with a DependencyPropertyAttribute set in the type '" + m_ownerType.Name + "'." );                
  141.             }
  142.  
  143.             // Create the dependency properties.
  144.             foreach (var item in matchingProperties)
  145.             {
  146.                 const string CONVENTION_ENABLED = "WPF dependency property conventions are enabled and not followed. ";
  147.  
  148.                 if (m_enforceWpfConvention)
  149.                 {
  150.                     // Find dependency property field identifier.
  151.                     const string IDENTIFIER_SUFFIX = "Property";
  152.                     const BindingFlags IDENTIFIER_MODIFIERS = BindingFlags.Public | BindingFlags.Static;
  153.                     string identifierField = item.Key.Name + IDENTIFIER_SUFFIX;
  154.                     FieldInfo identifier = m_ownerType.GetField( identifierField, IDENTIFIER_MODIFIERS );
  155.                     if (identifier == null || (identifier.FieldType != typeof( global::System.Windows.DependencyProperty )))
  156.                     {
  157.                         throw new InvalidOperationException(
  158.                             CONVENTION_ENABLED +
  159.                             "There is no public static dependency property field identifier \"" + identifierField +
  160.                             "\" available in the class \"" + m_ownerType.Name + "\"." );
  161.                     }
  162.  
  163.                     // Verify name when set.
  164.                     if (item.Value.Name != null && item.Key.Name != item.Value.Name)
  165.                     {
  166.                         throw new InvalidOperationException(
  167.                             CONVENTION_ENABLED + "The CLR property wrapper '" + item.Key.Name +
  168.                             "' doesn't match the name of the dependency property.");
  169.                     }
  170.                 }
  171.  
  172.                 CreateDependencyProperty(item.Key, item.Value);
  173.             }
  174.         }
  175.  
  176.         private void CreateDependencyProperty(PropertyInfo property, DependencyPropertyAttribute attribute)
  177.         {
  178.             // Set dependency property parameters.
  179.             string name = attribute.Name ?? property.Name;
  180.             Type propertyType = property.PropertyType;
  181.             // When no default value is set, and it is a value type, use the default value.
  182.             object defaultValue = attribute.DefaultValue ??
  183.                 (propertyType.IsValueType ? Activator.CreateInstance( propertyType ) : null);
  184.             // By default, readonly when setter is private.
  185.             MethodInfo setMethod = property.GetSetMethod();
  186.             bool readOnly = attribute.IsReadOnlySet() ?
  187.                 attribute.IsReadOnly() : (setMethod == null || setMethod.IsPrivate);
  188.  
  189.             T id = (T)attribute.GetId();
  190.  
  191.             // Find changed callback.
  192.             PropertyChangedCallback changedCallback =
  193.                 (PropertyChangedCallback)CreateCallbackDelegate<DependencyPropertyChangedAttribute>( id );
  194.             CoerceValueCallback coerceCallback =
  195.                 (CoerceValueCallback)CreateCallbackDelegate<DependencyPropertyCoerceAttribute>( id );
  196.  
  197.             if ( readOnly )
  198.             {
  199.                 CreateReadOnlyDependencyProperty( id, name, propertyType, defaultValue, changedCallback, coerceCallback );
  200.             }
  201.             else
  202.             {
  203.                 CreateDependencyProperty( id, name, propertyType, defaultValue, changedCallback, coerceCallback );
  204.             }            
  205.         }
  206.  
  207.         /// <summary>
  208.         /// Create a callback delegate for a dependency property.
  209.         /// </summary>
  210.         /// <typeparam name="TCallbackAttribute">The callback attribute type.</typeparam>
  211.         /// <param name="id">The ID of the dependency property.</param>
  212.         /// <returns>A delegate which can be used as a callback.</returns>
  213.         private Delegate CreateCallbackDelegate<TCallbackAttribute>( object id ) where TCallbackAttribute : AbstractDependencyPropertyCallbackAttribute
  214.         {
  215.             const BindingFlags ALL_STATIC_METHODS =
  216.                 BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Static;
  217.  
  218.             return
  219.                 (from method in m_ownerType.GetMethods(ALL_STATIC_METHODS)
  220.                  let methodAttributes =
  221.                      method.GetCustomAttributes( typeof( TCallbackAttribute ), false )
  222.                  where methodAttributes != null && methodAttributes.Length == 1
  223.                  let changed =
  224.                      (TCallbackAttribute)methodAttributes[ 0 ]
  225.                  where changed.GetId().Equals( id )
  226.                  select Delegate.CreateDelegate( changed.CallbackType, method )).FirstOrDefault();
  227.         }
  228.  
  229.         private void CreateDependencyProperty(
  230.             T identifier,
  231.             string name,
  232.             Type propertyType,
  233.             object defaultValue,
  234.             PropertyChangedCallback changedCallback,
  235.             CoerceValueCallback coerceCallback)
  236.         {
  237.             global::System.Windows.DependencyProperty property =
  238.                 global::System.Windows.DependencyProperty.Register(
  239.                     name,
  240.                     propertyType,
  241.                     m_ownerType,
  242.                     new PropertyMetadata(defaultValue, changedCallback, coerceCallback)
  243.                     );
  244.  
  245.             Properties.Add(identifier, property);
  246.         }
  247.  
  248.         private void CreateReadOnlyDependencyProperty(
  249.             T identifier,
  250.             string name,
  251.             Type propertyType,
  252.             object defaultValue,
  253.             PropertyChangedCallback changedCallback,
  254.             CoerceValueCallback coerceCallback )
  255.         {
  256.             DependencyPropertyKey propertyKey =
  257.                 global::System.Windows.DependencyProperty.RegisterReadOnly(
  258.                     name,
  259.                     propertyType,
  260.                     m_ownerType,
  261.                     new PropertyMetadata( defaultValue, changedCallback, coerceCallback )
  262.                     );
  263.             m_readOnlyProperties.Add( identifier, propertyKey );
  264.  
  265.             global::System.Windows.DependencyProperty property = propertyKey.DependencyProperty;
  266.             Properties.Add( identifier, property );
  267.         }
  268.  
  269.         /// <summary>
  270.         /// Returns the dependency property for a given ID.
  271.         /// </summary>
  272.         /// <param name="id">The ID of the dependency propert. (enum type of the class type parameter)</param>
  273.         /// <returns>The dependency property for the given ID.</returns>
  274.         public global::System.Windows.DependencyProperty this[T id]
  275.         {
  276.             get
  277.             {
  278.                 if (!Properties.ContainsKey(id))
  279.                 {
  280.                     throw new KeyNotFoundException(
  281.                         "The dependency property with the key \"" + id.ToString() + "\" doesn't exist. " +
  282.                         "Did you forget to add a DependencyPropertyAttribute?" );                    
  283.                 }
  284.  
  285.                 return Properties[ id ];
  286.             }
  287.         }
  288.  
  289.         /// <summary>
  290.         /// Get the value of a property.
  291.         /// </summary>
  292.         /// <param name="o">The dependency object from which to get the value.</param>
  293.         /// <param name="property">The property to get the value from.</param>
  294.         /// <returns>The value from the asked property.</returns>
  295.         public object GetValue(DependencyObject o, T property)
  296.         {
  297.             return o.GetValue(Properties[property]);
  298.         }
  299.  
  300.         /// <summary>
  301.         /// Set the value of a property, whether it is readonly or not.
  302.         /// </summary>
  303.         /// <param name="o">The dependency object on which to set the value.</param>
  304.         /// <param name="property">The property to set.</param>
  305.         /// <param name="value">The new value for the property.</param>
  306.         public void SetValue(DependencyObject o, T property, object value)
  307.         {
  308.             if (m_readOnlyProperties.ContainsKey(property))
  309.             {
  310.                 DependencyPropertyKey key = m_readOnlyProperties[ property ];
  311.                 o.SetValue( key, value );
  312.             }
  313.             else
  314.             {
  315.                 o.SetValue( Properties[property], value );
  316.             }
  317.         }
  318.     }
  319.  
  320.     /// <summary>
  321.     /// When applied to a member,
  322.     /// specifies how the dependency property should be created by the DependencyPropertyContainer.
  323.     /// </summary>
  324.     /// <author>Steven Jeuris</author>
  325.     public abstract class AbstractDependencyPropertyAttribute : Attribute
  326.     {
  327.         /// <summary>
  328.         /// The ID of the dependency property.
  329.         /// </summary>
  330.         private readonly object m_id;
  331.  
  332.         /// <summary>
  333.         /// Create a new dependency property attribute for a specified dependency property.
  334.         /// </summary>
  335.         /// <param name="id">The ID of the dependency property.</param>
  336.         protected AbstractDependencyPropertyAttribute(object id)
  337.         {
  338.             m_id = id;
  339.         }
  340.  
  341.         /// <summary>
  342.         /// Get the ID of the dependency property to which the attribute applies.
  343.         /// </summary>
  344.         /// <returns>The ID of the dependency property to which the attribute applies.</returns>
  345.         public object GetId()
  346.         {
  347.             return m_id;
  348.         }
  349.     }
  350.  
  351.     /// <summary>
  352.     /// When applied to a member,
  353.     /// specifies a callback for a dependency property.
  354.     /// </summary>
  355.     /// <author>Steven Jeuris</author>
  356.     public abstract class AbstractDependencyPropertyCallbackAttribute : AbstractDependencyPropertyAttribute
  357.     {
  358.         /// <summary>
  359.         /// The delegate type for the callback.
  360.         /// </summary>
  361.         public abstract Type CallbackType
  362.         {
  363.             get;
  364.         }
  365.  
  366.         /// <summary>
  367.         /// Create a new attribute which specifies how the dependency property should be created.
  368.         /// </summary>
  369.         /// <param name="id">The ID of the dependency property.</param>
  370.         protected AbstractDependencyPropertyCallbackAttribute(object id) : base(id)
  371.         {
  372.         }
  373.     }
  374.  
  375.     /// <summary>
  376.     /// When applied to the property of a type,
  377.     /// specifies how the dependency property should be created by the DependencyPropertyContainer.
  378.     /// </summary>
  379.     /// <author>Steven Jeuris</author>
  380.     [AttributeUsage(AttributeTargets.Property, AllowMultiple=false)]
  381.     public class DependencyPropertyAttribute : AbstractDependencyPropertyAttribute
  382.     {
  383.         private readonly bool m_readOnly;
  384.         private readonly bool m_readOnlySet = false;
  385.  
  386.         /// <summary>
  387.         /// The name to use for the dependency property. By default this is the string representation of Id.
  388.         /// </summary>
  389.         public string Name
  390.         {
  391.             get; set;
  392.         }
  393.  
  394.         /// <summary>
  395.         /// The default value for the dependency property. Should be of the same type as the property.
  396.         /// </summary>
  397.         public object DefaultValue
  398.         {
  399.             get; set;
  400.         }
  401.  
  402.         /// <summary>
  403.         /// Create a new attribute which gives information about the dependency property to create for a given property.
  404.         /// </summary>
  405.         /// <param name="id">The ID of the dependency property.</param>
  406.         public DependencyPropertyAttribute(object id) : base(id)
  407.         {
  408.             m_readOnlySet = false;
  409.         }
  410.  
  411.         public DependencyPropertyAttribute(object id, bool readOnly) : base(id)
  412.         {
  413.             m_readOnly = readOnly;
  414.             m_readOnlySet = true;
  415.         }
  416.  
  417.         /// <summary>
  418.         /// Should the dependency property be a readonly dependency property or not.
  419.         /// </summary>
  420.         /// <returns></returns>
  421.         public bool IsReadOnly()
  422.         {
  423.             return m_readOnly;
  424.         }
  425.  
  426.         /// <summary>
  427.         /// Has the readonly value been set?
  428.         /// </summary>
  429.         /// <returns></returns>
  430.         public bool IsReadOnlySet()
  431.         {
  432.             return m_readOnlySet;
  433.         }
  434.     }
  435.  
  436.     /// <summary>
  437.     /// When applied to a PropertyChangedCallback method,
  438.     /// specifies the changed callback for a dependency property.
  439.     /// </summary>
  440.     /// <author>Steven Jeuris</author>
  441.     [AttributeUsage(AttributeTargets.Method, AllowMultiple=false)]
  442.     public class DependencyPropertyChangedAttribute : AbstractDependencyPropertyCallbackAttribute
  443.     {
  444.         /// <summary>
  445.         /// Create a new attribute to assign a function as changed callback to a given dependency property ID.
  446.         /// </summary>
  447.         /// <param name="id">The ID of the dependency property.</param>
  448.         public DependencyPropertyChangedAttribute(object id) : base(id)
  449.         {
  450.         }
  451.  
  452.         /// <summary>
  453.         /// The delegate type for this callback.
  454.         /// </summary>
  455.         public override Type CallbackType
  456.         {
  457.             get { return typeof( PropertyChangedCallback ); }
  458.         }
  459.     }
  460.  
  461.     /// <summary>
  462.     /// When applied to a PropertyChangedCallback method,
  463.     /// specifies the coerce callback for a dependency property.
  464.     /// </summary>
  465.     /// <author>Steven Jeuris</author>
  466.     [AttributeUsage(AttributeTargets.Method, AllowMultiple=false)]
  467.     public class DependencyPropertyCoerceAttribute : AbstractDependencyPropertyCallbackAttribute
  468.     {
  469.         /// <summary>
  470.         /// Create a new attribute to assign a function as coerce callback to a given dependency property ID.
  471.         /// </summary>
  472.         /// <param name="id">The ID of the dependency property.</param>
  473.         public DependencyPropertyCoerceAttribute(object id) : base(id)
  474.         {
  475.         }
  476.  
  477.         /// <summary>
  478.         /// The delegate type for this callback.
  479.         /// </summary>
  480.         public override Type CallbackType
  481.         {
  482.             get { return typeof (CoerceValueCallback); }
  483.         }
  484.     }
RAW Paste Data

Adblocker detected! Please consider disabling it...

We've detected AdBlock Plus or some other adblocking software preventing Pastebin.com from fully loading.

We don't have any obnoxious sound, or popup ads, we actively block these annoying types of ads!

Please add Pastebin.com to your ad blocker whitelist or disable your adblocking software.

×