简体   繁体   中英

Drag and Drop Multiple Columns in DataGridView

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM