Click here to Skip to main content
15,867,686 members
Articles / Programming Languages / C#
Article

Drag and Drop support for column reordering in DataGrid control

Rate me:
Please Sign up or sign in to vote.
5.00/5 (17 votes)
5 Dec 20044 min read 149.1K   3.7K   52   15
Enhance the DataGrid control with drag and drop support for column reordering

Sample image

Introduction

DataGrid is one of the most powerful Windows® Forms control. By using default DataGrid property settings you can easily bind a DataSet to it to create an impressive Windows Forms user interface – one that display large amounts of data efficiently and intuitively without bewildering the user.

Thanks to the extensible architecture of DataGrid control, it gives developers the flexibility in hand to extend its functionalities, where native support is not sufficed. In this article, I am going to show you how to enhance the DataGrid control with the drag and drop support for column reordering. This is particular useful in situation when you need to compare columns side-by-side that may not reside together by default.

Background

In this implementation, I've made use of some unmanaged Win32 APIs to perform the graphics drawing process – one that draw the semi-transparent dragged column during the drag and drop operation. All these Win32 APIs are encapsulated in the WinGDI.cs file for the separation of managed and unmanaged code. Underpinnings the drawing of drag and drop support for column reordering is the function BitBlt.

BitBlt – Bit Block Transfer, one of the powerful drawing functions which transfer a block of bits (a bitmap) from one device context to another. . One point worth mentioning in this API is that currently there are 17 constants defined for the dwRop (Raster Operation Code) variable and you can easily create different kind of effects on image by changing the raster operation code. SRCCOPY (0xcc0020) is the easiest to use and performs a simple copy action.

C#
[DllImport("gdi32.dll")]
public static extern bool 
BitBlt
(
  IntPtr hdcDest,       // handle to destination DC (device context)
  int nXDest,           // x-coord of destination upper-left corner
  int nYDest,           // y-coord of destination upper-left corner
  int nWidth,          // width of destination rectangle
  int nHeight,          // height of destination rectangle
  IntPtr hdcSrc,       // handle to source DC
  int nXSrc,           // x-coordinate of source upper-left corner
  int nYSrc,            // y-coordinate of source upper-left corner
  System.Int32 dwRop    // raster operation code
);

For more information on this API, please consult the Win32 documentation.

Using the code

To preserve the existing functionalities provided by DataGrid control, I've created a new control named DataGridEx, which inherits from the existing DataGrid control. A property AllowUserToOrderColumns is added, which enables or disables the control's ability to reorder the columns. In order to provide drag and drop for column reordering, the methods OnMouseDown, OnMouseMove and OnMouseUp of the base control must be overridden.

OnMouseDown is the method that will be called when column header is clicked. To initiate the drag operation, it entails the AllowUserToOrderColumns property has been set to true and the mouse button that initiates the click event must be left mouse button. Just before starting the drag operation, ColumnStartDrag event will be fired to determine whether this column is drag-able. Developers at the application level can react to this event by setting the Cancel variable in the event arguments to true to stop the drag operation. It everything goes fine, the process of drag operation starts.

