Отобразить список настраиваемых объектов в качестве раскрывающегося списка в PropertiesGrid

Я хочу взять объект, скажем, этот объект:

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

и этот объект отобразит его ListName как "имя" части PropertiesGrid ( "Benchmark" будет хорошим), а для "значения" части PropertyGrid будет иметь раскрывающийся список IList < > контрольных показателей:

здесь находится объект Benchmark

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

Я бы хотел, чтобы раскрывающийся список показывал свойство Name в Benchmark для просмотра пользователями. Вот наглядный пример:

enter image description here

Итак, по сути, я пытаюсь получить коллекцию объектов Benchmark в раскрывающемся списке, и эти объекты должны показать свое свойство Name как значение в раскрывающемся списке.

Я читал другие статьи об использовании PropertiesGrid, включая ЭТО и ЭТО, но они сложнее, чем то, что я пытаюсь сделать.

Я обычно работаю на серверных материалах и не имею дело с UI через WebForms или WinForms, поэтому этот PropertiesGrid действительно заставляет меня кататься...

Я знаю, что мое решение заключается в реализации "ICustomTypeDescriptor", который позволит мне указать PropertiesGrid, какие значения он должен отображать независимо от свойств объекта, к которому я хочу привязать, в раскрывающемся списке, но Я просто не знаю, как и где его реализовать.

Любые указатели/помощь будут очень оценены.

Спасибо, Mike

UPDATE:

Хорошо, поэтому я немного меняю детали. Раньше я собирался за борт с объектами, которые, как я думал, должен был быть задействован, вот мой новый подход.

У меня есть объект, называемый аналитиком. Это объект, который должен быть связан с PropertiesGrid. Теперь, если я выставляю свойство, имеющее тип перечисления, PropertiesGrid позаботится о раскрывающемся списке для меня, что очень приятно. Если я выставляю свойство, которое представляет собой набор настраиваемого типа, PropertiesGrid не так хорош...

Вот код для аналитика, объект, который я хочу связать с 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;}
}

Эти два объекта будут добавлены в коллекцию списка контрольных объектов Analytic в коде 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. Обратите внимание, что свойство, отображаемое как enum, получает хороший раскрывающийся список без работы, но свойство, отображаемое как список, получает значение (Collection). Когда вы нажимаете на (Collection), вы получаете редактор Collection, а затем можете видеть каждый объект и свои соответствующие свойства:

enter image description here

Это не то, что я ищу. Как и в моем первом захвате экрана в этом сообщении, я пытаюсь отобразить свойство Benchmark Collection List как раскрывающийся список, который показывает свойство имени объекта как текст того, что может отображаться...

Спасибо

Ответы

Ответ 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, а вместо TypeConverter a UITypeEditor. Вы должны украсить свойство Benchmark с помощью UITypeEditor (как указано выше) и интерфейса IBenchmark с помощью TypeConverter следующим образом:

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