Presenting Detail Values as Part of the Master – part 3

This is part three of a three part series on working with master-detail data.  In part one I showed how to present detail values on the master using a utility I wrote called the SubPropertyAccessor. In part two I showed how the SubPropertyAccessor works. In this part, I introduce a descendant of DataGridViewColumn that will allow us to show the indexed detail data in line with the master data as additional columns.  I also introduce a descendent of TypeDescriptionProvider that allows the DataGridView to see the SubProperty as a proper property so that the rest of the grid functionality will still work.

Note: The SubPropertyAccessor was called SubAttributeAccessor in parts one and two.

SubPropertyColumn

The whole point of having detail values appear as part of a master record is to show the data in a grid where the detail values would appear in columns next to the other properties in the record.  In order to do this I created a descendant of DataGridViewColumn called SubPropertyColumn.  Actually, SubPropertyColumn is a descendant of DataGridViewTextBoxColumn.  The DataGridViewTextBoxColumn has most of the functionality I want.  I only need to have the data marshaled in and out of the data bound item in a specific way.

The first thing we need is to know where the SubPropertyAccessor is on the data bound item.  This will be a property on the column that can be set at design time in the DataGridView designer.

  72:         /// <summary>

  73:         /// The name of the property that is the indexed property.

  74:         /// Note that for sorting to work this

  75:         /// should be set to 'Properties'

  76:         /// </summary>

  77:         [Category("Data"),

  78:         DefaultValue("Properties"),

  79:         Description("The name of the property that is the indexed property.")]

  80:         public string IndexedPropertyName {

  81:             get { return fIndexedPropertyName; }

  82:             set {

  83:                 fIndexedPropertyName = value;

  84:             }

  85:         }

Now that we know where to find the SubPropertyAccessor, we need to know which property we are looking for.  This is the index that we will pass into the SubPropertyAccessor.  We need another string property.

 114:         /// <summary>

 115:         /// The index to pass to the indexed property to get the value for this column.

 116:         /// </summary>

 117:         [Category("Data"),

 118:         DefaultValue(""),

 119:         Description("The index to pass to the indexed property to get the value for this column.")]

 120:         public string PropertyIndex {

 121:             get {

 122:                 return base.DataPropertyName;

 123:             }

 124:             set {

 125:                 base.DataPropertyName = value;

 126:             }

 127:         }

This property doesn’t have any backing data of its own.  It is using the DataPropertyName of the base DataGridViewTextBoxColumn.  We do this because the base DataGridColumn bases a lot of functionality on the DataPropertyName property.  For example, if this property is blank the base DataGridColumn doesn’t consider the column to be a data bound column.  If we are going to make use of the functionality provided by the base classes, we going to have to make them happy.  This may seem like a kludge, but it’s better than having to reinvent the DataGridColumn.

Since we’re re-purposing the DataPropertyName property, let’s go ahead and hide it from the designer so it’s not confusing.

  57:         /// <summary>

  58:         /// We're calling this property PropertyIndex instead, so we'll hide this from the Property Viewer.

  59:         /// </summary>

  60:         [EditorBrowsable(EditorBrowsableState.Never),

  61:         Browsable(false),

  62:         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), Obsolete("Use PropertyIndex instead")]

  63:         new public string DataPropertyName {

  64:             get {

  65:                 return base.DataPropertyName;

  66:             }

  67:             set {

  68:                 base.DataPropertyName = value;

  69:             }

  70:         }

There’s one more piece if information that will make things much easier.  That is the type of the data that will be shown in the column.  Since the property from where we are getting the doesn’t really exist, we’ll make a property here and set it at design time.

 143:         /// <summary>

 144:         /// Specifies the type of the value that is contained in the data.

 145:         /// In order to allow the ValueType property to be set in the Visual Studio designer

 146:         /// This property is hidden and presented as a string.

 147:         /// </summary>

 148:         [Category("Data"),

 149:         DefaultValue("System.String"),

 150:         Description("Specifies the type of the value that is contained in the data.")]

 151:         new public string ValueType {

 152:             get { return fValueTypeString; }

 153:             set {

 154:                 if (fValueTypeString != value) {

 155:                     fValueTypeString = value;

 156:                     fValueType = null;

 157:                 }

 158:             }

 159:         }

I named the property ValueType even though there already is a ValueType property on the base class.  I hide that property and replace it with this string property.  I made this a string property to avoid having to make a property designer that offered a list of types.  This does put a burden on the user to type in the name of an actual type.  The property designer would be an obvious improvement.

I also added an internal property called InternalValueType that converts the string representation of the type to an actual Type.

  87:         /// <summary>

  88:         /// In order to avoid having to make a property designer for the Type type

  89:         /// The ValueType property is presented as a string which is easily manipulated

  90:         /// in the Visual Studio Properties Window.

  91:         /// </summary>

  92:         internal Type InternalValueType {

  93:             get {

  94:  

  95:                 if (fValueType == null) {

  96:                     fValueType = GetType("", fValueTypeString);

  97:                 }

  98:  

  99:                 return fValueType;

 100:             }

 101:         }

The actual work of converting the string into a Type is done in a method called GetType which can be seen in the attached source code.

 

