Question on Events and Types

BlitzMax Forums/BlitzMax GUI Programming/Question on Events and Types

Hello, I am having a bit of trouble in a couple of areas and have a couple of questions.

1) When handling an event using types, do you place the actions to be performed upon in the update_state method for that type that you want the action performed on?

For example, I have a TNewCanvas type and a TNewWindow type. When I resize the TNewWindow (MainWindow) Object I want to resize the TNewCanvas to fit the new size of the TNewWinow Object. Do I put the statement to reshap the canvas in the parent TNewWindow object update_state method or the TNewCanvas obect (CHild)? I am currently using an event hook to handle the resizing using a resizeWindows function. This seems to work but doesn't seem perfect.

2) I am not clear if its a good idea to have all my event handler case statements in my Type objects themselves. Is this a good idea?

3) I would like to make this application more event driven, where I check what event happened and which object it affects and take the appropriate action instead of going through each objects update_state method (This doesn't seem very effective).

I have seen Noel Cower's version wrapper but I can't say I really understand it as it doesn't have the main routine code that actually calls the different types\functions.

Sorry this is a long request but I would really appreciate any responses or direction anyone could give me. (Only been doing blitzmax for a short time).

Here is my code:


SuperStrict

Global GAME_WIDTH:Int=800
Global GAME_HEIGHT:Int=600

AutoMidHandle True

'Global tImg:TImage=LoadImage(LoadBank("C:\BlitzmaxDev\Saxon_MapEditor\Tilesets\tileset.png"))

'***********Tiles
'Global tImg_Tileset1:TImage=LoadAnimImage("C:\C:\BlitzmaxDev\Saxon_MapEditor\Tilesets\tileset.png",32,32,0,7)

Local wx:Int=(GadgetWidth(Desktop())-GAME_WIDTH)/2
Local wy:Int=(GadgetHeight(Desktop())-GAME_HEIGHT)/2


'*****************************MAIN OBJECT LIST*******************************************
Global GadgetObjectList:TList=CreateList()


'*****************************CREATE OUR MAIN WINDOW OBJECT******************************

Global MainWindow:TNewWindow= New TNewWindow
MainWindow.create(MainWindow,"The Main Window",wx,wy,GAME_WIDTH,GAME_HEIGHT,Null)

Global text$= "My current size (w,h) is " + ClientWidth(MainWindow.Tgad)+ "," + ClientHeight(MainWindow.Tgad)
SetStatusText MainWindow.Tgad, text$


'*****************************CREATE OUR MAIN DRAW CANVAS********************************



Global MainCanvas:TNewCanvas= New TNewCanvas

MainCanvas.Create(MainCanvas,0,0,ClientWidth(MainWindow.Tgad),ClientHeight(MainWindow.Tgad),MainWindow.Tgad,Null)


SetGraphics CanvasGraphics(MainCanvas.Tgad)


'*****************************Create the Panels*****************************************

Global TilePanel:TNewPanel = New TNewPanel
TilePanel.create(TilePanel,0,0,ClientWidth(MainCanvas.Tgad)/3,ClientHeight(MainCanvas.Tgad),MainCanvas.Tgad,PANEL_GROUP,"TILE PANEL")
SetGadgetLayout TilePanel.Tgad,1,0,1,0
'*****************************Create the Tile Canvas************************************

Global TileCanvas:TNewCanvas = New TNewCanvas
TileCanvas.create(TileCanvas, 0,ClientHeight(TilePanel.Tgad)/2,ClientWidth(TilePanel.Tgad),ClientHeight(TilePanel.Tgad)/1.5,TilePanel.Tgad,Null)
SetGraphics CanvasGraphics(TileCanvas.Tgad)
SetGadgetColor TileCanvas.Tgad,100,100,100

'****************************Main Processing Loop******************************

	
Repeat	
	
		
		Cls
		
		SetGraphics CanvasGraphics(Maincanvas.Tgad)
		SetGraphics CanvasGraphics(TileCanvas.Tgad) 
		
		For Local o:TMyGadget=EachIn GadgetObjectList
        	   
			o.UpdateState()
		Next
		Flip
	
Until KeyDown(KEY_ESCAPE) Or AppTerminate()

End
      
	 


'*********************************TYPES*****************************************8
'*******************************************************************************


Type TMyGadget

	Field Tgad:TGadget
	Field Tgadx:Int
	Field Tgady:Int
	Field Tgadw:Int
	Field Tgadh:Int
	
	
	Method UpdateState() Abstract



	
End Type

'************************************THE WINDOW TYPE*********************************

Type TNewWindow Extends TMyGadget

	Method Create(ObjectName:TNewWindow,wCaption:String,wX:Int,wY:Int,wWidth:Int,wHeight:Int,wParent:TGadget)	
		
		
		Tgad= CreateWindow(wCaption,wX,wY,wWidth,wHeight,wParent,WINDOW_TITLEBAR | WINDOW_RESIZABLE | WINDOW_MENU | WINDOW_STATUS)	
		ListAddLast GadgetObjectList, ObjectName
	
	End Method

	
	Method UpdateState()
	
		AddHook EmitEventHook, Mainhook	
			
		
		WaitEvent()
		Select EventID()

			Case EVENT_WINDOWCLOSE
		
				End	
			
		'	Case EVENT_WINDOWSIZE
				
				
			
			
		'	Case EVENT_MOUSEMOVE

								
		
		'	Case EVENT_GADGETPAINT
		'		SetGraphics CanvasGraphics(Maincanvas)	
		'		SetViewport 0, 0, ClientWidth(Mainwindow), ClientHeight(Mainwindow)				
		'		Cls

			Case EVENT_APPTERMINATE
				End
		End Select

		'Flip
	
	End Method	
	
		
End Type


'*************************************THE CANVAS TYPE***********************************************

Type TNewCanvas Extends TMyGadget

	Method Create(ObjectName:TNewCanvas,wX:Int,wY:Int,wWidth:Int,wHeight:Int,wParent:TGadget,Format:Int)
		
		Tgad= CreateCanvas(wX,wY,wWidth,wHeight,wParent)
		ListAddLast GadgetObjectList, ObjectName
	End Method

	
	Method UpdateState()
	
		
		AddHook EmitEventHook, Mainhook	
			
	
		WaitEvent()
		Select EventID()
		

			Case EVENT_WINDOWCLOSE
		
				End	
			
			'Case EVENT_WINDOWSIZE
		'		
		
							
			Case EVENT_MOUSEMOVE
				Local x:Int=0
				Local y:Int=0
     			x=EventX()
     			y=EventY()
				Local statustext:String
			
				statustext= text$ + "     X Pixel loc= " + x + " Y Pixel loc = " + y
				SetStatusText MainWindow.Tgad, statustext
								
		
			Case EVENT_GADGETPAINT
				SetGraphics CanvasGraphics(Maincanvas.Tgad)			
				

			Case EVENT_APPTERMINATE
				End
		End Select

		'Flip
	
	End Method	
		
End Type

'*****************************************PANEL TYPE****************************

Type TNewPanel Extends TMyGadget

	Method Create(ObjectName:TNewPanel,wX:Int,wY:Int,wWidth:Int,wHeight:Int,wParent:TGadget,Format:Int,Title:String)	
		
		
		Tgad= CreatePanel(wX,wY,wWidth,wHeight,wParent,Format,Title)	
		ListAddLast GadgetObjectList, ObjectName
	
	End Method

	
	Method UpdateState()
	
		AddHook EmitEventHook, Mainhook	
			
		
		WaitEvent()
		Select EventID()

			Case EVENT_WINDOWCLOSE
		
				End	
			

			Case EVENT_APPTERMINATE
				End
		End Select

		'Flip
	
	End Method	
	
		
End Type


'**********************************FUNCTIONS************************************
'*******************************************************************************

Function MainHook:Object(iId:Int,tData:Object,tContext:Object)
  		Local Event:TEvent=TEvent(tData)

  		If event=Null Return Null
  
  		If Event.source=MainCanvas And Event.ID=EVENT_MOUSEDOWN
   
     		Return Null
  		EndIf
  		
  		If Event.source=MainWindow Or MainCanvas And event.id=EVENT_WINDOWSIZE
				ResizeWindows()
			
					
  		End If

  		Return tData
End Function

				
'**********************************Resize our Windows***************************
Function ResizeWindows()
			
				SetGadgetShape(Maincanvas.Tgad, 0, 0, ClientWidth(Mainwindow.Tgad), ClientHeight(Mainwindow.Tgad))
				SetGadgetShape(TilePanel.Tgad,0,0,ClientWidth(MainCanvas.Tgad)/3,ClientHeight(MainCanvas.Tgad))
				SetGadgetShape(TileCanvas.Tgad,0,ClientHeight(TilePanel.Tgad)/2,ClientWidth(TilePanel.Tgad),ClientHeight(TilePanel.Tgad)/1.5)
				SetViewport 0, 0, ClientWidth(MainWindow.Tgad),ClientHeight(MainWindow.Tgad)					
				
End Function


Please use a codebox when posting large code, look here: http://www.blitzbasic.com/faq/faq_entry.php?id=2

1)Yes, normally that is what you want to do.
2)That is ok if you filter only those events you need in that specific type.
3)I think here is your biggest problem as the update state method doesn't work as you expect. In every call of the update you add another hook? You would do this only once. Actually it works the other way around. Create a type, register it once in the hook and do nothing... When an event occurs the registered hook function is run and you filter your needed event and react on it as you want.

