Sorting selected rows in DataGridView
I have a DataGridView in a Winforms application. I want to select a set of rows in it and sort those rows by a column (Timestamp)...
The rest开发者_StackOverflow中文版 of the rows should remain as they originally were.. Can this be done using the sort property of DGV
Thanks
Can this be done using the sort property of DGV
No
The Sort
method on the DataGridView
is used for a bit more simple sorting. Such as Ascending or Descending sorting and the SortOrder
property is also just for "simple" sorting.
Can this behavior be implemented? Sure.
I think the easiest way to do this, is this:
- Store the index of the first selected item
- Take out the items that you want to sort from the
DataGridView
- Sort the list using LINQ
- Append the two lists and add the sorted list at the index stored in the first step.
However you need to think about how you want to handle if you selecte items that are not followed by each other, for instance if you select index { 1, 3, 6, 9 }
you might stil lwant to append this to index 1
.
Edit
Okay so I played around with this a little bit and came up with a way that you can implement this. It's not very optimized but you'll get the idea on how I meant.
First of all this is my SortSelectedIndices
-method that I use:
static IEnumerable<T> SortSelectedIndices<T>(
IEnumerable<T> values,
IEnumerable<int> selectedIndices,
Func<IEnumerable<T>, IEnumerable<T>> sort)
{
var selectedValues = new List<T>();
for (var i = 0; i < selectedIndices.Count(); i++)
selectedValues.Add(values.ElementAt(selectedIndices.ElementAt(i)));
var sortedList = sort(selectedValues);
var finalList = new List<T>();
var startPositionFound = false;
for(var i = 0; i < values.Count(); i++)
{
if (selectedIndices.Contains(i))
{
if (startPositionFound) continue;
startPositionFound = true;
finalList.AddRange(sortedList);
}
else
finalList.Add(values.ElementAt(i));
}
return finalList;
}
Then I call it like this:
static void Main(string[] args)
{
var unsorted = new[] {3, 5, 6, 1, 2, 87, 432, 23, 46, 98, 44};
var selected = new[] {1, 4, 7};
Print(unsorted);
var sort = new Func<IEnumerable<int>, IEnumerable<int>>(
(x) => x.OrderBy(y => y).ToList());
var sorted = SortSelectedIndices(unsorted, selected, sort);
Print(sorted);
}
And this prints out the following:
{ 3,5,6,1,2,87,432,23,46,98,44 }
{ 3,2,5,23,6,1,87,432,46,98,44 }
I am just using a simple method here to print this out to the console:
static void Print<T>(IEnumerable<T> values)
{
Console.Write("{ ");
Console.Write(string.Join(",", values));
Console.WriteLine(" }");
}
So what you can do is to have a "sort"-button, when it's pressed you invoke SortSelectedIndices
and then rebind the list when you're done. Remember I have not profiled or refactored this code, it might not be as fast as you like, I just want to give you an idea on what you can do to acheive the solution.
Based on Filips definition of the problem, and his example as the correct answer, my somewhat less general (no generics, no Func) solution would be this:
Public Function SortSelectedIndices(unsortedList As IEnumerable(Of Integer), selectedIndices As IEnumerable(Of Integer)) As IEnumerable(Of Integer)
If Not selectedIndices.Any() Then
Return unsortedList
End If
Dim extractedValues = From s In selectedIndices Select unsortedList(s)
Dim sortedExtractedValues = From e In extractedValues Order By e
Dim listWithoutExtractedValues = unsortedList.Except(extractedValues)
Dim resultList = New List(Of Integer)(listWithoutExtractedValues)
Dim firstSelectedIndex = Aggregate s In selectedIndices Order By s Into First()
resultList.InsertRange(firstSelectedIndex, sortedExtractedValues)
Return resultList
End Function
Edit: Filip just pointed out that the question is tagged "C#". But there is no mention of this in the text, so I deem it's not that important. I also assume that any reader familiar with .NET can translate "dim" to "var" and the likes, all by himself. :-P
Definitely, the DataGridView cannot do this. So, the best solution would be to create a new collection (List), populate it with selected rows from the GridView and finally use its Sort method to order these rows. To sort data by a certain column, you can either write a custom IComparer class and pass it to the Sort method or use Linq:
var orderedList = someList.OrderBy(obj => obj.TimeStampField);
You can do any conceivable sort, directly in the DataGridView, by creating a custom IComparer, and passing it to DataGridView.Sort(IComparer). The trick is to figure out how to give that IComparer the needed info.
See How to: Customize Sorting in the Windows Forms DataGridView Control / Custom Sorting Using the IComparer Interface , specifically class RowComparer : System.Collections.IComparer
for details.
In your case, we must
a) associate each row with its original row number, and
b) indicate whether or not a row participates in the sort.
For the specific question, we want all the selected rows to stay after any rows before them, and before any rows after them. For example, if there are rows 0 - 9, and rows 3-7 are to be sorted, then rows 0-2 stay in their original order, as do rows 8 - 9. We can simplify the logic with a trick: assign the same row number to all the rows to be sorted, specifically the row number of the first such row. (3). Then when two rows have the same initial row number, sort on your sorting criteria.
One way to add this "extra" information to each row, is to add a "hidden" column to each row.
A second way is to put the information in a dictionary, and pass that in to your custom IComparer class.
(Untested) code for this way:
void SomeMethod()
{
DataGridView dgv = ...;
// Iterate through the rows, building the dictionary.
Dictionary<DataGridViewRow, int> rowToRowNumber = ...;
int iFirstSelected = -1;
for (int iRow = 0; iRow < dgv.Rows.Count; iRow++)
{
DataGridViewRow row = dgv.Rows[iRow];
int iAdjusted = iRow;
if (row.Selected)
{
if (iFirstSelected < 0)
iFirstSelected = iRow;
iAdjusted = iFirstSelected;
}
rowToRowNumber[row] = iAdjusted;
}
// Sort.
dgv.Sort(new RowComparer(rowToRowNumber));
}
private class RowComparer : System.Collections.IComparer
{
// Values are the row numbers before the sort was done,
// with all rows to sort set to the same value (the row number of the first row to sort).
Dictionary<DataGridViewRow, int> _rowToRowNumber;
public RowComparer(Dictionary<DataGridViewRow, int> rowToRowNumber)
{
_rowToRowNumber = rowToRowNumber;
}
public int Compare(object x, object y)
{
DataGridViewRow row1 = (DataGridViewRow)x;
DataGridViewRow row2 = (DataGridViewRow)y;
// Keep rows in order.
int result = _rowToRowNumber(row1).CompareTo(_rowToRowNumber(row2));
if (result != 0)
return result;
// Same row number, these are the rows to sort.
return CustomCompare(row1, row2);
}
private int CustomCompare(DataGridViewRow row1, DataGridViewRow row2)
{
// Replace with your custom logic.
// TODO: If cells contain strings, may have to parse them into the
// original data types, and compare those typed values rather than the cell values themselves.
return row1.Cells[1].Value.CompareTo(row2.Cells[1].Value);
}
}
NOTE: If the object from which a row was created is in row.Tag
, then it may be preferable to retrieve those typed objects, and do the comparison on their properties:
private int CustomCompare(DataGridViewRow row1, DataGridViewRow row2)
{
MyClass ob1 = (MyClass)(row1.Tag);
MyClass ob2 = (MyClass)(row2.Tag);
return ob1.MyProp.CompareTo(ob2.MyProp);
}
Don't have too much time to explain but this is what i did once.
var sortedSelectedRows = myGridControl.SelectedRows
.Cast<DataGridViewRow>()
.Select(x=> x.DataBoundItem)
.Cast<ItemModelClass>()
.OrderBy(x => x.TimeStamp)
.ToList();
精彩评论