Exposing DataGridView's columns property in UserControl doesn't work properly
Asked Answered
D

3

7

I put a DataGridView in a UserControl and create a public property in my usercontrol that exposes datagridview's columns property.
Here is the sample code:

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

    public DataGridViewColumnCollection MyDataGridColumns
    {
        get
        {
            return dataGridView1.Columns;
        }
    }
}

Then I add UserControl1 in my form and I click on MyDataGridColumns property in property window and add 1 or more columns. The problem happens when I rebuild my solution; All of the columns that I have just added disappear after rebuilding.

Can anyone explain to me why this happens? and how to solve it?

Diplocardiac answered 9/2, 2016 at 6:19 Comment(6)
How are you adding the columns? In design mode or programmatically?Forsyth
Try to add a setter to your property MyDataGridColumns.Chaperone
@Bioukh, Already tried it, Adding setter doesn't solve the problem.Diplocardiac
@AfshinAghazadeh...Do you feel the answers below solved your problem finally?Mobster
@S.Akbari That was the best answer yet because now I know the problem at least that's why i voted it, but I need a way to expose the inner DataGrid.Columns properly so I can change it's columns in design time to make a good UserControl.Diplocardiac
@AfshinAghazadeh I shared an answer to a similar question here: Expose Columns property of a DataGridView in UserControl and make it editable via Designer Hope you find it helpful.Monkshood
C
7

This works for me : I created a specific columns editor as it seems it is impossible to use the default columns editor for any control that does not extend DataGridView.

public partial class UserControl1 : UserControl, IDataGridView
{
    public UserControl1()
    {
        InitializeComponent();
    }

    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    [Browsable(false)]
    public DataGridView DataGridView
    {
        get { return dataGridView1; }
    }

    [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    [Editor(typeof(ExtendedDataGridViewColumnCollectionEditor), typeof(UITypeEditor))]
    [MergableProperty(false)]
    public DataGridViewColumnCollection MyDataGridColumns
    {
        get { return dataGridView1.Columns; }
    }

}

public interface IDataGridView
{
    DataGridView DataGridView { get; }
}

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 ctr = type.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)[0];
        return (Form)ctr.Invoke(new object[] { provider });
    }

    public static void SetLiveDataGridView(Form form, DataGridView grid)
    {
        var mi = form.GetType().GetMethod("SetLiveDataGridView", BindingFlags.NonPublic | BindingFlags.Instance);
        mi.Invoke(form, new object[] { grid });
    }

    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);

            //Unfortunately we had to make property which returns inner datagridview  
            //to access it here because we need to pass DataGridView into SetLiveDataGridView () method 
            var grid = ((IDataGridView)context.Instance).DataGridView;
            //we have to set Site property because it will be accessed inside SetLiveDataGridView () method 
            //and by default it's usually null, so if we do not set it here, we will get exception inside SetLiveDataGridView () 
            var oldSite = grid.Site;
            grid.Site = ((UserControl)context.Instance).Site;
            //execute SetLiveDataGridView () via reflection 
            SetLiveDataGridView(dataGridViewColumnCollectionDialog, grid);

            using (var transaction = host.CreateTransaction("DataGridViewColumnCollectionTransaction"))
            {
                if (service.ShowDialog(dataGridViewColumnCollectionDialog) == DialogResult.OK)
                    transaction.Commit();
                else
                    transaction.Cancel();
            }
            //we need to set Site property back to the previous value to prevent problems with serializing our control 
            grid.Site = oldSite;
        }

        return value;
    }

    public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
    {
        return UITypeEditorEditStyle.Modal;
    }
}
Chaperone answered 9/2, 2016 at 9:10 Comment(3)
Ok, that works for me too for the DataGridView's Columns collection. Now, how do we get all of the other Properties and Event Handlers in the Designer?Germangermana
In Visual Studio 2022 (version 17.0.3) using .NET 6 Windows Forms, the dialog's Add button does not raise the Add dialog. Add now only adds a new DataGridViewTextBoxColumn, and there is no designer property for changing that column type.Germangermana
@Germangermana You can modify the DataGridView column type directly in the Designer.cs file.Chaperone
M
2

This is because you didn't specify the type of the column. You should give the type of the column when adding a column (for example DataGridViewTextBoxColumn or DataGridViewCheckBoxColumn). In your Form1.cs do the following:

public Form1()
{
    InitializeComponent();
    DataGridViewColumn dgViewColumn = new DataGridViewTextBoxColumn();//Or DataGridViewCheckBoxColumn
    dgViewColumn.DataPropertyName = "dgViewColumn";
    dgViewColumn.HeaderText = @"dgViewColumn";
    dgViewColumn.Name = "dgViewColumn";
    userControl11.MyDataGridColumns.Add(dgViewColumn);
}
Mobster answered 14/2, 2016 at 14:20 Comment(1)
@Afshin Aghazadeh, Why do you insist on using the designer? Code offers way more control than the designer.Achernar
G
0

@Bioukh answer works in VS2019 and somewhat works in VS2022. However, the results of embedding the DataGridView control in my UserControl then adding and editing the Columns using the answer does not enable those Columns to migrate to another instance of the UserControl. For example: Copy/Paste the UserControl and all of the embedded DataGridView's columns disappear from the new copy.

To Work Around this issue I maintain my DataGridView instances as native and use a public DataGridView property in my UserControl with the binding and docking performed in the property setter. I then drop my_UserControl on my form, drop my_DataGridView on my form, and then set my_UserControl.DataGridView = my_DataGridView. This work around preserves the native properties and behaviors associated with the DataGridView.

In my_UserControl, I have a Panel named "GridPanel" and a VScrollBar. I then added the following property:

///<summary>
/// Associates a native DataGridView with this UserControl
/// then sets the DataGridView.Parent to the Panel in this UserControl
/// and sets the DataGridView.Dock to Fill the Panel
///</summary>
public DataGridView? ContainedDataGridView
{
  get
  {
    try
    {
      // if we have a DataGridView in our Panel then return it
      if ((this.GridPanel.Controls.Count == 1)
          && (this.GridPanel.Controls[0] is DataGridView view))
      {
        return view;
      }
    }
    catch (Exception ex)
    {
      //// TODO Handle "ContainedDataGridView get error"
    }

    // Return null if there is no DataGridView or there was an error checking for it.
    return null;
  }
  set
  {
    try
    {
      // Clear the panel to prevent adding more than one DataGridView
      this.GridPanel.Controls.Clear();

      if (value is not null)              
      {
        this.GridPanel.Controls.Add(value);
        value.Parent = this.GridPanel;
        value.Dock = DockStyle.Fill;
      }
      // else the panel remains cleared
    }
    catch (Exception ex)
    {
      //// TODO Handle "ContainedDataGridView set error"
    }
  }
}

The above snippet is coded as C# 10, .NET 6, Windows Forms App, UserControl and tested in Visual Studio 2022 version 17.0.3

Germangermana answered 17/12, 2021 at 15:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.