C#
protected 
override void OnMouseDown(System.Windows.Forms.MouseEventArgs 
e)
{
  try
  {
    DataGrid.HitTestInfo hti = HitTest(e.X, 
e.Y);
          
    /* 
     * The following conditions must be met 
in order to 
     * start of the drag 
operation
     * 1) Column Header is clicked with left 
mouse button.
     * 2) AllowUserToOrderColumns property 
has been set to true
     * 
     * */
    if (
      (hti.Type == 
DataGrid.HitTestType.ColumnHeader)
      && ((e.Button & 
MouseButtons.Left) == MouseButtons.Left)
      && 
(allowColumnsReorder))
      {
        //  Notify application that the drag operation is about to start 

   ColumnStartDragEventArgs csde = new 
ColumnStartDragEventArgs(
         hti.Column, 
GetColumnName(hti.Column), false);
        
OnColumnStartDrag(csde);
          
        if (!csde.Cancel)  //  This column is drag-able
        {
          //  Remember the column header clicked
          dragColumnIndex   = hti.Column;
          isColumnDragging = true;
            
 
          /*
           * Currently, there is no way to 
determine the drag column's top 
           * left position. 
 
           * Workaround : Assumes the mouse 
clicked in the middle of the 
           * drag 
column.
           * 
 
           * 
*/
          mouseDelta = new Point
            (
              e.X - (e.X - 
(GetColumnWidth(hti.Column) / 2)),
              (CaptionVisible == true ? 
GetCaptionHeight() : 0)
            
);
                        
 
          Point dragColumn  = PointToScreen(new Point(e.X, 
mouseDelta.Y));
          Point screenPoint = 
PointToScreen(new Point(e.X, e.Y));
          screenPoint.X  -= 
mouseDelta.X;
          screenPoint.Y  = 
dragColumn.Y;
                        
 
          //  Retrieve the DataGridColumnStyle for the drag column
          dragColumnStyle = GetColumnStyle(hti.Column);
            
 
          //  Create the screen graphic device
          
CreateDisplayDC();
            
 
          //  Determine the actual size of the dragged image
          dragImageSize = new 
Size(dragColumnStyle.Width, 
           ColumnHeaderHeight - 
2);
            
 
          //  Determine and create the image buffer.
          buffer = new 
Bitmap(Math.Max(dragColumnStyle.Width, 
           NODROPIMAGE_WIDTH + 2), 
ColumnHeaderHeight);
          bufferGraphics = 
Graphics.FromImage(buffer);
          pBufferDC = 
bufferGraphics.GetHdc();
          
          //  Copy the rectangluar image from the screen into buffer.
          WinGDI.BitBlt(pBufferDC, 0, 0, 
buffer.Width, buffer.Height,
            pDisplayDC, screenPoint.X, screenPoint.Y, 
WinGDI.SRCCOPY);
            
 
          //  Store the image buffer position for tracking
          bufferPos = new 
Point(screenPoint.X, screenPoint.Y);
        }
      }
    }
    catch(Exception) { /* Ignore */ 
}
    
finally
    {
      
base.OnMouseDown(e);
    }
  }
}

OnMouseMove method will be called to draw the semi-transparent dragged column as the mouse moving around in the DataGrid. If the dragged column is drag over to another column, ColumnSequenceChanging event will be fired to determine whether the dragged column can be dropped onto it. Developers at the application level can use this event to determine the droppable columns based on their condition. An X icon will be drawn on the dragged column to inform users that the particular column is not droppable.

C#
protected 
override void OnMouseMove(MouseEventArgs 
e)
{  
  try
  {  
 
    DataGrid.HitTestInfo hti = HitTest(e.X, 
e.Y);
        
    /* 
     * The following conditions must be met 
before 
     * performing the drawing of drag 
operation
     * 1) AllowUserToOrderColumns property 
has been set to true
     * 2) Column is being dragged with left 
mouse button
     * 
     * */
    if (
      
(allowColumnsReorder)
      && 
(isColumnDragging)
      && ((e.Button & 
MouseButtons.Left) == MouseButtons.Left))
      {  
 
        if (hti.Column >= 
0)
        {
          //  Re-calculate the mouse location in the screen
          Point screenPoint = 
PointToScreen(new Point(e.X, e.Y));
          screenPoint.X -= 
mouseDelta.X;
          screenPoint.Y  = 
bufferPos.Y;
          
          //  Performs the image swapping
          WinGDI.BitBlt(pDisplayDC, 
bufferPos.X, bufferPos.Y,
           buffer.Width, buffer.Height, 
pBufferDC, 0,
           0, 
WinGDI.SRCCOPY);
          WinGDI.BitBlt(pBufferDC, 0, 0, 
buffer.Width, buffer.Height,
           pDisplayDC, screenPoint.X, 
screenPoint.Y, 
           WinGDI.SRCCOPY);
          bufferPos = new 
Point(screenPoint.X, screenPoint.Y);
 
          
/*
           * Notify application that the 
dragged column is dragging over 
           * to another column and 
determines whether the dragged column
           * can be dropped onto 
it.
           * 
 
           * 
*/
          ColumnSequenceChangingEventArgs 
csce = new ColumnSequenceChangingEventArgs(dragColumnIndex, 
 
            GetColumnName(dragColumnIndex), 
hti.Column, 
            GetColumnName(hti.Column), 
false); 
          OnColumnSequenceChanging(csce);
            
 
          if (csce.Cancel)  // Drop on this column is not allow
            DrawDragImage(displayGraphics, 
screenPoint.X, screenPoint.Y, false);
          
else
            DrawDragImage(displayGraphics, 
screenPoint.X, screenPoint.Y, true);
        }
      }
    }
    catch(Exception) { /* Ignore */ 
}
    
finally
    {
      
base.OnMouseMove(e);
    }
  }
}