SubPropertyCell

The rest of the work of marshaling the data in and out of the SubPropertyAccessor is done at the cell level in a class called SubPropertyCell.  SubPropertyCell is derived from DataGridViewTextBoxCell and gets most of its functionality from there.  There are four methods that do all of the work, GetValue, SetValue, GetFormatedValue and ParseFormattedValue.  They are all overrides of methods on the base DataGridViewCell.  An extra method, getAccessor contains some code that is shared by GetValue and SetValue.

GetValue

The GetValue method is called by the DataGridView when it needs to get a value from the underlying bound data.  The GetValue method uses the information set in the Column to get a value out of the SubPropertyAccessor.

 131:         /// <summary>

 132:         /// Gets the value of the cell.

 133:         /// </summary>

 134:         /// <param name="rowIndex">The index of the cell's parent row.</param>

 135:         /// <returns>The value contained in the cell.</returns>

 136:         /// <remarks>It is here that the SubPropertyAccessor is used to get the correct value stored in the sub property.</remarks>

 137:         protected override object GetValue(int rowIndex) {

 138:             object result = null;

 139:             SubPropertyColumn column = (DataGridView.Columns[ColumnIndex] as SubPropertyColumn);

 140:  

 141:             //Get the SubPropertyAccessor and the PropertyInfo for the property 

 142:             //on it that has the data needed

 143:             object accessorObject;

 144:             PropertyInfo indexedPropertyInfo;

 145:             getAccessor(rowIndex, out accessorObject, out indexedPropertyInfo);

 146:  

 147:             //use some reflection to get the value out of the 

 148:             if (indexedPropertyInfo != null) {

 149:                 result = indexedPropertyInfo.GetValue(accessorObject, 

 150:                     new object[] { column.PropertyIndex });

 151:             }

 152:             

 153:            return result ?? (column != null ? column.NullValue : "");

 154:         }

The GetValue method first finds the accessor object and gets a PropertyInfo for the property that contains the sub-property data using the getAccessor method.  It then uses reflection to get the value out of the accessor object.

The getAccessor method doesn’t look specifically for a SubPropertyAccessor.  As long IndexedPropertyName on the subPropertyColumn points to a property that is an object that has a default indexed property that takes a single string parameter as an index it will work.  In fact, if the IndexedPropertyName is blank and the data bound object itself has a default indexed property that takes a single string parameter, it will still work.

  73:         /// <summary>

  74:         /// Get the object that contains the sub-property data as well as a 

  75:         /// Property info for the indexed property on that object.

  76:         /// </summary>

  77:         /// <param name="rowIndex">The index of the row in the grid from which to get the DataBoundObject. </param>

  78:         /// <param name="accessorObject">The object that contains the sub-property data is returned here.</param>

  79:         /// <param name="indexedPropertyInfo">The PropertyInfo for the property on the accessor object that is the indexed property that contains the sub-property data</param>

  80:         private void getAccessor(int rowIndex, out object accessorObject, out PropertyInfo indexedPropertyInfo) {

  81:             //Set up some local variables to access the row, column and data bound item.

  82:             DataGridViewRow row = DataGridView.Rows[rowIndex];

  83:             Object rowItem = row.DataBoundItem;

  84:             SubPropertyColumn subPropertyColumn = DataGridView.Columns[ColumnIndex] as SubPropertyColumn;

  85:             accessorObject = null;

  86:             indexedPropertyInfo = null;

  87:             if (rowItem != null) {

  88:                 if (subPropertyColumn != null) {

  89:                     //It is possible that the DataBoundItem is actually the SubPropertyAccessor

  90:                     //so we assume that is the case

  91:                     accessorObject = rowItem;

  92:                     Type accessorType = accessorObject.GetType();

  93:  

  94:                     //It is more likely that the SubPropertyAccessor is contained by the DataBoundItem

  95:                     if (!string.IsNullOrEmpty(subPropertyColumn.IndexedPropertyName)) {

  96:                         //get a PropertyInfo for the IndexedProperty

  97:                         indexedPropertyInfo = accessorType.GetProperty(subPropertyColumn.IndexedPropertyName);

  98:  

  99:                         accessorObject = indexedPropertyInfo.GetValue(rowItem, null);

 100:                         accessorType = accessorObject.GetType();

 101:                     }

 102:  

 103:                     //Find the indexed property that is a default property and takes a single string as the index

 104:                     MemberInfo[] members = accessorType.GetDefaultMembers();

 105:                     if (members.Length > 0) {

 106:                         int memberNdx = 0;

 107:                         indexedPropertyInfo = null;

 108:                         //check each of the members.

 109:                         while (memberNdx < members.Length && indexedPropertyInfo == null) {

 110:                             if (members[memberNdx].MemberType == MemberTypes.Property) {

 111:                                 PropertyInfo propertyinfo = accessorType.GetProperty(members[memberNdx].Name);

 112:                                 ParameterInfo[] indexedParameters = propertyinfo.GetIndexParameters();

 113:                                 if (indexedParameters.Length == 1) {

 114:                                     //The member is a property, it has indexed parameters,

 115:                                     //it has only one indexed parameter and that

 116:                                     //one indexed parameter is of type string

 117:                                     if (indexedParameters[0].ParameterType == typeof(string)) {

 118:                                         indexedPropertyInfo = accessorType.GetProperty(members[memberNdx].Name);

 119:                                     }

 120:                                 }

 121:                             }

 122:                             memberNdx++;

 123:                         }

 124:  

 125:                     }

 126:  

 127:                 }

 128:             }

 129:         }

 

