Databinding Magic with Dynamic PropertyDescriptors and Anonymous Methods

At the end of my post, Implementing ITypedList for Virtual Properties, I mentioned the possibility of having one, very-versatile, subclass of PropertyDescriptor that could handle all of your virtual property databinding needs. I was implementing ITypedList again and I didn’t want to write yet another set of type-specific PropertyDescriptor subclasses so I knocked this out. Everything went really well until I was creating anonymous methods inside of a foreach loop. Be sure to see the caveat below on this before you try to use this class. Without further ado, here is DynamicPropertyDescriptor (props to manoli.net for providing the code formatter I used in this post):

public delegate object DynamicGetValue(object component);
public delegate void DynamicSetValue(object component, object newValue);

public class DynamicPropertyDescriptor : PropertyDescriptor {
    protected Type _componentType;
    protected Type _propertyType;
    protected DynamicGetValue _getDelegate;
    protected DynamicSetValue _setDelegate;

    public DynamicPropertyDescriptor(Type componentType, string name, Type propertyType, 
        DynamicGetValue getDelegate, DynamicSetValue setDelegate)
        :
        base(name, null) {
        _componentType = componentType;
        _propertyType = propertyType;
        _getDelegate = getDelegate;
        _setDelegate = setDelegate;
    }

    public override bool CanResetValue(object component) {
        return false;
    }

    public override Type ComponentType {
        get { return _componentType; }
    }

    public override object GetValue(object component) {
        return _getDelegate(component);        
    }

    public override bool IsReadOnly {
        get { return _setDelegate == null; }
    }

    public override Type PropertyType {
        get { return _propertyType; }
    }

    public override void ResetValue(object component) {
    }

    public override void SetValue(object component, object value) {
        _setDelegate(component, value);
    }

    public override bool ShouldSerializeValue(object component) {
        return true;
    }
}

I will reuse the Person and related classes from the prior post. This time, I will demonstrate an actual crosstab with editable values by adding an “hours worked” array. I will also include a running solution at the end.

Here’s the upated Person Class:

public class Person {
    protected string _firstName;
    protected string _lastName;
    protected string _midName;
    protected DateTime _dob;
    protected decimal[] _hoursWorked;

    public Person(string firstName, string lastName, string midName, DateTime dob) {
        _firstName = firstName;
        _lastName = lastName;
        _midName = midName;
        _dob = dob;
        _hoursWorked = new decimal[7];
    }
    public string FirstName {
        get { return _firstName; }
    }
    public string LastName {
        get { return _lastName; }
    }
    public string MiddleName {
        get { return _midName; }
    }
    public DateTime DateOfBirth {
        get { return _dob; }
    }
    public decimal this[int day] {
        get { return _hoursWorked[day]; }
        set { _hoursWorked[day] = value; }
    }
}

The only change is the addition of the _hoursWorked member and the indexor property to allow getting and setting the values of _hoursWorked. Now to the good stuff. The method I demonstrated in the last post required me to create new subclasses of PropertyDescriptor for each type of component I wanted to work with. DynamicPropertyDescriptor uses anonymous methods to provide a reusable implementation of the PropertyDescriptor class. Here’s what the new view code looks like:

public class PersonFullNameAgeHoursView : IPersonViewBuilder {
    public PropertyDescriptorCollection GetView() {
        List<PropertyDescriptor> props = new List<PropertyDescriptor>();

        props.Add(new DynamicPropertyDescriptor(
            typeof(Person),
            "FullName",
            typeof(string),
            delegate(object p) { 
                return ((Person)p).FirstName + " " 
                    + ((Person)p).MiddleName + " " 
                    + ((Person)p).LastName; },
            null
        ));

        props.Add(new DynamicPropertyDescriptor(
            typeof(Person),
            "Age",
            typeof(string),
            delegate(object p) {
                return DateTime.Today.Year - ((Person)p).DateOfBirth.Year;
            },
            null
        ));

        for ( int day = 0; day < 7; day++ ) {
            // without this variable declared inside the loop, 
            // the anon methods won't work correctly
            // they will all reference the value of the 
            // foreach loop variables as set on the last iteration
            int bindableDay = day;
            props.Add(new DynamicPropertyDescriptor(
                typeof(Person),
                Enum.GetName(typeof(DayOfWeek), day),
                typeof(Decimal),
                delegate(object p) { 
                    return ((Person)p)[bindableDay];
                },
                delegate(object p, object newPropVal) { 
                    ((Person)p)[bindableDay] = (decimal) newPropVal; 
                }
            ));
        }


        PropertyDescriptor[] propArray = new PropertyDescriptor[props.Count];
        props.CopyTo(propArray);
        return new PropertyDescriptorCollection(propArray);
    }
}

