Search code examples
c#wpfdatagridpropertydescriptoricustomtypedescriptor

WPF Datagrid Databind to class with static properties and dictionary containing dynamic property value entries


UPDATED

I am updating this post because I did some more reading and decided to re-implement my solution.

Original Problem: I have a class with static properties and one Property that is a dynamic collection of properties (via a dictionary). I want to databind my class to a wpf datagrid where each static property should be a column and each dictionary entry should be a column in the grid.

After doing some more research, I decided to implement a PropertyBag class that will contain my Dictionary of properties and values. Almost everything is working now. I have my grid being displayed with all the correct columns and the static property values are being applied correctly.

However, now I am not able to get any of the values from the dictionary to be applied to the grid, and I am not sure where to go from here.

More info:

My database has 3 tables, a plate, a category, and a categoryplateassociation table. Each plate can have 0 to many categories. For now, I am populating each plate with all the categories and setting the strings to empty. Then, when an association is returned (between a plate and category), I am setting the real value on the specific category name. This all happens before the grid is created.

Property Bag:

public class PropertyBag
{
    private readonly Dictionary<string, string> values = new Dictionary<string, string>();

    public string this[string key]
    {
        get 
        {
            string value;
            values.TryGetValue(key, out value);
            return value;
        }
        set
        {
            if (value == null) values.Remove(key);
            else values[key] = value;
        }
    }
}

Revised Plate class

[TypeDescriptionProvider(typeof(PlateTypeDescriptionProvider))]
public class Plate : INotifyPropertyChanged
{
    public int ID;
    private string name;
    private string status;
    private string creator;
    private Uri location;
    private string description;

    public Plate()
    {
        CustomCategories = new PropertyBag();
    }

    public PropertyBag CustomCategories { get; set; }

    public string Name
    {
        get { return name;}
        set
        {
            name = value;
            NotifyPropertyChanged("Name");
        }
    }

    public string Status
    {
        get { return status; }
        set
        {
            status = value;
            NotifyPropertyChanged("Status");
        }
    }

    public string Creator
    {
        get { return creator; }
        set
        {
            creator = value;
            NotifyPropertyChanged("Creator");
        }
    }

    public Uri Location
    {
        get { return location; }
        set
        {
            location = value;
            NotifyPropertyChanged("Location");
        }
    }

    public string Description
    {
        get { return description; }
        set
        {
            description = value;
            NotifyPropertyChanged("Description");
        }
    }

    public static Plate ConvertDataPlateToBusinessPlate(TestPlate dataPlate)
    {
        var plate = new Plate
                        {
                            Name = dataPlate.Name, 
                            Status = dataPlate.Status,
                            Creator = dataPlate.Creator, 
                            Description = dataPlate.Description, 
                            Location = new Uri(dataPlate.Location)
                        };
        return plate;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Revised CustomTypeDescriptor:

public override PropertyDescriptorCollection GetProperties()
    {
        return GetProperties(null);
    }

    public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
    {
        var properties = new ArrayList();
        foreach (PropertyDescriptor propertyDescriptor in base.GetProperties(attributes))
        {
            if(propertyDescriptor.PropertyType.Equals(typeof(PropertyBag)))
            {
                //Static list of all category names
                var categoryNames = Categories.GetAll();
                foreach (var categoryName in categoryNames)
                {
                    properties.Add(new PropertyBagPropertyDescriptor(categoryName));
                }
            }
            else
            {
                properties.Add(propertyDescriptor);
            }

        }
        var props = (PropertyDescriptor[])properties.ToArray(typeof(PropertyDescriptor));
        return new PropertyDescriptorCollection(props);
    }

Revised PropertyDescriptor

    public class PropertyBagPropertyDescriptor : PropertyDescriptor
{
    public PropertyBagPropertyDescriptor(string name) : base(name, null)
    {}

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

    public override object GetValue(object component)
    {
        return ((PropertyBag) component)[Name];
    }

    public override void ResetValue(object component)
    {
        ((PropertyBag)component)[Name] = null;
    }

    public override void SetValue(object component, object value)
    {
        ((PropertyBag) component)[Name] = (string) value;
    }

    public override bool ShouldSerializeValue(object component)
    {
        return ((PropertyBag)component)[Name] != null;
    }

    public override Type ComponentType
    {
        get { return typeof(PropertyBag); }
    }

    public override bool IsReadOnly
    {
        get { return false; }
    }

    public override Type PropertyType
    {
        get { return typeof(string); }
    }
}

simple ViewModel

 public TestPlateAdministratorViewModel()
    {
        CommandAggregator = new TestPlateAdministratorCommandAggregator(this);
        LoadData();
    }

    public static TestPlateAdministratorCommandAggregator CommandAggregator { get; set; }
    public ObservableCollection<Plate> TestPlates{ get; set; }

    private static void LoadData()
    {
        CommandAggregator.LoadPlatesCommand.Execute(null);
        CommandAggregator.LoadCategoriesCommand.Execute(null);
    }
}

Solution

  • does your Dictionary in your PropertyBag has a fixed size or does the keys are known?

    you did not post your xaml for your datagrid, but the binding from the propertybag to one column could look like this:

    <DataGridTextColumn Header="Col4TestKey" Binding="{Binding CustomCategories[test]}"/>
    

    i really dont know wether your PropertyBag setter will work with binding. all in all this just would work if you have a know set of keys for your dictionary.

    btw, i use flat datatables in my projects for such dynamic stuff, there are really easy to handle.