Drag and drop in listbox

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
braunbaer
Posts: 483
Joined: 22 Feb 2016, 10:49

Drag and drop in listbox

Post by braunbaer » 22 Jun 2020, 16:00

Is it possible to react to the use of drag and drop in a listbox to move the items?

User avatar
divanebaba
Posts: 816
Joined: 20 Dec 2016, 03:53
Location: Diaspora

Re: Drag and drop in listbox

Post by divanebaba » 22 Jun 2020, 20:50

If I had understand you the right way, i can say it is possible by using A_GuiControl, which contains text or the name of the control, you dropped the files on.
See this example executing the if-statement and messaging the name of the variable when you drop any file onto the listbox.

Code: Select all

gui, add, listbox, vMyListBox
gui, show, w200 h200
return

GuiDropFiles:
	if (A_GuiControl = "MyListBox")
		msgbox % A_GuiControl
return
Einfach nur ein toller Typ. :mrgreen:

teadrinker
Posts: 4393
Joined: 29 Mar 2015, 09:41
Contact:

Re: Drag and drop in listbox

Post by teadrinker » 23 Jun 2020, 08:13

Proof of concept:

Code: Select all

#NoEnv
SetBatchLines, -1

Gui, Font, s12
Gui, Add, ListBox, r5 w200 hwndhLB, Drag me!|Item 2|Item 3|Item 4|Item 5
Gui, Show
Hook := new WindowsHook(WH_MOUSE_LL := 14, "LowLevelMouseProc", hLB)
Return

GuiClose() {
   ExitApp
}
   
LowLevelMouseProc(nCode, wParam, lParam) {
   static WM_MOUSEMOVE := 0x200, WM_LBUTTONDOWN := 0x201, WM_LBUTTONUP := 0x202
        , hListBox, items, captured, viewInfo, startX, startY
   (!hListBox && hListBox := A_EventInfo)
   
   if (wParam = WM_LBUTTONDOWN) {
      MouseGetPos,,,, hCtrl, 2
      if (hCtrl = hListBox)
         items := MapItems(hListBox, lParam, captured, startX, startY)
   }
   if (wParam = WM_MOUSEMOVE && captured) {
      viewInfo := CreateView(items, captured)
      captured := ""
   }
   if (wParam = WM_MOUSEMOVE && viewInfo) {
      MoveView(lParam, viewInfo, startX, startY)
   }
   if (wParam = WM_LBUTTONUP && !(captured := "") && viewInfo) {
      InsertItem(hListBox, lParam, items, viewInfo)
      viewInfo := ""
      Send, {LButton Up}
   }
   Return DllCall("CallNextHookEx", "Ptr", 0, "Int", nCode, "Ptr", wParam, "Ptr", lParam)
}

MapItems(hListBox, pMouseData, ByRef captured, ByRef startX, ByRef startY) {
   static LB_GETCOUNT := 0x18B, LB_GETITEMRECT := 0x198
   point := CreatePoint(hListBox, pMouseData, ctrlX, ctrlY, startX, startY)
   items := []
   SendMessage, LB_GETCOUNT,,,, ahk_id %hListBox%
   Loop % ErrorLevel {
      items.SetCapacity(A_Index, 16), pRECT := items.GetAddress(A_Index)
      SendMessage, LB_GETITEMRECT, A_Index - 1, pRECT,, ahk_id %hListBox%
      if DllCall("PtInRect", "Ptr", pRECT, "UInt64", point)
         captured := {item: A_Index, ctrlX: ctrlX, ctrlY: ctrlY}
   }
   Return items
}