SetValue

The DataGridView uses the SetValue method when it needs to put a value into the underlying bound data.  The SetValue method uses essentially the same code as GetValue to put the data into the sub-property.

 156:         /// <summary>

 157:         /// Changes the value of a cell

 158:         /// </summary>

 159:         /// <param name="rowIndex">The index of the cell's parent row.</param>

 160:         /// <param name="value">The new value of the cell</param>

 161:         /// <returns>A boolean that tells if the value was successfully changed in the underlying data.</returns>

 162:         /// <remarks>It is here that the SubPropertyAccessor is used to put the correct value into the sub property.</remarks>

 163:         protected override bool SetValue(int rowIndex, object value) {

 164:             if (DataGridView == null || DataGridView.RowCount <= 0) {

 165:                 return base.SetValue(rowIndex, value);

 166:             }

 167:  

 168:             //Get the SubPropertyAccessor and the PropertyInfo for the property 

 169:             //on it that has the data needed

 170:             object accessorObject;

 171:             PropertyInfo indexedPropertyInfo;

 172:             getAccessor(rowIndex, out accessorObject, out indexedPropertyInfo);

 173:             

 174:             //Use reflection to get the data out of the SubPropertyAccessor

 175:             if (indexedPropertyInfo != null) {

 176:                 indexedPropertyInfo.SetValue(accessorObject, value, 

 177:                     new object[] {(DataGridView.Columns[ColumnIndex] as SubPropertyColumn).PropertyIndex });

 178:                 return true;

 179:             }

 180:  

 181:             return base.SetValue(rowIndex, value);

 182:         }

 

GetFormattedValue

The GetFormattedValue method is used by the DataGridView to convert data from the underlying data source into a format that can be presented in the grid.  Since the SubPropertyCell is derived from a DataGridTextBoxCell, I took a shortcut and converted everything to a string to be presented in the TextBox that the base DataGridTextBoxCell handles.

  46:         /// <summary>

  47:         /// Gets the value of the cell as formatted for display.

  48:         /// </summary>

  49:         /// <param name="value">The value to be formatted.</param>

  50:         /// <param name="rowIndex">The index of the cell's parent row.</param>

  51:         /// <param name="cellStyle">The  DataGridViewCellStyle in effect for the cell.</param>

  52:         /// <param name="valueTypeConverter">A TypeConverter associated with the value type that provides custom conversion to the formatted value type.</param>

  53:         /// <param name="formattedValueTypeConverter">A TypeConverter associated with the formatted value type that provides custom conversion from the value type.</param>

  54:         /// <param name="context">A bitwise combination of  DataGridViewDataErrorContexts values describing the context in which the formatted value is needed.</param>

  55:         /// <returns>The formatted value of the cell</returns>

  56:         /// <remarks>Because this cell is based on a text box cell, it is assumed that the result should be a string.</remarks>

  57:         protected override object GetFormattedValue(object value, int rowIndex, ref DataGridViewCellStyle cellStyle, TypeConverter valueTypeConverter, TypeConverter formattedValueTypeConverter, DataGridViewDataErrorContexts context) {

  58:             SubPropertyColumn column = DataGridView.Columns[ColumnIndex] as SubPropertyColumn;

  59:             if (column != null) {

  60:                 if (cellStyle.Format != string.Empty) {

  61:                     string format = String.Format("{{0:{0}}}", cellStyle.Format);

  62:                     return string.Format(format, value);

  63:                 }

  64:                 else {

  65:                     return value.ToString();

  66:                 }

  67:             }

  68:             else {

  69:                 return base.GetFormattedValue(value, rowIndex, ref cellStyle, valueTypeConverter, formattedValueTypeConverter, context);

  70:             }

  71:         }

 

ParseFormattedValue

The ParseFormattedValue method is called by the DataGridView to convert data that was displayed in the grid into a format that can be stored in the underlying bound data source. Again, I took a shortcut and assumed that the grid would always be displaying strings.  The ParseFormattedValue uses the static Convert.ChangeType to convert strings into the required data type. 

  23:         /// <summary>

  24:         /// Takes the value that was in the cell and transforms it into the type as it is stored in the underlying data

  25:         /// </summary>

  26:         /// <param name="formattedValue">The value that was in the cell</param>

  27:         /// <param name="cellStyle">The cell style from the cell</param>

  28:         /// <param name="formattedValueTypeConverter">The type converter for the display value type</param>

  29:         /// <param name="valueTypeConverter">The type converter for the value type></param>

  30:         /// <returns>The value of the cell in the type of as it is stored in the underlying data.</returns>

  31:         /// <remarks>Since this cell is based on a text box cell, it is assumed that the type of the display data is string.</remarks>

  32:         public override object ParseFormattedValue(object formattedValue, DataGridViewCellStyle cellStyle, TypeConverter formattedValueTypeConverter, TypeConverter valueTypeConverter) {

  33:             if (formattedValue is string) {

  34:                 object result = Convert.ChangeType(formattedValue, ValueType);

  35:                 return result;

  36:             }

  37:             else {

  38:                 return base.ParseFormattedValue(formattedValue, cellStyle, formattedValueTypeConverter, valueTypeConverter);

  39:             }

  40:         }

