1

I'm trying to create a custom control that contains a label, a toolstrip and a Datagridview.

When the test form generates the Design code, it saves correctely the toolstrip items but it doesn't save the Datagridview columns. Code DGrid.cs

...
    [DesignerAttribute(typeof(MultiDesigner))]
    public partial class FDGrid : Panel
    {
        ....
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        [Category("Bars"), Description("Barra dei comandi.")]
        public ToolStrip Barra
        {
            get { return _barra; }
            set { _barra = value; }
        }
        
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        [Category("Grid"), Description("Griglia dati.")]
        public DataGridView Griglia
        {
            get { return _griglia; }
            set { _griglia = value; }
        }
        ....
        
    }
    
    [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = 
        "FullTrust")]
    public class MultiDesigner : System.Windows.Forms.Design.ControlDesigner
    {
        public override void Initialize(IComponent c)
        {
            base.Initialize(c);
            FDGrid ctl = (FDGrid)c;
            EnableDesignMode(ctl.Griglia, "Griglia");
            EnableDesignMode(ctl.Barra, "Barra");
        }
    }
...

Code FDGrid.Designer.cs

FDGrid.Designer.cs ....

private FDLabeledImage _titolo;
private ToolStrip _barra;
private DataGridView _griglia;
    

private void InitializeComponent()
{
    this._barra = new System.Windows.Forms.ToolStrip();
    this._griglia = new System.Windows.Forms.DataGridView();
    this._titolo = new FDControl.FDLabeledImage();
    ((System.ComponentModel.ISupportInitialize)(this._griglia)).BeginInit();
    this.SuspendLayout();
    ....
    ((System.ComponentModel.ISupportInitialize)(this._griglia)).EndInit();
    this.ResumeLayout(false);
}

....

Code Form1.Designer.cs

....    
            
            private void InitializeComponent()
            {
                this.fdGrid1 = new FDControl.FDGrid();  // OK
                this.fdGrid1.Column1 = new System.Windows.Forms.DataGridViewTextBoxColumn(); **// Error** 
                this.toolStripLabel1 = new System.Windows.Forms.ToolStripLabel(); //OK
                
            }
            
            ...
            private FDControl.FDGrid fdGrid1; //OK
            private System.Windows.Forms.ToolStripLabel toolStripLabel1; //OK
            private System.Windows.Forms.DataGridViewTextBoxColumnfdGrid1.Column1;//Error
...

help, I would like to understand where am I wrong?

Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398

1 Answers1

1