When the mouse button releases, OnMouseUp method will be called to invoke the MoveColumn function to reorder the columns if the dragged and dropped column is not the same. ColumnSequenceChanged event will be fired once the column reordering is done. This marks the end of the drag operation and performs the necessary house keeping.

C#
protected 
override void OnMouseUp(System.Windows.Forms.MouseEventArgs 
e)
{
  try
  {
    DataGrid.HitTestInfo hti = HitTest(e.X, 
e.Y);
 
    if(
      
(allowColumnsReorder)
      && 
(isColumnDragging)
      && ((e.Button & 
MouseButtons.Left) == MouseButtons.Left))
    {  
 
      //  Clean up the drawn image from the screen
      WinGDI.BitBlt(pDisplayDC, bufferPos.X, 
bufferPos.Y, buffer.Width,
       buffer.Height, pBufferDC, 0, 0, 
WinGDI.SRCCOPY);
          
      /* 
       * The following conditions must be 
met before moving the drag column
       * 1) Drop column is within 
bound
       * 2) Source Column <> 
Destination Column
       * 
*/
      if (hti.Column >= 0 && 
dragColumnIndex != hti.Column)
      {
        /*
         * Notify application that the 
dragged column is about to drop onto 
         * another 
column.
         * 
*/
        ColumnSequenceChangingEventArgs 
columnChanging =
         new 
ColumnSequenceChangingEventArgs(dragColumnIndex,  
          GetColumnName(dragColumnIndex), 
hti.Column, 
          GetColumnName(hti.Column), 
false);
        
OnColumnSequenceChanging(columnChanging);
                          
 
        if (!columnChanging.Cancel)  // Drop on this column is allowed
        {
          //  Move the column
          MoveColumn(dragColumnIndex,hti.Column);
            
 
          //  Notify application that the dragged column
          // is reordered to new location
          ColumnSequenceChangedEventArgs 
columnChanged = 
           new 
ColumnSequenceChangedEventArgs(columnChanging.SourceColumnIndex, 
 
            columnChanging.SourceColumnName, 
columnChanging.DestinationColumnIndex, 
            columnChanging.DestinationColumnName);
          
OnColumnSequenceChanged(columnChanged);
        }
      }
          
      //  House Keeping
      isColumnDragging = 
false;
      dragColumnIndex = 
-1;
      dragColumnStyle = 
null;
      
DisposeDisplayDC();
      
DisposeBufferDC();
    }
  }
  catch(Exception) { /* Ignore */ 
}
  finally
  {
    
base.OnMouseUp(e);
  }
}

The cornerstone of drawing the dragged column is encapsulated in the function DrawDragImage. A normal semi-transparent dragged column will be drawn when it drags over to those droppable columns. Otherwise, a semi-transparent dragged column with an X icon will be drawn.

