Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ListView Control Drag and Drop

Tags:

excel

vba

I am trying to use a ListView control for a drag and drop event. I want to drag an item from position 1 to somewhere else...say, position 5 (there are no subitems). But when I do that it does nothing. But, actually, when I step through the code the remove method removes the item. But it places right back in the same place so it looks like it does nothing. I needed to add the APIs according to here because it would always place it in the first position.

I got the code from here before researching and adding the API (which I thought was the issue) and tried to tailor it to my specific need, but I can't get it to work. I am running 32-bit Excel.

Global Constants and Handles

'Windows API Constants
Public Const LOGPIXELSX = 88
Public Const LOGPIXELSY = 90

'Windows API Function Declarations

'Get a handle to the Device Context (a drawing layer) for a window
Public Declare Function GetDC Lib "user32" (ByVal hWnd As Long) As Long

'Get the capabilities of a device, from its Device Context
Public Declare Function GetDeviceCaps Lib "gdi32" (ByVal hDC As Long, _
ByVal nIndex As Long) As Long

'Release the handle to the Device Context, to tidy up
Public Declare Function ReleaseDC Lib "user32" (ByVal hWnd As Long, _
ByVal hDC As Long) As Long

Drag and Drop Event

Private Sub lvSortableColumn_OLEDragDrop(Data As MSComctlLib.DataObject, Effect As Long, Button As Integer, Shift As Integer, x As Single, y As Single)

     Dim item As MSComctlLib.ListItem
     Dim lngXPixelsPerInch As Long, lngYPixelsPerInch As Long
     Dim lngDeviceHandle As Long

     'We must determine the Pixels per Inch for the display device.
     lngDeviceHandle = GetDC(0)
     lngXPixelsPerInch = GetDeviceCaps(lngDeviceHandle, LOGPIXELSX)
     lngYPixelsPerInch = GetDeviceCaps(lngDeviceHandle, LOGPIXELSY)
     ReleaseDC 0, lngDeviceHandle

     LVDragDropSingle lvSortableColumn, x * 1440 / lngXPixelsPerInch, y * 1440 / lngYPixelsPerInch

End Sub

Procedure

Public Sub LVDragDropSingle(ByRef lvList As ListView, ByVal x As Single, ByVal y As Single)

    'Item being dropped
    Dim objDrag As ListItem
    'Item being dropped on
    Dim objDrop As ListItem
    'Item being readded to the list
    Dim objNew As ListItem
    'Drop position
    Dim intIndex As Integer

    'Retrieve the original items
    Set objDrop = lvList.HitTest(x, y)
    Set objDrag = lvList.SelectedItem
    If (objDrop Is Nothing) Or (objDrag Is Nothing) Then
        Set lvList.DropHighlight = Nothing
        Set objDrop = Nothing
        Set objDrag = Nothing
        Exit Sub
    End If

    'Retrieve the drop position
    intIndex = objDrop.Index

    'Remove the dragged item
    lvList.ListItems.Remove objDrag.Index

    'Add it back into the dropped position 
    'Seems to fail on this line*****
    Set objNew = lvList.ListItems.Add(intIndex, objDrag.Key, objDrag.Text)  ', objDrag.Icon, objDrag.SmallIcon)

    'Reselect the item
    objNew.Selected = True

    'Destroy all objects
    Set objNew = Nothing
    Set objDrag = Nothing
    Set objDrop = Nothing
    Set lvList.DropHighlight = Nothing

End Sub

EDIT

Just an additional piece of information that might prove helpful before my bounty runs out. If I place a stop in one of the events I notice that when I drag an item it immediately highlights the first item. I think this could be why it won't work. It does this same thing in other ListViews on other userforms. For example, if the end user clicks an item, that item highlights. But if he checks the checkbox directly without clicking the actual item it highlights a random item (usually the same one). There is some very strange behavior with the ListView control in VBA (as noted by a few people online).

like image 398
Brian Avatar asked Oct 08 '18 14:10

Brian


Video Answer


1 Answers

@Brian I made your code to work in some crude way 1st of all changing Set objNew = lvList.ListItems.Add(intIndex, objDrag.Key, objDrag.Text) to lvList.ListItems.Add intIndex, objDrag.Key, objDrag.Text made it work. Also LvList.refresh added at the end. Then multpliying X & Y with 15 make drophighlight to work in some crude way. Further I used (20 as twips to point)

Xp = Application.ActiveWindow.PointsToScreenPixelsX(X * 20)
Yp = Application.ActiveWindow.PointsToScreenPixelsY(Y * 20)

and used Xp & Yp for HitTest. It gives more close result (but still not exactly). Xp & Yp are not declared and used as variant only. Declaring Xp Yp single will stop the conversion result to 0 as hittest X Y is single and PointstoScreen is Long. Csng() not working. My monitor is 1366 X 768.

Following are my observations (still not used in the program) I used Private Declare Function GetSystemMetrics Lib "user32" (ByVal whichMetric As Long) As Long successfully to get monitor width etc. Could not get gdi32 to work.

Xw = Application.ActiveWindow.UsableWidth
Yh = Application.ActiveWindow.UsableHeight

bringing in 1009.5 & 399. don't know what is the unit

Edit2: I forget to mention, I used your procedure code directly in OLEDragDrop event. I have also used OLEDragOver Event

Xp = Application.ActiveWindow.PointsToScreenPixelsX(X * 20)
Yp = Application.ActiveWindow.PointsToScreenPixelsY(Y * 20)
Set lvList.DropHighlight = lvList.HitTest(Xp, Yp)
  If lvList.DropHighlight Is Nothing Then
  Set lvList.DropHighlight = lvList.ListItems(lvList.ListItems.Count)
  End If
like image 53
Ahmed AU Avatar answered Oct 22 '22 04:10

Ahmed AU