Sorry, Im new here, so just use the tags
 and  
? Will do from now on.

When you say create a type and register it once in the hook and do nothing. I don't really understand what you mean. Are saying only have the hook function in one of the types? Can you provide an example?

Maybe you mean create the hook outside of the types before I start my for loop?

Look at this example.
Watch the debuglog output while resizing the window or clicking on the upper or lower canvas.

SuperStrict

Local Window1:TGadget = CreateWindow:TGadget("Window1",329,159,318,235,Null,WINDOW_TITLEBAR|WINDOW_RESIZABLE |WINDOW_STATUS |WINDOW_CLIENTCOORDS )
	Local Canvas1:TGadget = CreateCanvas:TGadget(17,145,284,80,Window1:TGadget,Null)
		SetGadgetLayout( Canvas1:TGadget,EDGE_ALIGNED,EDGE_ALIGNED,EDGE_ALIGNED,EDGE_ALIGNED )
	Local MyCanvas:TMyCanvas = TMyCanvas.Create(17,20,284,80,Window1:TGadget)
	MyCanvas.update()
	
Repeat
	WaitEvent()
	Select EventID()
		Case EVENT_WINDOWCLOSE
			Select EventSource()
				Case Window1	Window1_WC( Window1:TGadget )
			End Select

		Case EVENT_MOUSEDOWN
			Select EventSource()
				Case Canvas1	Canvas1_MD( Canvas1:TGadget , EventData() , Window1:TGadget )
			End Select

		Case EVENT_GADGETPAINT
			Select EventSource()
				Case Canvas1	Canvas1_GP( Canvas1:TGadget )
			End Select

	End Select