CreateView(items, captured) {
   pRECT := items.GetAddress(captured.item)
   
   itemLeft   := NumGet(pRECT +  0, "Int")
   itemTop    := NumGet(pRECT +  4, "Int")
   itemRight  := NumGet(pRECT +  8, "Int")
   itemBottom := NumGet(pRECT + 12, "Int")
   
   viewX := captured.ctrlX + itemLeft + 2
   viewY := captured.ctrlY + itemTop + 2
   viewWidth  := itemRight - itemLeft
   viewHeight := itemBottom - itemTop
   
   hDC := DllCall("GetDC", "Ptr", 0, "Ptr")
   hBM := DllCall("CreateCompatibleBitmap", "Ptr", hDC, "Int", viewWidth, "Int", viewHeight, "Ptr")
   hMDC := DllCall("CreateCompatibleDC", "Ptr", hDC, "Ptr")
   hObj := DllCall("SelectObject", "Ptr", hMDC, "Ptr", hBM)
   DllCall("BitBlt", "Ptr", hMDC, "Int", 0, "Int", 0, "Int", viewWidth, "Int", viewHeight
                   , "Ptr", hDC, "Int", viewX, "Int", viewY, "UInt", SRCCOPY := 0xCC0020)
   DllCall("SelectObject", "Ptr", hMDC, "Ptr", hObj)
   DllCall("DeleteDC", "Ptr", hMDC)
   DllCall("ReleaseDC", "Ptr", 0, "Ptr", hDC)
   
   Gui, New, -Caption +AlwaysOnTop +Owner +hwndhGui
   Gui, Margin, 0, 0
   Gui, Add, Pic,, HBITMAP:%hBM%
   Gui, Show, x%viewX% y%viewY% NA
   Return {hwnd: hGui, x: viewX, y: viewY, item: captured.item}
}

MoveView(pMouseData, viewInfo, startX, startY) {
   mouseX := NumGet(pMouseData + 0, "Int"), mouseY := NumGet(pMouseData + 4, "Int")
   Gui, % viewInfo.hwnd . ":Show", % "NA x" . mouseX - startX + viewInfo.x
                                     . " y" . mouseY - startY + viewInfo.y
}

InsertItem(hListBox, pMouseData, items, viewInfo) {
   Gui, % viewInfo.hwnd . ":Destroy"
   itemIdx := viewInfo.item
   point := CreatePoint(hListBox, pMouseData)
   ControlGet, list, List,,, ahk_id %hListBox%
   itemsText := StrSplit(list, "`n")
   Loop % items.Length() {
      i := A_Index
      if ( i = itemIdx || !DllCall("PtInRect", "Ptr", items.GetAddress(i), "UInt64", point) )
         continue
      b := i > itemIdx
      itemsText.InsertAt(i + b, itemsText[itemIdx])
      itemsText.RemoveAt(itemIdx + !b)
      for k, v in itemsText
         text .= "|" . v . (k = i ? "|" : "")
      GuiControl,, %hListBox%, % RegExReplace(text, "\|$", "||")
      break
   }
}

CreatePoint(hWnd, pMouseData, ByRef X := "", ByRef Y := "", ByRef mouseX := "", ByRef mouseY := "") {
   WinGetPos, X, Y,,, ahk_id %hWnd%
   mouseX := NumGet(pMouseData + 0, "Int") - 2
   mouseY := NumGet(pMouseData + 4, "Int") - 2
   Return mouseX - X | (mouseY - Y) << 32
}
   
class WindowsHook {
   __New(type, callback, eventInfo := "", isGlobal := true) {
      this.pCallback := RegisterCallback(callback, "Fast", 3, eventInfo)
      this.hHook := DllCall("SetWindowsHookEx", "Int", type, "Ptr", this.pCallback
                                              , "Ptr", !isGlobal ? 0 : DllCall("GetModuleHandle", "UInt", 0, "Ptr")
                                              , "UInt", isGlobal ? 0 : DllCall("GetCurrentThreadId"), "Ptr")
   }
   __Delete() {
      DllCall("UnhookWindowsHookEx", "Ptr", this.hHook)
      DllCall("GlobalFree", "Ptr", this.pCallback, "Ptr")
   }
}
Last edited by teadrinker on 24 Jun 2020, 11:59, edited 1 time in total.