Pay very close attention to the bindableDay variable. This whole thing would have been a piece of cake if not for that little issue. If you want to have a deep understanding of the issue, take a look at this post that explains how anonymous methods are implemented and this post that details the issue of generating anonymous methods that reference local variables in a loop.

Here’s the result of binding PersonCollection to a DataGridview (note that I typed the hour values into the grid, they did not come from the source):

Demo Output

Click here for a complete VS2005 solution.

I haven’t done any performance comparisons between using this method versus type specific subclasses other than to see that was “fast enough” for what I was using it for. However, in performance critical scenarios, you may want to go with the other method.

About these ads

20 Responses to “Databinding Magic with Dynamic PropertyDescriptors and Anonymous Methods”

  1. John Opincar’s Blue Corner Implementing ITypedList for Virtual Properties « Says:

    […] takes a member name, component type, getter and setter delegates that would serve all your needs (I ended up doing this myself) at the cost of being a little harder to follow and probably less […]

  2. Marcco Says:

    this is really, cool just what I was looking for ..but I don’t quite understand how to get the values once they have been changed ( other than interating through the grid itself ) I assume these updates would be available in the object? Could you explain, or post an example? The download lets an edit happen but does not show how to get the values of that edit.

  3. jopincar Says:

    Marcco, I’m not sure I understand your question. You are free to do whatever you want in the “virutal” property gettor and settor. In the example in the post, the Person object implements a indexor that sets values in the _hoursWorked array. The updated values would be available there. Let me know if that helps.

  4. Marcco Says:

    Jopincar, Happy Holidays ! Wow what a quick response, I guess I am still confused, as per your comments it uses an indexor so basically a smartArray? Is that correct? Since the _hoursWorked is in the Person class I should be able to access it there, is that correct by doing something like p[1][3]? ( p being my Person class ). So on the form I could do something like ..
    private void grd_CellEndEdit(object sender, DataGridViewCellEventArgs e)
    {
    for (int i = 0; i < pc.Count ; i++)
    {
    MessageBox.Show(pc[i][3].ToString());
    }
    }

    I have never seen anything like your example and Its very much what I am looking to do so I appreciate your example, I think I am starting to understand it now. How can I get the count of _hoursWorked from the indexor? ( I know its 7 but what if I didn’t ) Do I have to use an Enumerator? I don’t see a way to get a length or count. I am going to see if I can modify your example to take a dataload instead of loading with your predefined array

  5. Marcco Says:

    Jopincar, Happy Holidays ! Wow what a quick response, I guess I am still confused, as per your comments it uses an indexor so basically a smartArray? Is that correct? Since the _hoursWorked is in the Person class I should be able to access it there, is that correct by doing something like p[1][3]? ( p being my Person class ). So on the form I could do something like ..
    private void grd_CellEndEdit(object sender, DataGridViewCellEventArgs e)
    {
    for (int i = 0; i < pc.Count ; i++)
    {
    MessageBox.Show(pc[i][3].ToString());
    }
    }

    I have never seen anything like your example and Its very much what I am looking to do so I appreciate your example, I think I am starting to understand it now. How can I get the count of _hoursWorked from the indexor? ( I know its 7 but what if I didn’t ) Do I have to use an Enumerator? I don’t see a way to get a length or count. I am going to see if I can modify your example to take a dataload instead of loading with your predefined array

  6. jopincar Says:

    To get the count, you would need to expose a property or method that returned it. The “real world” problem that inspired this post took cross-tabbed power scheduling data (peak/off-peak, purchase/sale, location) by counterparty. The use of an indexor in the example is incidental to the main point of the post, using virtual properties to create cross-tabbed”views” of data using an existing object hierarchy.

  7. Marcco Says:

    Jopincar, thanks again for your Quick response. That is what I thought that I would need to do was to expose an property for the count. I did that and it works perfectly! Now the trick is going to be in getting the values back into their appropriate tables (especially the values that are coming from the indexor(s)) . I am tackling that right now. Getting the values to display was pretty easy once I understood you used indexors. I like the virtual property concept to create a cross-tabbed ‘view’ using the existing object hierarchy. This example is really well done and you have shown me a thing or two that I have not seen anywhere else on the net or in textbooks either. I really appreciate this code, Thanks !

  8. Gonzalo Says:

    Hello dude, you don’t have an idea of how useful is this example being for me.
    Thank you very much for sharing your knowledge.

    Saludos desde Uruguay !!

    Gonzalo

  9. Andrew Rev Says:

    Thanks!

    I was excited to see DynamicPropertyDescriptor name here, just 30 minutes after I started writing a class with the same name.

    My application for it is: DataGrid to bind WorkItemCollection (Microsoft.TeamFoundation.WorkItemTracking.Client namespace). WorkItem has a few hardcoded properties for most commo fields, such as Id, Title, or Description, yet, the rest of the fields are to be retrieved via Fields collection indexed property.

    And the Binder cannot use an indexer property of an object to retrieve the value. I had my wrapper object TfsWorkItem to inherit from ICustomTypeDescriptor interface and implement this method:

    public PropertyDescriptorCollection GetProperties()
    {
    var pds = from Tracking.Field field in this.WorkItem.Fields
    let fd = field.FieldDefinition
    select new DynamicPropertyDescriptor(
    typeof(TfsWorkItem),
    fd.ReferenceName,
    fd.SystemType,
    (object item) => ((TfsWorkItem)item)[fd.ReferenceName].Value,
    null
    );
    return new PropertyDescriptorCollection(pds.ToArray());
    }

  10. Bobby Says:

    Hi,

    I have been away from this a few weeks and am now doing some more investigation as to why i was getting that strange behaviour with the DynamicPropertyDescriptor after modifying the GetView method to suit my requirements.

    To recap on what i am trying to accomplish i basically have a class with two string properties… i have a collection of this class that when i bind to the grid i want each of the ‘Key’ properties within the collection to be the columns and each of the ‘Value’ properties within the collection to be the cell data for the corresponding ‘Key’.

    The DynamicPropertyDescriptor is walking over every item in my collection on each call… which is giving me unexpected data in the grid when i bind.

    I have walked through the code and it is hard to say why the DynamicPropertyDescriptor is adding the ‘Value’ property data to each of the ‘Key’ columns i have specified in the GetView… and the call stack isn’t helpful either as all you see is the binding source being set then the properties being hit in the DynamicPropertyDescriptor class.

    All this results from this input

    Item item = null;
    m_collection = new ItemCollection(new ItemView());
    item = new Item(“FirstName”, “Joe”);
    m_collection.Add(item);
    item = new Item(“LastName”, “Citizen”);
    m_collection.Add(item);

    my grid looks like this…

    Firstame | LastName
    Joe | Joe
    Citizen | Citizen

    instead of this…

    Firstame | LastName
    Joe | Citizen

    Thanks,
    Bobby

  11. jopincar Says:

    Based on the code above, how would you ever have more than one row in this grid? It seems like you are leaving out a level in the object hierarchy. You need a collection of collections of attributes. Or, you need something to tie a set of attributes together.

  12. Bobby Says:

    That is exactly right… I don’t want more than one row, the more ‘Key’\’Value’ items i add to the collection the more columns\cells i need populated.

    I want this in this format because the level of this data in the grid is displayed as a Layout View (DevExpress XtraGrid) which is similar to a Card View. This displays the data very nicely for the implementation i need to make.

    I just wanted to get it working to prove it out before i spend anymore time formatting the grid into it’s required view in case this approach could not be done.

    I have been playing with it for most of today and it does seem like the GetView method somehow copies the contents of the collection for every key i add as a column… any thoughts on how i might get around this.

    I have a mockup project that i could send you to help explain things if you have time to help me out.

    Bobby

  13. Peter Ho Says:

    Hi,

    Thanks for your article.

    In you example you gave, the column binding was used. Would it be possible to use the row for binding instead so in you example the grid would appear as

    First name John Steve
    Last Name Opincar Smith
    Age 23 64

    I am using the DataGridView control

    Howie

  14. Rune Says:

    Good example, but did you ever try this with the WPF Toolkit’s DataGrid?

    System.Windows.Data Error: 39 : BindingExpression path error: ‘FullName’ property not found on ‘object’ ”Person’ (HashCode=42708074)’. BindingExpression:Path=FullName; DataItem=’Person’ (HashCode=42708074); target element is ‘TextBlock’ (Name=”); target property is ‘Text’ (type ‘String’)

    System.Windows.Data Error: 39 : BindingExpression path error: ‘Age’ property not found on ‘object’ ”Person’ (HashCode=42708074)’. BindingExpression:Path=Age; DataItem=’Person’ (HashCode=42708074); target element is ‘TextBlock’ (Name=”); target property is ‘Text’ (type ‘String’)

    System.Windows.Data Error: 39 : BindingExpression path error: ‘Sunday’ property not found on ‘object’ ”Person’ (HashCode=42708074)’. BindingExpression:Path=Sunday; DataItem=’Person’ (HashCode=42708074); target element is ‘TextBlock’ (Name=”); target property is ‘Text’ (type ‘String’)

    System.Windows.Data Error: 39 : BindingExpression path error: ‘Monday’ property not found on ‘object’ ”Person’ (HashCode=42708074)’. BindingExpression:Path=Monday; DataItem=’Person’ (HashCode=42708074); target element is ‘TextBlock’ (Name=”); target property is ‘Text’ (type ‘String’)

    The ITypedList.GetValue() method doesn’t get invoked.

  15. jopincar Says:

    No, I haven’t used this code with WPF.

  16. carstengeest Says:

    Lol, I just implemented exactly this…

  17. Christian Says:

    Thank you for your article.

    I modified the code in order to improve readability, by introducing type parameters. In addition, I created a generic collection type to provide as data source.

    I nested the DynamicPropertyDescriptor class in a class inherited from TypeConverter:

    abstract class DynamicPropertyTypeConverter : TypeConverter {
    private PropertyDescriptorCollection properties;

    public abstract void FillProperties(IList properties);

    public sealed override bool GetPropertiesSupported(ITypeDescriptorContext context) {
    return true;
    }

    public BindingList CreateView(IList list) {
    return new DynamicPropertyCollection(list, this);
    }

    public sealed override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context,
    object value, Attribute[] attributes) {
    if (properties == null) {
    List props = new List();
    FillProperties(props);
    properties = new PropertyDescriptorCollection(props.ToArray());
    }
    return properties;
    }

    protected sealed class DynamicPropertyDescriptor : SimplePropertyDescriptor {
    public delegate V DynamicGetValue(T component);
    public delegate void DynamicSetValue(T component, V newValue);

    private readonly DynamicGetValue _getDelegate;
    private readonly DynamicSetValue _setDelegate;

    public DynamicPropertyDescriptor(string name, DynamicGetValue getDelegate, DynamicSetValue setDelegate)
    : base(typeof(T), name, typeof(V)) {
    _getDelegate = getDelegate;
    _setDelegate = setDelegate;
    }

    public override object GetValue(object component) {
    return _getDelegate((T)component);
    }

    public override bool IsReadOnly {
    get { return _setDelegate == null; }
    }

    public override void SetValue(object component, object value) {
    _setDelegate((T)component, (V)value);
    }
    }
    }

    sealed class DynamicPropertyCollection : BindingList, ITypedList {
    private readonly DynamicPropertyTypeConverter converter;
    public DynamicPropertyCollection(IList list, DynamicPropertyTypeConverter converter)
    : base(list) {
    this.converter = converter;
    }

    public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors) {
    return converter.GetProperties(null, null, null);
    }

    public string GetListName(PropertyDescriptor[] listAccessors) {
    return string.Empty;
    }
    }

    Your PersonFullNameAgeHoursView class could then be implemented as follows:

    class PersonFullNameAgeHoursView : DynamicPropertyTypeConverter {

    public override void FillProperties(IList props) {
    props.Add(new DynamicPropertyDescriptor(“FullName”,
    p => p.FirstName + ” ” + p.MiddleName + ” ” + p.LastName,
    null
    ));
    props.Add(new DynamicPropertyDescriptor(“Age”,
    p => DateTime.Today.Year – p.DateOfBirth.Year,
    null
    ));

    for (int day = 0; day < 7; day++) {
    // without this variable declared inside the loop,
    // the anon methods won't work correctly
    // they will all reference the value of the
    // foreach loop variables as set on the last iteration
    int bindableDay = day;
    props.Add(new DynamicPropertyDescriptor(
    Enum.GetName(typeof(DayOfWeek), day),
    p => p[bindableDay],
    (p , newPropVal) => p[bindableDay] = newPropVal
    ));
    }
    }
    }

    The DataSource could be initialized as follows (not taking into account the IPersonViewBuilder):

    IList pc = new List {
    new Person(“John”, “Opincar, Jr”, “Thomas”, new DateTime(1968, 8, 12)),
    new Person(“Abraham”, “Lincoln”, “X”, new DateTime(1825, 1, 1)),
    new Person(“John”, “Smith”, “David”, new DateTime(1985, 2, 15))
    };
    this.grd.DataSource = new PersonFullNameAgeHoursView().CreateView(pc);

  18. Dynamic Wpf DataGrid Columns using PropertyDescriptors | Lucid Snippets Says:

    […] but such approach didn’t work out of the box at all for the Microsoft WPF DataGrid. Actually John Opincar mentioned the whole idea of ITypedList and PropertyDescriptors a long time ago already. My solution is a bit more advanced, […]


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: