Why doesn't this work?

BlitzMax Forums/BlitzMax GUI Programming/Why doesn't this work?

Hi All,

I am trying to write a custom gadget using a simiar event model to the one below.

But I can only get one of them to work at a time. If I create more than one instance, either the application will exit without a word (in Debug Build), or only one of the works (Non Debug).

Should I post it in the Bug forum, or am I doing something silly?

' redrawgadget.bmx

Strict

Type TApplet 

	Method OnEvent(Event:TEvent) Abstract

	Method New()
		AddHook EmitEventHook,eventhook,Self
	End Method

	Function eventhook:Object(id,data:Object,context:Object)
		Local	event:TEvent
		Local	app:TApplet
		event=TEvent(data)
		app=TApplet(context)
		app.OnEvent event	
	End Function

End Type

Type TSpinningApplet Extends TApplet

	Field	canvas:TGadget
	Field	timer:TTimer
	
	Method Draw()
		SetGraphics CanvasGraphics(canvas)
		SetViewport 0,0,GraphicsWidth(),GraphicsHeight()
		SetBlend ALPHABLEND
		SetRotation MilliSecs()*.1
		SetClsColor 255,0,0
		Cls
		DrawRect GraphicsWidth()/2,GraphicsHeight()/2,128,128
		Flip
	End Method
	
	Method OnEvent(Event:TEvent)
		Select event.id
		Case EVENT_WINDOWCLOSE
			End
		Case EVENT_TIMERTICK
			RedrawGadget canvas
		Case EVENT_GADGETPAINT
			draw
		End Select
	End Method
	
	Method Create:TSpinningApplet(Win:Tgadget)
		Local	a:TApplet
		Local	w,h
		w = ClientWidth(win)
		h=ClientHeight(win)
		canvas=CreateCanvas(0,0,w,h,win)
		canvas.SetLayout 1,1,1,1
		timer=CreateTimer(90)
		Return Self		
	End Method
	
End Type

Local spinner:TSpinningApplet
Local Window:tgadget 
Local Spinner2:TSpinningApplet
Local Window2:TGadget

window=CreateWindow("Test1",20,20,512,512)
spinner=New TSpinningApplet.Create(Window)

window2=CreateWindow("Test2",20,20,512,512)
spinner2=New TSpinningApplet.Create(Window2)

While True
	WaitEvent
Wend


This just runs & exits for me...

...and it's the AddHook that fails. I'm not sure you can reference "Self" in a "New" method?

Okay, the above code runs for me if I amend eventhook to check that I received an event before processing it - presumably I'm getting some kind of weird non-event being raised? In any case, window test2 is not showing anything, which presumably is your bug...
	Function eventhook:Object(id,data:Object,context:Object)
		Local	event:TEvent
		Local	app:TApplet
		event = TEvent(data)
		If Not event Then return
		app=TApplet(context)
		app.OnEvent event	
	End Function


And here's the fix:

	Function eventhook:Object(id,data:Object,context:Object)
		Local	event:TEvent
		Local	app:TApplet
		event = TEvent(data)
		If Not event Then Return data
		app=TApplet(context)
		app.OnEvent event	
		Return data
	End Function

Why does this fix things? When Bmx calls the eventhooks, it calls them in sequence until one returns Null. If Null is returned, it assumes the eventhook has handled the event and stops calling subsequent eventhooks, if the object is returned, it moves on to the next eventhook. You were "firing blanks", and so your second gadget never received a darn thing!

Sometimes you want the event to "pass through" all eventhooks, and do something with each, and this is what I've done here. This is the simplest thing to do, but means lots of unnecessary processing, and potential for bugs if you forget to check what gadget the event is intended for.

The best thing to do however, is to use the following:
	
	Method OnEvent:Integer(Event:TEvent) Abstract

	Function eventhook:Object(id,data:Object,context:Object)
		Local	event:TEvent
		Local	app:TApplet
		event = TEvent(data)
		If Not event Then Return data
		app=TApplet(context)
		If app.OnEvent(event) Return
		Return data
	End Function

You then change your code in OnEvent to return null if it has handled the event and nothing else needs to know about it.

There's actually another issue in your OnEvent code. You are handling ALL events for ALL gagdets here. You should check that the event is related to your gadget(s) before handling it. For example, there is a timer and a canvas in each gadget. you are processing each event twice in each gadget, because you're not checking that it relates to you.

So, here's the fully fixed code (unless I missed something else of course!)
' redrawgadget.bmx

Strict

Type TApplet 

	Method OnEvent:Int(Event:TEvent) Abstract

	Method New()
		AddHook EmitEventHook,eventhook,Self
	End Method

	Function eventhook:Object(id,data:Object,context:Object)
		Local	event:TEvent
		Local	app:TApplet
		event = TEvent(data)
		If Not event Then Return data
		app=TApplet(context)
		If app.OnEvent(event) Return
		Return data
	End Function

End Type

Type TSpinningApplet Extends TApplet

	Field	canvas:TGadget
	Field	timer:TTimer
	
	Method Draw()
		SetGraphics CanvasGraphics(canvas)
		SetViewport 0,0,GraphicsWidth(),GraphicsHeight()
		SetBlend ALPHABLEND
		SetRotation MilliSecs()*.1
		SetClsColor 255,0,0
		Cls
		DrawRect GraphicsWidth()/2,GraphicsHeight()/2,128,128
		Flip
	End Method
	
	Method OnEvent:Int(Event:TEvent)
		If event.source = canvas And event.id = EVENT_GADGETPAINT Then draw ; Return True
		If event.source = timer And event.id = EVENT_TIMERTICK Then RedrawGadget canvas ; Return True
		Return False
	End Method
	
	Method Create:TSpinningApplet(Win:Tgadget)
		Local	a:TApplet
		Local	w,h
		w = ClientWidth(win)
		h=ClientHeight(win)
		canvas=CreateCanvas(0,0,w,h,win)
		canvas.SetLayout 1,1,1,1
		timer=CreateTimer(90)
		Return Self		
	End Method
	
End Type

Local spinner:TSpinningApplet
Local Window:tgadget 
Local Spinner2:TSpinningApplet
Local Window2:TGadget

window=CreateWindow("Test1",20,20,512,512)
spinner=New TSpinningApplet.Create(Window)

window2=CreateWindow("Test2",20,20,512,512)
spinner2=New TSpinningApplet.Create(Window2)

While True
	WaitEvent
	If EventID()=EVENT_WINDOWCLOSE And (EventSource() = window Or EventSource() = window2) Then End
Wend


Hi,

Thanks for that. The hook code was copied from the examples in the BlitzMax documentation, so I wasn't really expecting it to bomb out.

Thanks for the help.