在 PropertiesGrid 中将自定义对象列表显示为下拉列表

发布于 2024-10-20 12:33:03 字数 3186 浏览 0 评论 0原文

我想要一个对象,假设这个对象:

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 属性显示为下拉列表中的值。

我读过有关使用 PropertiesGrid 的其他文章,包括 THIS 和 < a href="http://www.codeproject.com/KB/miscctrl/bending_property.aspx" rel="noreferrer">这个,但它们比我想做的更复杂。

我通常从事服务器端的工作,并且不通过 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 代码中的分析对象的基准列表集合中:

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 公开的属性会获得 (Collection) 值。当您单击(集合)时,您将获得集合编辑器,然后可以看到每个对象及其各自的属性:

输入图像描述这里

这不是我要找的。就像我在这篇文章中的第一个屏幕截图中一样,我试图将 List 的属性 Benchmark 集合渲染为下拉列表,该下拉列表将对象的名称属性显示为可显示内容的文本...

谢谢

I want to take an object, let's say this object:

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

and have that object display its ListName as the "name" part of the PropertiesGrid ("Benchmark" would be good), and for the "value" part of the PropertyGrid, to have a drop-down list of the IList<> of Benchmarks:

here is the Benchmark object

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

I would want the drop-down to show the Name property of the Benchmark for what the users can see. Here is a visual example:

enter image description here

So, essentially, I'm trying to get a collection of Benchmark objects into a drop-down list, and those objects should show their Name property as the value in the drop-down.

I've read other articles on using the PropertiesGrid, including THIS and THIS, but they are more complex than what I'm trying to do.

I usually work on server-side stuff, and don't deal with UI via WebForms or WinForms, so this PropertiesGrid is really taking me for a ride...

I do know my solution lies in implementing "ICustomTypeDescriptor", which will allow me to tell the PropertiesGrid what values it should be displaying regardless of the properties of the object to which I want to bind into the drop-down list, but I'm just not sure how or where to implement it.

Any pointers/help would be much appreciated.

Thanks,
Mike

UPDATE:

Okay, so I'm changing the details around a little. I was going overboard before with the objects I thought should be involved, so here is my new approach.

I have an object called Analytic. This is the object that should be bound to the PropertiesGrid. Now, if I expose a property that is of an enum type, PropertiesGrid will take care of the drop-down list for me, which is very nice of it. If I expose a property that is a collection of a custom type, PropertiesGrid is not so nice...

Here is the code for Analytic, the object I want to bind to the 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);
        }
    }
}

Here is a short example of two objects that implement the IBenchmark interface:

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

These two objects will be added to the Analytic object's Benchmark List collection in the WinForms code:

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

Here is a screen-grab of the output in the PropertiesGrid. Note that the property exposed as an enum gets a nice drop-down list with no work, but the property exposed as an of List on gets a value of (Collection). When you click on (Collection), you get the Collection editor and then can see each object, and their respective properties:

enter image description here

This is not what I'm looking for. Like in my first screen grab in this post, I'm trying to render the property Benchmark collection of List as a drop-down list that shows the object's name property as the text of what can be displayed...

Thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

打小就很酷 2024-10-27 12:33:03

通常,属性网格中的下拉列表用于设置给定列表中的属性值。这意味着您最好拥有一个类似 IBenchmark 类型的“Benchmark”的属性以及其他地方可能的 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();
    }
}

In general, a drop down list in a property grid is used for setting the value of a property, from a given list. Here that means you should better have a property like "Benchmark" of type IBenchmark and a possible list of IBenchmark somewhere else. I have taken the liberty of changing your Analytic class like this:

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

What you need now is not an ICustomTypeDescriptor, but instead a TypeConverter an an UITypeEditor. You need to decorate the Benchmark property with the UITypeEditor (as above) and the IBenchmark interface with the TypeConverter like this:

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

Here is a sample TypeConverter implementation:

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

And here is a sample UITypeEditor implementation:

// 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();
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文