Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Drag&Drop Support for ListBoxes


  • Please log in to reply
15 replies to this topic
dadepp
  • Members
  • 27 posts
  • Last active: Feb 28 2012 08:33 PM
  • Joined: 12 Jun 2008
Well since i like AutohotKey and coded this up already (over the time of a few weeks, since I didn't have that much freetime) I decided to put it into an include file, make a few wraper functions and share it with the comunity.
I apologize right away if my writing contains errors, because I'm not native English. So correct me if I'm wrong.

This idea came as I ran into a design problem with one of my Guis, and I thought this can be done simpler: I had a listbox with entries loaded from an ini file, and the user would have to sort them to his preferences. The first solution was using 2 buttons alongside the listbox labeled "up" and "down" which uppon click moved the selected entry one position up or down. This was not so elegant and tendious with a larger list. The next solution was to make use of Drag&Drop. Since there was nothing in the forums i coded this up, after a few problems I ran into.
I'm always up for any suggestions and/or corrections, so fire away if you have some.

First is the Library itself, current version 1.3:

Download LBDDLib.ahk

Second is a Demo i made, current version 1.3:
(It requires the above code to be saved as DragListFunctionLib.ahk, or saved as LBDDLib.ahk in the same folder or the AHK-Libfolder, but then the #Include must be removed!)
#SingleInstance, Force

Gui, Add, Text, x12 y2 w220 h15 Section, Drag source:
Gui, add, ListBox, xs y+15 w220 h200 HwndLBHwnD, 00|01|02|03|04|05|06|abcdef
Gui, Add, Text, xs y+25 w220 h15, Drop target with changing Delimiter:
Gui, Add, Edit, xs y+3 w220 HwndEditHwnd vedit1,
Gui, Add, CheckBox, xs y+8 vCheckB3 gCheckB3G, Clear editbox with every drop onto it
Gui, Add, CheckBox, xs y+8 vCheckB4 gCheckB4G, Remove items from listboxes after drop
Gui, Add, Text, xs y+30 w220 h15, Custom drop target:
Gui, Add, Button, xp+100 yp-4 w220 HwndButtonHwnd gbtest, Test
Gui, Add, Text, xs+240 y2  w220 h15 Section, Drop target, without deleting original:
Gui, add, ListBox, xs y+15 w220 h200 HwndLBHwnD2, 01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40
Gui, Add, DropDownList, xs y+43 w180 h20 Choose1 R3 gDDL_Select2 vDDL_value2 AltSubmit, Indicate drop with Arrow|Indicate drop with Line|Indicate drop with both
Gui, Add, CheckBox, xs y+8 vCheckB6 gCheckB6G, Use thick line for drop indicator
Gui, Add, Text, xs y+9, RGB-Hex value: #
Gui, Add, Edit, xp+90 yp-2 vedit2, 000000
Gui, Add, Button, xp+60 yp-1 gsetcolor, set
Gui, Add, Text, xs+240 y2 w220 h15 Section, Listbox with changeable Options:
Gui, Add, ListBox, xs y+15 w220 h200 HwndLBHwnD3, AA|BB|CC|DD|EE|FF|GG|HH|II|JJ|KK|LL|MM|NN|OO|PP|QQ|RR|SS|TT|UU|VV|WW|XX|YY|ZZ
Gui, Add, CheckBox, xs y+25 vCheckB5, Disable drop for above ListBox!
Gui, Add, CheckBox, xs y+8 vCheckB1 Checked, Allow Drag for the above ListBox!
Gui, Add, DropDownList, xs y+8 w180 h20 Choose1 R3 gDDL_Select vDDL_value AltSubmit, Listbox works only with itself|Listbox accepts "drops"|Normal Drag&Drop listbox
Gui, Add, CheckBox, xs y+8 vCheckB2 gCheckB2G Checked, Remove items from other listbox if`ndropped onto above listbox
Gui, 2:+owner1 +ToolWindow -SysMenu
Gui, 2:Add, ListBox, w220 h150 HwndLBHwnd4, Test with second|Gui-Window and longer|text inside the listbox.|Settings for this listbox are:|-No removing of original item.|-Showing a yellow line instead of|an arrow for the insertion mark.|-Only accepts drops, from itself and others,| but dragging to others is not possible.
Gui, 2:Add, ListBox, x+30 w220 h100 HwndLBHwnd5, If you find any bugs|or have suggestions,|please post in the forums!|It might be a time till I respond, though!
Gui, 2:Add, Text, y+8 HwndTextHwnd, This is a custom Drop-Target!`n--

LBDDLib_Init("gttip vttip httip UseEventNames")
LBDDLib_Add(LBHwnD)
LBDDLib_Add(LBHwnD2, "LB", "noremove")
LBDDLib_Add(EditHwnd, "edit", "gMyTest d%A_Space%|%A_Space% add noremove")
LBDDLib_Add(ButtonHwnd, "custom", "gMyTestButton")
LBDDLib_Add(LBHwnD3, "ddlb", "OnlySelf vvertest hvertest")
LBDDLib_Add(LBHwnD4, "DDLB", "Drop noremove InsLine #yellow")
LBDDLib_Add(LBHwnD5)
LBDDLib_Add(TextHwnd, "custom", "gMyTestText")
MyCount := 0
MyButtonBool := True

Gui, Show, y50, DragListBox demo
Gui, 2:Show, y500 NoActivate
return

ttip(){
  Global LBHwnD, LBHwnD2, LBHwnD3, EditHwnd, ButtonHwnd
  event := LBDDLib_UserVar("event")
  hWnd := LBDDLib_UserVar("ThWnd")
  ShWnd := LBDDLib_UserVar("ShWnd")
  drag := LBDDLib_UserVar("ItemToMove")
  curr := LBDDLib_UserVar("NewPosition")
  MouseGetPos, mx, my

;  ToolTip, %A_TickCount%, 0,0,2
; WARNING: Uncomment above line, and script will hang sometimes, because it
; no longer detects the mouse-button release (the drop) properly.
; This behaviour occurs if more than 1 tooltip is used!

  if (hWnd == LBHwnD+0)
    hWnd := "T: ListBox1"
  else if (hWnd == LBHwnD2+0)
    hWnd := "T: ListBox2"
  else if (hWnd == LBHwnD3+0)
    hWnd := "T: ListBox3"
  else if (hWnd == EditHwnd+0)
    hWnd := "T: EditBox"
  else if (hWnd == ButtonHwnd+0)
    hWnd := "T: Button"

  if (ShWnd == LBHwnD+0)
    ShWnd := "S: ListBox1"
  else if (ShWnd == LBHwnD2+0)
    ShWnd := "S: ListBox2"
  else if (ShWnd == LBHwnD3+0)
    ShWnd := "S: ListBox3"
  else if (ShWnd == EditHwnd+0)
    ShWnd := "S: EditBox"
  else if (ShWnd == ButtonHwnd+0)
    ShWnd := "S: Button"

  if (event = "verify")
    ToolTip, %ShWnd%`n%hWnd%`n%drag%`n%curr%, mx+20, my+20
  else if (event = "Hover")
    ToolTip, %ShWnd%`n%hWnd%`n%drag%`n%curr%, mx+20, my+20
  else if (event = "OutOfBounds")
    ToolTip,
  else if (event = "Drop")
    ToolTip,
  else if (event = "DropOutOfBounds")
    ToolTip,
  else if (event = "DragCancel")
    ToolTip,
}

vertest(){
  Global CheckB1, CheckB5, LBHwnD3
  if (LBDDLib_UserVar("event") == 0){
    Gui, Submit, NoHide
    return CheckB1
  } else if (LBDDLib_UserVar("event") == 1){
    return not CheckB5
  }
}

MyTestText(){
  Texthandle := LBDDLib_UserVar("thwnd")
  lbhandle := LBDDLib_UserVar("sHWnD")
  item := LBDDLib_UserVar("ITemtomoVE")
  MyText := LBDDLib_LBGetItemText(lbhandle, item)
  MyText := "This is a custom Drop-Target!`n" . MyText
  ControlSetText,, %MyText%, ahk_id %Texthandle%
}

MyTestButton(){
  buttonhandle := LBDDLib_UserVar("thwnd")
  lbhandle := LBDDLib_UserVar("sHWnD")
  item := LBDDLib_UserVar("ITemtomoVE")
  MyText := LBDDLib_LBGetItemText(lbhandle, item)
  ControlSetText,, %MyText%, ahk_id %buttonhandle%
  LBDDLib_LBDelItem(lbhandle, item)
}

MyTest(){
  Global ButtonHwnd, EditHwnd, MyCount
  p1 := LBDDLib_UserVar(1)
  p2 := LBDDLib_UserVar(2)
  p3 := LBDDLib_UserVar(3)
  if (MyCount == 0)
    LBDDLib_Modify(EditHwnd, "d%A_Space%|%A_Space%")
  else if (MyCount == 1)
    LBDDLib_Modify(EditHwnd, "d|||")
  else if (MyCount == 2)
    LBDDLib_Modify(EditHwnd, "d%A_Space%")
  else if (MyCount == 3)
    LBDDLib_Modify(EditHwnd, "d§²³$")
  else if (MyCount == 4)
    LBDDLib_Modify(EditHwnd, "d&""&")
  else if (MyCount == 5)
    LBDDLib_Modify(EditHwnd, "d%")
  else if (MyCount == 6)
    LBDDLib_Modify(EditHwnd, "d%A_Space%%A_Space%g")
  else if (MyCount == 7)
    LBDDLib_Modify(EditHwnd, "d---")
  MyCount ++
  if MyCount > 7
    MyCount := 0
  ControlSetText,, Dropped: %p3%, ahk_id %ButtonHwnd%
  LBDDLib_CallBack()
}

setcolor:
  Gui, Submit, NoHide
  LBDDLib_Modify(LBHwnD2, "#" . edit2)
return

btest:
  MyButtonBool := not MyButtonBool
  if not MyButtonBool {
    LBDDLib_Modify(ButtonHwnd, "g")
    ControlSetText,, Disabled drop function, ahk_id %ButtonHwnd%
  } else {
    LBDDLib_Modify(ButtonHwnd, "gMyTestButton")
    ControlSetText,, Enabled, ahk_id %ButtonHwnd%
  }
return

DDL_Select:
  Gui, Submit, NoHide
  if (DDL_value == 1)
    LBDDLib_Modify(LBHwnD3, "onlyself")
  else if (DDL_value == 2)
    LBDDLib_Modify(LBHwnD3, "drop")
  else if (DDL_value == 3)
    LBDDLib_Modify(LBHwnD3, "global")
return

DDL_Select2:
  Gui, Submit, NoHide
  if (DDL_value2 == 1)
    LBDDLib_Modify(LBHwnD2, "InsArrow")
  else if (DDL_value2 == 2)
    LBDDLib_Modify(LBHwnD2, "InsLine")
  else if (DDL_value2 == 3)
    LBDDLib_Modify(LBHwnD2, "InsArrowLine")
return

CheckB2G:
  Gui, Submit, NoHide
  if CheckB2
    LBDDLib_Modify(LBHwnD3, "remove")
  else
    LBDDLib_Modify(LBHwnD3, "noremove")
return

CheckB3G:
  Gui, Submit, NoHide
  if CheckB3
    LBDDLib_Modify(EditHwnd, "del")
  else
    LBDDLib_Modify(EditHwnd, "add")
return

CheckB4G:
  Gui, Submit, NoHide
  if CheckB4
    LBDDLib_Modify(EditHwnd, "remove")
  else
    LBDDLib_Modify(EditHwnd, "noremove")
return

CheckB6G:
  Gui, Submit, NoHide
  if CheckB6
    LBDDLib_Modify(LBHwnD2, "thickline")
  else
    LBDDLib_Modify(LBHwnD2, "nothickline")
return

~ESC::
  if LBDDLib_UserVar("isDrag")
    return
GuiClose:
ExitApp

Know issues:
- Does NOT work with multiselect listboxes. (maybe i find a way to implement this)
- During debuging I used ToolTip windows, and somehow those affected the registering of the Drop-event, meaning sometimes the script wouldn't notice I released the left mouse. This happend more often, the more ToolTips I used. Drove me nuts until i figuered it out (pure luck :oops: ).

Stuff to do:
- Write more coments into the code other than the docu at the start. (I know, I'm lazy, but comenting a completed code feels like doing the work again, so I do it when I have a bit more freetime.)
- More mouse cursor support, maybe providing an included source (have a few ideas), but definately giving the coder a way to use his own cursor resources.
- A mouse cursor overlay showing the text of the dragged item moving with the mouse cursor similar to what happens in Explorer.
- Find a way to implement multiselect listbox support (might happen or might not! Still don't know if I undertake this task.)

Stuff done:
- Find a way to implement a function call to give the coder a chance to deny the Dragevent if a certain item in the listbox is dragged. Meaning to implement a way to Exclude some items to be dragged.
- implement another way of showing the insertion mark: A line inside the listbox.
- FIXED: If the mouse while in Dragging leaves the listbox the side and returns into the scroling rectangle the insert-arrow won't be drawn until the mouse moves over a listbox item, even though the correct values are used and the draw function is called. Couldn't find the error yet, but wil be fixed (hopefully).


I don't know how often I'm around, and for how long, but I'll try to implement the ToDo-stuff.

Feedback is always welcomed.

Edit (2009-04-15): Updated above codes!

Edit (2011-10-03): Fixed a bug if the target listbox is empty the drop "vanishes" unless its released on the top of the target listbox. (thx Invalid User)

Edit (2011-10-11): Fixed a known issue, and made the lib unicode compatible, removed the global variables and added a few new features.

majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006
Great library and very pro done. Thank you.

Some comments:
- Library name is pretty bad IMO. For instance DDLib would be better
- I don't like to use magic numbers like:
MyDLBFunc_Add(LBHwnD, 0) 
MyDLBFunc_Add(LBHwnD2, 1, "o0")

I would make it like:
DDLib_Add(LBHwnD) ;default is what u do now as 0, i.e. drag & drop
DDLib_Add(LBWND2, "S target", "O noremove")  ;Style: target, Option: no remove
- Better world docs

Otherwise, great job as I said. Really valuable.

Find a way to implement multiselect listbox support (might happen or might not! Still don't know if I undertake this task.)

I don't see why this is a problem. You have 2 types of multiselect: 1 with the holes in selection and one without holes. In 2nd case, you simply add to the target listbox all selected items starting from droped position, and delete orignal items. In first case, when there are holes situation can be a bit tricky if you want to keep the holes at the target. You can avoid this by threating this as case 2 when target is in question.

- Wish:
API to disable/enable drag&droping for particular control and generaly more API for dynamic programming.

- Tip:
It would be good to somehow distinguish exposed or useful functions in the module for outer world from those that are private. I usualy do this by starting private functions by small letter (Camel style). This way by just looking at the function list you can know what is there for you, and what shouldn't be touched. I also move such functions to the end of the code:

Your code (what is exposed API here ???)
MyDLBFunc_Add(hWnd, Switch, Options=0) 
MyDLBFunc_DrawInsert(hWnd, Switch, ArrayNum=0, ItemNum=0) 
MyDLBFunc_GetFunc(ArrayNum, Switch=0) 
MyDLBFunc_GetHandle(ArrayNum) 
MyDLBFunc_GetLBInfo(Switch, ArrayNum) 
MyDLBFunc_GetType(ArrayNum) 
MyDLBFunc_Init() 
MyDLBFunc_InitRects() 
MyDLBFunc_IsValidHandle(hWnd) 
MyDLBFunc_IsValidRect(Mx, My) 
MyDLBFunc_ItemFromPt(ArrayNum, Mx, My, bAutoScroll) 
MyDLBFunc_LBDelItem(hWnd, Item) 
....

Example of what I talk about:
Panel_Add(hPanel, hCtrl, def="", txt="") {
Panel_Align(hCtrl, type="", dim=""){
Panel_Anchor(hCtrl, aDef=""){
Panel_GuiSize(w,h,e,main){
Panel_Hide(hPanel) {
Panel_New(hGui, x, y, w, h, style="", text="") { 
Panel_Show(hPanel) {
Panel_getToken(s, r=0) {
Panel_parse(def, ByRef a, ByRef x){
Panel_redrawDelayed(ctrl){
Panel_registerClass(){
Panel_size(hwnd, pw, ph) {
Panel_wndProc(hwnd, uMsg, wParam, lParam) { 

You clearly see what are exposed functions, even without documentation


Cheers!

ps: You may be interested in checking this
Posted Image