Forever

Function Window1_WC( Window:TGadget )
	DebugLog "Window Window1 wants to be closed"

	End
End Function

Function Canvas1_MD( Canvas:TGadget , MouseButton:Int , Window:TGadget=Null )
	DebugLog "Canvas Canvas1 detected Mouse Button "+ MouseButton +" pressed down"
	SetGraphics CanvasGraphics ( Canvas )
	SetViewport 0,0,GadgetWidth( Canvas ),GadgetHeight( Canvas )
	Cls
	SetColor Rand(0,255) , Rand(0,255) , Rand(0,255)
	DrawRect (EventX() , EventY() , 20 , 20)
	Flip
End Function

Function Canvas1_GP( Canvas:TGadget )
	DebugLog "Canvas Canvas1 needs to be redrawn"
	SetGraphics CanvasGraphics ( Canvas )
	SetViewport 0,0,GadgetWidth( Canvas ),GadgetHeight( Canvas )
	
	SetColor( 208,216,61 )
	SetClsColor( 118,131,184 )
	Cls
	DrawText( "LogicZone" ,1,1 )
	Flip

End Function


Type TMyCanvas
	Field canvas:TGadget
	Field parent:TGadget

	Function eventhook:Object(id:Int,data:Object,context:Object)
		If TMyCanvas(context) 
			TMyCanvas(context).OnEvent TEvent(data)
			Return data
		End If
	EndFunction
	
	Method New()
		AddHook EmitEventHook,eventhook,Self
	End Method

	Method OnEvent(event:TEvent)
		If event.source=Canvas
			If event.id = EVENT_MOUSEDOWN
				DebugLog "Event MOUSEDOWN from hook"
				update()
			End If
		End If
		If event.id = EVENT_WINDOWSIZE
			DebugLog "Event WINDOWSIZE from hook"
			update()
		End If
	End Method
	
	Method update()
		SetGraphics CanvasGraphics(canvas)
		SetViewport 0,0,GadgetWidth( Canvas ),GadgetHeight( Canvas )
		SetClsColor Rand(0,255) , Rand(0,255) , Rand(0,255)
		Cls
		Flip
	End Method
	
	Function create:TMyCanvas(x:Int,y:Int,width:Int,height:Int,parent:TGadget)
		Local temp:TMyCanvas=New TMyCanvas
		temp.canvas=CreateCanvas(x,y,width,height,parent)
		SetGadgetLayout( temp.canvas:TGadget,EDGE_ALIGNED,EDGE_ALIGNED,EDGE_ALIGNED,EDGE_ALIGNED )
		temp.parent=parent
		Return temp
	End Function

	
