Advertisement
Guest User

Dependency Property Factory 3

a guest
Nov 12th, 2010
630
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 20.42 KB | None | 0 0
  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.     }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement