2

I'm trying to get my first application up and running, but i'm struggling with drag and drop operations in the datagridview control.

I have created a datagrid view that has a datasource connected to it.

Public oBodyAssembly As New BindingList(Of BodyComponent)
DataGridView1.DataSource = oBodyAssembly

In this DataSource the users creates new objects and these are displayed in the datagridview. To allow the user to correct or alter his initial order of adding objects I would like to have them drag and drop rows to rearrange the position of the objects in the grid and also in the DataSource.

I have tried this example code I have found written in C# and altered it to VB.NET, it works in the fact that I can determinate the row I drag and determinate the position of the drop. Link to the example code

But then the code in the sample inserts a new row and removes the old. This doesn't work for me. The removing works fine, and the object is also deleted from my DataSource. The inserting of the new row on the other hand doesn't.

My datasource is a BindingList(Of BodyComponent) It only contains object that are derived from the BodyComponent class.

How can I get this operation to work? I'm stuck..

Here is the code I have so far for the drag and drop operation.

    Public oRowIndexMouseDown As Integer
Public oRow As DataGridViewRow

Private Sub BodyAssemblyDrag_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) _
    Handles DataGridView1.MouseDown
    If DataGridView1.SelectedRows.Count = 1 Then
        If e.Button = MouseButtons.Left Then
            oRow = DataGridView1.SelectedRows(0)
            oRowIndexMouseDown = DataGridView1.SelectedRows(0).Index
            'Debug.Print("Row to move = " & oRowIndexMouseDown)

            DataGridView1.DoDragDrop(sender, DragDropEffects.Move)
        End If
    End If
End Sub

Private Sub BodyAssemblyDrag_dragenter(ByVal sender As Object, ByVal e As DragEventArgs) Handles DataGridView1.DragEnter
    If DataGridView1.SelectedRows.Count = 1 Then
        e.Effect = DragDropEffects.Move
    End If
End Sub

Private Sub BodyAssemblyDrag_dragdrop(ByVal sender As Object, ByVal e As DragEventArgs) Handles DataGridView1.DragDrop
    Dim oPoint As Point
    oPoint = DataGridView1.PointToClient(New Point(e.X, e.Y))

    Dim oRowIndexMouseDrop As Integer
    oRowIndexMouseDrop = DataGridView1.HitTest(oPoint.X, oPoint.Y).RowIndex

    'Debug.Print("Drop row @ " & oRowIndexMouseDrop)

    If Not oRowIndexMouseDrop = oRowIndexMouseDown Then
        'DataGridView1.Rows.RemoveAt(oRowIndexMouseDown)
        'DataGridView1.Rows.Insert(oRowIndexMouseDrop, oRow)
    End If
End Sub

Screenshot of winform

Add: method of creating objects in the list.

    Public oBodyAssembly As New List(Of BodyComponent)


Private Sub BTN_BODY_ADD_CILINDER_Click(sender As Object, e As EventArgs) Handles BTN_BODY_ADD_CILINDER.Click

    ' Create a new cylinder and add it into the oBodyAssembly
    Dim oCylinder As New Body_Cylinder
    oBodyAssembly.Add(oCylinder)

    ' Set the index number for this cylinder
    oCylinder.Index = oBodyAssembly.Count

    ' Set the component type
    oCylinder.Type = BodyComponent.BodyComponentType.Cylinder

End Sub

Private Sub BTN_BODY_ADD_CONE_Click(sender As Object, e As EventArgs) Handles BTN_BODY_ADD_CONE.Click

    ' Create a new cone and add it into the oBodyAssembly
    Dim oCone As New Body_Cone
    oBodyAssembly.Add(oCone)

    ' Set the index number for this cylinder
    oCone.Index = oBodyAssembly.Count

    ' Set the component type
    oCone.Type = BodyComponent.BodyComponentType.Cone_reduction

End Sub

Classes:

Public Class BodyComponent