C#
private void 
DrawDragImage(Graphics g, int x, int y, bool 
bEnabled)
{
  try
  {  
 
    g.SmoothingMode = 
SmoothingMode.AntiAlias;  
 
      // Anti-aliased rendering
        
    Rectangle rect = new Rectangle(x, y, 
dragImageSize.Width,
     
dragImageSize.Height);
    using(Pen p = new 
Pen(Color.Black))
    {  
 
      //  Semi-Transparent
      using(Brush backBrush = new 
SolidBrush(Color.FromArgb(127, 
       Color.LightSteelBlue)))
      {
        using(Brush foreBrush = new 
SolidBrush(Color.Black))
        {
          g.FillRectangle(backBrush, 
rect.Left + 1, rect.Top + 1, 
           rect.Width - 2, rect.Height - 
2);
          g.DrawRectangle(p, rect.Left, 
rect.Top, rect.Width - 1, 
           rect.Height - 
1);
              
 
          using(StringFormat sf = new 
StringFormat(StringFormatFlags.NoWrap))
          
{
            sf.LineAlignment = 
StringAlignment.Center;
 
            if (dragColumnStyle.Alignment == 
HorizontalAlignment.Center)
              sf.Alignment = 
StringAlignment.Center;
            else if 
(dragColumnStyle.Alignment == 
HorizontalAlignment.Left)
              sf.Alignment = 
StringAlignment.Near;
            else if 
(dragColumnStyle.Alignment == 
HorizontalAlignment.Right)
              sf.Alignment = 
StringAlignment.Far;
                              
            g.DrawString(dragColumnStyle.HeaderText, 
HeaderFont,
             foreBrush, rect, 
sf);
          
}
              
 
          // Draw an X icon in the semi-transparent drag column
          if 
(!bEnabled)
          
{
            using(Pen redPen = new Pen(new 
SolidBrush(Color.Red), 3))
            
{
              //  Note: Those offset applied here is meant to
              //  avoid drawing the X icon outside
              //  of the bound rectangle.
              Point p1; Point p2; // X
              Point p3; Point p4;  // Y
              if (dragImageSize.Width > 
NODROPIMAGE_WIDTH)
              
{
                int left = rect.Left + 
((dragImageSize.Width 
                 - NODROPIMAGE_WIDTH) / 
2);
                p1 = new Point(left, rect.Top + 
1);
                p2 = new Point(left + 
NODROPIMAGE_WIDTH, rect.Top 
                 + buffer.Height - 
2);
                    
 
                p3 = new Point(left + 
NODROPIMAGE_WIDTH, rect.Top + 1);
                p4 = new Point(left, 
rect.Top + buffer.Height - 2);
              
}
              
else
              
{
                p1 = new Point(rect.Left + 
1, rect.Top + 1);
                p2 = new Point(rect.Left + 
NODROPIMAGE_WIDTH - 1,
                 rect.Top + buffer.Height - 
2);
                    
 
                p3 = new Point(rect.Left + 
NODROPIMAGE_WIDTH - 1,
                 rect.Top + 
1);
                p4 = new Point(rect.Left + 
1, rect.Top + buffer.Height - 2);
              
}
              redPen.StartCap = 
LineCap.Round;
              redPen.EndCap   = 
LineCap.Round;
                  
 
              g.DrawLine(redPen, p1, 
p2);
              g.DrawLine(redPen, p3, 
p4);
            
}
          
}
        }  
 
      }
    }
  }
  catch(Exception) { /* Ignore */ 
}
}

MoveColumn performs the intelligent column reordering where dragged column will be positioned before the dropped column if the dragged column was previously positioned after the dropped column and vice versa.

