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

1

6

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.

Menton answered 22/4, 2016 at 7:17 Comment(0)
E
8

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);
    }
}
Erysipeloid answered 22/4, 2016 at 13:16 Comment(13)
Checked it, and it works. Be aware though that the columns that you add during design are added as members to the form, not to the user control. Luckily I don't need them in this situation. So I can set GenerateMember to falseMenton
Thank you for feedback :) I believe this is an standard behavior because the columns belong to the control. It works like other controls, when you add an item to a collection, collection items will be generated at form level.Erysipeloid
@RezaAghaei I have followed your steps, but I have a problem. I have created an user control that contains a DataGridView. The user control has a Columns property as you specified. The project compiles succesfully, but when I enter in design mode in a Form that contains my custom user control and I click on Columns property for edit the content it shows a messagebox that says No parameterless constructor defined for this object. I have put a public constructor without params in my user control, also into CustomColumnEditor and TypeDescriptionContext, but it stills showing the same message.Antennule
@Antennule I checked it again and it works properly. Check your Columns property definition and attribute and compare it with the property that I have defined in the class. I first put a dataGridView1 on the user control design surface and then went to code and added such property decorated with suitable editor attribute.Erysipeloid
@RezaAghaei I think that I have found my problem. In the UserControl constructor I had three custom columns (DataGridViewImageButtonColumn) where the instruction this.grid.Columns.Add(columnButton); has made that fails when I was trying to open the dialog for editing columns properties. To solve it, I have moved the code of columns into Load event UserControl and I have encapsulated them with a condition if (!DesignMode) This link also has helped me: linkAntennule
However, sometimes when I recompile the project the HeaderText, Visibility,.. And other properties become her initial value.Antennule
@Antennule If you see an unexpected behavior sometimes, it's not a good news and you should find the problem and solve it. To solve the problem you should know (1) I don't have any problem with standard column types and if your custom column types work normally with DataGridVIewin design mode, they should work with this approach too. So check your custom columns (2) You should not change designer generated code, because you don't know how the designer works and if you make some changes in designer code you will receive unexpected behaviors most of times.Erysipeloid
@Antennule (3) You may find this post and my answer helpful. It describes an unexpected behavior in a custom grid.Erysipeloid
I am getting some mixed results here: The UserControl w/ DataGridView inside is being used in another UserControl. During design time it adds more and more columns instead of changing the existing columns. Anybody experience the same?Billington
Update: I had "AutogenerateColumns" enabled which caused the confusion. Seems this affects all DataGridViews so disabling fixed it!Billington
@fschricker Unfortunately I couldn't find time to take a look at the problem. Thanks for sharing the finding.Erysipeloid
This method is not working so well in .NET6 VisualStudio 2022. The editor dialog seems to work, but the Add button in the dialog does not display the Add column dialog, but rather only adds the Text column type. Also some of the column properties seem to be missing - like the ability to change column type.Bottali
@Bottali I'll give it a try in .NET 6 and get back to you if I have anything to share. As far as I remember .NET 5+ designer features are still in progress.Erysipeloid

© 2022 - 2024 — McMap. All rights reserved.