How to do drag'n'drop?

BlitzMax Forums/BlitzMax Programming/How to do drag'n'drop?

I'm trying to drag and drop from one canvas to another, but I'm having a few problems...

1. When you release the mouse button, the EVENT_MOUSEUP EventSource is the original canvas. (as expected, but it would have been nice anyway)
2. When you click and drag, it seems to ignor all other events apart from the ones from the original canvas, until you release the mouse button again, so I can't use EVENT_MOUSEENTER/MOUSELEAVE to catch it myself.

Simple tester...
Strict

Type TApplet
	
	Field	window:TGadget
	Field  canvas1:TGadget
	Field  canvas2:TGadget
	Field Grabbing:Int
	
	' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

	Method New()
		window  = CreateWindow("Test",20,20,512,512)
		Local w = ClientWidth(window)
		Local h = ClientHeight(window)
		canvas1 = CreateCanvas(0,0,(w/2)-20,h,window,PANEL_BORDER)
		canvas1.SetLayout 1,0,1,1
		canvas2 = CreateCanvas((w/2)+20,0,(w/2)-20,h,window,PANEL_BORDER)
		canvas2.SetLayout 0,1,1,1
		AddHook EmitEventHook,eventhook,Self
	End Method

	' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	
	Function eventhook:Object(id,data:Object,context:Object)
		TApplet(context).OnEvent TEvent(data)
		Return data
	End Function
	
	Method OnEvent(event:TEvent)
		Select event.id
		
			Case EVENT_WINDOWCLOSE ; End
				
			Case EVENT_GADGETPAINT
				Select Event.Source
					Case Canvas1 ; Render1
					Case Canvas2 ; Render2
				EndSelect
				
			Case EVENT_MOUSEDOWN
				Select Event.Source
					Case Canvas1 ; Print "Grab from canvas1"
						Grabbing = True ; SetPointer(POINTER_HAND)
					Case Canvas2 ; Print "Grab from canvas2"
						Grabbing = True ; SetPointer(POINTER_HAND)
				EndSelect

			Case EVENT_MOUSEUP
				If Grabbing = True 
					SetPointer(POINTER_DEFAULT) ; Grabbing = False
					Select Event.Source
						Case Canvas1 ; Print "Drop in canvas1"
						Case Canvas2 ; Print "Drop in canvas2"
					EndSelect
				EndIf
				
			Case EVENT_MOUSEENTER
				Select Event.Source
					Case Canvas1 ; Print "Enter canvas1"
					Case Canvas2 ; Print "Enter canvas2"
				EndSelect

			Case EVENT_MOUSELEAVE
				Select Event.Source
					Case Canvas1 ; Print "Leave canvas1"
					Case Canvas2 ; Print "Leave canvas2"
				EndSelect
			
		End Select
	End Method

	' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	
	Method Render1()
		SetGraphics CanvasGraphics(canvas1)
		SetViewport 0,0,GraphicsWidth(),GraphicsHeight()
		SetClsColor 0,0,0 ; Cls
		DrawText "Canvas1",5,5
		Flip
	End Method

	Method Render2()
		SetGraphics CanvasGraphics(canvas2)
		SetViewport 0,0,GraphicsWidth(),GraphicsHeight()
		SetClsColor 0,0,150 ; Cls
		DrawText "Canvas2",5,5
		Flip
	End Method
	
	' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	
End Type



Local	applet:TApplet = New TApplet

While True
	WaitEvent
Wend


Any ideas how to do it?

