
Listbox Drag & Drop

tony
Fri, 07/31/2020 - 06:32 am
For 'default' appearance listboxes, this seems to work.
#pragma rtGlobals=3
#pragma version=1.60
menu "Macros"
"Drag and Drop Demo"
end
function DragAndDropDemo()
KillWindow /Z demo
Make /O/T LB1ListWave = {"cat","dog","rabbit","horse","pig","cow"}
Make /O/T LB2ListWave = {"blue","green","red","yellow","orange","pink"}
Make /O/N=6 LB1SelWave = 0, LB2SelWave = 0
NewPanel /K=1/N=demo/W=(100,100,400,350) as "drag & drop demo"
ListBox LB1, win=demo, pos={10, 40}, size={130, 200}, listwave=LB1ListWave
ListBox LB1, win=demo, selwave=LB1SelWave, mode=9, focusring=0
ListBox LB1, win=demo, fsize=16, Proc=ListBoxProc
ListBox LB2, win=demo, pos={160, 40}, size={130, 200}, listwave=LB2ListWave
ListBox LB2, win=demo, selwave=LB2SelWave, mode=9, focusring=0
ListBox LB2, win=demo, fsize=9, Proc=ListBoxProc
PopupMenu DragPop, win=demo, pos={10, 10}, value="Drag Between Boxes;Drag to Reorder;", Proc=PopupDragType
end
function PopupDragType(STRUCT WMPopupAction &s)
// mode 1 for reordering, mode 9 for drag between boxes
ListBox LB1, win=$s.win, selRow=-1, mode=(s.popNum==1) ? 9 : 1
ListBox LB2, win=$s.win, selRow=-1, mode=(s.popNum==1) ? 9 : 1
end
function ListBoxProc(STRUCT WMListboxAction &lba)
ControlInfo /W=$lba.win DragPop
if(V_Value == 1)
DragAndDrop(lba)
else
DragReorder(lba)
endif
end
function DragAndDrop(STRUCT WMListboxAction &lba)
if(!(lba.eventCode & 0x03)) // neither mouseup nor mousedown
return 0
endif
variable f = 72/PanelResolution(lba.win) // point/pixel
int dragStarted = strlen(GetUserData(lba.win, lba.ctrlName, "drag"))
string otherListBox = SelectString(cmpstr(lba.ctrlName, "LB1")==0, "LB1", "LB2")
if (lba.eventCode==2 && dragStarted) // mouseup, drag completed
// find whether mouse is within OTHER listbox
if(isInControl(lba.mouseLoc, lba.win, otherListBox))
ControlInfo /W=$lba.win $otherListBox
int beforeItem = round(V_startRow + (lba.mouseLoc.v-V_top/f)/V_rowHeight)
wave /SDFR=$S_DataFolder otherListBoxWave=$S_Value
beforeItem = limit(beforeItem, 0, numpnts(otherListBoxWave))
moveSelection(otherListBox, lba.selwave, lba.listwave, beforeItem)
endif
ListBox $lba.ctrlName, win=$lba.win, userdata(drag)=""
endif
if(lba.eventCode==1 && dragStarted==0) // mousedown, new drag
if( lba.row < 0 || lba.row >= (DimSize(lba.listWave, 0)) )
return 0
endif
int i, numBoxes, startrow, endrow, mode, fontSize
// figure out visible rows
ControlInfo /W=$lba.win $lba.ctrlName
startrow = V_startRow
endrow = min(numpnts(lba.selwave)-1, startrow + ceil((V_height/f/V_rowHeight)-2))
// record current value of mode & fsize
string strMode, strFsize
SplitString/E=("mode=\s?([[:digit:]]+)") S_recreation, strMode
mode = strlen(strMode) ? str2num(strMode) : 1
SplitString/E=("fSize=\s?([[:digit:]]+)") S_recreation, strFsize
fontSize = strlen(strFsize) ? str2num(strFsize) : 9
// stops cell selection as mouse moves by setting mode=0
ListBox $lba.ctrlName, win=$lba.win, userdata(drag)="started", mode=0
// userdata(drag) indicates dragging is active, cleared on mouseup
// create a titlebox for every visible selected item
numBoxes=0
string DBname, strTitle
variable height, width, top , left
for (i=startrow;i<endrow+1;i++)
if (lba.selwave[i] & 0x09)
wave /T listwave=lba.listWave
DBname = "DragBox" + num2str(numBoxes)
height = f*(V_rowHeight-1)
width = f*(lba.ctrlRect.right-lba.ctrlRect.left)
top = f*(lba.ctrlRect.top+(i-startrow)*V_rowHeight+1.5)
left = f*lba.ctrlRect.left
sprintf strTitle, "\\sa%+03d\\x%+03d %s", 3-(fontSize>12), (20-fontSize)*0.625, listwave[i]
TitleBox $DBname, win=$lba.win, title=strTitle, labelBack=(41760,52715,65482), pos={left, top}
TitleBox $DBname, win=$lba.win, fsize=fontSize, fixedSize=1, frame=0, size={width, height}
numBoxes ++
endif
endfor
// save coordinates of other listbox
ControlInfo /W=$lba.win $otherListBox
struct rect pixelRect
pixelRect.left = v_left/f // point -> pixel
pixelRect.right = v_right/f
pixelRect.top = v_top/f
pixelRect.bottom = pixelRect.top + v_height/f
// monitor mouse movement until mouseup
variable dx, dy, buttondown
do
GetMouse /W=$lba.win
buttondown = V_flag & 1
dx = v_left - lba.mouseLoc.h // pixels
dy = v_top - lba.mouseLoc.v // pixels
// keep current mouse position updated as mouse moves
lba.mouseLoc.h = v_left
lba.mouseLoc.v = v_top
// move titleboxes with mouse
for(i=0; i<numBoxes; i++)
TitleBox /Z $"DragBox"+num2str(i), win=$lba.win, pos+={dx,dy}
endfor
// draw focus ring when mouse is over other listbox
if (pointInRect(lba.mouseLoc, pixelRect)) // all units are pixels
ListBox $otherListBox, win=$lba.win, focusRing=1
ModifyControl $otherListBox activate
else
ModifyControl $lba.ctrlName activate
endif
DoUpdate /W=$lba.win
while(buttondown)
// clear titleboxes and return listboxes to normal mode
for(i=0; i<numBoxes; i++)
KillControl /W=$lba.win $"DragBox"+num2str(i)
endfor
ListBox $otherListBox, win=$lba.win, focusRing=0
ListBox $lba.ctrlName, win=$lba.win, mode=mode
ModifyControl $lba.ctrlName activate
endif // end of drag
end
// point and rect structures must have same units
function pointInRect(STRUCT point &pnt, STRUCT rect &r)
return ( pnt.h>r.left && pnt.h<r.right && pnt.v>r.top && pnt.v<r.bottom )
end
function isInControl(STRUCT point &mouse, string strWin, string strCtrl)
ControlInfo /W=$strWin $strCtrl
variable f = 72/PanelResolution(strWin)
variable hpoint = mouse.h * f
variable vpoint = mouse.v * f
return ( hpoint>V_left && hpoint<(V_right) && vpoint>V_top && vpoint<(V_top+V_height) )
end
function moveSelection(string toLB, wave selwave, wave /T listwave, variable beforeItem)
Extract /free/T listwave, switchwave, (selwave & 0x09)
Extract /O/T listwave, listwave, !(selwave & 0x09)
Extract /O selwave, selwave, !(selwave & 0x09)
wave destSelWave = $toLB + "SelWave"
wave /T destListWave = $toLB + "ListWave"
destSelWave = 0
variable numItems = numpnts(switchwave)
InsertPoints beforeItem, numItems, destSelWave, destListWave
destSelWave[beforeItem, beforeItem+numItems-1] = 1
destListWave[beforeItem, beforeItem+numItems-1] = switchwave[p-beforeItem]
end
function DragReorder(STRUCT WMListboxAction &lba)
if (lba.eventCode == 2) // mouseup
ListBox $lba.ctrlName, win=$lba.win, userdata(drag)=""
endif
if (lba.eventCode == 1) // mousedown
ListBox $lba.ctrlName, win=$lba.win, userdata(drag)=num2str(lba.row)
//userdata(drag) indicates dragging is active, cleared on mouseup
endif
if (lba.eventCode == 4) // selection of lba.row
variable dragNum = str2num(GetUserData(lba.win,lba.ctrlName,"drag"))
if(numtype(dragNum)!=0 || min(dragNum,lba.row)<0 || max(dragNum,lba.row)>=numpnts(lba.listwave))
return 0
endif
Duplicate /free lba.selwave order
order = (p == dragNum) ? lba.row - 0.5 + (lba.row > dragNum) : x
Sort order, lba.listwave
ListBox $lba.ctrlName, win=$lba.win, userdata(drag)=num2str(lba.row)
endif
end
#pragma version=1.60
menu "Macros"
"Drag and Drop Demo"
end
function DragAndDropDemo()
KillWindow /Z demo
Make /O/T LB1ListWave = {"cat","dog","rabbit","horse","pig","cow"}
Make /O/T LB2ListWave = {"blue","green","red","yellow","orange","pink"}
Make /O/N=6 LB1SelWave = 0, LB2SelWave = 0
NewPanel /K=1/N=demo/W=(100,100,400,350) as "drag & drop demo"
ListBox LB1, win=demo, pos={10, 40}, size={130, 200}, listwave=LB1ListWave
ListBox LB1, win=demo, selwave=LB1SelWave, mode=9, focusring=0
ListBox LB1, win=demo, fsize=16, Proc=ListBoxProc
ListBox LB2, win=demo, pos={160, 40}, size={130, 200}, listwave=LB2ListWave
ListBox LB2, win=demo, selwave=LB2SelWave, mode=9, focusring=0
ListBox LB2, win=demo, fsize=9, Proc=ListBoxProc
PopupMenu DragPop, win=demo, pos={10, 10}, value="Drag Between Boxes;Drag to Reorder;", Proc=PopupDragType
end
function PopupDragType(STRUCT WMPopupAction &s)
// mode 1 for reordering, mode 9 for drag between boxes
ListBox LB1, win=$s.win, selRow=-1, mode=(s.popNum==1) ? 9 : 1
ListBox LB2, win=$s.win, selRow=-1, mode=(s.popNum==1) ? 9 : 1
end
function ListBoxProc(STRUCT WMListboxAction &lba)
ControlInfo /W=$lba.win DragPop
if(V_Value == 1)
DragAndDrop(lba)
else
DragReorder(lba)
endif
end
function DragAndDrop(STRUCT WMListboxAction &lba)
if(!(lba.eventCode & 0x03)) // neither mouseup nor mousedown
return 0
endif
variable f = 72/PanelResolution(lba.win) // point/pixel
int dragStarted = strlen(GetUserData(lba.win, lba.ctrlName, "drag"))
string otherListBox = SelectString(cmpstr(lba.ctrlName, "LB1")==0, "LB1", "LB2")
if (lba.eventCode==2 && dragStarted) // mouseup, drag completed
// find whether mouse is within OTHER listbox
if(isInControl(lba.mouseLoc, lba.win, otherListBox))
ControlInfo /W=$lba.win $otherListBox
int beforeItem = round(V_startRow + (lba.mouseLoc.v-V_top/f)/V_rowHeight)
wave /SDFR=$S_DataFolder otherListBoxWave=$S_Value
beforeItem = limit(beforeItem, 0, numpnts(otherListBoxWave))
moveSelection(otherListBox, lba.selwave, lba.listwave, beforeItem)
endif
ListBox $lba.ctrlName, win=$lba.win, userdata(drag)=""
endif
if(lba.eventCode==1 && dragStarted==0) // mousedown, new drag
if( lba.row < 0 || lba.row >= (DimSize(lba.listWave, 0)) )
return 0
endif
int i, numBoxes, startrow, endrow, mode, fontSize
// figure out visible rows
ControlInfo /W=$lba.win $lba.ctrlName
startrow = V_startRow
endrow = min(numpnts(lba.selwave)-1, startrow + ceil((V_height/f/V_rowHeight)-2))
// record current value of mode & fsize
string strMode, strFsize
SplitString/E=("mode=\s?([[:digit:]]+)") S_recreation, strMode
mode = strlen(strMode) ? str2num(strMode) : 1
SplitString/E=("fSize=\s?([[:digit:]]+)") S_recreation, strFsize
fontSize = strlen(strFsize) ? str2num(strFsize) : 9
// stops cell selection as mouse moves by setting mode=0
ListBox $lba.ctrlName, win=$lba.win, userdata(drag)="started", mode=0
// userdata(drag) indicates dragging is active, cleared on mouseup
// create a titlebox for every visible selected item
numBoxes=0
string DBname, strTitle
variable height, width, top , left
for (i=startrow;i<endrow+1;i++)
if (lba.selwave[i] & 0x09)
wave /T listwave=lba.listWave
DBname = "DragBox" + num2str(numBoxes)
height = f*(V_rowHeight-1)
width = f*(lba.ctrlRect.right-lba.ctrlRect.left)
top = f*(lba.ctrlRect.top+(i-startrow)*V_rowHeight+1.5)
left = f*lba.ctrlRect.left
sprintf strTitle, "\\sa%+03d\\x%+03d %s", 3-(fontSize>12), (20-fontSize)*0.625, listwave[i]
TitleBox $DBname, win=$lba.win, title=strTitle, labelBack=(41760,52715,65482), pos={left, top}
TitleBox $DBname, win=$lba.win, fsize=fontSize, fixedSize=1, frame=0, size={width, height}
numBoxes ++
endif
endfor
// save coordinates of other listbox
ControlInfo /W=$lba.win $otherListBox
struct rect pixelRect
pixelRect.left = v_left/f // point -> pixel
pixelRect.right = v_right/f
pixelRect.top = v_top/f
pixelRect.bottom = pixelRect.top + v_height/f
// monitor mouse movement until mouseup
variable dx, dy, buttondown
do
GetMouse /W=$lba.win
buttondown = V_flag & 1
dx = v_left - lba.mouseLoc.h // pixels
dy = v_top - lba.mouseLoc.v // pixels
// keep current mouse position updated as mouse moves
lba.mouseLoc.h = v_left
lba.mouseLoc.v = v_top
// move titleboxes with mouse
for(i=0; i<numBoxes; i++)
TitleBox /Z $"DragBox"+num2str(i), win=$lba.win, pos+={dx,dy}
endfor
// draw focus ring when mouse is over other listbox
if (pointInRect(lba.mouseLoc, pixelRect)) // all units are pixels
ListBox $otherListBox, win=$lba.win, focusRing=1
ModifyControl $otherListBox activate
else
ModifyControl $lba.ctrlName activate
endif
DoUpdate /W=$lba.win
while(buttondown)
// clear titleboxes and return listboxes to normal mode
for(i=0; i<numBoxes; i++)
KillControl /W=$lba.win $"DragBox"+num2str(i)
endfor
ListBox $otherListBox, win=$lba.win, focusRing=0
ListBox $lba.ctrlName, win=$lba.win, mode=mode
ModifyControl $lba.ctrlName activate
endif // end of drag
end
// point and rect structures must have same units
function pointInRect(STRUCT point &pnt, STRUCT rect &r)
return ( pnt.h>r.left && pnt.h<r.right && pnt.v>r.top && pnt.v<r.bottom )
end
function isInControl(STRUCT point &mouse, string strWin, string strCtrl)
ControlInfo /W=$strWin $strCtrl
variable f = 72/PanelResolution(strWin)
variable hpoint = mouse.h * f
variable vpoint = mouse.v * f
return ( hpoint>V_left && hpoint<(V_right) && vpoint>V_top && vpoint<(V_top+V_height) )
end
function moveSelection(string toLB, wave selwave, wave /T listwave, variable beforeItem)
Extract /free/T listwave, switchwave, (selwave & 0x09)
Extract /O/T listwave, listwave, !(selwave & 0x09)
Extract /O selwave, selwave, !(selwave & 0x09)
wave destSelWave = $toLB + "SelWave"
wave /T destListWave = $toLB + "ListWave"
destSelWave = 0
variable numItems = numpnts(switchwave)
InsertPoints beforeItem, numItems, destSelWave, destListWave
destSelWave[beforeItem, beforeItem+numItems-1] = 1
destListWave[beforeItem, beforeItem+numItems-1] = switchwave[p-beforeItem]
end
function DragReorder(STRUCT WMListboxAction &lba)
if (lba.eventCode == 2) // mouseup
ListBox $lba.ctrlName, win=$lba.win, userdata(drag)=""
endif
if (lba.eventCode == 1) // mousedown
ListBox $lba.ctrlName, win=$lba.win, userdata(drag)=num2str(lba.row)
//userdata(drag) indicates dragging is active, cleared on mouseup
endif
if (lba.eventCode == 4) // selection of lba.row
variable dragNum = str2num(GetUserData(lba.win,lba.ctrlName,"drag"))
if(numtype(dragNum)!=0 || min(dragNum,lba.row)<0 || max(dragNum,lba.row)>=numpnts(lba.listwave))
return 0
endif
Duplicate /free lba.selwave order
order = (p == dragNum) ? lba.row - 0.5 + (lba.row > dragNum) : x
Sort order, lba.listwave
ListBox $lba.ctrlName, win=$lba.win, userdata(drag)=num2str(lba.row)
endif
end

Forum

Support

Gallery
Igor Pro 9
Learn More
Igor XOP Toolkit
Learn More
Igor NIDAQ Tools MX
Learn More
Wow! A real tour-de-force of tricky Igor programming, Tony! Using a titlebox as the drag picture is very clever.
July 31, 2020 at 09:37 am - Permalink
Thanks, John.
I edited the snippet to draw a focus ring when mouse is over receiving listbox.
To add click-and-drag reordering to your own code, you need only the DragReorder() function. Create a listbox with mode=1 and add DragReorder(lba) to the listbox control function.
August 3, 2020 at 04:32 am - Permalink
Very cool!
August 3, 2020 at 10:13 am - Permalink
Edit: v. 1.4, simplified reordering code
August 5, 2020 at 12:43 am - Permalink
Edit: v. 1.6 compatible with Igor 9 style control panel expansion
July 18, 2021 at 02:29 am - Permalink