Change items of a dropdown property editor based on another property?

[亡魂溺海] 提交于 2021-01-27 22:10:15

问题


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

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 ?


回答1:


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:

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);
    }
}



回答2:


I managed to do it, but am unsure if it's a good and stable solution.

What I did is create a static class to hold the list

public static class NameList
{
    public static List<TableName> list = new List<TableName>();

    public static void UpdateItems(List<string> tableNames)
    {
        list.Clear();
        foreach (string item in tableNames)
        {
            list.Add(new TableName() { Name = item });
        }
    }

}

Then used that list in class TableNameService

public class TableNameService
{
    List<TableName> list = NameList.list;

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

and now I can update the list from the setter from another property like this

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

        NameList.UpdateItems(tableNames);
    }

I tried it and it actual works, the list is changing correct.

But I would like some comments on this solution, are there any problems I have not foreseen. What are the drawbacks on doing it this way ?



来源:https://stackoverflow.com/questions/65842530/change-items-of-a-dropdown-property-editor-based-on-another-property

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!