End Type


[EDIT]
The lower canvas is maintained by the waitevent queue, that is quite ok for all gadgets which don't need to react on events while you drag something (e.g. A slider movement or window resize). The upper canvas hooks itself (only once during creation) into the event queue and doesn't do anything then. When there is an event the OnEvent Method is called and filters those events it want to do something on (calls here for update)...

Thank you this is very helpful, I understand now how to hook a type (Itself) to an event hook.

What If I have 2 canvas's on my window and want them both resized when I resize the window\and maximize it?

Is it a bad idea to have 2 canvas's on one window? I realize having them overlap is a bad idea, so they do not.

Here is the code you sent but I modified it so that I have 2 canvas types and they both attach themselves to the hook and then get updated at the same time when an event occurs. (I moved the hook functions out of the types for re-usability to minimize code)

Is this a bad idea to update them at the same time? I have been having some trouble getting both of my canvas's to resize to the window properly in my other project (One I originally sent), it seems like it works about 90% of the time and other times it doesn't resize one of the canvas's or both.

Here is your code that I update, would this be a good way to do it? Or is it better practice to only have 1 canvas on a window use a hook and any others handled a different way?

SuperStrict
	
Local Window1:TGadget = CreateWindow:TGadget("Window1",329,159,318,235,Null,WINDOW_TITLEBAR|WINDOW_RESIZABLE |WINDOW_STATUS |WINDOW_CLIENTCOORDS )

	Global MyCanvas2:TMyCanvas2 = New TMyCanvas2
	MyCanvas2.Create(17,145,284,80,Window1:TGadget)
	MyCanvas2.update()
	
	Global MyCanvas:TMyCanvas = New TMyCanvas
	MyCanvas.Create(17,20,284,80,Window1:TGadget)
	MyCanvas.update()

	
