ListBoxes and Mouse Cases

Dear fellow Igorians,

In trying to understand how to program with panels and controls, I came across a spinet that allows one to shuffle (drag and drop) items within a list. Please, have a look at the code bellow. The case of double-clicking should be ignored. However, when I double-click an item, I receive the notice: " ** a wave read gave error: Index out of range for wave "testList"." and Igor replaces the value of the list I have doubled-clicked by the last value on that list. Can anyone explain why Igor is doing this?


#pragma rtGlobals=3		// Use modern global access method and strict wave access.
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
The value of row passed to the action procedure can be more than the last index of the list wave.

See the Debugger screenshot I've attached.

--Jim Prouty
Software Engineer, WaveMetrics, Inc.
ListboxRow4.PNG (76.92 KB)
The first problem is that a double click actually sends a series of events to your procedure: 1, 4, 2, 3, 4, 2 -- in that order. The case 3 case doesn't capture the entire sequence.

The second problem is that the first execution of case 2 kills your V_MouseDownRow global variable. So the second time case 2 is called, this variable is null. The check for row != null is true, so the following code executes and as Jim pointed out, row is greater than the last index in your wave. The default Igor behavior is to substitute the last value in a wave if the last index is exceeded.
Hence the error message and the reason row is replaced with the last value in the wave.

I've modified your list box proc somewhat and it seems to work for my limited testing.

As a final comment, you can learn a lot about how code works by judiciously sprinkling it with diagnostic print statements, also learning to use the debugger pays off quite handsomely.

Hope this helps.

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
			//quit if V_MouseDownRow isn't a string or numeric variable
			//otherwise create a pointer to it.
			if( exists( "V_MouseDownRow" ) == 2 )
				NVAR V_MouseDownRow
			else
				break
			endif
			if(row != V_MouseDownRow)						// dragged?
				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
 	print "code 1: ", lba.eventcode
	return 1
End
Thank you both! It makes more sense to me now!
Thanks for the suggestions also!

R.