braunbaer
Posts: 483
Joined: 22 Feb 2016, 10:49

Re: Drag and drop in listbox

Post by braunbaer » 23 Jun 2020, 16:05

Thank you for your answers, I will try the code tomorrow.

@teadrinker
That code looks aweful! Did not know such things are possible within ahk. I will need some time to understand what you are doing.

teadrinker
Posts: 4393
Joined: 29 Mar 2015, 09:41
Contact:

Re: Drag and drop in listbox

Post by teadrinker » 23 Jun 2020, 16:16

Feel free to ask questions. :)

braunbaer
Posts: 483
Joined: 22 Feb 2016, 10:49

Re: Drag and drop in listbox

Post by braunbaer » 24 Jun 2020, 10:25

divanebaba
Thank you for your answer, though it does not address my question. I don't want to drag file names from another program, I want to use drag and drop for moving items within the list. Teadrinkers code works perfectly well for me.

Teadrinker
Thank you for your code, it works perfectly.

Still, I would like to fully understand what it does, and I have several questions (maybe more to come :) )

1.

Code: Select all

new WindowsHook
Obviously this creates a new object. In the ahk documentation, I have not found the keyword "new", it does not appear in the help index and I did not find it in the objects section of the ahk help either. Is the usage of "new" documented somewhere? same question for windowshook, it is quite clear what is meant, but I would be glad to know what this code does exactly. Is "windowshook" just a class identifier arbitrariliy chosen, is it a predefined class or is it part of the language with a special meaning?

2.

Code: Select all

static WM_MOUSEMOVE := 0x200, WM_LBUTTONDOWN := 0x201, WM_LBUTTONUP := 0x202
        , LB_GETCOUNT := 0x18B, LB_GETITEMRECT := 0x198
Where can I find a description of these constants? I am sure there are many more constants of this kind...

3.

Code: Select all

(!hLB && hLB := A_EventInfo)
???? I don't even see how this is valid ahk code - How can the left side of an assignment be an expression? And what does this construct do?

teadrinker
Posts: 4393
Joined: 29 Mar 2015, 09:41
Contact:

Re: Drag and drop in listbox

Post by teadrinker » 24 Jun 2020, 11:14

  1. Some links:
    new
    Custom Objects
    Construction and Destruction
    By this way the new instance of WindowsHook class is created and the __New() method is called. In this method winapi SetWindowsHookEx is called with the WH_MOUSE_LL hook type. This hook passes all mouse events through LowLevelMouseProc() function.
  2. Google them like this: #define WM_MOUSEMOVE
  3. This is the same as

    Code: Select all

    if !hLB {
       hLB := A_EventInfo
    }

teadrinker
Posts: 4393
Joined: 29 Mar 2015, 09:41
Contact:

Re: Drag and drop in listbox

Post by teadrinker » 24 Jun 2020, 11:59

I've redesigned the script to have it more clear.

braunbaer
Posts: 483
Joined: 22 Feb 2016, 10:49

Re: Drag and drop in listbox

Post by braunbaer » 24 Jun 2020, 14:41

teadrinker wrote:
24 Jun 2020, 11:59
I've redesigned the script to have it more clear.
Thank you for your help!

1. Thats a lot of links to read. I was not aware that there is much more online help than in the chm-file that I have been using until now as documentation.

2. ok, as I thought, a lot of constants for a lot of purposes...

3. This is VICIOUS :D Even after your explanation, it took me quite a while to figure out how that works and why that works - using an assignment within an expression, using the fact that AHK short-circuits the evaluation of boolean expressions, and discarding the result of the expression...
Is that common practice to reduce the line count of the script by 1, or does it increase execution speed?

teadrinker
Posts: 4393
Joined: 29 Mar 2015, 09:41
Contact:

Re: Drag and drop in listbox

Post by teadrinker » 24 Jun 2020, 15:13