*EDIT* BTW going to work now, so wont be around for about 12hrs :(

I have no idea how you would do this but couldn't you create an EVENT_MOUSEUP event with the source as the canvas you entered. Then PostEvent it with the update flag as true so it would replace the event it would replace? I may have misunderstood how that event stuff works though.

Or maybe send a mouse up event as you leave one canvas and send a mousedown as you enter the new one so the program has focus in that one? Again I wouldn't have a clue how to do that (i tied but failed) and I may have again misunderstood how the events work.

Is it just me or is drag and drop extremely highly overrated? I hardly ever use it. It seems like a poor attempt to get two applications to share data in a very cumbersom manner, caused by the very fact that applications start out being so isolated from one another. What is really needed is a paradigm shift in the philosophy behind the degree of unity in the system.

On Windows and Linux, drag and drop is indeed overrated. On OS X, though, it works surprisingly well...

What is really needed is a paradigm shift in the philosophy behind the degree of unity in the system

What's this, frigging StarTrek :)

Yeah, I rarely use drag and drop myself, but in this case its the best option.

I want to be able to drag from my MAC to my Win32 (and then to my Linux Box. do i just need to connect the monitor to all 3 systems? maybe splice the wire?


sorry to drag-n- drop this topic :)

splice the wire?

You should give it a go, let us know how you get on :P

Well I've sorted both problems (with disablegadget then enablegadget again), but now the mouse pointer isn't changing when I tell it to. :(
Strict

Type TApplet
	
	Field	window:TGadget
	Field  canvas1:TGadget
	Field  canvas2:TGadget
	Field Grabbing:Int
	
	' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

	Method New()
		window  = CreateWindow("Test",20,20,512,512)
		Local w = ClientWidth(window)
		Local h = ClientHeight(window)
		canvas1 = CreateCanvas(0,0,(w/2)-20,h,window,PANEL_BORDER)
		canvas1.SetLayout 1,0,1,1
		canvas2 = CreateCanvas((w/2)+20,0,(w/2)-20,h,window,PANEL_BORDER)
		canvas2.SetLayout 0,1,1,1
		AddHook EmitEventHook,eventhook,Self
	End Method

	' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	
	Function eventhook:Object(id,data:Object,context:Object)
		TApplet(context).OnEvent TEvent(data)
		Return data
	End Function
	
	Method OnEvent(event:TEvent)
		Select event.id
		
			Case EVENT_WINDOWCLOSE ; End
				
			Case EVENT_GADGETPAINT
				Select Event.Source
					Case Canvas1 ; Render1
					Case Canvas2 ; Render2
				EndSelect
				
			Case EVENT_MOUSEDOWN
				Select Event.Source
					Case Canvas1 ; Print "Grab from canvas1"
						Grabbing = True ; SetPointer(POINTER_HAND)
						DisableGadget(canvas1)
						EnableGadget(canvas1)
					Case Canvas2 ; Print "Grab from canvas2"
						Grabbing = True ; SetPointer(POINTER_HAND)
						DisableGadget(canvas2)
						EnableGadget(canvas2)
				EndSelect

			Case EVENT_MOUSEUP
				If Grabbing = True 
					SetPointer(POINTER_DEFAULT) ; Grabbing = False
					Select Event.Source
						Case Canvas1 ; Print "Drop in canvas1"
						Case Canvas2 ; Print "Drop in canvas2"
					EndSelect
				EndIf
				
			Case EVENT_MOUSEENTER
				Select Event.Source
					Case Canvas1 ; Print "Enter canvas1"
					Case Canvas2 ; Print "Enter canvas2"
				EndSelect

			Case EVENT_MOUSELEAVE
				Select Event.Source
					Case Canvas1 ; Print "Leave canvas1"
					Case Canvas2 ; Print "Leave canvas2"
				EndSelect
			
		End Select
	End Method

	' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	
	Method Render1()
		SetGraphics CanvasGraphics(canvas1)
		SetViewport 0,0,GraphicsWidth(),GraphicsHeight()
		SetClsColor 0,0,0 ; Cls
		DrawText "Canvas1",5,5
		Flip
	End Method

	Method Render2()
		SetGraphics CanvasGraphics(canvas2)
		SetViewport 0,0,GraphicsWidth(),GraphicsHeight()
		SetClsColor 0,0,150 ; Cls
		DrawText "Canvas2",5,5
		Flip
	End Method
	
	' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	
End Type



Local	applet:TApplet = New TApplet

While True
	WaitEvent
Wend


*papa lazarou voice* It's okay I've fixed it now!
Strict

Type TApplet
	
	Field	window:TGadget
	Field  canvas1:TGadget
	Field  canvas2:TGadget
	Field Grabbing:Int
	
	' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

	Method New()
		window  = CreateWindow("Test",200,20,512,512)
		Local w = ClientWidth(window)
		Local h = ClientHeight(window)
		canvas1 = CreateCanvas(0,0,(w/2)-20,h,window,PANEL_BORDER)
		canvas1.SetLayout 1,0,1,1
		canvas2 = CreateCanvas((w/2)+20,0,(w/2)-20,h,window,PANEL_BORDER)
		canvas2.SetLayout 0,1,1,1
		AddHook EmitEventHook,eventhook,Self
	End Method

	' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	
	Function eventhook:Object(id,data:Object,context:Object)
		TApplet(context).OnEvent TEvent(data)
		Return data
	End Function
	
	Method OnEvent(event:TEvent)
		Select event.id
		
			Case EVENT_WINDOWCLOSE ; End
				
			Case EVENT_GADGETPAINT
				Select Event.Source
					Case Canvas1 ; Render1
					Case Canvas2 ; Render2
				EndSelect
				
			Case EVENT_MOUSEDOWN
				Select Event.Source
					Case Canvas1 ; Print "Grab from canvas1"
						DisableGadget(canvas1)
						EnableGadget(canvas1)
						Grabbing = True ; SetPointer(POINTER_HAND)
					Case Canvas2 ; Print "Grab from canvas2"
						DisableGadget(canvas2)
						EnableGadget(canvas2)
						Grabbing = True ; SetPointer(POINTER_HAND)
				EndSelect

			Case EVENT_MOUSEUP
				If Grabbing = True 
					SetPointer(POINTER_ARROW) ; Grabbing = False
					Select Event.Source
						Case Canvas1 ; Print "Drop in canvas1"
						Case Canvas2 ; Print "Drop in canvas2"
					EndSelect
				EndIf
				
		End Select
	End Method

	' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	
	Method Render1()
		SetGraphics CanvasGraphics(canvas1)
		SetViewport 0,0,GraphicsWidth(),GraphicsHeight()
		SetClsColor 0,0,0 ; Cls
		DrawText "Canvas1",5,5
		Flip
	End Method

	Method Render2()
		SetGraphics CanvasGraphics(canvas2)
		SetViewport 0,0,GraphicsWidth(),GraphicsHeight()
		SetClsColor 0,0,150 ; Cls
		DrawText "Canvas2",5,5
		Flip
	End Method
	
	' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	
End Type



Local	applet:TApplet = New TApplet

While True
	WaitEvent
Wend


FFFT :)

Nice. Funny how tricky things like that look so simple once they're worked out (but it's the working out that's the challenge!). That's going in my "Others' Source" folder. :)