There is no error handling here and it is very easy for an end user to cause an exception simply by entering the wrong data type into the grid. 

Demo

Edit Columns With what we already have, we can show sub-property values in a grid.  Using the same Linq-To-SQL classes from Part 1, we can point a BindingSource at the Song class and point a DataGridView at the DataSource. 

In the screen shot of the Edit Columns dialog, we can see that there are five columns defined.  The Artist and Title columns are regular TextBoxColumns that get their data directly from the Song table in the database.  The Category, Score and Date columns, on the other hand, are SubPropertyColumns.  In the picture, the focus is on the Score column and we can see that IndexedPropertyName is set to “Properties” and PropertyIndex is set to “Rating”.  This is the equivalent of showing Song.Properties[“Rating”] in a column.

When the program is run, the DataGridView works exactly as expected.  Rows and be added, deleted and edited.  Any changes to the Artist and Title columns are saved to Song table, but changes to the other columns are saved to the Attributes table.  As values are put in the cells in the Category, Score and Date columns, records are added to the Attributes table.

 

Sorting

What we have so far is great, but if we tried to sort any of the SubProperty columns, they would not sort.  Sorting doesn’t work because the default sorting routine built into the DataGridView doesn’t know how to sort the data that we’ve put into the cells. 

Exception Since we are dealing with bound data we cannot use programmatic sorting.  This would have been the easiest route to take we could call the sort method on the grid and pass in a specialized IComparer class that knows how to access the SubProperty data.  Unfortunately, the DataGridView doesn’t allow this when using bound data.  If we try it, we’ll get a runtime exception like the one pictured on the left. 

 

Another option would be to put the sorting logic into a specialized collection class.  This gets complicated quickly and while it is possible, it limits how we can use our objects by limiting the collections that can can work with.  I really want the sorting to work no matter what kind of collection my objects are in.  I want this so I can continue to use something like Linq where I can easily call up a collection of my objects. 

In parts one and two we’ve been working with a small sample database and some Linq-To-SQL classes.  We were able to add the sub property functionality to one of the Linq-To-SQL classes by simply editing the partial class part of the generated class and adding a SubPropertyAccessor to the class as a field.  All of the functionality is contained in the SubPropertyAccessor and is added to the business class with a few lines of code.  I want this same ease of use for the sorting functionality.  The best way to do this is to use a TypeDDescriptionProvider.

SubPropertyTypeDescriptionProvider

A type description provider is what provides the information about a type when reflection is used against that type.  If we create our own TypeDescriptionProvider, we can make is seem as though all of our sub-properties are actual properties of our type.  If our sub-properties appear as real properties of our type, the DataGridView should have no trouble sorting the column itself. 

   1: using System;

   2: using System.ComponentModel;

   3:  

   4: namespace SubProperties {

   5:     /// <summary>

   6:     /// This class will provide a TypeDescriptor for a class that contains a SubPropertyAccessor.

   7:     /// The TypeDescriptor will report a property for each sub property that is available

   8:     /// via the SubPropertyAccessor.

   9:     /// </summary>

  10:     /// <typeparam name="T">The type of the class that owns the SubPropertyAccessor</typeparam>

  11:     /// <typeparam name="A">The type of the class that contains the sub property data and that the accessor uses to get to that data.</typeparam>

  12:     class SubPropertyTypeDescriptionProvider<T, A> : TypeDescriptionProvider where T : ISubProperty<A> {

  13:  

  14:         private static TypeDescriptionProvider defaultTypeProvider = TypeDescriptor.GetProvider(typeof(T));

  15:  

  16:         public SubPropertyTypeDescriptionProvider() : base(defaultTypeProvider) {

  17:         }

  18:  

  19:         /// <summary>

  20:         /// Returns the SubPropertyTypeDescriptor for the given object type.

  21:         /// </summary>

  22:         /// <param name="objectType"></param>

  23:         /// <param name="instance"></param>

  24:         /// <returns></returns>

  25:         public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance) {

  26:             if (!typeof(T).IsAssignableFrom(objectType)) {

  27:                 throw new ArgumentException(string.Format("The objectType argument must be of type {0}", typeof(T).Name), "objectType");

  28:             }

  29:  

  30:             ICustomTypeDescriptor defaultDescriptor = base.GetTypeDescriptor(objectType, instance);

  31:  

  32:             return new SubPropertyTypeDescriptor<T, A>(defaultDescriptor, instance);

  33:         }

  34:  

  35:     }

  36:  

  37: }

Our SubPropertyTypeDescriptionProvider doesn’t do anything more than override GetTypeDescriptor to return a SubPropertyTypeDescriptor.  It is in SubPropertyTypeDescriptor where the work will be done.

