【问题标题】:display list of custom objects as a drop-down in the PropertiesGrid在 PropertiesGrid 中将自定义对象列表显示为下拉列表
【发布时间】:2011-07-07 11:04:57
【问题描述】:

我想拿一个物体,比如说这个物体:

public class BenchmarkList
{
    public string ListName { get; set; }
    public IList<Benchmark> Benchmarks { get; set; }
}

并让该对象将其 ListName 显示为 PropertiesGrid 的“名称”部分(“基准”会很好),对于 PropertyGrid 的“值”部分,有一个 IList 的下拉列表 基准:

这里是基准对象

public class Benchmark
{
    public int ID {get; set;}
    public string Name { get; set; }
    public Type Type { get; set; }
}

我希望下拉菜单显示用户可以看到的 Benchmark 的 Name 属性。这是一个视觉示例:

因此,本质上,我正在尝试将 Benchmark 对象的集合放入下拉列表中,这些对象应将其 Name 属性显示为下拉列表中的值。

我读过其他关于使用 PropertiesGrid 的文章,包括 THISTHIS,但它们比我想要做的要复杂。

我通常处理服务器端的东西,不通过 WebForms 或 WinForms 处理 UI,所以这个 PropertiesGrid 真的很带我去兜风......

我知道我的解决方案在于实现“ICustomTypeDescriptor”,这将允许我告诉 PropertiesGrid 它应该显示什么值,而不管我想绑定到下拉列表中的对象的属性,但是我只是不确定如何或在哪里实现它。

任何指针/帮助将不胜感激。

谢谢, 迈克

更新:

好的,所以我稍微改变一下细节。我之前在我认为应该涉及的对象上做得太过火了,所以这是我的新方法。

我有一个名为 Analytic 的对象。这是应该绑定到 PropertiesGrid 的对象。现在,如果我公开一个枚举类型的属性,PropertiesGrid 将为我处理下拉列表,这非常好。如果我公开一个作为自定义类型集合的属性,PropertiesGrid 就不那么好了...

这里是 Analytic 的代码,我想绑定到 PropertiesGrid 的对象:

public class Analytic
{ 
    public enum Period { Daily, Monthly, Quarterly, Yearly };
    public Analytic()
    {
        this.Benchmark = new List<IBenchmark>();
    }
    public List<IBenchmark> Benchmark { get; set; }
    public Period Periods { get; set; }
    public void AddBenchmark(IBenchmark benchmark)
    {
        if (!this.Benchmark.Contains(benchmark))
        {
            this.Benchmark.Add(benchmark);
        }
    }
}

以下是实现 IBenchmark 接口的两个对象的简短示例:

public class Vehicle : IBenchmark
{
    public Vehicle()
    {
        this.ID = "00000000-0000-0000-0000-000000000000";
        this.Type = this.GetType();
        this.Name = "Vehicle Name";
    }

    public string ID {get;set;}
    public Type Type {get;set;}
    public string Name {get;set;}
}

public class PrimaryBenchmark : IBenchmark
{
    public PrimaryBenchmark()
    {
        this.ID = "PrimaryBenchmark";
        this.Type = this.GetType();
        this.Name = "Primary Benchmark";
    }

    public string ID {get;set;}
    public Type Type {get;set;}
    public string Name {get;set;}
}

这两个对象将被添加到 WinForms 代码中 Analytic 对象的 Benchmark List 集合中:

private void Form1_Load(object sender, EventArgs e)
{
    Analytic analytic = new Analytic();
    analytic.AddBenchmark(new PrimaryBenchmark());
    analytic.AddBenchmark(new Vehicle());
    propertyGrid1.SelectedObject = analytic;
}

这是 PropertiesGrid 中输出的屏幕截图。请注意,作为枚举公开的属性会得到一个漂亮的下拉列表,但没有任何工作,但作为 List on 公开的属性会获得 (Collection) 的值。当你点击 (Collection) 时,你会得到 Collection 编辑器,然后可以看到每个对象,以及它们各自的属性:

这不是我要找的。就像我在这篇文章中的第一个屏幕截图一样,我正在尝试将 List 的属性 Benchmark 集合呈现为一个下拉列表,该列表将对象的 name 属性显示为可以显示的文本...

谢谢

