Custom DataGridView Column duplicates when using it in Designer
Asked Answered
R

2

3

I made a custom DataGridView component which has a standard DataGridViewImageColumn inside. A new property changes the visibility of the column when i don't need in in a particular table. I add the column in the constructor and update it on CellFormatting event. That is the part working like intended.

When I drop the control onto a new form it shows up with the new column in it. Running the program results in two image columns in the grid.

Screenshot in designer

A new form just added the component and set Dock.Fill

Screenshot from application running

When i start it without changing anything it shows me two columns. The first is working like it should and the second one always shows the missing x image (Had no data in it so they show both the x).

In the designer of the form there is a ne image column added automatically.

private CustomDataGridView customDataGridView1;
private System.Windows.Forms.DataGridViewImageColumn dataGridViewImageColumn1;

When i keep editing in the form it sometimes happens that VS creates even more copies of the same columns. To fix the problem i have to clear the DGV.Columns listing every now and then.

How can i prevent VS from copying my fields?

The following code is the minimal part to reproduce the problem.

public class CustomDataGridView : DataGridView
{
    private DataGridViewImageColumn EditStatusIcons;

    private bool hasIcons = true;
    public bool HasIcons
    {
        get { return this.hasIcons; }
        set
        {
            if (this.Columns["EditIcons"] == null) return;

            this.Columns["EditIcons"].Visible = value;
            this.hasIcons = value;
        }
    }

    public CustomDataGridView()
    {
        this.EditStatusIcons = new System.Windows.Forms.DataGridViewImageColumn();

        this.EditStatusIcons.HeaderText = "";
        this.EditStatusIcons.Name = "EditStatusIcons";

        this.Columns.Add(this.EditStatusIcons);
    }
}

Edit: I also tried to use this.AutoGenerateColumns = false; like in Custom DataGridView adds columns everytime I build said. Nothing changes.

Clearing the columns listing on the form removes the working column and leaves a column dataGridViewImageColumn1 which is just a complete empty column with the wrong name.

Retortion answered 8/4, 2016 at 14:10 Comment(3)
How many columns do you have add in datagridview's designer?Alister
None. I Just dropped the component on the form and set the Dock style to Fill. VS added the column on its own.Retortion
The control is acting as expected. You added a column in constructor, then the designer serializes the column. Then you run the application and it adds a column in constructor. So you have 2 columns. And this continues this way and is not limited to 2 columns. Even you don't need to build and run, it's enough to open the form which contains the grid and make a small change in form and save it. A new column is born!Magnetostriction
M
4

The control is acting as expected.

Reason

You added a column in constructor, then the designer serializes the column. Then you run the application and it adds a column in constructor. So you have 2 columns. And this continues this way and is not limited to 2 columns. Even you don't need to build and run, it's enough to open the form which contains the grid and make a small change in form and save it. A new column is born!

Solutions

Based on your requirements, to solve the problem, you can consider these options:

  1. Check if the control is in design mode, then don't add the column and add it only at run-time.

  2. You can check if the control contains an existing such image column then don't add another one. You can make the column unique by it's type of some properties.

  3. You can create a custom designer for the control and put the code of adding the column in initialization of control. This way the code only runs firs time you add the control to the form.


Some notes about solutions

Choosing between solutions is completely based on your requirements and all options are available. But consider these notes about solutions:

  1. About the 1st solution, don't use DesignMode property because it doesn't work in constructor. Instead perform check this way:

    if (System.ComponentModel.LicenseManager.UsageMode != LicenseUsageMode.Designtime)
    {
        //The control is not in design mode, add the column here.
    }
    
  2. About the 2nd solution, for example it's enough to add a new MyCustomImageColumn type and only check existence of a column of type MyCustomImageColumn and if it exists, don't add another one, because one of them is enough.

  3. About the 3rd Solution. At least currently while you can use both of previous simple solutions I don't recommend to follow it. Because DataGridView has its own Designer names DataGridViewDesigner which is internal and you can not inherit from it and if you only create a designer because of this requirement you will miss some features of original designer. Probably you can workaround this or even create a ToolBoxItem instead of a designer, but you don't need it. The option is here to make the answer more complete and useful for future readers for such cases.