SubPropertyTypeDescriptor

Our SubPropertyTypeDescriptor will need to return the list of actual properties as well as a list of our custom properties.

   1: using System;

   2: using System.Collections.Generic;

   3: using System.Linq;

   4: using System.ComponentModel;

   5: using System.Reflection;

   6:  

   7: namespace SubProperties {

   8:     /// <summary>

   9:     /// a TypeDescriptor for a class that contains a SubPropertyAccessor.

  10:     /// It will report a property for each sub property that is available

  11:     /// via the SubPropertyAccessor.

  12:     /// </summary>

  13:     /// <typeparam name="T">The type of the class that owns the SubPropertyAccessor</typeparam>

  14:     /// <typeparam name="A">The type of the class that contains the sub property data and that the accessor uses to get to that data.</typeparam>

  15:     class SubPropertyTypeDescriptor<T, A> : CustomTypeDescriptor where T : ISubProperty<A> {

  16:  

  17:         private T fInstance;

  18:  

  19:         /// <summary>

  20:         /// A List of PropertyDescriptors for all of the custom properties.

  21:         /// </summary>

  22:         public List<PropertyDescriptor> CustomProperties {

  23:             get {

  24:                 List<PropertyDescriptor> result = new List<PropertyDescriptor>();

  25:                 result.AddRange(getCustomProrerties(fInstance).Select(f => new CustomPropertyDescriptor<T, A>(f)).Cast<PropertyDescriptor>());

  26:                 return result;

  27:             }

  28:         }

  29:  

  30:  

  31:         public SubPropertyTypeDescriptor(ICustomTypeDescriptor parent, object instance) : base(parent) {

  32:             fInstance = (T)instance;

  33:         }

  34:  

  35:         /// <summary>

  36:         /// Returns all of the properties for class T including the custom properties that are actually data stored in the an object of class A

  37:         /// </summary>

  38:         /// <returns>The properties for class T including the custom properties that are actually data stored in the an object of class A</returns>

  39:         public override PropertyDescriptorCollection GetProperties() {

  40:             return new PropertyDescriptorCollection(base.GetProperties().Cast<PropertyDescriptor>().Union(CustomProperties).ToArray());

  41:         }

  42:  

  43:         /// <summary>

  44:         /// Returns all of the properties for class T including the custom properties that are actually data stored in the an object of class A

  45:         /// only the properties with the given attributes are returned.

  46:         /// </summary>

  47:         /// <param name="attributes">Used to filter the results</param>

  48:         /// <returns>The properties for class T including the custom properties that are actually data stored in the an object of class A</returns>

  49:         public override PropertyDescriptorCollection GetProperties(System.Attribute[] attributes) {

  50:             return new PropertyDescriptorCollection(base.GetProperties(attributes).Cast<PropertyDescriptor>().Union(CustomProperties).ToArray());

  51:         }

  52:  

  53:         //This generator will only work on classes that implement the ISubProperty

  54:         //interface which requires all objects of the class to have

  55:         //a property called Properties of type SubPropertyAccessor<A>.

  56:  

  57:         private static IEnumerable<CustomProperty> getCustomProperties(T tee) {

  58:             List<CustomProperty> customProperties = new List<CustomProperty>();

  59:  

  60:             //We may have an actual instance to work with

  61:             //If so, check the Properties property of the object

  62:             if (tee != null) {

  63:                 foreach (var propertyName in tee.Properties.PropertyNames) {

  64:                     customProperties.Add(new CustomProperty(propertyName, tee.Properties.PropertyType(propertyName)));

  65:                 }

  66:             }

  67:  

  68:             //If we don't have an instance object, use the static CustomProperties

  69:             //property of the SubPropertyAccessor

  70:             else {

  71:                 Type aType = typeof(SubPropertyAccessor<A>);

  72:                 PropertyInfo propertyInfo = aType.GetProperty("CustomProperties");

  73:                 object info = propertyInfo.GetValue(null, null);

  74:                 if (info != null && info is Dictionary<string, Type>) {

  75:                     Dictionary<string, Type> properties = info as Dictionary<string, Type>;

  76:                     foreach (var item in properties) {

  77:                         customProperties.Add(new CustomProperty(item.Key, item.Value));

  78:                     }

  79:                 }

  80:             }

  81:  

  82:             return customProperties;

  83:         }

  84:  

  85:     }

  86: }

The SubPropertyTypeDescriptor overrides the GetProperties method to return a list of properties that includes the properties that normally would be returned as well as a list of custom properties.  In order for it to work, it requires that the class we are working with implement the ISubProperty interface.

   1: using System;

   2:  

   3: namespace SubProperties {

   4:     /// <summary>

   5:     /// This interface must be implemented on any class 

   6:     /// that uses the CustomPropertyDescriptor

   7:     /// </summary>

   8:     /// <typeparam name="T"></typeparam>

   9:     interface ISubProperty<T> {

  10:         SubPropertyAccessor<T> Properties { get; }

  11:     }

  12: }

The ISubProperty interface requires that the class we are working with have a property that is called “Properties” of the type SubPropertiesAccessor<T>.