C#
public void 
MoveColumn(int fromColumn, int toColumn) 
{ 
 
  if(fromColumn == toColumn) 
return;
    
  DataGridTableStyle oldTS = 
TableStyles[0];
  DataGridTableStyle newTS = new 
DataGridTableStyle();
  newTS.MappingName = 
oldTS.MappingName;  // Table Name
  CopyTableStyle(oldTS, newTS);  
    // Copy the old TableStyle to new TableStyle
      
  for(int i = 0; i < 
oldTS.GridColumnStyles.Count; i++) 
  {
    if(i != fromColumn && fromColumn 
< toColumn)
      
newTS.GridColumnStyles.Add(oldTS.GridColumnStyles[i]); 
 
      
    if(i == toColumn) 
 
      newTS.GridColumnStyles.Add(oldTS.GridColumnStyles[fromColumn]); 
 
      
    if(i != fromColumn && fromColumn 
> toColumn) 
      newTS.GridColumnStyles.Add(oldTS.GridColumnStyles[i]);      
  } 
  TableStyles.Remove(oldTS); 
 
  TableStyles.Add(newTS); 
 
}

Points of Interest

For those developers who are interested to convert the drawing functions from unmanaged code to managed code, feel free to try the DrawImage managed API. However, the performance of DrawImage function is slower than the Win32 counterpart, as it creates and destroys new device context for each call.

Conclusion

Column Reordering is just one enhancement to the DataGrid control. If the native functionalities provided in the Windows Forms controls aren't exactly what you need for all your scenarios, you can take advantage of the extensibility features of Windows Forms architecture to override and extend these controls to make them fit your particular needs.

History

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Singapore Singapore
Elvin Cheng is currently living in Woodlands, Singapore. He has been developing applications with the .NET Framework, using C# and ASP.NET since October 2002. Elvin specializes in building Real-time monitoring and tracking information system for Semi-conductor manufacturing industry. During his spare time, he enjoys reading books, watching movie and gym.

Comments and Discussions

 
Generaldragdrop to another datagridview Pin
Pappan28-May-09 21:18
Pappan28-May-09 21:18 
Thank you for the help. can you please tell me, if i want to drag one column or columns to another datagridview, where should i change the existing code.

pappan
GeneralASP.NET 2.0 Gridview Pin
Muneer Safi6-May-08 5:41
Muneer Safi6-May-08 5:41 
QuestionIs it possible to deactive ordering if dragging? Pin
Pinho27-Sep-07 5:48
Pinho27-Sep-07 5:48 
Generalproblem Pin
netJP12L23-Mar-07 11:19
netJP12L23-Mar-07 11:19 
QuestionHow to paging datagrid Pin
Robert Wang19838-May-06 23:48
Robert Wang19838-May-06 23:48 
AnswerRe: How to paging datagrid Pin
Robert Ruisl20-Jul-06 3:01
Robert Ruisl20-Jul-06 3:01 
AnswerRe: How to paging datagrid Pin
Robert Ruisl20-Jul-06 3:04
Robert Ruisl20-Jul-06 3:04 
QuestionIs it possible to implement in .NET CF? Pin
madthing27-Feb-06 13:59
madthing27-Feb-06 13:59 
GeneralFlickering... Pin
oleglyamin15-Sep-05 2:25
oleglyamin15-Sep-05 2:25 
GeneralGreat job - but little question Pin
Michael Hachen8-Mar-05 4:47
Michael Hachen8-Mar-05 4:47 
GeneralLittle mistake Pin
Robert Rohde7-Dec-04 10:11
Robert Rohde7-Dec-04 10:11 
GeneralNow available in a VB.net version Pin
ifucdseeme23-Nov-04 4:36
ifucdseeme23-Nov-04 4:36 
GeneralRe: Now available in a VB.net version Pin
Elvin Cheng28-Nov-04 16:18
Elvin Cheng28-Nov-04 16:18 
GeneralRe: Now available in a VB.net version Pin
Gabriel.Lam18-Jan-06 5:08
Gabriel.Lam18-Jan-06 5:08 
GeneralVery good job! Thank you. Pin
Vasia28-Oct-04 3:06
Vasia28-Oct-04 3:06 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.