Search code examples
c#.netwinformscustom-componentpropertygrid

PropertyGrid - Change items of a dropdown property editor based on another property value


I am trying to implement a drop-down property in a custom component, and I used This SO Answer and this answer as a guide.

So far I managed to get it working, with predefined items in the drop-down list.
But I still need to figure out how to alter the items in the drop-down list ?

This is the code I have so far (build from the link mentioned above)

[TypeConverter(typeof(TableNameConverter))]
public TableName gttTableName
{ get; set; }

...

public class TableName
{
    public string Name { get; set; }

    public override string ToString()
    {
        return $"{Name}";
    }
}

public class TableNameService
{
    List<TableName> list = new List<TableName>() {
        new TableName() {Name = "naam 1" },
        new TableName() {Name = "naam 2" },
    };

    public IEnumerable<TableName> GetAll()
    {
        return list;
    }
}

public class TableNameConverter : TypeConverter
{
    public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
    {
        var svc = new TableNameService();
        return new StandardValuesCollection(svc.GetAll().ToList());
    }

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

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

    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        if (sourceType == typeof(string))
            return true;
        return base.CanConvertFrom(context, sourceType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        if (value != null && value.GetType() == typeof(string))
        {
            var v = $"{value}";
            //var id = int.Parse(v.Split('-')[0].Trim());
            var name = v.ToString();
            var svc = new TableNameService();
            //return svc.GetAll().Where(x => x.Id == id).FirstOrDefault();
            return svc.GetAll().Where(x => x.Name == name).FirstOrDefault();
        }
        return base.ConvertFrom(context, culture, value);
    }
}

This looks like this in VS property window

enter image description here

Now for the problem, when a certain property changes then the items in the drop-down property must change. This should be done in the method UpdateTableNames(code is below).
In other words, in the setter of another property the items naam 1, naam 2 can change to a complete new set, with more or less items and with different values.
What I can not figure out yet is how can I alter these items ?

Let me explain the situation.

  • A user drops the custom component on a form
  • when he looks at the property gttTableName it will now show naam 1 and naam 2
  • Now the user changes another property (gttDataModule) and in the setter of this property the items of the gttTableName property can change.
  • So if he looks again at the property gttTableName it should now show a complete other list of values.

The code for the property gttDataModule is this

public gttDataModule gttDataModule
{
    get { return _gttDataModule; }
    set
    {
        _gttDataModule = value;
        UpdateTableNames();
    }
}

private void UpdateTableNames()
{
    List<string> tableNames = new List<string>();
    if (_gttDataModule != null)
    {
        foreach (gttDataTable table in _gttDataModule.gttDataTables)
        {
            tableNames.Add(table.Table.TableName);
        }
    }

    // at this point the list tableNames will be populated with values.
    // What I need is to replace the list in TableNameService from 'naam 1', 'naam 2' 
    // to the values in this list.
    // so they won't be 'naam 1' and 'naam 2' anymore 
    // It could be more or less items or even none 
    // They could have different values
    // for example the list could be 'tblBox', 'tblUser', tblClient', tblOrders'
    // or it could be empty
    // or it could be 'vwCars', 'tblSettings'
}

How can I change the items in the list of the TableNameService ?


