65.9K
CodeProject is changing. Read more.
Home

DataGrid with Movable Rows

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.33/5 (8 votes)

Oct 8, 2009

CPOL

2 min read

viewsIcon

58397

downloadIcon

2586

The DataGridMovableRows control lets you move rows around in a DataGrid

Introduction

The DataGridMovableRows control lets you move rows around in a DataGrid! We created it for our Gantt control implementation and decided to share it with the community.

The DataGridMovableRows control (deriving from DataGrid) allows the end user to select rows and simply drag them around to a new location. A visual drag cue is provided when the user moves the mouse over the row header of the selected row and visual cues are also provided to indicate drop location.

BeforeMovingRows and RowsMoved events are fired. RowsMoved will tell you which rows are being moved and to what new location. You could typically update your bound list to reflect this change, in this event handler.

Background

Our Silverlight Gantt control required a DataGrid with support for movable rows. This obviously was not supported internally by the DataGrid and so we had to implement it ourselves. This ended up being a non-trivial task due to customized scrolling behavior implemented in the DataGrid. As the grid is scrolled vertically, the top rows that are going out of view are actually getting hidden, rather than simply being scrolled out. This makes it very hard to determine the co-ordinates of a row so that the drag-cue and drop-cue can be rendered properly.

We created a new control DataGridMovableRows which encapsulates all the tracking details and presents you two simple event handlers as mentioned above, BeforeMovingRows and RowsMoved.

Note that the only way to persist the order of items in a table/list is to include a separate field, for example "SortOrder". This field should be updated accordingly as the user drags the rows around in a DataGrid. The attached sample uses this approach to persist the new order. This also means that before binding your table/list to the DataGrid, make sure to sort it by this "SortOrder" field.

Using the Code

To begin with, listen to the BeforeMovingRows event handler where you can optionally cancel the move based on what rows are selected. Then make the selected rows contiguous, the built-in logic does not support moving rows that are not adjacent, so this step is necessary.

private void dataGrid1_BeforeMovingRows
	(object sender, System.ComponentModel.CancelEventArgs e)
{ // If the selected includes the first row, cancel the selection.
if (this.dataGrid1.SelectedItems.Contains(this.firstItem))
{ e.Cancel = true; return; }
// Make selected items contiguous.
this.FlattenSelection(); }

// If the user selected row 2 and row 5, then this method will also select row 3 and 4.
// This is necessary because the logic here assumes that all selected objects are
// adjacent to each other.
private void FlattenSelection()
{ int topRowIndex = Int32.MaxValue; int botRowIndex =Int32.MinValue; 
IList<CustomObject> list = this.dataGrid1.ItemsSource as IList<CustomObject>;
foreach (CustomObject item in this.dataGrid1.SelectedItems) 
		{ int index = list.IndexOf(item);
if (index < topRowIndex) topRowIndex = index; 
if (index > botRowIndex) botRowIndex = index; }
if (this.dataGrid1.SelectedItems.Count < (botRowIndex - topRowIndex + 1))
{ for (int i = topRowIndex; i <= botRowIndex; i++)
{ CustomObject o = list[i]; 
if (this.dataGrid1.SelectedItems.Contains(o)== false) 
	this.dataGrid1.SelectedItems.Add(o); } } }

Then listen to the RowsMoved event handler where you can adjust the order of the items as follows:

void dataGrid1_RowsMoved(object sender, RowsMovedEventArgs args)
{ // args.StartIndex - The start index of the set of rows that are being moved
// args.Count - The number of adjacent rows below the start index that are being moved.
// args.DestinationIndex - The destination for this move
// Interpreting this move varies by the application.
// Here we will adjust the SortOrder of the bound objects and 
// then rebind them with the grid.
IList<CustomObject> list = this.dataGrid1.ItemsSource as IList<CustomObject>;
this.MoveInList(list, args.StartIndex, args.Count, args.DestinationIndex); }

// There are 2 ways to handle this:
// 1) Move items and adjust their SortOrder (this is the technique used here).
// 2) Another option is to just adjust the SortOrder 
// and then rebind the DataGrid with a new list
// that's properly sorted based on SortOrder.
public int MoveInList(IList<CustomObject> list, int start, int count, int dest) 
{ int from; int to; int newDestOfStart; 
if (dest > start) { from = start; to = dest - 1; 
for (int i = 0; i < count; i++) { CustomObject view = list[start]; 
list.RemoveAt(start); list.Insert(dest - 1, view); 
} 
newDestOfStart = dest - count; } 
else 
{ from = dest; to = start + count - 1; 
for (int i = 0; i < count; i++) 
{ CustomObject view = list[start + i]; 
list.RemoveAt(start + i); list.Insert(dest + i, view); } 
newDestOfStart = dest; } for (int i = from; i <= to; i++) 
{ ((CustomObject)list[i]).SortOrder = i; } 
return newDestOfStart; }

Points of Interest

We understand that being able to drag and drop rows from the grid into other controls is another useful scenario. If you are interested in this, please update the comments section and we will create a new article for this.

History

  • 7th October, 2009: Initial version