your code have an error with the mouse pointer

grab in any canvas, go outside the window and release the mouse botton.
after that back to your window and push in the other canvas.

^ I remember wrestling with that issue when doing panels as image buttons. It's only a problem if the user changes his mind half-way through, but it needs to be handled in case he does. I had to keep track of two states separately: "mouse over" and "primed" (the mouse button has been pressed down but hasn't yet been let up). There are different outcomes when EVENT_MOUSEUP occurs depending on the current value of those two states, and they change depending on what the user does with the mouse pointer and button.

I ended up cheating and using the panel's Text field as a way of storing those two states rather than creating a proper "TApplet"-sort of type... which I probably should go back and do with a rewrite for 1.14 (especially since 1.14 changed handle requirements with Strict and today's synchmods fixed the shadow panel problem on the Mac).

argh cr#p, I'm not sure if I can get around that without getting OS specific, errrmm...

*EDIT*
Dodgy workaround...
Strict

Type TApplet
	
	Field	window:TGadget
	Field    panel:TGadget
	Field  canvas1:TGadget
	Field  canvas2:TGadget
	Field Grabbing:Int
	
	' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

	Method New()
		window  = CreateWindow("Test",200,20,512,512)
		Local w = ClientWidth(window)
		Local h = ClientHeight(window)
		panel   = CreatePanel(0,0,w,h,window,PANEL_ACTIVE)
		panel.SetLayout 1,1,1,1
		canvas1 = CreateCanvas(0,0,(w/2)-20,h,panel,PANEL_BORDER)
		canvas1.SetLayout 1,0,1,1
		canvas2 = CreateCanvas((w/2)+20,0,(w/2)-20,h,panel,PANEL_BORDER)
		canvas2.SetLayout 0,1,1,1
		AddHook EmitEventHook,eventhook,Self
	End Method

	' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	
	Function eventhook:Object(id,data:Object,context:Object)
		TApplet(context).OnEvent TEvent(data)
		Return data
	End Function
	
	Method OnEvent(event:TEvent)
		Select event.id
		
			Case EVENT_WINDOWCLOSE ; End
				
			Case EVENT_GADGETPAINT
				Select Event.Source
					Case Canvas1 ; Render1
					Case Canvas2 ; Render2
				EndSelect
				
			Case EVENT_MOUSEDOWN
				If Grabbing
					Event.ID = EVENT_MOUSEUP	' resend this event as a mouseup ;)
					EmitEvent(Event)
				Else
					Select Event.Source
						Case Canvas1 ; Print "Grab from canvas1"
							DisableGadget(canvas1)
							EnableGadget(canvas1)
							Grabbing = True ; SetPointer(POINTER_HAND)
						Case Canvas2 ; Print "Grab from canvas2"
							DisableGadget(canvas2)
							EnableGadget(canvas2)
							Grabbing = True ; SetPointer(POINTER_HAND)
					EndSelect
				EndIf

			Case EVENT_MOUSEUP
				If Grabbing = True 
					SetPointer(POINTER_ARROW) ; Grabbing = False
					Select Event.Source
						Case Canvas1 ; Print "Drop in canvas1"
						Case Canvas2 ; Print "Drop in canvas2"
						Default ; Print "Discarded"
					EndSelect
				EndIf

		End Select
	End Method

	' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	
	Method Render1()
		SetGraphics CanvasGraphics(canvas1)
		SetViewport 0,0,GraphicsWidth(),GraphicsHeight()
		SetClsColor 0,0,0 ; Cls
		DrawText "Canvas1",5,5
		Flip
	End Method

	Method Render2()
		SetGraphics CanvasGraphics(canvas2)
		SetViewport 0,0,GraphicsWidth(),GraphicsHeight()
		SetClsColor 0,0,150 ; Cls
		DrawText "Canvas2",5,5
		Flip
	End Method
	
	' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	