Repeat
	WaitEvent()
	

Forever



Type TMyCanvas
	Field canvas:TGadget
	Field parent:TGadget

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

	
	Method update()
		SetGraphics CanvasGraphics(canvas)
		SetViewport 0,0,GadgetWidth( Canvas ),GadgetHeight( Canvas )
		SetClsColor Rand(0,255) , Rand(0,255) , Rand(0,255)
		Cls
		Flip
	End Method
	
	Method create:TMyCanvas(x:Int,y:Int,width:Int,height:Int,parent:TGadget)
		
		canvas=CreateCanvas(x,y,width,height,parent)
		SetGadgetLayout( canvas:TGadget,EDGE_ALIGNED,EDGE_ALIGNED,EDGE_ALIGNED,EDGE_ALIGNED )
		canvas.parent=parent
		
	End Method

	
End Type

Type TMyCanvas2
	Field canvas:TGadget
	Field parent:TGadget
	
	Method New()
		AddHook EmitEventHook,eventhook,Self
	End Method

	
	Method update()
		SetGraphics CanvasGraphics(canvas)
		SetViewport 0,0,GadgetWidth( Canvas ),GadgetHeight( Canvas )
		SetClsColor Rand(0,255) , Rand(0,255) , Rand(0,255)
		Cls
		Flip
	End Method
	
	Method create:TMyCanvas2(x:Int,y:Int,width:Int,height:Int,parent:TGadget)
		
		canvas=CreateCanvas(x,y,width,height,parent)
		SetGadgetLayout( canvas:TGadget,EDGE_ALIGNED,EDGE_ALIGNED,EDGE_ALIGNED,EDGE_ALIGNED )
		canvas.parent=parent
	
	End Method

	
End Type

	Function eventhook:Object(id:Int,data:Object,context:Object)
		If TMyCanvas(context) 
			OnEvent TEvent(data)
			Return data
		End If
		If TMyCanvas2(context) 
			OnEvent TEvent(data)
			Return data
		End If
	EndFunction
	
	Function OnEvent(event:TEvent)
		If event.source=MyCanvas.canvas Or MyCanvas2.canvas
			If event.id = EVENT_MOUSEDOWN
				DebugLog "Event MOUSEDOWN from hook"
				MyCanvas.update()
				MyCanvas2.update
			End If
		
			If event.id = EVENT_WINDOWSIZE
				DebugLog "Event WINDOWSIZE from hook"
				MyCanvas.update()
				MyCanvas2.update
			End If	
		End If
	EndFunction


Aaarg, sorry I didnt use the codebox the first time I just used Code tags. I tried to update it, but apparently the update of a post to add codebox tags doesnt work?

No, your codebox ending has the slash in the wrong direction...

/codebox

Ahh, what a dork:) Fixed now in both posts.

Two canvas should be no problem. This is what your example also does. And yes overlapping is a bad idea. Just keep in mind to init the correct canvas always before writing to it (as in the example).

Here is the code you sent but I modified it


Although this looks a good approach it is not! Why? Because every time you add something you re-add another already existing event-type connection. You can see it just best in your example when you run it and only single click onto one canvas, the result is 2 times the debuglog output! That means although you only once clicked, your hooked routine was run twice and both canvas switch it's color although only the one clicked onto should.

Is this a bad idea to update them at the same time?


Try to separate always the logic from the drawing. They can be updated of course in one cycle but every canvas should have it's own routine for it.

Here is your code that I update, would this be a good way to do it? Or is it better practice to only have 1 canvas on a window use a hook and any others handled a different way?


I think this is just a personal thing. I would say it depends a bit on the structure of the program. For my personal taste it doesn't make sense to make a certain thing too complicated if it's not needed. The hook functions are very nice but need more work. The waitevent queue is easy but is somewhat limited. In the past i worked always with callbacks (hooks in BM), they are more direct, but although they can be considered more advanced they can give you some headache when it comes to complete eventqueue swapping.