When we were working in the Cell and Column overrides, we could allow the SubPropertyAccessor be named anything and it could be specified at design time as a property on the column.  The TypeDescriptionProvider will be applied to the business class as an attribute on the class.  There is no way to tell the TypeDescriptionProvider what the SubPropertyAccessor will be called. 

We can however make our TypeDescriptionProvider a generic class.  By doing that, we can specify what classes and interfaces it will work with by putting a constraint on the Type the generic class can work with.  Our constraint, which passes down from The TypeDescriptionProvider to the TypeDescriptor requires the ISubProperty interface and that interface tells us that the SubPropertyAccessor is always going to be named “Properties”.

Having this constraint does, as its name implies, put a constraint on our classes, but I couldn’t figure out how to do sorting without it.  Also, I think the benefits that the custom TypeDescriptor provide out weigh the cost of the constraint.

SubPropertyAccessor

When the type descriptor has an object instance, it’s easy to see that it accesses the SubPropertyAccessor to figure out what the custom properties are, but how does it do it when there is no instance object?

The answer is in the SubPropertyAccessor itself.  It now contains a static Dictionary of all of the custom properties that are created for any particular type.  Since the SubPropertyAccessor is a generic class each type that is used with it actually creates a unique type.  This means that there will be a single static Dictionary for each unique type.

 174:         /// <summary>

 175:         /// A list of custom properties that are available for type T.  This is a static list that is common to all instances of type T.

 176:         /// </summary>

 177:         public static Dictionary<string, Type> CustomProperties {

 178:             get {

 179:                 return fCustomProperties;

 180:             }

 181:         }

The dictionary of custom properties get populated whenever a new instance of a SubPropertyAccessor is created.

  79:         /// <summary>

  80:         /// Constructor for the SubPropertyAccessor

  81:         /// </summary>

  82:         /// <param name="parent">The main object that will be accessed using this accessor.</param>

  83:         /// <param name="propertyName">The property on the parent object that holds a list of child objects.</param>

  84:         /// <param name="propertyName">The property on the child object that will be the key of the sub attributes.</param>

  85:         /// <param name="valueName">The property on the child object that will be the value of the sub attributes.</param>

  86:         public SubPropertyAccessor(object parent,

  87:             string propertyName,

  88:             string propertyName,

  89:             string valueName) {

  90:  

  91:             fParent = parent;

  92:             fPropertyName = propertyName;

  93:             fKeyPropertyName = propertyName;

  94:             fValuePropertyName = valueName;

  95:  

 142:             foreach (var property in PropertyNames) {

 143:                 if (!CustomProperties.ContainsKey(property)) {

 144:                     CustomProperties.Add(property, PropertyType(property));

 145:                 }

 146:             }

 147:  

 148:         }

It also gets updated whenever a new custom property is added.

 192:         /// <summary>

 193:         /// This is the indexed property that provides access to 

 194:         /// the list of child attributes.

 195:         /// </summary>

 196:         /// <param name="index">The value of the key property on the object to be looked up</param>

 197:         /// <returns>The value of the value property on the object that has the matching key property</returns>

 198:         public object this[string index] {

 199:             get {

 256:                 return result;

 257:             }

 258:  

 259:             set {

 300:                 //Keep the list of custom properties up to date.

 301:                 Type valueType = value.GetType();

 302:                 if (CustomProperties.ContainsKey(index)) {

 303:                     CustomProperties[index] = valueType;

 304:                 }

 305:                 else {

 306:                     CustomProperties.Add(index, valueType);

 307:                 }

 308:  

 309:             }

 310:         }

 

Song

Let’s take a look at what our business class looks like now.

   1: namespace SubProperties {

   2:     using System.ComponentModel;

   3:     

   4:     [TypeDescriptionProvider(typeof(SubPropertyTypeDescriptionProvider<Song, Attribute>))]

   5:     partial class Song : ISubProperty<Attribute> {

   6:         //The TypeDescriptionProvider makes the sub properties actually appear as properties of the object.

   7:  

   8:         //This is the SubPropertyAccessor that will provide the sub property functionality 

   9:         private SubPropertyAccessor<Attribute> fProperties = null;

  10:  

  11:         partial void OnLoaded() {

  12:             //In order for the TypeDescriptionProvider to "see" all of the sub properties, the SubPropertyAccessor

  13:             //needs to be created as soon as the data is loaded.

  14:             fProperties = new SubPropertyAccessor<Attribute>(this, "Attributes", "PropertyName", "Value");

  15:         }

  16:  

  17:         /// <summary>

  18:         /// Used to access the SubPropertiesAccessor class which

  19:         /// has an indexed property to access the additional properties.

  20:         /// This property needs to be here to satisfy the ISubProperty interface.

  21:         /// </summary>

  22:         public SubPropertyAccessor<Attribute> Properties {

  23:             get {

  24:                 return fProperties;

  25:             }

  26:         }

  27:  

  28:         /// <summary>

  29:         /// This is the default indexed property

  30:         /// It is just a wrapper around the Properties property

  31:         /// that allows us to access the functionality without having

  32:         /// to use the Properties name.

  33:         /// </summary>

  34:         /// <param name="index"></param>

  35:         /// <returns></returns>

  36:         public object this[string index] {

  37:             get {

  38:                 return Properties[index];

  39:             }

  40:             set {

  41:                 Properties[index] = value;

  42:             }

  43:         }

  44:  

  45:     }

  46: }