' Basic properties that are required for all of the bodycompenents
' regardless of the type.
Public Property Index() As Double
Public Property Type() As BodyComponentType
Public Property Height() As Double
Public Property Thickness() As Double
Public Property Elevation() As Double
Private Property Mass() As Double

' Type Enum that defines what kind of body component is created.
Public Enum BodyComponentType
    Cylinder = 0001
    Cone_reduction = 0002
End Enum End Class

Derived object ( same for cone )

Public Class Body_Cylinder

' Get the base properties
Inherits BodyComponent

' Set new properties that are only required for cylinders
Public Property Segments() As Integer
Public Property LW_Orientation() As Double End Class
Mech_Engineer
  • 535
  • 1
  • 19
  • 46
  • You are going to need a different collection. You cant sort or reorder a BindingList. Are you sure you want to initiate a DargDrop whenever the mouse is down? – Ňɏssa Pøngjǣrdenlarp Mar 16 '16 at 13:36
  • Thank you for the fast reply, What collection would you suggest then? I'm not very familiar with the collection types.. And no it should not initiate a dragdrop whenever the mouse is down. It should be down and moving? But I haven't done a thing like that before know how tho write it. – Mech_Engineer Mar 16 '16 at 13:50

1 Answers1

2

First, since a BindingList cannot be sorted or ordered (without recreating the whole collection), I would use a simple List(Of T) with a BindingSource:

' Form level declarations:
Private Animals As List(Of AnimalEx)
Private BSAnimal As BindingSource

Then, once the list is created:

Animals = New List(Of AnimalEx)
' add Animals aka BodyComponent objects, then...
BSAnimal = New BindingSource(Animals, Nothing)
dgv.DataSource = BSAnimal

You will have to learn some new methods to manage the data. Since now, the List holds the data but the BindingSource provides the binding capabilities, some things you do to the List and some thru the BindingSource.


As for the row drag-drop, the code in this answer is a nice starting point, but there are a few things lacking. It doesnt account for a) A bound DGV, b) Users trying to drag the NewRow, c) users clicking on Non-Row areas of the DGV (empty/open portions) d) Allow the mouse to do other things like resize columns. I fixed those, but there may be other mouse ops to exempt.

' Form-level declarations
Private fromIndex As Integer = -1
Private bMouseDn As Boolean = False
Private MouseDnPt As Point = Point.Empty

Private Sub dgv_DragOver(sender As Object, e As DragEventArgs) Handles dgv.DragOver
    e.Effect = DragDropEffects.Move
End Sub

Private Sub dgv_MouseDown(sender As Object, e As MouseEventArgs) Handles dgv.MouseDown
    bMouseDn = (e.Button = Windows.Forms.MouseButtons.Left)
End Sub

Private Sub dgv_MouseMove(sender As Object, e As MouseEventArgs) Handles dgv.MouseMove
    If bMouseDn Then
        ' first time, just grab the start location
        If (MouseDnPt = Point.Empty) Then
            MouseDnPt = e.Location
            Exit Sub
        End If
    End If
    If bMouseDn AndAlso MouseDnPt <> Point.Empty Then
        Dim hitTst = dgv.HitTest(e.X, e.Y)
        If hitTst IsNot Nothing AndAlso fromIndex = -1 AndAlso hitTst.RowIndex > -1 Then
            fromIndex = hitTst.RowIndex

            If dgv.Rows(fromIndex).IsNewRow = False Then
                dgv.DoDragDrop(dgv.Rows(fromIndex), DragDropEffects.Move)
            End If
        End If
    End If
End Sub

Private Sub dgv_MouseUp(sender As Object, e As MouseEventArgs) Handles dgvDD.MouseUp
    If bMouseDn AndAlso (e.Button = Windows.Forms.MouseButtons.Left) Then
        bMouseDn = False
    End If
End Sub