Magnetostriction answered 8/4, 2016 at 15:45 Comment(10)
I tried the first one. if (!DesignMode) around the column creation and adding but it is in the designer anyways. Or am i getting something wrong? The second method was what i was doing before. I created the columns in the setter of my property when there was no right column there but it was not very usable because i could not find which column should be there and which not. In production i am using two properties for different image columns. I go read a bit about the custom designer solution.Retortion
To the creation of new columns i did not find when it exactly was happening but after working a while on on form i had about 40 columns in my form instead of the two that should be there @.@Retortion
The reason is exactly what I said. Even you don't need to build and run, it's enough to open the form which contains the grid and make a small change in form and save it. A new column is born!Magnetostriction
About the solutions, It depends on your requirement, for example, probably you checked for design mode using DesignMode property which doesn't work for you in constructor. Instead you should use if (System.ComponentModel.LicenseManager.UsageMode != LicenseUsageMode.Designtime)Magnetostriction
About the second solution, for example it's enough to add a new MyCustomImageColumn type and only check existence of a column of type MyCustomImageColumn and if it exists, don't add another one, because one of them is enough.Magnetostriction
About the 3rd Solution. At least currently while you can use both of previous simple solutions I don't recommend to follow it. Because DataGridView has its own Designer names DataGridViewDesigner which is internal and you can not inherit from it and if you only create a designer because of this requirement you will miss some features of original designer. Probably you can workaround this or even create a ToolBoxItem instead of a designer, but you don't need it. The option is here to make the answer more complete and useful for future readers for such cases :)Magnetostriction
I used the first solution now. Did not know that the DesignMode property is not working. It's a simple and elegant solution to the problem. Thanks a lot. +1Retortion
Let us continue this discussion in chat.Retortion
As usual, beat me to an answer while I was researching, so I had to reword everything to act as an additional information answer. ;) +1Unsustainable
@Unsustainable ;) +1 + some commentsMagnetostriction
U
1

People have fought against this issue for years, but as Reza stated this is expected behavior - hence why it's never been "fixed". There's a lengthy MSDN forum thread discussing this very issue. Among numerous failed solution proposals, most of the successful suggestions provided by Reza are alluded to in some degree.

In the above mentioned thread, a fourth suggestion first proposed by user CoolDadTx is to wrap the DataGridView in a UserControl and then add the column that way. Further reasoning:

Using Inheritance for setting properties is a bad practice, inheritance should be used for adding functionality or overriding the behavior of current one.

If you want to make this kind of customization you should use usercontrol instead or using a shared function which configure you gridviewproperty on runtime.

-- Marco Guignard, July 17, 2014

The advantage is that the UserControl will only add properties to the designer that you've explicitly exposed. Therefore your added column(s) will not be duplicated. Although it is not as concise as a DesignTime if-statement, usage might look like the following:

public class CustomControl : UserControl
{
    private DataGridViewImageColumn EditStatusIcons;
    public DataGridView DGV;

    private bool hasIcons = true;
    public bool HasIcons
    {
        get { return this.hasIcons; }
        set
        {
            if (this.DGV.Columns["EditStatusIcons"] == null) return;

            this.dataGridView1.Columns["EditStatusIcons"].Visible = value;
            this.hasIcons = value;
        }
    }

    public CustomDataGridView()
    {
        this.EditStatusIcons = new System.Windows.Forms.DataGridViewImageColumn();

        this.EditStatusIcons.HeaderText = "";
        this.EditStatusIcons.Name = "EditStatusIcons";

        this.DGV= new DataGridView();
        this.DGV.Dock = DockStyle.Fill;
        this.DGV.Columns.Add(this.EditStatusIcons);

        this.Controls.Add(this.DGV);
    }
}
Unsustainable answered 8/4, 2016 at 18:13 Comment(3)
The solution depends on the requirements. In the current question, I prefer to use inheritance and make the the property as a run-time-only property. The user control solution is also good for many situations, but it has some problems, specially this one: this way you loose the ability to work with the hosted control at designer.Magnetostriction
For cases which we don't need a run-time-only property, I recommend creating a custom Designer (if you don't miss the original designer) and override InitializeNewComponent or as another applicable option creating a custom ToolBox item and override CreateComponentsCore.Magnetostriction
Great point about the hosted control in the designer. Second comment is probably equally as useful, but admittedly I'd have to study into that for further understanding.Unsustainable

© 2022 - 2024 — McMap. All rights reserved.