End Type



Local	applet:TApplet = New TApplet

While True
	WaitEvent
Wend

It would be nice to get EVENT_MOUSELEAVE events for windows, then I could just cancel the drag'n'drop stuff out when the mouse leaves the window. oh well that'll do for now.

^ Seems to work well enough with a quick look, though I see what you mean if the user drops outside the Blitz window. I haven't messed with EVENT_MOUSELEAVE for whole windows. If I ever use it, I'll sic (my self-imposed) "picky police" on it to see if it can be made perfectly "regular" (and share it here).

It's a pain, ain't it: having to deal with changing one's mind in the middle of a drag-n-drop. Ah, well, that's life... Users have no idea how complex those little gadgets are in order to allow that, and they don't need to... :) But, it's really professional when done just right.

Strict
Global Grabbing:Int
Global enter:Int
Global leave:Int

Type TApplet
	
	Field	window:TGadget
	Field    panel:TGadget
	Field  canvas1:TGadget
	Field  canvas2:TGadget
'	Field Grabbing:Int
'	Field enter:Int
'	Field leave:Int
	
	' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

	Method New()
		window  = CreateWindow("Test",200,20,512,512)
		Local w = ClientWidth(window)
		Local h = ClientHeight(window)
		panel   = CreatePanel(0,0,w,h,window,PANEL_ACTIVE)
		panel.SetLayout 1,1,1,1
		canvas1 = CreateCanvas(0,0,(w/2)-20,h,panel)',PANEL_BORDER)
		canvas1.SetLayout 1,0,1,1
		canvas2 = CreateCanvas((w/2)+20,0,(w/2)-20,h,panel)',PANEL_BORDER)
		canvas2.SetLayout 0,1,1,1
		AddHook EmitEventHook,eventhook,Self
	End Method

	' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	
	Function eventhook:Object(id,data:Object,context:Object)
		TApplet(context).OnEvent TEvent(data)
		Return data
	End Function
	
	Method OnEvent(event:TEvent)

		Select event.id
		
			Case EVENT_WINDOWCLOSE ; End
				
			Case EVENT_GADGETPAINT
				Select Event.Source
					Case Canvas1 ; Render1
					Case Canvas2 ; Render2
				EndSelect
				
			Case EVENT_MOUSEDOWN
				If Grabbing
					Event.ID = EVENT_MOUSEUP	' resend this event as a mouseup ;)
					EmitEvent(Event)
				Else
					Select Event.Source
						Case Canvas1 ; Print "Grab from canvas1"
							DisableGadget(canvas1)
							EnableGadget(canvas1)
							Grabbing = True ; SetPointer(POINTER_HAND); enter=1
						Case Canvas2 ; Print "Grab from canvas2"
							DisableGadget(canvas2)
							EnableGadget(canvas2)
							Grabbing = True ; SetPointer(POINTER_HAND); enter=1
					EndSelect
				EndIf

			Case EVENT_MOUSEUP
				If Grabbing = True 
					SetPointer(POINTER_ARROW) ; Grabbing = False
					Select Event.Source
						Case Canvas1 ; Print "Drop in canvas1"
						Case Canvas2 ; Print "Drop in canvas2"
						Default ; Print "Discarded"
					EndSelect
				EndIf
			Case EVENT_MOUSEENTER
				Select Event.Source
					Case Canvas1 enter=True
					Case Canvas2 enter=True
					Case panel enter=True
				EndSelect
			Case EVENT_MOUSELEAVE
				Select Event.Source
					Case Canvas1 enter=False
					Case Canvas2 enter=False
					Case panel enter=False
				EndSelect; leave=MilliSecs()
		End Select
	End Method

	' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	
	Method Render1()
		SetGraphics CanvasGraphics(canvas1)
		SetViewport 0,0,GraphicsWidth(),GraphicsHeight()
		SetClsColor 0,0,0 ; Cls
		DrawText "Canvas1",5,5
		Flip
	End Method

	Method Render2()
		SetGraphics CanvasGraphics(canvas2)
		SetViewport 0,0,GraphicsWidth(),GraphicsHeight()
		SetClsColor 0,0,150 ; Cls
		DrawText "Canvas2",5,5
		Flip
	End Method
	
	' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	
