Introduction
I had to add a 'maximum number of rows' (called MaxRows
) property to a DataGridView
control recently, and came across two ways of handling this problem. Either of which is overly complicated, but none-the-less I thought I'll share my findings.
In the example code, I provided two grids on two different forms (the reason will be explained later), both with the same customized property (MaxRows
) that does exactly the same thing, but done in different ways.
Approaches
I will elaborate on the two approaches first, before going into detail (well, sort of) with the code. I feel this is more of a software design issue rather than a coding issue.
IExtenderProvider
My first approach was using the IExtenderProvider
interface (which lives under the System.ComponentModel
namespace). IExtenderProvider
is a component which attaches itself to other components, like a parasite. The ErrorProvider
control is an example of IExtenderProvider
, where you add it to your design form and new properties will appear in the property panel for the specified controls.
The good thing about using IExtenderProvider
is that it can attach properties to more than one control at a time (e.g., can attach itself to buttons, panels, grids...).
By dropping the ExtenderProvider component on to your form, it will attach itself to all the specified controls. Hence we have two different forms for this example; otherwise, IExtenderProvider
will attach to my Inherit Control example as well.
One thing I noticed with IExtenderProvider
: it seems to be a lot of tedious code just to add one property to a control.
Inherit Control
My second approach is inheriting from a DataGridView
control and creating my own custom DataGridView
control. This I found to be a much easier and cleaner way for adding new properties to a control. There wasn't much code either. Actually, it is extremely easy.
But if you are already quite far into a project, and/or doing maintenance programming, this might not be the best approach, as you'll have to change all the controls on all the forms or base forms manually. Hence, designing your software project from the start with inherited components will help overcome these issues down the track.
Verdict
Adding custom properties to a control? Inheriting is the best approach in my opinion. The IExtenderProvider
interface seems more like parasite code that attaches itself to controllers than well structured code. But by saying that, if you want to create a property that affects many controls in a container, this would be an okay approach.
I still think creating your own base controls to start with will ease headaches in the long term. Especially on GUI demanding projects.
To add new properties to your controls will be a breeze afterwards as well, as all your controls will update as soon as your base gets a new property.
Remember to make a default value (like -1) that does nothing.
Plus visually, inheriting looks better in the VS Property Panel as seen in the figure above.
The Code
I found creating an inherited control a much cleaner and less painful experience than creating a property via the IExtenderProvider
interface.
Let's look at the code for both techniques. Straight away, one can see the difference in the amount of code between the two techniques. Remember, they do exactly the same thing.
Inheriting
Inherit from the DataGridView
control by adding System.Windows.Forms.DataGridView
after your class name.
namespace CodeProject_Inherit_Vs_IExtProv
{
public partial class InheritDataGrid : System.Windows.Forms.DataGridView
Now we've inherited from a DataGridView
called InheritDataGrid
. It's now time to add our MaxRow
property.
Set the [DefaultValue
], [Category]
, and [Description]
attributes. These are the display values that appear on the property panel in VS. The attributes are quite self-explanatory.
Add a public property called MaxRows
. This will get, set the maximum rows for our InheritDataGrid
control by connecting to an event that will trigger the CheckMaxRows
method.
The CheckMaxRows
method will check how many rows are currently shown, and will disable adding rows when the set value is reached. The default property '-1' will give you unlimited rows to add.
private int maxRows = -1;
[DefaultValue(-1)]
[Category("Data")]
[Description("Set the maximum number of rows for DataGrid")]
public int MaxRows
{
get { return maxRows; }
set
{
base.RowValidated +=
new DataGridViewCellEventHandler(CheckMaxRows);
this.maxRows = value;
}
}
void CheckMaxRows(object sender, EventArgs e)
{
DataGridView grid = (DataGridView)sender;
if ((grid.RowCount > MaxRows) && (MaxRows != -1))
grid.AllowUserToAddRows = false;
else
grid.AllowUserToAddRows = true;
}
That's it for inheriting...
IExtenderProvider
The foundation of InheritDataGrid
. One can pretty much use this as a template. Inherit from System.ComponentModel.Component
and add the IExtenderProvider
interface to the public class reference. The ProvideProperty
attribute specifies the name of the property and the type of the component it should latch onto. Use Hashtable
to track instances of controls.
The IExtenderProvider.CanExtend
method has to be set for IExtenderProvider
to know which controls to infect.
Then create the properties and ensure the property by calling EnsurePropertiesExists
.
namespace CodeProject_Inherit_Vs_IExtProv
{
[ProvideProperty("MaxRows", typeof(DataGridView))]
public class ExtenderProviderDataGrid :
System.ComponentModel.Component, IExtenderProvider
{
private Hashtable properties;
private System.ComponentModel.Container components = null;
public ExtenderProviderDataGrid()
{
properties = new Hashtable();
}
public ExtenderProviderDataGrid(
System.ComponentModel.IContainer container): this()
{
container.Add(this);
}
bool IExtenderProvider.CanExtend(object ctrl)
{
return (ctrl is DataGridView);
}
private class Properties
{
public int MaxRows;
public Properties()
{
MaxRows = -1;
}
}
private Properties EnsurePropertiesExists(object key)
{
Properties p = (Properties)properties[key];
if (p == null)
{
p = new Properties();
properties[key] = p;
}
return p;
}
Now I add my logic to the code. Create the Get-Set methods. 'Get' + add the ProvideProperty
name = GetMaxRows
method. Same for 'Set', which equals SetMaxRows
. Add a default value to SetMaxRows
that does nothing and add your new functionality to an event.
Set the [DefaultValue
], [Category]
, and [Description]
attributes. These are the display values that appear on the property panel in VS. The attributes are quite self-explanatory.
[DefaultValue(-1)]
[Category("Data")]
[Description("Set the maximum number of rows for DataGrid")]
public int GetMaxRows(DataGridView g)
{
return EnsurePropertiesExists(g).MaxRows;
}
public void SetMaxRows(DataGridView g, int value)
{
if ((value == -1) || (value > 0))
EnsurePropertiesExists(g).MaxRows = value;
g.RowValidated += new DataGridViewCellEventHandler(CheckMaxRows);
g.Invalidate();
}
void CheckMaxRows(object sender, EventArgs e)
{
DataGridView grid = (DataGridView)sender;
Properties ctrlProperties;
ctrlProperties = (Properties)properties[(grid as Control)];
if ((grid.RowCount > ctrlProperties.MaxRows) &&
(ctrlProperties.MaxRows != -1))
grid.AllowUserToAddRows = false;
else
grid.AllowUserToAddRows = true;
}
Please note that I did use IExtenderProvider
in a limited capacity for this example. For a more in-depth and well-written article, please view Getting to know IExtenderProvider by James T. Johnson.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.