Listbox Drag & Drop

For 'default' appearance listboxes, this seems to work.

#pragma TextEncoding="UTF-8"
#pragma rtGlobals=3
#pragma version=1.4

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 8 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 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
            variable beforeItem = V_startRow + ceil((lba.mouseLoc.v-V_top)/V_rowHeight) - 1
            wave /SDFR=$S_DataFolder otherListBoxWave=$S_Value
            beforeItem = min(beforeItem, 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
        variable i, j, 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/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
        j=0
        string DBname, strTitle
        for(i=startrow;i<endrow+1;i++)
            if(lba.selwave[i]&0x09)
                wave /T listwave=lba.listWave
                DBname="DragBox"+num2str(j)
                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)
                TitleBox $DBname,win=$lba.win, pos={lba.ctrlRect.left,lba.ctrlRect.top+(i-startrow)*V_rowHeight+1.5}
                TitleBox $DBname,win=$lba.win, size={lba.ctrlRect.right-lba.ctrlRect.left,V_rowHeight-1}
                TitleBox $DBname,win=$lba.win, fsize=fontSize, fixedSize=1, frame=0
                j++
            endif
        endfor

        // monitor mouse movement until mouseup
        variable dx, dy, buttondown
        do
            GetMouse /W=$lba.win
            buttondown = V_flag&1
            dx = v_left - lba.mouseLoc.h
            dy = v_top - lba.mouseLoc.v
            lba.mouseLoc.h = v_left
            lba.mouseLoc.v = v_top
            // move titleboxes with mouse
            for(i=0; 1; i++)
                ControlInfo /W=$lba.win $"DragBox"+num2str(i)
                if(v_flag==0)
                    break
                endif
                TitleBox /Z $"DragBox"+num2str(i), win=$lba.win, pos+={dx,dy}
            endfor
            // draw focus ring when mouse is over other listbox
            if(isInControl(lba.mouseLoc, lba.win, otherListBox))
                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; 1; i++)
            ControlInfo /W=$lba.win $"DragBox"+num2str(i)
            if(v_flag==0)
                break
            endif
            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

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 isInControl(STRUCT point &mouse, string strWin, string strCtrl)
   
    ControlInfo /W=$strWin $strCtrl
    return ( mouse.h>V_left && mouse.h<(V_left+V_width) && mouse.v>V_top && mouse.v<(V_top+V_height) )
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 = x
        order[dragNum] = (lba.row>dragNum) ? lba.row+0.5 : lba.row-0.5
        Sort order, lba.listwave
        ListBox $lba.ctrlName, win=$lba.win, userdata(drag)=num2str(lba.row)
    endif
end

 

Wow! A real tour-de-force of tricky Igor programming, Tony! Using a titlebox as the drag picture is very clever.

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.

Forum

Support

Gallery

Igor Pro 8

Learn More

Igor XOP Toolkit

Learn More

Igor NIDAQ Tools MX

Learn More