End Type



Local	applet:TApplet = New TApplet

While True
	PollEvent
			If Grabbing And Not enter And MilliSecs()-leave>300 Then
				Print "Discarded (Exit from window)"; 
				SetPointer(POINTER_ARROW) ; Grabbing = False
		EndIf
Wend


Nice one Sp1der, the problem with that is we don't get ENTER/LEAVE events for other gadgets like buttons, so if the pointer goes over a button the drag and drop is cancelled :(

Strict
Global Grabbing:Int
Global enter:Int
Global leave:Int

Type TApplet
	
	Field	window:TGadget
	Field    panel:TGadget
	Field  canvas1:TGadget
	Field  canvas2:TGadget
	Field   button:TGadget
'	Field Grabbing:Int
'	Field enter:Int
'	Field leave:Int
	
	' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

	Method New()
		window  = CreateWindow("Test",200,20,512,512)
		Local w = ClientWidth(window)
		Local h = ClientHeight(window)
		panel   = CreatePanel(0,0,w,h,window,PANEL_ACTIVE)
		panel.SetLayout 1,1,1,1
		canvas1 = CreateCanvas(0,0,(w/2)-20,h,panel)',PANEL_BORDER)
		canvas1.SetLayout 1,0,1,1
		canvas2 = CreateCanvas((w/2)+20,0,(w/2)-20,h,panel)',PANEL_BORDER)
		canvas2.SetLayout 0,1,1,1
		button  = CreateButton("button",(w/2)-20,100,40,50,panel)
		button.SetLayout 1,1,1,1
		AddHook EmitEventHook,eventhook,Self
	End Method

	' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	
	Function eventhook:Object(id,data:Object,context:Object)
		TApplet(context).OnEvent TEvent(data)
		Return data
	End Function
	
	Method OnEvent(event:TEvent)

		Select event.id
		
			Case EVENT_WINDOWCLOSE ; End
				
			Case EVENT_GADGETPAINT
				Select Event.Source
					Case Canvas1 ; Render1
					Case Canvas2 ; Render2
				EndSelect
				
			Case EVENT_MOUSEDOWN
				If Grabbing
					Event.ID = EVENT_MOUSEUP	' resend this event as a mouseup ;)
					EmitEvent(Event)
				Else
					Select Event.Source
						Case Canvas1 ; Print "Grab from canvas1"
							DisableGadget(canvas1)
							EnableGadget(canvas1)
							Grabbing = True ; SetPointer(POINTER_HAND); enter=1
						Case Canvas2 ; Print "Grab from canvas2"
							DisableGadget(canvas2)
							EnableGadget(canvas2)
							Grabbing = True ; SetPointer(POINTER_HAND); enter=1
					EndSelect
				EndIf

			Case EVENT_MOUSEUP
				If Grabbing = True 
					SetPointer(POINTER_ARROW) ; Grabbing = False
					Select Event.Source
						Case Canvas1 ; Print "Drop in canvas1"
						Case Canvas2 ; Print "Drop in canvas2"
						Default ; Print "Discarded"
					EndSelect
				EndIf
				
			Case EVENT_MOUSEENTER
				Print "enter "+Event.source.tostring()
				enter=True
				'Select Event.Source
				'	Case Canvas1 enter=True
				'	Case Canvas2 enter=True
				'	Case panel enter=True
				'EndSelect
				
			Case EVENT_MOUSELEAVE
				Print "leave "+Event.source.tostring()
				leave=MilliSecs()+300 ; enter=False
				'Select Event.Source
				'	Case Canvas1 enter=False
				'	Case Canvas2 enter=False
				'	Case panel enter=False
				'EndSelect; leave=MilliSecs()
				
		End Select
	End Method

	' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	
	Method Render1()
		SetGraphics CanvasGraphics(canvas1)
		SetViewport 0,0,GraphicsWidth(),GraphicsHeight()
		SetClsColor 0,0,0 ; Cls
		DrawText "Canvas1",5,5
		Flip
	End Method

	Method Render2()
		SetGraphics CanvasGraphics(canvas2)
		SetViewport 0,0,GraphicsWidth(),GraphicsHeight()
		SetClsColor 0,0,150 ; Cls
		DrawText "Canvas2",5,5
		Flip
	End Method
	
	' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	
End Type



Local	applet:TApplet = New TApplet

While True
	PollEvent
	If Grabbing And Not enter And MilliSecs()>leave Then
		Print "Discarded (Exit from window)"; 
		SetPointer(POINTER_ARROW) ; Grabbing = False
	EndIf
Wend


and for some reason it doesn't alway work on my system, especially if I move the mouse out the window really fast it doesn't always get the leave event. (is that a bug?)