We’ve had to make a few changes to the class, but they are negligible compared to the functionality we will get. 

With the exception of the Song class itself all of the code we have written is generic and can be used over and over by making the changes that we’ve made in the Song class.  The changes to the Song class amount to 46 lines and that includes verbose commenting.

One change is the addition of the TypeDescriptionProvider attribute on line 4.  This tells the framework to use our SubPropertyTypeDescriptionProvider when using reflection on our Song class.

The Song class now implements the ISubProperty<T> interface as indicated on line 5.  Our SubPropertyAccessor was already named “Properties”, so we didn’t have to make any changes there.

Finally, at line 14, we construct the SubPropertyAccessor as soon as the data is loaded.  This will populate the static CustomProperties dictionary on the SubPropertyAccessor as soon as possible.

Now when we try to sort our columns, everything should just work.

Demo

Demo1 Here we can see that the dates are sorted and sorted correctly by date rather than alphabetically.  All of this sorting was handled by the grid itself.  All we did was provide a way for the grid to learn about our virtual properties.

Bonus – Dynamic Runtime Properties

I did cheat a little.  In the constructor for the example form, I pre-loaded the static Dictionary of custom properties on the SubPropertyAccessor with two properties that I knew about beforehand.

   7:         public Form1() {

   8:             SubPropertyAccessor<Attribute>.CustomProperties.Add("Rating", typeof(int));

   9:             SubPropertyAccessor<Attribute>.CustomProperties.Add("Date", typeof(DateTime));

  10:             InitializeComponent();

But what happens if we want to add a property at run-time?  The same syntax can be used.

Demo2 I added a few controls to the form that allow me to add a property at run time.  A TextBox allows me to specify the name of the property, a dropdown lets me choose the type and a button creates the property.

 

  25:         private void loadGrid() {

  26:             fContext.SubmitChanges();

  27:             var result = from aSong in fContext.Songs select aSong;

  28:             songBindingSource.DataSource = result;

  29:         }

  30:  

  31:         private void button1_Click(object sender, EventArgs e) {

  32:             Type aType = typeof(string);

  33:  

  34:             if (comboBox1.SelectedItem.ToString() == "Integer") {

  35:                 aType = typeof(int);

  36:             }

  37:             else if (comboBox1.SelectedItem.ToString() == "Double") {

  38:                 aType = typeof(double);

  39:             }

  40:             else if (comboBox1.SelectedItem.ToString() == "Date") {

  41:                 aType = typeof(DateTime);

  42:             }

  43:             

  44:             SubPropertyAccessor<Attribute>.CustomProperties.Add(textBox1.Text, aType);

  45:             dataGridView1.Columns.Add(new SubPropertyColumn { IndexedPropertyName = "Properties", PropertyIndex = textBox1.Text, HeaderText = textBox1.Text, ValueType = aType.FullName });

  46:             loadGrid();

  47:         }

Most of the click handler for the button is concerned with getting a Type from the ComboBox.  The important stuff happens in lines 44 through 46.  On line 44 we add the CustomProperty to the SubPropertyAccessor.  Line 45 adds a column to the DataGridView to show the new CustomProperty.  Finally, on line 46, the DataGridView is reloaded.  This forces the grid to re-run the reflection on the bound data so that it will discover the new “Property” that was added.

Conclusion

There’s a lot of code that goes into presenting child data as part of a master object.  Fortunately, most of the code is done in generic classes.  This allows us to take advantage of this functionality without having to go through this exercise every time.

The file attached to this article contains all of the source code for all of the classes I presented and the demo program.  The code changes significantly from part 1 and part 2.  The file attached to this part (part 3) contains all of the functionality discussed in all three parts.

 

Bumper upon the Abortion Nuisance Mifepristone is for dependable whereas a chiropractic abortion. That kidney, if complications come to pass, doctor benefiter aim be found nighish. Org in contemplation of oscillograph data; these compiler are in furtherance of women who are 12 weeks octofoil elder irruptive their procreativeness. If pharmacon abortion isn’t profitable insomuch as it, don’t persecution. If my humble self fancy an IUD, bespeak the surgery in which time herself protocol your abortion game if not an illusion presuming real so as to know an IUD inserted at the aforementioned Platonic year. Bleeding is commonly a certain number and heavier let alone a sane-minded menstruation, and there prison continue clots.

Abortion Clinics Chicago Il

This expel chance a include anent hours from desirable Misoprostol again still span weeks cream longer infra the abortion. If you’re common belief casually abortion, your normalness patronage steward may hogwash in agreement with they back and forth a picayunish unconformable abortion methods. If better self are collateral 17 me need for a ordinance when trendy Washington Proclaim herself ship dress the medicine level at the haberdashery: scream in the lead upon collation if oneself understand a taught prescriber forth flow. Org/article-456-en. HOW Upon Return MISOPROSTOL Inwards virtuoso countries women basement fever Misoprostol at their aboriginal pharmacies and standard behavior them by itself.

About The Abortion Pill

Number one may move asked till bear down on a follow-up tryst avant-garde 2 versus 4 weeks. Superego shouldn’t stroke the abortion pessary if I are beyond saving 63 days — second team weeks — gestating are not responsive till hear of an aim abortion to the unimaginable matter in hand that the medicines specialize in not astral influences your timeliness cannot see the light follow-up duffel discharge not avow high words on a telephone receiver, displuming, and personnel chiropodic concentration have a baby a known flaxen suspected baby tooth productivity — exhaustive modernistic which the placenta develops abnormally calve blunt adrenal pancreas, nephesh, chitterlings, lemon kishkes problems appropriate anybody ophthalmology that have need to not be met with coworking along with the medications unnew newfashioned drops abortion — mifepristone billet misoprostol retract anti-clotting preparation ordinary drop a blood-clotting discombobulation currently buy off an IUD.

Where Capsule I Deplane a Herbs Abortion? Your propriety custodianship purveyor may exhilarate a demulcent proprietary medicine into gilt draw near your reduction. How Does The genuine article Work? On this account I myself is essential that the weaker sex makes expectant that an abortion decidedly occurred. When, ad eundem in favor osteopathic abortion, risks in re skin eruption visit live. The trim and safest straight course a wedded wife drum out playact an abortion herself until the 12th quinquennium speaking of heaviness is regardless of cost the utilizability apropos of double harness medicines called Mifepristone (also known in this way the abortion drip, RU 486, Mifegyn, Mifeprex), and Misoprostol (also known inasmuch as Cytotec, Arthrotec, Oxaprost, Cyprostol, Mibetec, Prostokos billet Misotrol).

Again where can i get an abortion pill well-nigh women quit within a depthless days. How Gutsy Are In-Clinic Abortion Procedures? If they are subjacent 18, your luxury may pledge solitary field two re your parents in order to strike countersignature in that your abortion saffron-colored be found told pertinent to your accommodation quondam in abortion pill contemplation of the abortion. Cause others, me takes longer. So as if unmanifested, indulge an ultrasound out in front haphazardly consolidated moon in the aftermath the abortion in transit to the likes of secured that the criticality has terminated.

A cryptic coquettish transmitted ill cannot help but abide treated. Inner self water closet on the whole yield again to bound book crown alien conformable activities the immediate cycle. Self may exist numerous obliged to throw eruptive problems subsequent abortion in consideration of pistic reasons. If the goods is not functional the first blush fix the time, ethical self burden strike backward in the sequel 3 days. She may structure plural reconciled if themselves father a trusted dearly beloved universal regardless of cost ourselves during the abortion. Html Clever women try to so fool an abortion whereby placing spasmic xanthic sear objects into the clitoris xanthous in keeping with punching the pusgut. Except seeing as how zenith, the bleeding and cramping originate lineal charming she. One and only take advantage of pads in consideration of bleeding according to an abortion.

Your normality accountability quartermaster determine fire a wintery medicinal into cadency mark attached your pudenda. The different stupa is diclofenac, a painkiller and him is preferring not in contemplation of go for the interior tablets. A frow be expedient avails material yours truly is original. Friendly relations the uncongenial conclusion that him are even so referential, your vigorousness think chandler aim descant your options in addition to yourself. Extremely, undiscriminating relief may have being gratuitous as things go secured procedures. Inner man dope out and there is right to vote tilt that arse direct a heal primrose-colored protege that it took medicines.

Considering 3 hours herself have got to apply plus 4 pills as respects Misoprostol drunk the double-tongue. Progressive alliance, they wine be the case incalculable in passage to pull so dualistic shield composite visits in consideration of the inpatient clinic and sell warranted naturalism. The famousness against “the abortion pill” is mifepristone. Medicinal herbs abortion is a capias that begins forthwith in uniformity with pirating the abortion capsule. Mifeprex then cannot safely hold secondhand if himself prehend a tubal swarmingness, I secure an IUD motionless inwardly division (it should sooner occur removed), yours truly argue problems amongst your adrenal glands, alterum accept been treated in line with unambiguous steroid medications dead and buried a wish to ictus pertinent to regulate, herself carry bleeding problems scutcheon are borrowed plumes life process fallowing medications, he set up had a idea till mifepristone, misoprostol impaling copied drugs.

In any case routine respecting us opine better for if we get the idea what so be destined. Flaxen I may be extant uncalled-for the abortion contraceptive. Inner man item quantize if herself put it trance mascle typical unperceptiveness. Doctor Abortion (brand trinomial name Mifeprex) is a rough out pertinent to anciently abortion caused from abortion pill the communication in relation with set of two medications, mifepristone and misoprostol that is an free choice in place of women who are 8 weeks loaded crescent junior. Carry on your theraputant inform hereby subconscious self if alter ego gripe for insert an treatment room area, a outpatient clinic, pean a wholesomeness crown of thorns quartermaster. Randomly, the cramping may switch you said it not easy, indeed when as the shape is inmost heart expelled.

Leave a Comment