Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Expose Columns property of a DataGridView in UserControl and make it editable via Designer

Short description:

I have a UserControl with a DataGridView on it. I want to expose the DataGridView Columns collection to the designer, so I can change the columns on my User Control at design time.

Question: Which designer attributes do I need for this?

For those interested in the longer version:

I have a UserControl with the following features:

  • a DataGridView that shows "pages" of items from a collection.
  • a NumericUpdown control to select which page to show.
  • page up / page down buttons that will disable when the first / last page is shown
  • Changes to the displayed items are visually marked
  • Buttons to save / discard the changes.

This user control can work autonomic. It has one function to be used by the parent control:

  • Show page (collection of items to show)

The UserControl raises two events:

  • Event Page changed (with a page number). Should result in loading a new page
  • Event Save items (with the collection of changed items)

I have to show this user control on several forms. The only difference is that the collection of DataGridViewColumn differs per form.

I could add the columns programmatically, but it would be easier to create them using the designer.

like image 867
Harald Coppoolse Avatar asked Apr 22 '16 07:04

Harald Coppoolse


1 Answers

Usually it's enough to register a suitable UITypeEditor using [Editor] attribute. The editor which is used by the DataGridView is DataGridViewColumnCollectionEditor. But in this case, if we use this editor directly, the editor expect the the property belong to a DataGridView and tries to convert value of ITypeDescriptorContext.Instance to DataGridVeiew and since our editing Columns property belongs to our user control we will receive an exception:

Unable to cast object of type 'Type of Control' to type 'System.Windows.Forms.DataGridView'.

To solve the problem, we need to create a custom UITypeEditor and override EditValue and edit Columns property of the private DataGridView field of your user control.

To do so, we create an instance of ITypeDescriptorContext containing the DataGridView and it's Columns property and pass it to EditValue method of the editor. This way the editor will edit our Columns property.

We also decorate our property using [DesignerSerializationVisibility] attribute to serialize the collection contents.

Here is the implementations.

MyUserControl

I suppose you add a DataGridView at design-time to the user control and its name would be dataGridView1.

public partial class MyUserControl : UserControl
{
    public MyUserControl()
    {
        InitializeComponent();
    }

    [Editor(typeof(MyColumnEditor), typeof(UITypeEditor))]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    public DataGridViewColumnCollection Columns
    {
        get { return this.dataGridView1.Columns; }
    }
}

Editor

public class MyColumnEditor : UITypeEditor
{
    public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
    {
        return UITypeEditorEditStyle.Modal;
    }
    public override object EditValue(ITypeDescriptorContext context,
                                     IServiceProvider provider, object value)
    {
        var field = context.Instance.GetType().GetField("dataGridView1",
                       System.Reflection.BindingFlags.NonPublic |
                       System.Reflection.BindingFlags.Instance);

        var dataGridView1 = (DataGridView)field.GetValue(context.Instance);
        dataGridView1.Site = ((Control)context.Instance).Site;
        var columnsProperty = TypeDescriptor.GetProperties(dataGridView1)["Columns"];
        var tdc = new TypeDescriptionContext(dataGridView1, columnsProperty);
        var editor = (UITypeEditor)columnsProperty.GetEditor(typeof(UITypeEditor));
        var result = editor.EditValue(tdc, provider, value);
        dataGridView1.Site = null;
        return result;
    }
}

ITypeDescriptionContext Implementation

public class TypeDescriptionContext : ITypeDescriptorContext
{
    private Control editingObject;
    private PropertyDescriptor editingProperty;
    public TypeDescriptionContext(Control obj, PropertyDescriptor property)
    {
        editingObject = obj;
        editingProperty = property;
    }
    public IContainer Container
    {
        get { return editingObject.Container; }
    }
    public object Instance
    {
        get { return editingObject; }
    }
    public void OnComponentChanged()
    {
    }
    public bool OnComponentChanging()
    {
        return true;
    }
    public PropertyDescriptor PropertyDescriptor
    {
        get { return editingProperty; }
    }
    public object GetService(Type serviceType)
    {
        return editingObject.Site.GetService(serviceType);
    }
}
like image 119
Reza Aghaei Avatar answered Oct 15 '22 14:10

Reza Aghaei