views:

473

answers:

1

Okay, I've read a couple of questions regarding the use of the PropertyGrid and collections. But, I'm having a difficult time understanding how/if [TypeConverter] will work for me. I've read the little blurb-age that MSDN puts out there, and frankly, it's a bit lacking to this poor, self-taught programmer.

So, here is what I have. First a collection:

[Serializable]
public List<ModuleData> Modules
{ get { return modules; } }

private List<ModuleData> modules;

The object in the collection:

[Serializable]
internal class ModuleData : IEquatable<ModuleData>
{
    // simple data class with public properties
    // to display in the propgrid control
}

I have a ListView control that contains items describing both ModuleData objects and BatchData objects. When I select a BatchData item from the ListView, the PropertyGrid, as expected, displays the collection editor. Is there a way to limit the collection editor to any ModuleData items listed in the ListView control only? Ideally I would not want a BatchData item (from the ListView) to be added to a BatchData collection - especially since the collection is not 'typed' for BatchData object types.

If any further code samples are requested, I'll be more than happy to edit some snippets in.

For clarity, ModuleData is a custom class that holds data required to instance a class within a specified assembly. All it contains are fields and public/internal properties. What I would like to do is use the collection editor assembled with the property grid control to add ModuleData objects to the BatchData Module collection. The ModuleData objects that are qualified to be added are listed in the ListView control.

EDIT: Removed the : List<ModuleData> inheritance.

UPDATE: If I am going to create a custom collection editor, does that mean I am building my own custom form/dialog? Then basically providing the propertygrid the information to display my custom collection dialog through attributes and inheritance of an UITypeEditor?

+3  A: 

First off I'm a little unsure about why this both inherits (: List<ModuleData>) and wraps (public List<ModuleData> Modules { get { return this; } }) a list - either individually should be fine.

However! To define the types of new objects you can create you need to derive from CollectionEditor and override the NewItemTypes property - and associate this editor with your type. I'm a little bit unclear on what objects you want to be addable, and whether this is the best design. If you want to add existing objects you may need a completely custom editor / uitypeeditor.


With the updated question, it definitely sounds like a job for a custom UITypeEditor; here's a version that uses a drop-down; you can do popups too (see methods on svc):

using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing.Design;
using System.Windows.Forms;
using System.Windows.Forms.Design;
using System.Collections;

static class Program
{
    static void Main()
    {
        MyWrapper wrapper = new MyWrapper();
        wrapper.Modules.Add(new ModuleData { ModuleId = 123 });
        wrapper.Modules.Add(new ModuleData { ModuleId = 456 });
        wrapper.Modules.Add(new ModuleData { ModuleId = 789 });

        wrapper.Batches.Add(new BatchData(wrapper) { BatchId = 666 });
        wrapper.Batches.Add(new BatchData(wrapper) { BatchId = 777 });

        PropertyGrid props = new PropertyGrid { Dock = DockStyle.Fill };
        ListView view = new ListView { Dock = DockStyle.Left };
        foreach (ModuleData mod in wrapper.Modules) {
            view.Items.Add(mod.ToString()).Tag = mod;
        }
        foreach (BatchData bat in wrapper.Batches) {
            view.Items.Add(bat.ToString()).Tag = bat;
        }
        view.SelectedIndexChanged += delegate {
            var sel = view.SelectedIndices;
            if(sel.Count > 0) {
                props.SelectedObject = view.Items[sel[0]].Tag;
            }
        };

        Application.Run(new Form { Controls = { props, view} });
    }
}

class MyWrapper
{
    private List<ModuleData> modules = new List<ModuleData>();
    public List<ModuleData> Modules { get { return modules; } }

    private List<BatchData> batches = new List<BatchData>();
    public List<BatchData> Batches { get { return batches; } }
}

class ModuleListEditor : UITypeEditor
{
    public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
    {
         return UITypeEditorEditStyle.DropDown;
    }
    public override object  EditValue(ITypeDescriptorContext context, System.IServiceProvider provider, object value)
    {
        IWindowsFormsEditorService svc;
        IHasModules mods;
        IList selectedModules;
        if (context == null || (selectedModules = (IList)value) == null ||
            (mods = context.Instance as IHasModules) == null
            || (svc = (IWindowsFormsEditorService)
            provider.GetService(typeof(IWindowsFormsEditorService))) == null)
        {
            return value;
        }
        var available = mods.GetAvailableModules();
        CheckedListBox chk = new CheckedListBox();
        foreach(object item in available) {
            bool selected = selectedModules.Contains(item);
            chk.Items.Add(item, selected);
        }
        chk.ItemCheck += (s, a) =>
        {
            switch(a.NewValue) {
                case CheckState.Checked:
                    selectedModules.Add(chk.Items[a.Index]);
                    break;
                case CheckState.Unchecked:
                    selectedModules.Remove(chk.Items[a.Index]);
                    break;
            }
        };


        svc.DropDownControl(chk);

        return value;
    }
    public override bool IsDropDownResizable {
        get {
            return true;
        }
    }
}


interface IHasModules
{
    ModuleData[] GetAvailableModules();
}

internal class BatchData : IHasModules {
    private MyWrapper wrapper;
    public BatchData(MyWrapper wrapper) {
        this.wrapper = wrapper;
    }
    ModuleData[] IHasModules.GetAvailableModules() { return wrapper.Modules.ToArray(); }
    [DisplayName("Batch ID")]
    public int BatchId { get; set; }
    private List<ModuleData> modules = new List<ModuleData>();
    [Editor(typeof(ModuleListEditor), typeof(UITypeEditor))]
    public List<ModuleData> Modules { get { return modules; } set { modules = value; } }

    public override string ToString() {
        return "Batch " + BatchId;
    }
}

internal class ModuleData {
    [DisplayName("Module ID")]
    public int ModuleId { get; set; }

    public override string ToString() {
        return "Module " + ModuleId;
    }
}
Marc Gravell
My whole comment just went away. :( Okay, one more time...I have the property `return this;` because I couldn't see the collection in the `propgrid.SelectedObject = myBatchData`. I'll check out the links tomorrow after a some sleeping on it. Thx!!!
dboarman
@Marc: OMG...are you sure you aren't impersonating Jon Skeet and writing answers before the question is written? LOL...this answers the question...that...would have been...
dboarman
dboarman