braunbaer wrote: there is much more online help than in the chm-file
No, online help and the chm-file should be identical. Make sure that your chm is latest.
braunbaer wrote: Is that common practice to reduce the line count of the script by 1, or does it increase execution speed?
I'm not sure that it's common practice, some people consider that bad practice. I like using it to save place, if it doesn't impair readability.


teadrinker
Posts: 4393
Joined: 29 Mar 2015, 09:41
Contact:

Re: Drag and drop in listbox

Post by teadrinker » 24 Jun 2020, 21:53

This can be used, but this doesn't simplify the task, since for some reason doesn't send the DL_DRAGGING notification, and you need to track cursor moving in any way to move an item picture.

Code: Select all

#NoEnv
SetBatchLines, -1

Gui, Font, s12
Gui, Add, ListBox, r5 w200 hwndhLB, Drag me!|Item 2|Item 3|Item 4|Item 5
DllCall("MakeDragList", "Ptr", hLB)
Gui, Show

DRAGLISTMSGSTRING := "commctrl_DragListMsg"
msg := DllCall("RegisterWindowMessage", "Str", DRAGLISTMSGSTRING)
OnMessage(msg, "OnDrag")
Return

GuiClose() {
   ExitApp
}

OnDrag(wp, lp) {
   static DL_BEGINDRAG := 1157, DL_DRAGGING := 1158, DL_DROPPED := 1159, DL_CANCELDRAG := 1160
   ToolTip % NumGet(lp + 0, "UInt")
}

teadrinker
Posts: 4393
Joined: 29 Mar 2015, 09:41
Contact:

Re: Drag and drop in listbox

Post by teadrinker » 25 Jun 2020, 07:19

teadrinker wrote: for some reason doesn't send the DL_DRAGGING notification
Can't get what is the issue. However, using LBItemFromPt gives some simplification:

Code: Select all

#NoEnv
SetBatchLines, -1

Gui, Font, s12
Gui, Add, ListBox, r5 w200 hwndhLB, Drag me!|Item 2|Item 3|Item 4|Item 5
DllCall("MakeDragList", "Ptr", hLB)
Gui, Show

DRAGLISTMSGSTRING := "commctrl_DragListMsg"
msg := DllCall("RegisterWindowMessage", "Str", DRAGLISTMSGSTRING)
OnMessage(msg, "OnDrag")
Return

GuiClose() {
   ExitApp
}

OnDrag(wp, lp) {
   static DL_BEGINDRAG := 1157, DL_DROPPED := 1159, DL_CANCELDRAG := 1160
        , WH_MOUSE_LL := 14, viewInfo, pViewInfo, Hook
   
   ntf      := NumGet(lp + 0, "UInt")
   hListBox := NumGet(lp + A_PtrSize)
   POINT    := NumGet(lp + A_PtrSize*2, "UInt64")
   item := DllCall("LBItemFromPt", "Ptr", hListBox, "UInt64", POINT, "UInt", true)
   
   if (ntf = DL_BEGINDRAG) {
      captured := GetCapturedItem(hListBox, POINT, item)
      viewInfo := CreateView(captured)
      pViewInfo := Object(viewInfo)
      Hook := new WindowsHook(WH_MOUSE_LL, "LowLevelMouseProc", pViewInfo)
   }
   if (ntf = DL_DROPPED || ntf = DL_CANCELDRAG) {
      Hook := ""
      InsertItem(hListBox, item, viewInfo)
      ObjRelease(pViewInfo), viewInfo := ""
   }
}

GetCapturedItem(hListBox, POINT, idx) {
   item := {idx: idx, hCtrl: hListBox}
   item.SetCapacity("RECT", 16), pRECT := item.GetAddress("RECT")
   SendMessage, LB_GETITEMRECT := 0x198, idx, pRECT,, ahk_id %hListBox%
   WinGetPos, X, Y,,, ahk_id %hListBox%
   item.ctrlX := X, item.ctrlY := Y
   item.startX := POINT & 0xFFFFFFFF, item.startY := POINT >> 32
   Return item
}

