Move up or down small, complex rows of a Multi-select DataGridView
I have a DataGridView like this:
Now, in C # , or VB.Net , using a button, I would like to move up or down one position at a time the selected rows, how could I do that ?. Multiple choice requires making it harder for me.
The files that appear in the DataGridView will be merged into one executable file, then the files will be executed in the order of the Order column , so " Order " should be unavailable when moving rows up or down.
I am not using any data source.
I tried to parse this sample from MSDN , it consists of some extension methods, but it requires a data source and data source, I don't have any problems using a data source and data source, but I just don't know how to adapt the code sample for mine DataGridView. The sample doesn't support multiple choice anyway:
Move rows up / down and remember data binding order of DataGridView and ListBoxs
I've also seen some C # questions about this on StackOverflow, but they ask for a single row selection:
How to move selected grid row up / down by pressing KeyUp or Keydown key
Selected row DataGridView Move UP and DOWN
Then I don't have a starting point, just these methods move the SINGLE string, which also does not store the Order value , if someone can help me extend the functionality for my needs: Private Sub Button_MoveUp_Click (sender as object, e As EventArgs) _ Button_MoveUp handles. Click
Me.MoveUpSelectedRows(Me.DataGridView_Files)
End Sub
Private Sub Button_MoveDown_Click(sender As Object, e As EventArgs) _
Handles Button_MoveDown.Click
Me.MoveDownSelectedRows(Me.DataGridView_Files)
End Sub
Private Sub MoveUpSelectedRows(ByVal dgv As DataGridView)
Dim curRowIndex As Integer = dgv.CurrentCell.RowIndex
Dim newRowIndex As Integer = curRowIndex - 1
Dim curColIndex As Integer = dgv.CurrentCell.ColumnIndex
Dim curRow As DataGridViewRow = dgv.CurrentRow
If (dgv.SelectedCells.Count > 0) AndAlso (newRowIndex >= 0) Then
With dgv
.Rows.Remove(curRow)
.Rows.Insert(newRowIndex, curRow)
.CurrentCell = dgv(curColIndex, newRowIndex)
End With
End If
End Sub
Private Sub MoveDownSelectedRows(ByVal dgv As DataGridView)
Dim curRowIndex As Integer = dgv.CurrentCell.RowIndex
Dim newRowIndex As Integer = curRowIndex + 1
Dim curColIndex As Integer = dgv.CurrentCell.ColumnIndex
Dim curRow As DataGridViewRow = dgv.CurrentRow
If (dgv.SelectedCells.Count > 0) AndAlso (dgv.Rows.Count > newRowIndex) Then
With dgv
.Rows.Remove(curRow)
.Rows.Insert(newRowIndex, curRow)
.CurrentCell = dgv(curColIndex, newRowIndex)
End With
End If
End Sub
UPDATE
I'm trying to use @Plutonix's method (with a little modification), the only problem is it doesn't move the correctly selected rows in the UP direction.
Steps to fix the problem:
-
Select two rows that are together (e.g. row index 2 and row index 3, NOT row index 2 and row index 4)
-
Try to move the lines UP.
How can I fix this?
Public Enum MoveDirection As Integer
Up = -1
Down = 1
End Enum
Private Sub MoveRows(ByVal dgv As DataGridView, ByVal moveDirection As MoveDirection)
Dim rows As DataGridViewRowCollection = dgv.Rows
' row index
Dim thisRow As DataGridViewRow
' put selection back
Dim selectedRows As New List(Of Integer)
' max rows
Dim lastRowIndex As Integer =
If(dgv.AllowUserToAddRows,
rows.Count - 2,
rows.Count - 1)
For n As Integer = lastRowIndex To 0 Step -1
If Not rows(n).IsNewRow Then
If rows(n).Selected Then
selectedRows.Add(n)
MsgBox(n)
Select Case moveDirection
Case Main.MoveDirection.Down
If ((n + moveDirection) <= lastRowIndex) AndAlso (n + moveDirection >= 0) AndAlso rows(n + moveDirection).Selected = False Then
selectedRows(selectedRows.Count - 1) = (n + moveDirection)
thisRow = rows(n)
rows.Remove(thisRow)
rows.Insert(n + moveDirection, thisRow)
End If
Case Main.MoveDirection.Up
If ((n + moveDirection) <= lastRowIndex) Then
MsgBox(selectedRows(selectedRows.Count - 1))
selectedRows(selectedRows.Count - 1) = (n + moveDirection)
thisRow = rows(n)
rows.Remove(thisRow)
rows.Insert(n + moveDirection, thisRow)
End If
End Select
End If
End If
Next n
' reselect the original selected rows
For n As Integer = 0 To lastRowIndex
dgv.Rows(n).Selected = selectedRows.Contains(n)
' renumber the order (optional & unknown, but trivial)
dgv.Rows(n).Cells(0).Value = (n + 1)
Next n
End Sub
source to share
(Updated)
You need to iterate through the collection of strings, check if they are selected, and then swap the strings. In order for two selected rows to switch with each other when they hit the top or bottom, a separate Up and Down method is needed.
' list of hash codes of the selected rows Private Function GetSelectedRows() As List(Of Integer) Dim selR As New List(Of Integer) ' have to clear selected so the NEXT row ' doesnt cause odd behavior For n As Integer = 0 To dgv.Rows.Count - 1 If dgv.Rows(n).IsNewRow = False AndAlso dgv.Rows(n).Selected Then selR.Add(dgv.Rows(n).GetHashCode) dgv.Rows(n).Selected = False End If Next Return selR End Function ' restore original selected rows Private Sub SetSelectedRows(selRows As List(Of Integer)) For n As Integer = 0 To dgv.Rows.Count - 1 If dgv.Rows(n).IsNewRow = False Then dgv.Rows(n).Selected = selRows.Contains(dgv.Rows(n).GetHashCode) ' reset Order col: dgv.Rows(n).Cells(0).Value = n + 1 End If Next End Sub Private Sub MoveRowsUp() ' short ref Dim rows As DataGridViewRowCollection = dgv.Rows ' row index Dim thisRow As DataGridViewRow ' put selection back Dim selectedRows As List(Of Integer) ' max rows Dim LastRow = If(dgv.AllowUserToAddRows, rows.Count - 2, rows.Count - 1) selectedRows = GetSelectedRows() For n As Int32 = 0 To LastRow If rows(n).IsNewRow = False Then If (selectedRows.Contains(rows(n).GetHashCode)) AndAlso (n - 1 >= 0) AndAlso (selectedRows.Contains(rows(n - 1).GetHashCode) = False) Then thisRow = rows(n) rows.Remove(thisRow) rows.Insert(n - 1, thisRow) End If End If Next SetSelectedRows(selectedRows) End Sub Private Sub MoveRowsDn() Dim rows As DataGridViewRowCollection = dgv.Rows Dim thisRow As DataGridViewRow Dim selectedRows As New List(Of Integer) Dim LastRow = If(dgv.AllowUserToAddRows, rows.Count - 2, rows.Count - 1) selectedRows = GetSelectedRows() For n As Int32 = LastRow To 0 Step -1 If rows(n).IsNewRow = False Then If (selectedRows.Contains(rows(n).GetHashCode)) AndAlso (n + 1 <= LastRow) AndAlso (selectedRows.Contains(rows(n + 1).GetHashCode) = False) Then thisRow = rows(n) rows.Remove(thisRow) rows.Insert(n + 1, thisRow) End If End If Next SetSelectedRows(selectedRows) End Sub
Using:
MoveRowsUp() ' move down: MoveRowsDn()
Moving around the lines causes the dgv to be reset. The methods first go through and get a list of HashCodes from the selected strings, and then at the end resets each property Row.Selected
based on that.
The code changes the value of Cell (0) to match the order and order of the order (which means that the value of the Order column has been changed).
Move checks to see both if this line is at the end OR if a line at destination is also selected. This prevents the strings from moving from under the swap when they hit the top or bottom.
Before:
After:
Note that when Zalgo got to the bottom (meaning 3 and 5 were selected), the other could still move down one by one, so this happened while Ziggy stayed in place. The ability of "Ziggy (3)" to move down or not is based on the next line / index (only) - the next line is not behind the bottom AND is not selected, so it can still move down 1, whereas "Zalgo (5))" is not managed.
source to share
This is my final code, all thanks to @Plutonix, I just translated the logic into extension methods and also extended the original functionality to automate the saving of cells by providing a set of cell indices to store their values:
#Region " Members Summary "
' ยท Public Methods
'
' MoveSelectedRows(direction)
' MoveSelectedRows(direction, preserveCellsIndex)
#End Region
#Region " Option Statements "
Option Strict On
Option Explicit On
Option Infer Off
#End Region
#Region " Imports "
Imports System.Diagnostics
Imports System.Runtime.CompilerServices
Imports System.Windows.Forms
#End Region
''' <summary>
''' Contains sofisticated extension methods for a <see cref="DataGridView"/> control.
''' </summary>
''' <remarks></remarks>
Public Module DataGridViewExtensions
#Region " Enumerations "
''' <summary>
''' Specifies a direction for a move operation of a rows collection.
''' </summary>
Public Enum RowMoveDirection As Integer
''' <summary>
''' Move row up.
''' </summary>
Up = 0
''' <summary>
''' Move row down.
''' </summary>
Down = 1
End Enum
#End Region
#Region " Public Extension Methods "
''' <summary>
''' Moves up or down the selected row(s) of the current <see cref="DataGridView"/>.
''' </summary>
''' <param name="sender">The <see cref="DataGridView"/>.</param>
''' <param name="direction">The row-move direction.</param>
<DebuggerStepThrough>
<Extension>
Public Sub MoveSelectedRows(ByVal sender As DataGridView,
ByVal direction As RowMoveDirection)
DoRowsMove(sender, direction)
End Sub
''' <summary>
''' Moves up or down the selected row(s) of the current <see cref="DataGridView"/>.
''' </summary>
''' <param name="sender">The <see cref="DataGridView"/>.</param>
''' <param name="direction">The row-move direction.</param>
''' <param name="preserveCellsIndex">A sequence of cell indexes to preserve its cell values when moving the row(s).</param>
<DebuggerStepThrough>
<Extension>
Public Sub MoveSelectedRows(ByVal sender As DataGridView,
ByVal direction As RowMoveDirection,
ByVal preserveCellsIndex As IEnumerable(Of Integer))
DoRowsMove(sender, direction, preserveCellsIndex)
End Sub
#End Region
#Region " Private Methods "
''' <summary>
''' Moves up or down the selected row(s) of the specified <see cref="DataGridView"/>.
''' </summary>
''' <param name="dgv">The <see cref="DataGridView"/>.</param>
''' <param name="direction">The row-move direction.</param>
''' <param name="preserveCellsIndex">Optionally, a sequence of cell indexes to preserve its cell values when moving the row(s).</param>
<DebuggerStepThrough>
Private Sub DoRowsMove(ByVal dgv As DataGridView,
ByVal direction As RowMoveDirection,
Optional ByVal preserveCellsIndex As IEnumerable(Of Integer) = Nothing)
' Keeps tracks of a cell value to preserve, to swap them when moving rows.
Dim oldCellValue As Object
Dim newCellValue As Object
' Short row collection reference.
Dim rows As DataGridViewRowCollection = dgv.Rows
' Keeps track of the current row.
Dim curRow As DataGridViewRow
' The maximum row index.
Dim lastRowIndex As Integer =
If(dgv.AllowUserToAddRows,
rows.Count - 2,
rows.Count - 1)
' List of hash codes of the selected rows.
Dim selectedRows As New List(Of Integer)
' Get the hash codes of the selected rows
For i As Integer = 0 To (rows.Count - 1)
If (rows(i).IsNewRow = False) AndAlso (rows(i).Selected) Then
selectedRows.Add(rows(i).GetHashCode)
rows(i).Selected = False
End If
Next i
' Move the selected rows up or down.
Select Case direction
Case RowMoveDirection.Up
For i As Integer = 0 To lastRowIndex
If Not rows(i).IsNewRow Then
If (selectedRows.Contains(rows(i).GetHashCode)) AndAlso
(i - 1 >= 0) AndAlso
(Not selectedRows.Contains(rows(i - 1).GetHashCode)) Then
curRow = rows(i)
rows.Remove(curRow)
rows.Insert(i - 1, curRow)
If preserveCellsIndex IsNot Nothing Then
For Each cellIndex As Integer In preserveCellsIndex
oldCellValue = curRow.Cells(cellIndex).Value
newCellValue = rows(i).Cells(cellIndex).Value
rows(i).Cells(cellIndex).Value = oldCellValue
curRow.Cells(cellIndex).Value = newCellValue
Next cellIndex
End If
End If
End If
Next i
Case RowMoveDirection.Down
For i As Integer = lastRowIndex To 0 Step -1
If Not rows(i).IsNewRow Then
If (selectedRows.Contains(rows(i).GetHashCode)) AndAlso
(i + 1 <= lastRowIndex) AndAlso
(Not selectedRows.Contains(rows(i + 1).GetHashCode)) Then
curRow = rows(i)
rows.Remove(curRow)
rows.Insert(i + 1, curRow)
If preserveCellsIndex IsNot Nothing Then
For Each cellIndex As Integer In preserveCellsIndex
oldCellValue = curRow.Cells(cellIndex).Value
newCellValue = rows(i).Cells(cellIndex).Value
rows(i).Cells(cellIndex).Value = oldCellValue
curRow.Cells(cellIndex).Value = newCellValue
Next cellIndex
End If
End If
End If
Next i
End Select
' Restore selected rows.
For i As Integer = 0 To (rows.Count - 1)
If Not rows(i).IsNewRow Then
rows(i).Selected = selectedRows.Contains(rows(i).GetHashCode)
End If
Next i
End Sub
#End Region
End Module
source to share