Drag & drop listbox reordering

This example shows how to reorder items in a listbox by simply dragging an item to a new location. To try it out, paste the code in your Procedure window and execute OpenDragNDropExample() from the command line.

This reordering function does not draw any neat visual cues (such as a thick line between items) to indicate the destination of the drag. Instead, the selected (darkened) row moves with the mouse while you depress the button.

Function OpenDragNDropExample()
    Make/O/T/N=4 root:testList = {"item 1", "item 2", "item 3", "item 4"}
    Make/O/N=4 root:testSel
    DoWindow/K Panel_DragNDrop
    Execute/Q "Panel_DragNDrop()"
End

Window Panel_DragNDrop() : Panel
    PauseUpdate; Silent 1       // building window...
    NewPanel /W=(350,125,650,325) as "Drag List Items Example"
    ListBox list0,pos={1,2},size={298,197},proc=ListBoxProc_DragNDropLB
    ListBox list0,listWave=root:testList,selWave=root:testSel,mode= 1,selRow= 1
EndMacro

Function ListBoxProc_DragNDropLB(lba) : ListBoxControl
    STRUCT WMListboxAction &lba

    Variable row = lba.row
    Variable col = lba.col
    WAVE/T/Z listWave = lba.listWave
    WAVE/Z selWave = lba.selWave

    switch( lba.eventCode )
        case -1: // control being killed
            break
        case 1: // mouse down
            Variable/G V_MouseDownRow = row
            break
        case 2: // mouse up
            if(row != V_MouseDownRow)                       // dragged?
                NVAR V_MouseDownRow
                String item = listWave[V_MouseDownRow]
                DeletePoints V_MouseDownRow, 1, listWave    // do swap
                InsertPoints row, 1, listWave
                listWave[row] = item
            endif
            KillVariables V_MouseDownRow    // cleanup variable
            break
        case 3: // double click
            break
        case 4: // cell selection
        case 5: // cell selection plus shift key
            break
        case 6: // begin edit
            break
        case 7: // finish edit
            break
    endswitch

    return 0
End
Changed
Make/O/T/N=4 root:testList

to
Make/O/T/N=4 root:testList = {"item 1", "item 2", "item 3", "item 4"}

above.
Right now this is pretty much the only way to get drag and drop functionality. One thing I usually do in this situation is to store the row in the mouse down event in the control's named user data instead of a global variable. That's a little cleaner (in my opinion) and can be easier to keep track of in the case where you have multiple controls that do drag and drop (if you didn't kill the variable at the end of the drop, like you do above).
I like this snippet, I've always wanted to do something like this. In the same vein, is it possible to copy/cut/paste/delete the content in multiple contiguous rows? This snippet is useful for swapping rows, sometimes you want to delete or reorder lots of rows together, rather than doing them separately.

I've figured out that one can tell if ctrl or cmd is pressed using getkeystate(0), but how does one test if the delete button or c,v,x buttons are being held down at the same time. They don't get reported in the event procedure and they don't appear in the cells.

@andyfaff: The list control action procedure only gets events passed to it that Igor itself doesn't handle (I believe), and the copy/paste/cut shortcuts are probably events that get filtered out. You might get them in the window's hook procedure, but probably not.
Hmm... I haven't used window hooks enough to really be familiar with what can and can't be done with them, and aclight might be correct that Igor "scoops up" the copy/cut/paste commands before they can reach the hook. However, a quick glance at Advanced Topics.ihf suggests that you could detect a keyboard event, then have a switch on keycode for char2num("x") (and "c", "v"), and then use the window hook's eventMod field inside the switch to detect if Ctrl is depressed. You'd have to rely on Ctrl+X, rather than Del, I think, because the description for keycode says that it can't return function keys (it only returns ASCII)..

Actually, you guys have given me the idea that if you could properly code a nice, fancy window hook function, then you could have a sort of wrapper to convert a listbox to a drag-n-drop listbox (similar to how the WaveSelectorWidget has a function that transforms a button, popup, or listbox into the properly-functioning widget). Imagine something like MakeListboxDragNDrop(listboxName). It could set a hook that listens on mouse and keyboard events and uses ControlInfo to get necessary internal info, such as the selection. Then the user doesn't have to code all this stuff into a ListBoxProc on a listbox-by-listbox basis. It could also support various types of behavior (e.g., multi-row drag), based on the mode of the listbox.

This rapidly becomes quite complex, though, and illustrates why it'd be better if Igor could add drag-n-drop features in a later version. Not to knock WM at all, but I think I remember that LabView has a pretty nice drag-n-drop option on its listboxes (but maybe not with multi-row drag). I'm just sayin'... ;)

P.S. - @aclight, I like your idea to use the control's user data to store the starting row. I'm a geek for slick little tricks like that.
One way of doing copy/cut/paste or deleting would be to have a popupcontextual menu with those options on it. However, I don't like using the mouse to do all those editing things, I would prefer to use the keyboard. Still having a way is better than having no way at all.
By the way, if anyone want's to see drag and drop work in a way similar to what's described above, download and play around with my Create Layouts package here on the site. It uses a list box that displays pictures for drag and drop. When I wrote it one of the issues with faking drag and drop like this is that the user doesn't get much visual feedback about where the drop site will be. I think that's one of the major limitations to faking drag and drop as this package and snipped above do.

I can pretty much promise you that in Igor x there will be the ability to have pretty drag and drop list boxes. The question is what the value of x is. :)
Some time ago I tried to build a complex interface and stumbled over IgorMenuHook, which might be appropriate for what you're trying to do. Here's the first sentence from the documentation: "IgorMenuHook is a user-defined function that Igor calls just before and just after menu selection (whether by mouse or keyboard)." I think you can trap "copy" and "paste" events with this.

Forum

Support

Gallery

Igor Pro 9

Learn More

Igor XOP Toolkit

Learn More

Igor NIDAQ Tools MX

Learn More