【问题讨论】:

    标签: c# winforms propertygrid


    【解决方案1】:

    通常,属性网格中的下拉列表用于设置给定列表中的属性值。在这里,这意味着您最好拥有一个类似“Benchmark”的 IBenchmark 类型的属性和一个可能的 IBenchmark 列表。我冒昧地像这样更改您的分析类:

    public class Analytic
    {
        public enum Period { Daily, Monthly, Quarterly, Yearly };
        public Analytic()
        {
            this.Benchmarks = new List<IBenchmark>();
        }
    
        // define a custom UI type editor so we can display our list of benchmark
        [Editor(typeof(BenchmarkTypeEditor), typeof(UITypeEditor))]
        public IBenchmark Benchmark { get; set; }
    
        [Browsable(false)] // don't show in the property grid        
        public List<IBenchmark> Benchmarks { get; private set; }
    
        public Period Periods { get; set; }
        public void AddBenchmark(IBenchmark benchmark)
        {
            if (!this.Benchmarks.Contains(benchmark))
            {
                this.Benchmarks.Add(benchmark);
            }
        }
    }
    

    您现在需要的不是 ICustomTypeDescriptor,而是 TypeConverterUITypeEditor。您需要使用 UITypeEditor(如上)装饰 Benchmark 属性,并使用 TypeConverter 装饰 IBenchmark 接口,如下所示:

    // use a custom type converter.
    // it can be set on an interface so we don't have to redefine it for all deriving classes
    [TypeConverter(typeof(BenchmarkTypeConverter))]
    public interface IBenchmark
    {
        string ID { get; set; }
        Type Type { get; set; }
        string Name { get; set; }
    }
    

    这是一个示例 TypeConverter 实现:

    // this defines a custom type converter to convert from an IBenchmark to a string
    // used by the property grid to display item when non edited
    public class BenchmarkTypeConverter : TypeConverter
    {
        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            // we only know how to convert from to a string
            return typeof(string) == destinationType;
        }
    
        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
        {
            if (typeof(string) == destinationType)
            {
                // just use the benchmark name
                IBenchmark benchmark = value as IBenchmark;
                if (benchmark != null)
                    return benchmark.Name;
            }
            return "(none)";
        }
    }
    

    这是一个示例 UITypeEditor 实现:

    // this defines a custom UI type editor to display a list of possible benchmarks
    // used by the property grid to display item in edit mode
    public class BenchmarkTypeEditor : UITypeEditor
    {
        private IWindowsFormsEditorService _editorService;
    
        public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
        {
            // drop down mode (we'll host a listbox in the drop down)
            return UITypeEditorEditStyle.DropDown;
        }
    
        public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
        {
            _editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
    
            // use a list box
            ListBox lb = new ListBox();
            lb.SelectionMode = SelectionMode.One;
            lb.SelectedValueChanged += OnListBoxSelectedValueChanged;
    
            // use the IBenchmark.Name property for list box display
            lb.DisplayMember = "Name";
    
            // get the analytic object from context
            // this is how we get the list of possible benchmarks
            Analytic analytic = (Analytic)context.Instance;
            foreach (IBenchmark benchmark in analytic.Benchmarks)
            {
                // we store benchmarks objects directly in the listbox
                int index = lb.Items.Add(benchmark);
                if (benchmark.Equals(value))
                {
                    lb.SelectedIndex = index;
                }
            }
    
            // show this model stuff
            _editorService.DropDownControl(lb);
            if (lb.SelectedItem == null) // no selection, return the passed-in value as is
                return value;
    
            return lb.SelectedItem;
        }
    
        private void OnListBoxSelectedValueChanged(object sender, EventArgs e)
        {
            // close the drop down as soon as something is clicked
            _editorService.CloseDropDown();
        }
    }
    

    【讨论】:

    • 西蒙,一切都很好!我了解 BenchmarkTypeConverter 类,但 BenchmarkTypeEdit 类对我来说仍然是个谜。看起来每当您单击下拉列表时,整个列表都必须自行重建,然后选择新值。我认为这就是 PropertyGrid 必须“在幕后”工作的方式,这就是为什么做一些非常直接的事情可能需要大量代码的原因。无论哪种方式,非常感谢您伸出援助之手!我比以前走得更远。一些好的因果报应即将到来!迈克
    • 其他一些很好的资源来解释西蒙在这里做了什么。再次西蒙,非常感谢! msdn.microsoft.com/en-us/library/ms171839.aspxmsdn.microsoft.com/en-us/library/ms171840.aspx
    • Simon,你已经很好地解释了这个概念,感谢你提供了一个非常好的和简单的工作示例 :) 干杯!
    • 如何让ListBox的宽度为属性网格的宽度?我已经使用您的代码完成了它,但 ListBox 显示的宽度为 120 像素。
    • @jstuardo - 如果未指定列表框大小,它应该采用属性网格值列的​​宽度(如果网格很小,则具有最小大小)。否则你可以手动设置它的宽度。或者问另一个问题。
    猜你喜欢
    • 2020-07-25
    • 1970-01-01
    • 2012-12-17
    • 1970-01-01
    • 1970-01-01
    • 2021-09-03
    • 2019-07-13
    • 1970-01-01
    • 2014-12-21
    相关资源
    最近更新 更多