The idea
Handling window messages can be done in multiple ways - by reading the state asynchronously, in tight message pumps, using callback mechanisms - ThinBASIC has it all.
While experimenting with more heavily GUI based languages, I realised the approach "one event -> one procedure" is another quite nice way to manage things.
The most common approach in ThinBASIC now is using callbacks - it is fast, it is quite elegant, but still - you usually end with SELECT CASE of epic proportions, handling Win32 dark magic such as wParam and lParam.
To give an example, here little demo:
callback function dlgCallback() as long
select case msg
case %WM_LBUTTONDOWN
Long x = LO(WORD, cblParam)
Long y = HI(WORD, cblParam)
...
It works, it is standard, but it can be a bit cryptic sometimes and when the SELECT CASE grows too much, the program tends to be slightly noodle like.
I was thinking about creating something like a mixture between the power of direct Win32 access and the high levelity seen for example in C#.
The possible approach
My first attempt is lightweight module called simply EventShaper. It allows to subscribe for processing of various events in way which is more human friendly than Win32 in the end (of course, subjective feeling).
How does this work?
- Step 1: You define wrapper from Win32 to TYPE of your choice, directly in ThinBASIC
- you do this once, and it can be used in multiple projects later
- Step 2: You define custom event handling routines, which use the "nice" syntax
To give you better idea, here is sample code:
'
' Simple test of Event Shaper
' Created by Petr Schreiber, 2012
'
#MINVERSION 1.9.0.0
Uses "UI", "EventShaper"
Uses "Console"
#INCLUDE "Wrapper_Definitions.tBasicI"
' -- Create dialog here
Function TBMain()
Local hDlg As DWord
Dialog New Pixels, 0, "Click on this window",-1,-1, 320, 240, _
%WS_POPUP Or %WS_VISIBLE Or %WS_CAPTION Or %WS_SYSMENU Or %WS_MINIMIZEBOX To hDlg
EventShaper_SubscribeWindowEvent(hDlg, %WM_LBUTTONDOWN, "Main_OnLButtonDown")
EventShaper_SubscribeWindowEvent(hDlg, %WM_LBUTTONUP , "Main_OnLButtonUp")
EventShaper_SubscribeWindowEvent(hDlg, %WM_CLOSE , "Main_OnClose")
Dialog Show Modal hDlg, Call cbDialog
End Function
' -- Event handler of OnLButtonDown receives data in nice form
Function Main_OnLButtonDown()
' -- Single line to retrieve all the parameters
Dim eArgs As tEvent_OnLButtonDown At EventShaper_GetEventArguments()
' -- We can look them up and print out to console
PrintL "OnLButtonDown"
PrintL "hWnd", eArgs.hWnd
PrintL "X", eArgs.x
PrintL "Y", eArgs.y
PrintL
End Function
' -- Event handler of OnLButtonUp receives data in nice form
Function Main_OnLButtonUp()
' -- Single line to retrieve all the parameters
Dim eArgs As tEvent_OnLButtonUp At EventShaper_GetEventArguments()
' -- We can look them up and print out to console
PrintL "OnLButtonUp"
PrintL "hWnd", eArgs.hWnd
PrintL "X", eArgs.x
PrintL "Y", eArgs.y
PrintL
End Function
Function Main_OnClose()
MsgBox 0, "Yeah, yeah, I will quit now..."
End Function
' -- Callback for dialog - still needed, not sure why
CallBack Function cbDialog()
End Function
As you can see, it looks quite straightforward and the program is not complicated with much Win32 specifics.
The example above does not show the wrapping part, because that is the thing you do typically once, keep that ugly beast in separate include file and never look at it again
The wrapping is 100% under programmer control. Here example for WM_LBUTTONDOWN:
'[!] WM_LButtonDown
' -- Completely custom type
Type tEvent_OnLButtonDown
hWnd As DWord
x As Long
y As Long
End Type
' -- Wrapper routine
Function Wrapper_WM_LBUTTONDOWN()
' -- Retrieve the needed Win32 params (EventShaper engine picks them for you)
DWord hWnd, wParam, lParam
EventShaper_GetWrapperParams(hWnd, wParam, lParam)
' -- Process them to human readable form
Dim event As tEvent_OnLButtonDown
event.hWnd = hWnd
event.x = LO(Word, lParam)
event.y = HI(Word, lParam)
' -- Pack them for the event handler in nice form
EventShaper_SetEventArguments(event)
' -- EventShaper module will call the appropriate high level routine when ready
End Function
' -- Link the routine
EventShaper_SetWrapper(%WM_LBUTTONDOWN, "Wrapper_WM_LBUTTONDOWN")
In ideal world, the module would do this wrapping internally - but doing this for whole Win32 range of messages would have these disadvantages:
- It would take lot of time to prepare (the module as is took 1 day to prepare)
- It would mean having the events hardwired, adding others would mean recompiling the module
- It would force the user to accept one and only way the parameters are organised
The approach now is more free in the way you can add any event you want and publish the data from it in way which you like most (because how will the TYPE with data look is completely up to you).
The wrapper setup and window-procedure binding is separate, which means you can reuse the wrapper include later in other project, as it won't contain any project specific code.
Module functions
There are two functions for specifying the Win32 message wrapper:
EventShaper_SetWrapper(BYVAL Win32Message AS DWORD, BYVAL wrapperFunction AS FUNCTION)
- Specifies wrapper function for given Win32 message
EventShaper_ClearWrapper(BYVAL Win32Message AS DWORD)
- Cancels wrapper function for given Win32 message
Then there are two functions to be used inside the wrapper function on ThinBASIC side:
EventShaper_GetWrapperParams(BYREF hWnd AS DWORD, BYREF wParam AS DWORD, BYREF lParam AS DWORD)
- Allows wrapper function to retrieve the hWnd, wParam and lParam
EventShaper_SetArguments(BYVAL param AS ANY)
- Allows wrapper function to pass out the processed parameters
Then one function to retrieve the parameters in the event handler:
EventShaper_GetArguments() AS DWORD
- Allows event function to retrieve the processed parameters
And of course, the last but not least, functions to actually link the particular window to high level event handler:
EventShaper_SubscribeWindowEvent(BYVAL hWnd AS DWORD, BYVAL Win32Message AS DWORD, BYVAL eventFunction AS FUNCTION)
- Subscribes wrapped processing for passed window
EventShaper_UnSubscribeWindowEvent(BYVAL hWnd AS DWORD, BYVAL Win32Message AS DWORD, BYVAL eventFunction AS FUNCTION)
- Unsubscribes wrapped processing for passed window
Demo + Source code
You will probably need ThinBASIC 1.9.0.0 to try this out: http://www.thinbasic.biz/projects/th...ic_1.9.0.0.zip
The attachement contains:
- Simple demo application with module attached
- Full source code for PB/WIN 9
Please note this is the first version, so the chance to find a bug is quite high and there is not really much error checking done yet.
Let me know what you think about it!
Petr
Bookmarks