Solution

  • I would create an example based on the answer in this post: PropertyGrid - Load dropdown values dynamically.

    The basic idea is having custom type converter and overriding GetStandardValues to return a list of supported values for the editing property.

    The point here is using the context.Instance to get an instance of the object which is editing and trying to filter the list based on the other properties of the editing object.

    In the following example, I'll edit a Product which has a Category and a SubCategory property and there's a relation between categories and sub-categories. For example if you choose category 1, then the sub-categories list should show the sub-categories of category 1, but if you choose category 2, then the list should show a different group of sub categories, like this:

    enter image description here

    And this is the code for the example:

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Globalization;
    using System.Linq;
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        private Category category;
        [TypeConverter(typeof(CategoryConverter))]
        public Category Category
        {
            get { return category; }
            set
            {
                if (category?.Id != value?.Id)
                {
                    category = value;
                    SubCategory = null;
                }
            }
        }
        [TypeConverter(typeof(SubCategoryConverter))]
        public SubCategory SubCategory { get; set; }
    }
    public class Category
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public override string ToString()
        {
            return $"{Id} - {Name}";
        }
    }
    public class SubCategory
    {
        public int Id { get; set; }
        public int CategoryId { get; set; }
        public string Name { get; set; }
        public override string ToString()
        {
            return $"{Id} - {Name}";
        }
    }
    public class CategoryService
    {
        List<Category> categories = new List<Category> {
            new Category() { Id = 1, Name = "Category 1" },
            new Category() { Id = 2, Name = "Category 2" },
        };
        List<SubCategory> subCategories = new List<SubCategory> {
            new SubCategory() { Id = 11, Name = "Sub Category 1-1", CategoryId= 1 },
            new SubCategory() { Id = 12, Name = "Sub Category 1-2", CategoryId= 1 },
            new SubCategory() { Id = 13, Name = "Sub Category 1-3", CategoryId= 1 },
            new SubCategory() { Id = 21, Name = "Sub Category 2-1", CategoryId= 2 },
            new SubCategory() { Id = 22, Name = "Sub Category 2-2", CategoryId= 2 },
        };
        public IEnumerable<Category> GetCategories()
        {
            return categories;
        }
        public IEnumerable<SubCategory> GetSubCategories()
        {
            return subCategories.ToList();
        }
    }
    public class CategoryConverter : TypeConverter
    {
        public override StandardValuesCollection GetStandardValues(
            ITypeDescriptorContext context)
        {
            var svc = new CategoryService();
            return new StandardValuesCollection(svc.GetCategories().ToList());
        }
        public override bool GetStandardValuesSupported(
            ITypeDescriptorContext context)
        {
            return true;
        }
        public override bool GetStandardValuesExclusive(
            ITypeDescriptorContext context)
        {
            return true;
        }
        public override bool CanConvertFrom(ITypeDescriptorContext context,
            Type sourceType)
        {
            if (sourceType == typeof(string))
                return true;
            return base.CanConvertFrom(context, sourceType);
        }
        public override object ConvertFrom(ITypeDescriptorContext context,
            CultureInfo culture, object value)
        {
            if (value != null && value.GetType() == typeof(string))
            {
                var v = $"{value}";
                var id = int.Parse(v.Split('-')[0].Trim());
                var svc = new CategoryService();
                return svc.GetCategories()
                    .Where(x => x.Id == id).FirstOrDefault();
            }
            return base.ConvertFrom(context, culture, value);
        }
    }
    public class SubCategoryConverter : TypeConverter
    {
        public override StandardValuesCollection GetStandardValues(
            ITypeDescriptorContext context)
        {
            var svc = new CategoryService();
            var categoryId = ((Product)context.Instance).Category.Id;
            return new StandardValuesCollection(svc.GetSubCategories()
                .Where(x => x.CategoryId == categoryId).ToList());
        }
        public override bool GetStandardValuesSupported(
            ITypeDescriptorContext context)
        {
            return true;
        }
        public override bool GetStandardValuesExclusive(
            ITypeDescriptorContext context)
        {
            return true;
        }
        public override bool CanConvertFrom(ITypeDescriptorContext context, 
            Type sourceType)
        {
            if (sourceType == typeof(string))
                return true;
            return base.CanConvertFrom(context, sourceType);
        }
        public override object ConvertFrom(ITypeDescriptorContext context, 
            CultureInfo culture, object value)
        {
            if (value != null && value.GetType() == typeof(string))
            {
                var v = $"{value}";
                var id = int.Parse(v.Split('-')[0].Trim());
                var svc = new CategoryService();
                return svc.GetSubCategories()
                    .Where(x => x.Id == id).FirstOrDefault();
            }
            return base.ConvertFrom(context, culture, value);
        }
    }