CreateView(captured) {
   pRECT := captured.GetAddress("RECT")
   
   itemLeft   := NumGet(pRECT +  0, "Int")
   itemTop    := NumGet(pRECT +  4, "Int")
   itemRight  := NumGet(pRECT +  8, "Int")
   itemBottom := NumGet(pRECT + 12, "Int")
   
   viewX := captured.ctrlX + itemLeft + 2
   viewY := captured.ctrlY + itemTop + 2
   viewWidth  := itemRight - itemLeft
   viewHeight := itemBottom - itemTop
   
   hDC := DllCall("GetDC", "Ptr", 0, "Ptr")
   hBM := DllCall("CreateCompatibleBitmap", "Ptr", hDC, "Int", viewWidth, "Int", viewHeight, "Ptr")
   hMDC := DllCall("CreateCompatibleDC", "Ptr", hDC, "Ptr")
   hObj := DllCall("SelectObject", "Ptr", hMDC, "Ptr", hBM)
   DllCall("BitBlt", "Ptr", hMDC, "Int", 0, "Int", 0, "Int", viewWidth, "Int", viewHeight
                   , "Ptr", hDC, "Int", viewX, "Int", viewY, "UInt", SRCCOPY := 0xCC0020)
   DllCall("SelectObject", "Ptr", hMDC, "Ptr", hObj)
   DllCall("DeleteDC", "Ptr", hMDC)
   DllCall("ReleaseDC", "Ptr", 0, "Ptr", hDC)
   
   Gui, New, -Caption +AlwaysOnTop +Owner +hwndhGui
   Gui, Margin, 0, 0
   Gui, Add, Pic,, HBITMAP:%hBM%
   Gui, Show, x%viewX% y%viewY% NA
   Return {hwnd: hGui, hCtrl: captured.hCtrl, x: captured.startX - viewX, y: captured.startY - viewY, item: captured.idx}
}

InsertItem(hListBox, targetIdx, viewInfo) {
   static LB_INSERTSTRING := 0x181, LB_DELETESTRING := 0x182, LB_SETCURSEL := 0x186
   Gui, % viewInfo.hwnd . ":Destroy"
   itemIdx := viewInfo.item
   if (targetIdx = itemIdx || targetIdx = -1)
      Return
   
   ControlGet, list, List,,, ahk_id %hListBox%
   itemText := StrSplit(list, "`n")[itemIdx + 1]
   SendMessage, LB_DELETESTRING, itemIdx,,, ahk_id %hListBox%
   SendMessage, LB_INSERTSTRING, targetIdx, &itemText,, ahk_id %hListBox%
   SendMessage, LB_SETCURSEL, targetIdx,,, ahk_id %hListBox%
}

LowLevelMouseProc(nCode, wParam, lParam) {
   static WM_MOUSEMOVE := 0x200, LB_SETCURSEL := 0x186, prevItem
   if (wParam = WM_MOUSEMOVE) {
      viewInfo := Object(A_EventInfo)
      mouseX := NumGet(lParam + 0, "Int"), mouseY := NumGet(lParam + 4, "Int")
      Gui, % viewInfo.hwnd . ":Show", % "NA x" . mouseX - viewInfo.x
                                        . " y" . mouseY - viewInfo.y
      POINT := mouseX | mouseY << 32
      item := DllCall("LBItemFromPt", "Ptr", viewInfo.hCtrl, "UInt64", POINT, "UInt", true)
      if (item != prevItem) {
         prevItem := item
         SendMessage, LB_SETCURSEL, item,,, % "ahk_id" . viewInfo.hCtrl
      }
   }
   Return DllCall("CallNextHookEx", "Ptr", 0, "Int", nCode, "Ptr", wParam, "Ptr", lParam)
}