I post you another example, maybe things are getting clearer then. You will find only one type as before (a bit modified) which will be created 4 times. Every time the type is build it hooks itself into the eventqueue. When the event occurs it sorts out itself where it belongs to. Canvas 4 for instance stays always red in this update! Of course there are many other ways to do it...
Setting the layout for every canvas gives the freedom to adjust them to the window.

SuperStrict
	
Local Window1:TGadget = CreateWindow:TGadget("Window1",329,159,318,235,Null,WINDOW_TITLEBAR|WINDOW_RESIZABLE |WINDOW_STATUS |WINDOW_CLIENTCOORDS )
	Local MyCanvas1:TMyCanvas = TMyCanvas.Create(10,10,100,100,Window1:TGadget)
	SetGadgetLayout( MyCanvas1.canvas:TGadget,1,2,1,2 )
	MyCanvas1.update
	
	Local MyCanvas2:TMyCanvas = TMyCanvas.Create(10,120,100,100,Window1:TGadget)
	SetGadgetLayout( MyCanvas2.canvas:TGadget,1,2,2,1 )

	Local MyCanvas3:TMyCanvas = TMyCanvas.Create(120,10,100,100,Window1:TGadget)
	SetGadgetLayout( MyCanvas3.canvas:TGadget,2,1,1,2 )
	
	Local MyCanvas4:TMyCanvas = TMyCanvas.Create(120,120,100,100,Window1:TGadget)
	SetGadgetLayout( MyCanvas4.canvas:TGadget,2,1,2,1 )


	
Repeat
	WaitEvent()
	

Forever



Type TMyCanvas
	Global Counter:Int
	Field CanvasNr:Int
	Field canvas:TGadget
	Field parent:TGadget

	Function eventhook:Object(id:Int,data:Object,context:Object)
		If TMyCanvas(context) 
			TMyCanvas(context).OnEvent TEvent(data)
			Return data
		End If
	EndFunction
	
	Method New()
		AddHook EmitEventHook,eventhook,Self
	End Method

	Method OnEvent(event:TEvent)
		If event.source=Canvas
			If event.id = EVENT_MOUSEDOWN
				DebugLog "Event MOUSEDOWN from hook"
				update()
			End If
		End If
		If event.id = EVENT_WINDOWSIZE
			DebugLog "Event WINDOWSIZE from hook"
			If CanvasNr < 4
				update()
			Else
				SpecialUpdate
			End If
			
		End If
	End Method
	
	Method update()
		If canvas = Null Return
		DebugLog "Update for Canvas="+CanvasNr
		SetGraphics CanvasGraphics(canvas)
		SetViewport 0,0,GadgetWidth( Canvas ),GadgetHeight( Canvas )
		SetClsColor Rand(0 , 255) , Rand(0 , 255) , Rand(0 , 255)
		Cls
		SetColor 0 , 0 , 0
		DrawText( CanvasNr , 10,10)
		Flip
	End Method

	Method SpecialUpdate()
		If canvas = Null Return
		DebugLog "Update for Canvas="+CanvasNr
		SetGraphics CanvasGraphics(canvas)
		SetViewport 0,0,GadgetWidth( Canvas ),GadgetHeight( Canvas )
		SetClsColor 255 , 0 , 0
		Cls
		SetColor 0 , 0 , 0
		DrawText( CanvasNr+" -> always red" , 10,10)
		Flip
	End Method
	
	Function create:TMyCanvas(x:Int,y:Int,width:Int,height:Int,parent:TGadget)
		Local temp:TMyCanvas=New TMyCanvas
		temp.canvas=CreateCanvas(x,y,width,height,parent)
		temp.parent = parent
		temp.Counter:+ 1
		Temp.CanvasNr=temp.Counter
		Return temp
	End Function

	
End Type



Excellent example, this helps very much thank you! I wasn't real clear on how to handle events for different objects so this make it clear.