【问题标题】:PropertyGrid - Change items of a dropdown property editor based on another property valuePropertyGrid - 根据另一个属性值更改下拉属性编辑器的项目
【发布时间】:2021-04-26 18:21:51
【问题描述】:

我正在尝试在自定义组件中实现下拉属性,并使用This SO Answerthis answer 作为指导。

到目前为止,我设法通过下拉列表中的预定义项目使其正常工作。
但是我还需要弄清楚如何更改下拉列表中的项目?

这是我到目前为止的代码(从上面提到的链接构建)

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

在 VS 属性窗口中是这样的

现在的问题是,当某个属性更改时,下拉属性中的项目必须更改。这应该在方法UpdateTableNames(代码如下)中完成。
换句话说,在另一个属性的设置器中,项目naam 1naam 2 可以更改为一个完整的新集合,具有更多或更少的项目和不同的值。
我还想不通的是如何更改这些项目?

让我解释一下情况。

  • 用户将自定义组件拖放到表单上
  • 当他查看属性 gttTableName 时,现在将显示 naam 1naam 2
  • 现在用户更改了另一个属性 (gttDataModule),并且在此属性的设置器中,gttTableName 属性的项目可以更改。
  • 因此,如果他再次查看属性 gttTableName,它现在应该会显示完整的其他值列表。

gttDataModule 属性的代码是这样的

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'
}

如何更改TableNameServicelist 中的项目?

【问题讨论】:

    标签: c# .net winforms custom-component propertygrid


    【解决方案1】:

    我将根据这篇文章中的答案创建一个示例:PropertyGrid - Load dropdown values dynamically

    基本思想是使用自定义类型转换器并覆盖 GetStandardValues 以返回编辑属性的支持值列表。

    这里的重点是使用context.Instance 获取正在编辑的对象的实例,并尝试根据编辑对象的其他属性过滤列表。

    在下面的示例中,我将编辑一个Product,它具有一个Category 和一个SubCategory 属性,并且类别和子类别之间存在关系。例如,如果您选择类别 1,则子类别列表应显示类别 1 的子类别,但如果您选择类别 2,则列表应显示不同的子类别组,如下所示:

    这是示例的代码:

    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个类型的转换器,它只是为了示例的目的。这里的要点是使用context.Instance 获取正在编辑的对象的实例,并尝试根据编辑对象的其他属性过滤列表。对于提供列表的类,我将它们视为存储库,它们包含所有内容,您可以加载/过滤您需要的内容。
    • 无论如何,您对您的要求有更多的了解,这可能与我在这里尝试展示的有所不同,但我相信您已经明白了。希望它有所帮助:)
    • 另一点是关于更改存储库中的项目:我假设存储库正在从永久存储加载数据,并且更新永久存储的数据在这里不是问题。然而,没有什么能阻止您拥有更新内存存储库的方法。在这种情况下(内存存储库,将内存存储为静态列表完全有意义,一点也不臭。)
    • 谢谢你的例子,它很有教育意义。但是在我的情况下,子类别中的项目不是固定的。它们由另一个组件提供,可以是字面上的任何东西。所以我想我会选择我可以操作的静态列表选项。
    • Reza,在我的问题中,我想链接到您在此处提供的相同链接,您在另一个问题中的答案。我注意到不知何故我设法把错误的链接放在那里,尽管这个链接也涵盖了相同的主题,但不太好。我编辑了我的问题,所以现在纠正了这个错误
    【解决方案2】:

    我设法做到了,但不确定它是否是一个好的和稳定的解决方案。

    我所做的是创建一个静态类来保存列表

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

    然后在TableNameService类中使用该列表

    public class TableNameService
    {
        List<TableName> list = NameList.list;
    
        public IEnumerable<TableName> GetAll()
        {
            return list;
        }
    }
    

    现在我可以从像这样的另一个属性的 setter 更新列表

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

    我试过了,它确实有效,列表更改正确。

    但是我想要一些关于这个解决方案的 cmets,有没有我没有预见到的问题。这样做有什么缺点?

    【讨论】:

    • 你有这个想法,这是好的部分。但是我对那些静态类/方法没有好感。例如,您是否尝试查看如果您在同一个页面中有两个 PropertyGrid 在同一个表单中并尝试编辑这些属性网格会发生什么?
    • @RezaAghaei 我也有同样的感觉,感觉有些不对劲。但我还没有找到另一种方法来做到这一点。必须有一种适当而优雅的方式来制作这样的财产。也许我需要弄清楚如何使用编辑器,例如 TextBox 的 DataBinding/Text 属性
    猜你喜欢
    • 1970-01-01
    • 2023-01-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多