class WindowsHook {
   __New(type, callback, eventInfo := "", isGlobal := true) {
      this.pCallback := RegisterCallback(callback, "Fast", 3, eventInfo)
      this.hHook := DllCall("SetWindowsHookEx", "Int", type, "Ptr", this.pCallback
                                              , "Ptr", !isGlobal ? 0 : DllCall("GetModuleHandle", "UInt", 0, "Ptr")
                                              , "UInt", isGlobal ? 0 : DllCall("GetCurrentThreadId"), "Ptr")
   }
   __Delete() {
      DllCall("UnhookWindowsHookEx", "Ptr", this.hHook)
      DllCall("GlobalFree", "Ptr", this.pCallback, "Ptr")
   }
}

teadrinker
Posts: 4393
Joined: 29 Mar 2015, 09:41
Contact:

Re: Drag and drop in listbox

Post by teadrinker » 25 Jun 2020, 11:46

That's better:

Code: Select all

#NoEnv
SetBatchLines, -1

Gui, Font, s12
Gui, Add, ListBox, r5 w200 hwndhLB, Drag me!|Item 2|Item 3|Item 4|Item 5
DllCall("MakeDragList", "Ptr", hLB)
Gui, Show

DRAGLISTMSGSTRING := "commctrl_DragListMsg"
msg := DllCall("RegisterWindowMessage", "Str", DRAGLISTMSGSTRING)
OnMessage(msg, "OnDrag")
Return

GuiClose() {
   ExitApp
}

OnDrag(wp, lp) {
   static DL_BEGINDRAG := 1157, DL_DROPPED := 1159, DL_CANCELDRAG := 1160
        , WH_MOUSE_LL := 14, viewInfo, pViewInfo, Hook
   
   ntf      := NumGet(lp + 0, "UInt")
   hListBox := NumGet(lp + A_PtrSize)
   POINT    := NumGet(lp + A_PtrSize*2, "UInt64")
   item := DllCall("LBItemFromPt", "Ptr", hListBox, "UInt64", POINT, "UInt", true)
   
   if (ntf = DL_BEGINDRAG) {
      captured := GetCapturedItem(hListBox, POINT, item)
      viewInfo := CreateView(captured)
      pViewInfo := Object(viewInfo)
      Hook := new WindowsHook(WH_MOUSE_LL, "LowLevelMouseProc", pViewInfo)
   }
   if (ntf = DL_DROPPED || ntf = DL_CANCELDRAG) {
      Hook := ""
      Gui, % viewInfo.hwnd . ":Destroy"
      ObjRelease(pViewInfo), viewInfo := ""
   }
}

GetCapturedItem(hListBox, POINT, idx) {
   item := {idx: idx, hCtrl: hListBox}
   item.SetCapacity("RECT", 16), pRECT := item.GetAddress("RECT")
   SendMessage, LB_GETITEMRECT := 0x198, idx, pRECT,, ahk_id %hListBox%
   WinGetPos, X, Y,,, ahk_id %hListBox%
   item.ctrlX := X, item.ctrlY := Y
   item.startX := POINT & 0xFFFFFFFF, item.startY := POINT >> 32
   Return item
}