I used a simple Point in place of the Rectangle, it tests for non-row area clicks and only begins to drag when the mouse moves and has the left button down. It also declines to DragDrop the NewRow.

Like the original version, it is dragging a DataGridViewRow. But since we want (must) change the DataSource, not the DGV rows, we have to get the item back from the DataSource:

Private Sub dgv_DragDrop(sender As Object, e As DragEventArgs) Handles dgv.DragDrop

    Dim p As Point = dgv.PointToClient(New Point(e.X, e.Y))
    Dim dragIndex = dgv.HitTest(p.X, p.Y).RowIndex
    If (e.Effect = DragDropEffects.Move) Then
        ' cast to a row
        Dim dragRow As DataGridViewRow = CType(e.Data.GetData(GetType(DataGridViewRow)), 
                                  DataGridViewRow)
        ' get related Animal object
        Dim a As AnimalEx = CType(dragRow.DataBoundItem, AnimalEx)

        ' manipulate DataSource:
        BSAnimal.RemoveAt(fromIndex)
        BSAnimal.Insert(dragIndex, a)

        ' if the DGV is SingleSelect, you may want:
        'dgv.Rows(dragIndex).Selected = True

        ' we are done dragging
        bMouseDn = False
        fromIndex = -1
        MouseDnPt = Point.Empty
    End If

End Sub

Result:

enter image description hereenter image description here

The "non row" area mentioned is the yellowish areas.

Community
  • 1
  • 1
Ňɏssa Pøngjǣrdenlarp
  • 38,411
  • 12
  • 59
  • 178
  • Hi, i'm currently trying the List(of.. ) but it's not working for me, as I would like. When I create the databinding, `Public oBodyAssembly As List(Of BodyComponent) Private BSAnimal As BindingSource Public Sub New() ' This call is required by the designer. InitializeComponent() ' Add assembly to body grid. oBodyAssembly = New List(Of BodyComponent) BSAnimal = New BindingSource(oBodyAssembly, Nothing) DataGridView1.DataSource = BSAnimal End Sub` and then add a new object into the list(of ..) it's not visible in the dgv? – Mech_Engineer Mar 17 '16 at 19:15
  • As best i can tell from unformatted code, there are no items in your list. Note that the answer sets the DGV Datasource *after* the list has items. The BindingSource 'fills in' some of the role you had with BindingList. use `BSAnimal.Add(New AnimalEx With {...})` to add items to the List/BindingSource – Ňɏssa Pøngjǣrdenlarp Mar 17 '16 at 19:35
  • Okay, I added my code for the creation of item in the list, but I got it now, If I add the item via the bindingsource it works. and as you can see in my edited post I was adding them into my list directly. – Mech_Engineer Mar 17 '16 at 20:08
  • It actually ends up in the list, you just need to go thru the BS so it can be aware to update the UI. – Ňɏssa Pøngjǣrdenlarp Mar 17 '16 at 20:13
  • I also tested the drag and drop code and that works as I would like to have it working. Just one more thing, the objects in my list are derived classes from the base class that is populating the dgv. Am I loosing data on these items by doing this? – Mech_Engineer Mar 17 '16 at 20:25
  • Not lost, but the more specific properties wont be accessible unless/until you cast them back to that Type. Please click the checkmark next to the answer if we solved these issues. – Ňɏssa Pøngjǣrdenlarp Mar 17 '16 at 20:31
  • I have spend some time using this drag and drop as you provided it, at first it works 100% fine but ... when i make a right mouse button click I'm not able to perform a drag/drop anymore, this also occurs after entering values into the view. This is kind of annoying and I can't figure out why it's behaving like this. – Mech_Engineer Mar 23 '16 at 10:53
  • Posted a change to the `MouseUp` event. Since you can vote now, please upvote it. – Ňɏssa Pøngjǣrdenlarp Mar 23 '16 at 13:25
  • 1
    I think you accidently didn't include the mouse up event when editing? – Mech_Engineer Mar 23 '16 at 15:08