Not the most elegant, but a working solution:

  1. Create a custom MyDataGridView which derives from DataGridView:

     using System;
     using System.ComponentModel;
     using System.ComponentModel.Design;
     using System.Drawing.Design;
     using System.Reflection;
     using System.Windows.Forms;
     using System.Windows.Forms.Design;
     public class MyDataGridView : DataGridView
     {
         [Editor(typeof(ExtendedDataGridViewColumnCollectionEditor), typeof(UITypeEditor))]
         public new DataGridViewColumnCollection Columns { get => base.Columns; }
    
         private class ExtendedDataGridViewColumnCollectionEditor : UITypeEditor
         {
             private Form dataGridViewColumnCollectionDialog;
             private ExtendedDataGridViewColumnCollectionEditor() { }
             private static Form CreateColumnCollectionDialog(IServiceProvider provider)
             {
                 var assembly = Assembly.Load(typeof(ControlDesigner).Assembly.ToString());
                 var type = assembly.GetType("System.Windows.Forms.Design.DataGridViewColumnCollectionDialog");
                 var ctor = type.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)[0];
                 return (Form)ctor.Invoke(new object[] { provider });
             }
             public static void SetLiveDataGridView(Form form, DataGridView dgv)
             {
                 var method = form.GetType().GetMethod("SetLiveDataGridView", BindingFlags.NonPublic | BindingFlags.Instance);
                 method.Invoke(form, new object[] { dgv });
             }
             public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
             {
                 if (provider != null && context != null)
                 {
                     var service = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
                     if (service == null || context.Instance == null)
                         return value;
    
                     var host = (IDesignerHost)provider.GetService(typeof(IDesignerHost));
                     if (host == null)
                         return value;
    
                     if (dataGridViewColumnCollectionDialog == null)
                         dataGridViewColumnCollectionDialog = CreateColumnCollectionDialog(provider);
    
                     var dgv = ((MyDataGridView)context.Instance);
                     var oldSite = dgv.Site;
                     dgv.Site = ((MyDataGridView)context.Instance).Parent?.Site;
                     SetLiveDataGridView(dataGridViewColumnCollectionDialog, dgv);
    
                     using (var transaction = host.CreateTransaction("DataGridViewColumnCollectionTransaction"))
                     {
                         if (service.ShowDialog(dataGridViewColumnCollectionDialog) == DialogResult.OK)
                             transaction.Commit();
                         else
                             transaction.Cancel();
                     }
                     dgv.Site = oldSite;
                 }
                 return value;
             }
             public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
             {
                 return UITypeEditorEditStyle.Modal;
             }
         }
     }
    
  2. Build the solution.

  3. Create your UserControl and drop an instance of MyDataGridView on it.

  4. Expose the DataGridView property:

     public partial class MyUserControl : UserControl
     {
         public MyUserControl()
         {
             InitializeComponent();
         }
         [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
         public DataGridView DataGridView { get => dataGridView1; }
     }
    
  5. Build the solution.

  6. Drop an instance of MyUserControl on your form, then in property editor window, find the DataGridView property, expand it and edit Columns and Save the form.

The columns are serialized now. There you go.

Note: The custom UITypeEditor is based on this post with some changes.

The result looks like:

private void InitializeComponent()
{
    ...
    ...

    this.Column1 = new System.Windows.Forms.DataGridViewTextBoxColumn();
    this.Column2 = new System.Windows.Forms.DataGridViewTextBoxColumn();
    ((System.ComponentModel.ISupportInitialize)(this.myUserControl1.DataGridView)).BeginInit();
    this.SuspendLayout();
    // 
    // myUserControl1
    // 
    // 
    // 
    // 
    this.myUserControl1.DataGridView.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
    this.myUserControl1.DataGridView.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
    this.Column1,
    this.Column2});

    ...
    ...

    // 
    // Column1
    // 
    this.Column1.HeaderText = "Column1";
    this.Column1.MinimumWidth = 6;
    this.Column1.Name = "Column1";
    this.Column1.Width = 125;
    // 
    // Column2
    // 
    this.Column2.HeaderText = "Column2";
    this.Column2.MinimumWidth = 6;
    this.Column2.Name = "Column2";
    this.Column2.Width = 125;
    
    ...
    ...

    ((System.ComponentModel.ISupportInitialize)(this.myUserControl1.DataGridView)).EndInit();
    this.ResumeLayout(false);

}

...

private System.Windows.Forms.DataGridViewTextBoxColumn Column1;
private System.Windows.Forms.DataGridViewTextBoxColumn Column2;

...
Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
  • I removed unnecessary parts of the code which was remaining there based on my tests. (The custom ColumnCollection and override of CreateColumnCollectionDialog were unnecessary.) – Reza Aghaei Dec 24 '20 at 20:31
  • Now it's probably the most elegant solution – Reza Aghaei Dec 24 '20 at 20:32
  • thanks for your reply and the time you have dedicated to me. In my case It doesn't work, when I add a column and save it app and visualstudio. – Domenico Formoso Dec 24 '20 at 21:08
  • Well, the solution is tested in VS 2019 - .NET 4.8. I suggest you start with a clean project and exactly follow the steps. After you made it working, you can modify it to whatever you want to have in your control. – Reza Aghaei Dec 24 '20 at 21:10
  • i confused component class and usercontrol in step 3. It works fine. Thanks – Domenico Formoso Dec 24 '20 at 21:23
  • Last note: It works if I navigate in the DataGridView properties and change the columns (step 6) but it doesn't work if I change the columns with the datagridview menu editor (right click and edit column) – Domenico Formoso Dec 24 '20 at 21:53
  • I'd create a custom designer as well or approach the problem like [this](https://stackoverflow.com/a/54247547/3110834). – Reza Aghaei Dec 24 '20 at 22:19
  • In Visual Studio 2022 .NET 6 Windows.Forms designer, I can the the DataGridViewColumnCollectionEditor dialog, but when I click "Add" I get an error "At least one of the DataGridView control's columns has no cell template." – gridtrak Dec 17 '21 at 03:41
  • @gridtrak I've tested it in .NET 4.X, but not with later versions, I'll try it and get back to you if I have something to share. – Reza Aghaei Dec 17 '21 at 07:57