CreateView(captured) {
   pRECT := captured.GetAddress("RECT")
   
   itemLeft   := NumGet(pRECT +  0, "Int")
   itemTop    := NumGet(pRECT +  4, "Int")
   itemRight  := NumGet(pRECT +  8, "Int")
   itemBottom := NumGet(pRECT + 12, "Int")
   
   viewX := captured.ctrlX + itemLeft + 2
   viewY := captured.ctrlY + itemTop + 2
   viewWidth  := itemRight - itemLeft
   viewHeight := itemBottom - itemTop
   
   hDC := DllCall("GetDC", "Ptr", 0, "Ptr")
   hBM := DllCall("CreateCompatibleBitmap", "Ptr", hDC, "Int", viewWidth, "Int", viewHeight, "Ptr")
   hMDC := DllCall("CreateCompatibleDC", "Ptr", hDC, "Ptr")
   hObj := DllCall("SelectObject", "Ptr", hMDC, "Ptr", hBM)
   DllCall("BitBlt", "Ptr", hMDC, "Int", 0, "Int", 0, "Int", viewWidth, "Int", viewHeight
                   , "Ptr", hDC, "Int", viewX, "Int", viewY, "UInt", SRCCOPY := 0xCC0020)
   DllCall("SelectObject", "Ptr", hMDC, "Ptr", hObj)
   DllCall("DeleteDC", "Ptr", hMDC)
   DllCall("ReleaseDC", "Ptr", 0, "Ptr", hDC)
   
   Gui, New, -Caption +AlwaysOnTop +Owner +hwndhGui
   Gui, Margin, 0, 0
   Gui, Add, Pic,, HBITMAP:%hBM%
   Gui, Show, x%viewX% y%viewY% NA
   
   ControlGet, list, List,,, % "ahk_id" . captured.hCtrl
   Return { hwnd: hGui, hCtrl: captured.hCtrl
          , item: captured.idx, text: StrSplit(list, "`n")[captured.idx + 1]
          , x: captured.startX - viewX, y: captured.startY - viewY }
}

LowLevelMouseProc(nCode, wParam, lParam) {
   static WM_MOUSEMOVE := 0x200, LB_INSERTSTRING := 0x181, LB_DELETESTRING := 0x182, LB_SETCURSEL := 0x186
   if (wParam = WM_MOUSEMOVE) {
      viewInfo := Object(A_EventInfo)
      (viewInfo.prevItem = "" && viewInfo.prevItem := viewInfo.item)
      mouseX := NumGet(lParam + 0, "Int"), mouseY := NumGet(lParam + 4, "Int")
      Gui, % viewInfo.hwnd . ":Show", % "NA x" . mouseX - viewInfo.x
                                        . " y" . mouseY - viewInfo.y
      POINT := mouseX | mouseY << 32
      item := DllCall("LBItemFromPt", "Ptr", viewInfo.hCtrl, "UInt64", POINT, "UInt", true)
      if !(item = viewInfo.prevItem || item = -1) {
         SendMessage, LB_DELETESTRING, viewInfo.prevItem,,, % "ahk_id" . viewInfo.hCtrl
         SendMessage, LB_INSERTSTRING, item, viewInfo.GetAddress("text"),, % "ahk_id" . viewInfo.hCtrl
         SendMessage, LB_SETCURSEL, item,,, % "ahk_id" . viewInfo.hCtrl
         viewInfo.prevItem := item
      }
   }
   Return DllCall("CallNextHookEx", "Ptr", 0, "Int", nCode, "Ptr", wParam, "Ptr", lParam)
}

class WindowsHook {
   __New(type, callback, eventInfo := "", isGlobal := true) {
      this.pCallback := RegisterCallback(callback, "Fast", 3, eventInfo)
      this.hHook := DllCall("SetWindowsHookEx", "Int", type, "Ptr", this.pCallback
                                              , "Ptr", !isGlobal ? 0 : DllCall("GetModuleHandle", "UInt", 0, "Ptr")
                                              , "UInt", isGlobal ? 0 : DllCall("GetCurrentThreadId"), "Ptr")
   }
   __Delete() {
      DllCall("UnhookWindowsHookEx", "Ptr", this.hHook)
      DllCall("GlobalFree", "Ptr", this.pCallback, "Ptr")
   }
}

User avatar
kczx3
Posts: 1649
Joined: 06 Oct 2015, 21:39

Re: Drag and drop in listbox

Post by kczx3 » 25 Jun 2020, 19:40

@teadrinker that's pretty slick!

This is how I'd have done it probably (I use AHK v2). Thanks to your help on some of the early parts of the code. Is the value of DRAGLISTMSGSTRING anything special or is it random? Also, you weren't getting DL_DRAGGING because you're not returning true when ntf = DL_BEGINDRAG.

