Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

DataGridViewKRBColumnSelector

4.88/5 (11 votes)
14 Feb 2010CPOL4 min read 31.9K   1.7K  
DataGridView Column Selector Sample
Image 1

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.

derives.gif

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

Prop.PNG

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.

C#
class GradientBarConverter : ExpandableObjectConverter
{
    #region Override Methods
    
    //All the CanConvertTo() method needs to is check 
    //that the target type is a string.
    public override bool CanConvertTo
    (ITypeDescriptorContext context, Type destinationType)
    {
        if (destinationType == typeof(string))
            return true;
        else
            return base.CanConvertTo(context, destinationType);
    }
    
    //ConvertTo() simply checks that it can indeed convert to the desired type.
    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);
    }
    
    /* The exact same process occurs in reverse when 
    converting a GradientBar object to a string.
    First the Properties window calls CanConvertFrom(). 
    If it returns true, the next step is to call
    the ConvertFrom() method. */
    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)
    {
        // Gelen object tipimizi GradientBar tipine 
        // dönüstürüyoruz ve ayiklama islemine basliyoruz.
        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();
            
            // Retrieve the colors
            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.

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

               // Initializes a new instance of the ToolStripControlHost
               // class that hosts the our ColumnSelector control.
               ToolStripControlHost controlHost =
                   new ToolStripControlHost(dropDownControl);
               controlHost.Margin = Padding.Empty;
               controlHost.Padding = Padding.Empty;
               controlHost.AutoSize = false;

               // Show column selector.
               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.

C#
#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.)

C#
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.

C#
/// <summary> 
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; 
/// otherwise, false.</param>
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();
    }
    
    // Clean up any unmanaged resources.
    
    base.Dispose(disposing);
} 

History

  • Version 2010

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)