Introduction
This article discusses how to add column show/hide capability to a DataGridView
. When user right-clicks the column's header, a custom selector menu is shown.
Supported Features
- You can use this component for your projects
- Event Notifications (color changes, menu item changes, selector close callback)
- Custom gradient style (normal or mouse hover states)
- System Drop Shadow (you can change this easily from the property editor)
- Exit Button Styles (
System.Windows.Forms.Border3DStyle enum
) - Design-Time Support
- Font property for the selector menu
- Automatically generates size of the menu
- Max columns supports
The DataGridViewKRBColumnSelector
is a designable class but we don't need this. (We don't add another component or control to designer window of the class). However, this component is not a control, and as a result it doesn't get a piece of form real estate, we'll only use it to assign a DataGridView
control. Typically, if you want to create a custom component that's not a control, you'll derive your class from the component class. So, our DataGridViewKRBColumnSelector
class derives from the base component class.
DataGridViewKRBColumnSelector Base Classes.
You can add this component class to the Toolbox in Visual Studio. If you drop a control onto a form, it appears exactly where you've placed it. If you drop a component (such as DataGridViewKRBColumnSelector
) on a form, it appears in the component tray under the form.
Properties
MaxColumn
- Determines how much column is included in the selector DropShadow
- Determines whether the system drop shadow is visible or not BackColor
- Gets or sets the back color of the column selector BorderColor
- Gets or sets the border color of the column selector GridView
- Gets or sets the DataGridView
to which the DataGridViewKRBColumnSelector
is attached Alignment
- Gets or sets the positioning characteristics of the text in a text rectangle Font
- The font used to display text in the column selector MenuItem,BarGradient,AreaCheckBox,HoverAreaCheckBox,ExitButton
- You can change the appearance of these classes from the property editor
Events
Closed
- Occurs when the selector menu is closed by the user MenuItemChanged
- Occurs when the menu item checked state changed GradientChanged
- Occurs when the specific properties changed (Using by ItemMenu
, GradientBar
, CheckBoxArea
, CheckBoxHoverArea
class) ExitButtonChanged
- Occurs when the properties changed of the ButtonExit
class
Using the Code
The component class provides a basic implementation of the IComponent
interface but we need to create many more properties and methods in our DataGridViewKRBColumnSelector
component. We have some classes and their properties to change the visibility of our custom selector menu. These classes are ItemMenu
, GradientBar
, CheckBoxArea
, CheckBoxHoverArea
and ButtonExit
. There is no way to set the subproperties of these classes at design time. To solve this problem, we need to create our custom type converters for conversion between two types. (Object to a String, and then convert the string back to a live class object.)
Our custom type converters derive from the ExpandableObjectConverter
. Also we have some TypeEditors
which give you the change to get a little fancy by creating custom thumbnail of the specific class in the properties window. All you need to do is create a type editor for the specific class and override the PaintValue()
method.
class GradientBarConverter : ExpandableObjectConverter
{
#region Override Methods
public override bool CanConvertTo
(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
return true;
else
return base.CanConvertTo(context, destinationType);
}
public override object ConvertTo
(ITypeDescriptorContext context, System.Globalization.CultureInfo culture,
object value, Type destinationType)
{
if (destinationType == typeof(string))
return ToString(value);
else
return base.ConvertTo(context, culture, value, destinationType);
}
public override bool CanConvertFrom
(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
return true;
else
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture,
object value)
{
if (value is string)
return FromString(value);
else
return base.ConvertFrom(context, culture, value);
}
#endregion
#region Helper Methods
private string ToString(object value)
{
GradientBar gradient = value as GradientBar;
ColorConverter converter = new ColorConverter();
return String.Format("{0}, {1}, {2}",
converter.ConvertToString(gradient.ColorStart),
converter.ConvertToString(gradient.ColorEnd),
gradient.GradientStyle);
}
private GradientBar FromString(object value)
{
string[] result = ((string)value).Split(',');
if (result.Length != 3)
throw new ArgumentException("Could not convert to value");
try
{
GradientBar gradient = new GradientBar();
ColorConverter converter = new ColorConverter();
gradient.ColorStart = (Color)converter.ConvertFromString(result[0]);
gradient.ColorEnd = (Color)converter.ConvertFromString(result[1]);
gradient.GradientStyle = (LinearGradientMode)Enum.Parse
(typeof(LinearGradientMode), result[2], true);
return gradient;
}
catch (Exception)
{
throw new ArgumentException("Could not convert to value");
}
}
#endregion
}
class GradientBarEditor : UITypeEditor
{
#region Override Methods
public override bool GetPaintValueSupported(ITypeDescriptorContext context)
{
return true;
}
public override void PaintValue(PaintValueEventArgs e)
{
GradientBar gradient = e.Value as GradientBar;
using (LinearGradientBrush brush =
new LinearGradientBrush(e.Bounds, gradient.ColorStart,
gradient.ColorEnd, gradient.GradientStyle))
{
e.Graphics.FillRectangle(brush, e.Bounds);
}
}
#endregion
}
When a user right clicks any column's header, a popup menu is shown. The following code snippet shows how you can handle the CellMouseClick
event to shows this selector menu. We use the ToolStripControlHost
class to host our ColumnSelector
custom control.
private void gridView_CellMouseClick
(object sender, DataGridViewCellMouseEventArgs e)
{
if (e.Button == MouseButtons.Right && e.RowIndex == -1)
{
dropDownControl = new ColumnSelector(this, maxColumn);
dropDownControl.Closed += new EventHandler(dropDownControl_Closed);
dropDownControl.MenuItemChanged +=
new ColumnSelector.MenuItemEventHandler
(dropDownControl_MenuItemChanged);
ToolStripControlHost controlHost =
new ToolStripControlHost(dropDownControl);
controlHost.Margin = Padding.Empty;
controlHost.Padding = Padding.Empty;
controlHost.AutoSize = false;
popup.Items.Add(controlHost);
popup.DropShadowEnabled = dropShadow;
popup.Show(Cursor.Position, ToolStripDropDownDirection.Default);
}
}
The ColumnSelector
is our task class that derives from Control
class, we make the painting process with this class.We set the specified System.Windows.Forms.ControlStyles
flag to true
parameter in the constructor of the class. We have three overloading constructors for this class. The first and second overloading constructor contain a DataGridViewKRBColumnSelector
class parameter for access to a DataGridView
and base implementation of our component class. If you want to determine a limit to the column that appears in the selector menu, you can use the second overloading constructor of the class.
#region Constructor
public ColumnSelector()
{
this.SetStyle(ControlStyles.AllPaintingInWmPaint |
ControlStyles.OptimizedDoubleBuffer |
ControlStyles.UserPaint |
ControlStyles.UserMouse | ControlStyles.FixedWidth |
ControlStyles.FixedHeight, true);
}
public ColumnSelector(DataGridViewKRBColumnSelector selector)
: this()
{
this.selector = selector;
this.Font = this.selector.Font;
DataGridView dataGridView = this.selector.GridView;
if (dataGridView == null)
throw new ArgumentNullException("DataGridView cannot be null.");
else
{
for (int i = 0; i < dataGridView.Columns.Count &&
i <= MAXCOLUMNS - 1; i++)
{
DataGridViewColumn currentColumn = dataGridView.Columns[i];
items.Add(new MenuItem
(currentColumn.HeaderText, currentColumn.Visible));
}
CreatesRectangles();
}
}
public ColumnSelector
(DataGridViewKRBColumnSelector selector, int maxColumns)
: this()
{
this.selector = selector;
this.Font = this.selector.Font;
DataGridView dataGridView = this.selector.GridView;
if (dataGridView == null)
throw new ArgumentNullException
("DataGridView cannot be null.");
else
{
for (int i = 0; i < dataGridView.Columns.Count &&
i <= maxColumns - 1; i++)
{
DataGridViewColumn currentColumn = dataGridView.Columns[i];
items.Add(new MenuItem
(currentColumn.HeaderText, currentColumn.Visible));
}
CreatesRectangles();
}
}
#endregion
We have some paint methods for custom selector menu. These methods are called from the override OnPaint()
method of the control class. If the mouse cursor is on any menu item of the selector menu or exit button, we don't always create a baseBitmap
. In this way, we get rid of unnecessary operations. We are only performing a single method operation. (DrawMenuItems method.)
protected override void OnPaint(PaintEventArgs pe)
{
if (baseBitmap == null)
{
baseBitmap = new Bitmap(this.Width, this.Height);
graphOne = Graphics.FromImage(baseBitmap);
DrawBarGradient(graphOne);
DrawBackground(graphOne);
DrawBorder(graphOne);
}
using (Bitmap overlay = new Bitmap(baseBitmap))
using (Graphics gr = Graphics.FromImage(overlay))
{
DrawMenuItems(gr);
pe.Graphics.DrawImage(overlay, this.ClientRectangle, 0, 0,
overlay.Width, overlay.Height, GraphicsUnit.Pixel);
}
}
Naturally, if our component class holds references to any IDisposable
classes or variables, we need to clean them up properly. For this, we need to override Dispose()
method and call Dispose()
on those objects. This clean up code makes sure your code runs optimally at runtime and in the design environment.
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
font.Dispose();
menuItem.Dispose();
barGradient.Dispose();
areaCheckBox.Dispose();
hoverAreaCheckBox.Dispose();
exitButton.Dispose();
popup.Closed -=
new System.Windows.Forms.ToolStripDropDownClosedEventHandler(popup_Closed);
popup.Dispose();
if (dropDownControl != null)
dropDownControl.Dispose();
}
base.Dispose(disposing);
}
History