I have a program with a DataGridView that my users want to be able to drag and drop multiple columns within.
Dragging and Dropping a single columns is easy, that's builtin behavior. But I cannot find any setting that allows selecting multiple (adjacent) columns and then dragging them to another column index.
Is there a setting for this? If not, has anyone ever done this before and/or how can it be done?
(I have tried to Google this extensively, but could not find anything that specifically matched my question)
From the answers and comments already received, things seem to be going in the wrong directions, so I want to clarify what the problems are:
Dragging and dropping a single column is not a problem, as indicated above, I already know how to do that
Neither is dragging and dropping a single row a problem. Although different from dragging and dropping a column, I do know how to do this.
Dragging and dropping multiple columns (or rows) is the problem, ...
Specifically , how to select the multiple columns and then initiate the DragDrop
What is not obvious until you actually try to do it is that the default DataGridView UI semantics of selecting a single column (or row) for DragDrop cannot work for dragging and dropping multiple columns (or rows). That is what I am looking for a solid example of how to do. I can probably handle the Drop-completion stuff just fine on my own. It's the multiple-selection and Drag- initiation that's the problem.
It should be possible. I've done it for dragging and dropping rows between grids so the same principles should apply. This isn't a solution but it is the building block that should, hopefully, allow you to develop one.
Firstly, you need to handle the MouseMove event to see if a Drag is happening:
AddHandler dgv.MouseMove, AddressOf CheckForDragDrop
Here's my CheckForDragDrop code. You would need to check to see if columns are selected to detect whether a drag is starting.
Private Sub CheckForDragDrop(sender As Object, e As MouseEventArgs)
If e.Button = MouseButtons.Left Then
Dim ht As DataGridView.HitTestInfo = Me.HitTest(e.X, e.Y)
If (ht.Type = DataGridViewHitTestType.RowHeader OrElse ht.Type = DataGridViewHitTestType.Cell) AndAlso Me.Rows(ht.RowIndex).Selected Then
_ColumnDragInProgress = True
If Me.SelectedRows.Count > 1 Then
Me.DoDragDrop(Me.SelectedRows, DragDropEffects.All)
ElseIf Me.CurrentRow IsNot Nothing Then
Me.DoDragDrop(Me.CurrentRow, DragDropEffects.All)
End If
End If
End If
End Sub
You also need to handle the DragEnter and DragDrop events:
AddHandler dgv.DragEnter, AddressOf PerformDragEnter
AddHandler dgv.DragDrop, AddressOf PerformDragDrop
Here's sample code, but remember I'm dragging rows between grids, not columns within a grid:
Private Sub PerformDragEnter(sender As Object, e As DragEventArgs)
If e.Data.GetDataPresent(GetType(DataGridViewRow)) OrElse e.Data.GetDataPresent(GetType(DataGridViewSelectedRowCollection)) Then
e.Effect = DragDropEffects.Copy
End If
End Sub
Private Sub PerformDragDrop(sender As Object, e As DragEventArgs)
Dim dscreen As Point = New Point(e.X, e.Y)
Dim dclient As Point = Me.PointToClient(dscreen)
Dim HitTest As HitTestInfo = Me.HitTest(dclient.X, dclient.Y)
'If dropped onto an Existing Row, use that Row as Sender - otherwise Sender=This DGV.
If HitTest.RowIndex >= 0 Then _Dropped_RowHit = Me.Rows(HitTest.RowIndex) Else _Dropped_RowHit = Nothing
Dim DroppedRows As New List(Of DataGridViewRow)
If e.Data.GetDataPresent(GetType(DataGridViewRow)) Then
DroppedRows.Add(e.Data.GetData(GetType(DataGridViewRow)))
ElseIf e.Data.GetDataPresent(GetType(DataGridViewSelectedRowCollection)) Then
Dim Rows As DataGridViewSelectedRowCollection = e.Data.GetData(GetType(DataGridViewSelectedRowCollection))
For Each rw As DataGridViewRow In Rows
DroppedRows.Add(rw)
Next
Else
DroppedRows = Nothing
Exit Sub
End If
If DroppedRows(0).DataGridView Is Me Then Exit Sub
e.Data.SetData(DroppedRows)
_DraggedFrom_Name = DroppedRows(0).DataGridView.Name
'Drop occurred, add your code to handle it here
End Sub
I know this doesn't answer the exact question you asked, but hopefully it'll get you started along the right direction.
* UPDATE *
Thinking again about this, I believe this could be simpler.
Firstly, create your own custom DataGridView that inherits DataGridView like this:
Public Class CustomDGV
Inherits DataGridView
Set the DGV SelectionMode to DataGridViewSelectionMode.ColumnHeaderSelect or whatever mode you prefer to allow for column selection
Create some local variables to track whether drag in progress and what's being moved, eg:
Dim ColDragInProgress as Boolean = False
Dim SelectedColumns as new List(Of Integer)
then Override the MouseDown and MouseUp events to handle column drag:
The MouseDown event needs to test whether you've clicked on a selected column. If so, you could be looking to drag so flag that and record all selected columns (in whatever way you prefer):
Protected Overrides Sub OnMouseDown(e As MouseEventArgs)
If e.Button = MouseButtons.Left Then
Dim ht As DataGridView.HitTestInfo
ht = Me.HitTest(e.X, e.Y)
If ht.ColumnIndex>=0 AndAlso Me.Columns(ht.ColumnIndex).Selected Then
ColDragInProgress = True
SelectedColumns = StoreAllSelectedCols()
Else
ColDragInProgress = False
MyBase.OnMouseDown(e)
End If
Else
MyBase.OnMouseDown(e) 'in all other cases call the base function
End If
End Sub
and then handle the MouseUp event to see if a drag has actually taken place. I've assumed it has if you MouseUp on an unselected column. You could of course just record the initial column and check whether the mouse up is on a different column, even if selected.
Protected Overrides Sub OnMouseUp(ByVal e As MouseEventArgs)
If e.Button = Windows.Forms.MouseButtons.Right AndAlso ColDragInProgress Then
ColDragInProgress = False
Dim ht As DataGridView.HitTestInfo
ht = Me.HitTest(e.X, e.Y)
If ht.ColumnIndex>=0 AndAlso Not Me.Columns(ht.ColumnIndex).Selected Then 'On a Not selected Column so assume Drag Complete
Dim MoveToColumn As Integer = ht.ColumnIndex
PerformMovedColsToNewPosition(MoveToColumn, SelectedColumns) 'Your code to reorder cols as you want
Else
MyBase.OnMouseDown(e)
End If
Else
MyBase.OnMouseUp(e) 'now call the base Up-function
End If
End Sub
Finally you can override the OnMouseMove to display the correct mouse pointer when dragging like this:
Protected Overrides Sub OnMouseMove(e As System.Windows.Forms.MouseEventArgs)
If ColDragInProgress AndAlso e.Button = Windows.Forms.MouseButtons.Left Then
Dim dropEffect As DragDropEffects = Me.DoDragDrop(SelectedColumns, DragDropEffects.Copy)
Else
MyBase.OnMouseMove(e) 'let's do the base class the rest
End If
End Sub
Other Idea is to use OnColumnDisplayIndexChanged to find when one of selected column was move, Remember Position selected columns before. And after place them at position of column that we drag. For this case we need switch selection mode from FullRow/Cell in to column but this will work only with sort mode DataGridViewColumnSortMode.Programmatic maybe that help you Default key is "Alt" to drag and drop coulmn in this mode
Dim newGrid As New MultiCollOrder
newGrid.Columns.Add("col_1", "col_1")
newGrid.Columns.Add("col_2", "col_2")
newGrid.Columns.Add("col_3", "col_3")
newGrid.Columns.Add("col_4", "col_4")
newGrid.Columns.Add("col_5", "col_5")
newGrid.Rows.Add({"1", "2", "3", "4", "5"})
newGrid.Rows.Add({"1a", "2a", "3a", "4a", "5a"})
newGrid.Rows.Add({"1b", "2b", "3b", "4b", "5b"})
newGrid.AllowUserToOrderColumns = True
Me.Controls.Add(newGrid)
newGrid.Dock = Windows.Forms.DockStyle.Fill
Public Class MultiCollOrder
Inherits System.Windows.Forms.DataGridView
Private _NewOrder As List(Of Integer) 'New Order
Private _orgSelectedOrder As New List(Of Integer) 'orginal order
Protected Overrides Sub OnCellClick(e As DataGridViewCellEventArgs)
If e.RowIndex >= 0 Then
'for rows
Me.SelectionMode = DataGridViewSelectionMode.FullRowSelect
For Each ecol As DataGridViewColumn In Me.Columns
ecol.SortMode = DataGridViewColumnSortMode.Automatic
Next
Else
'for column
_orgSelectedOrder.Clear() '
For Each ecol As DataGridViewColumn In (From esor As DataGridViewColumn In Me.SelectedColumns Order By esor.DisplayIndex Ascending)
_orgSelectedOrder.Add(ecol.Index)
Next
End If
MyBase.OnCellClick(e)
End Sub
Protected Overrides Sub OnColumnHeaderMouseClick(e As DataGridViewCellMouseEventArgs)
For Each ecol As DataGridViewColumn In Me.Columns
ecol.SortMode = DataGridViewColumnSortMode.Programmatic
Next
Me.SelectionMode = DataGridViewSelectionMode.ColumnHeaderSelect
MyBase.OnColumnHeaderMouseClick(e)
End Sub
Protected Overrides Sub OnColumnDisplayIndexChanged(e As DataGridViewColumnEventArgs)
If Me.SelectedColumns.Count > 1 And e.Column.Selected And _NewOrder Is Nothing Then
_NewOrder = New List(Of Integer)
For Each ec As DataGridViewColumn In (From esor As DataGridViewColumn In Me.Columns Order By esor.DisplayIndex Ascending)
If ec.Index = e.Column.Index Then
For Each esc In _orgSelectedOrder
_NewOrder.Add(esc)
Next
Else
If ec.Selected = False Then _NewOrder.Add(ec.Index)
End If
Next
End If
MyBase.OnColumnDisplayIndexChanged(e)
End Sub
Protected Overrides Sub OnPaint(e As PaintEventArgs)
If Not _NewOrder Is Nothing Then ' Apply New order
For x As Integer = 0 To _NewOrder.Count - 1
Me.Columns(_NewOrder(x)).Selected = False
Me.Columns(_NewOrder(x)).DisplayIndex = x
Next
_NewOrder = Nothing
End If
MyBase.OnPaint(e)
End Sub
End Class
The way the UI is programmed for DataGridView doesn't support an intuitive way to do this, because if you left-click inside the selection, it changes the selection.
Your best bet might be to check e.Button
in the DataGridView.CellMouseDown
event for a right- or middle- button click. You can then access the .SelectedCells
property to store the column selection, then perform the 'drop' operation in the DataGridView.CellMouseUp
event.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.