Code: Select all

main := Gui.new()
main.setFont("S10", "Tahoma")
main.onevent("close", (*) => ExitApp())

lb := main.add("Listbox", "r5", ["item 1", "thing 2", "stuff 3", "horse 4", "straw 5"])
DllCall("MakeDragList", "Ptr", lb.hwnd, "Int")

DRAGLISTMSGSTRING := "commctrl_DragListMsg"
msg := DllCall("RegisterWindowMessage", "Str", DRAGLISTMSGSTRING)
OnMessage(msg, "OnDrag")

main.show()

OnDrag(w, l, msg, hwnd) {
    static DL_BEGINDRAG := 1157, DL_DRAGGING := 1158, DL_DROPPED := 1159, DL_CANCELDRAG := 1160, dragItem := false
    
    ntf := NumGet(l + 0, "UInt")
    hListBox := NumGet(l, A_PtrSize)
    POINT := NumGet(l, A_PtrSize*2, "UInt64")
    item := DllCall("LBItemFromPt", "Ptr", hListBox, "UInt64", POINT, "UInt", true, "Int")
    
    Switch (ntf) {
        case DL_BEGINDRAG:
            dragItem := item
            return True ; must return true here otherwise you won't get DL_DRAGGING
        case DL_DRAGGING:
            drawInsert(item)
        case DL_DROPPED:
            drawInsert(-1)
            
            if (dragItem != item) {
                moveItem()
            }
        case DL_CANCELDRAG:
            drawInsert(-1)
    }
    
    drawInsert(relativeItem) {
        DllCall("DrawInsert", "Ptr", hwnd, "Ptr", hListBox, "Int", relativeItem)
    }
    
    moveItem() {
        static LB_INSERTSTRING := 0x181
        
        ctrl := GuiCtrlFromHwnd(hListbox)
        
        ; get the text of the currently selected item and delete it
        selectedText := ctrl.text
        ctrl.Delete(dragItem + 1)
        
        ; don't think the guictrl object gives a way to insert at an arbitrary position so use DllCall
        SendMessage(LB_INSERTSTRING, item, StrPtr(selectedText), hListbox)
        
        ; setting the value for a ListBox ultimately changes the selected item
        ctrl.value := item + 1
    }
}

teadrinker
Posts: 4393
Joined: 29 Mar 2015, 09:41
Contact:

Re: Drag and drop in listbox

Post by teadrinker » 25 Jun 2020, 19:54

kczx3 wrote: Is the value of DRAGLISTMSGSTRING anything special or is it random?
It's defined in commctrl.h.
kczx3 wrote: you weren't getting DL_DRAGGING because you're not returning true when ntf = DL_BEGINDRAG
Ah, thanks, now it works. :wave:

User avatar
kczx3
Posts: 1649
Joined: 06 Oct 2015, 21:39

Re: Drag and drop in listbox

Post by kczx3 » 25 Jun 2020, 19:59

teadrinker wrote:
25 Jun 2020, 19:54
It's defined in commctrl.h.
[/quote]

Excellent. Thanks!

teadrinker
Posts: 4393
Joined: 29 Mar 2015, 09:41
Contact:

Re: Drag and drop in listbox

Post by teadrinker » 26 Jun 2020, 08:11

@kczx3
Mouse Hook vs DL_DRAGGING
DL_DRAGGING starts to be sent only when the cursor leaves an dragged element, and a slight lag occurs.

User avatar
kczx3
Posts: 1649
Joined: 06 Oct 2015, 21:39

Re: Drag and drop in listbox

Post by kczx3 » 26 Jun 2020, 09:31

I think it’s just a matter of preference and complexity. I think the built-in notification is sufficient enough and I prefer to use methods provided by Microsoft whenever possible. I think changing the cursor at BEGINDRAG would help to convey a drag operation is possible

Post Reply

Return to “Ask for Help (v1)”