Tutorial: events handling with the Form Designer
Posted: Wed May 20, 2015 4:11 pm
The Basic Anatomy of a PureBasic Program
At its very basic, a PureBasic window-based program would look something like this (window-based and not Windows-based - referring to any program on any operating system that uses forms, dialogs, or windows):There are two parts to this code.
The first part builds the interface, and simply instantiates a 300-pixel wide by 200-pixel high window at position 100, 100 on the screen, and gives it a title of My Window. In order to be referenced later, PureBasic also assigns the window an identifier of 0 - the first parameter.
The second part is known as the event loop, and is the backbone of any window-based PureBasic program. Although the example uses a Repeat-Until loop, any type of loop could be used in its place. The ultimate purpose is to listen for events that the operating system passes to the program, using the WaitWindowEvent() function, and to process those received events by executing any relevant code associated with the said event. This is known as an event-driven paradigm, and is the architecture that PureBasic is built upon.
It should be noted that the event loop is not running in a mad frenzy, like a normal endless loop. The WaitWindowEvent() function actually pauses program execution until it receives an event, at which time the event is passed into the loop to be processed. Then the loop resumes, and pauses again at the WaitWindowEvent() function to await the next event. This process continues until the #PB_Event_CloseWindow event is received, and the program terminates.
In the above skeleton example, the loop simply waits for events, but does not process them in any way. The only event that is processed is the #PB_Event_CloseWindow event, which as you can see, breaks the loop, and essentially ends the program:
The code instantiates a fully functional window, which behaves almost like any window of the respective operating system. However, this window can only be moved around or closed. It can't be maximised, minimised, or resized. To do that, the proper directives should be added to the OpenWindow() command, like so:As you can see, some flags have been added to the OpenWindow() command, which is quite self-explanatory. These are just a few of the many directives that could be added to this command to control the attributes of the resulting window. Another very useful one is the #PB_Window_ScreenCentered flag, which simply centres the window on the screen. With this directive, the x and y positions of the OpenWindow() would be ignored, and could just be assigned zero values:Another thing to note is the difference between the #PB_Window_MaximizeGadget & #PB_Window_MinimizeGadget flags and #PB_Window_Maximize & #PB_Window_Minimize. As we have seen, the first two implements the maximise and minimise controls on the window. On the other hand, the second two sets the state of the window when the program starts, to either maximised or minimised. Needless to say, only one should be used at a time.
Another thing you'll notice is that the constants of these window flags are pretty verbose, which could result in lengthy commands. One way to handle this clutter would be to make use of PureBasic's line continuation feature, and break the flags up into separate lines, like this:That looks better, and greatly improves readability as well. Another approach would be to assign these flags to a variable, and then use that variable in the OpenWindow() command, like this:Not functionally relevant, but simply good coding practices.
There are many other window functions and features, but we'll leave that to the manual.
Handling Gadgets and Events
Now, let's add some gadgets to the window, and see how they work in the scheme of things. To demonstrate this simply, we'll use a button, label, and text box. Here's the code:The window now contains three gadgets; a TextGadget(), a StringGadget(), and a ButtonGadget(), positioned accordingly, and assigned identifiers of 1, 2 and 3 respectively. The label's caption has been set as "This is a label", and the button's caption has been set as "Click Me"; but the text box has been left blank. However, the window still does nothing besides displaying itself along with its contents. This is because the event loop is not processing any events yet. So, let's do that now.
Before we begin, here are a few points about how PureBasic manages the events that it receives. Every event carries with it the identifiers of its intended window and gadget. Accordingly, in the case of multi-window applications, we'd first have to determine which window the event is intended for. This is done with the EventWindow() function. Then, we'd check which gadget it's meant for, and process it accordingly. This simplified pseudo-flow might illustrate it better:Notice how the loop structure has changed, from Repeat-Until to Repeat-Forever. In multi-window applications, closing any one window will trigger the #PB_Event_CloseWindow event, but that does not necessarily mean to end the program. Therefore, this event is processed within each window's sub-loop, so to speak, and the relevant code would be executed accordingly; which is to either simply close the window, or end the program.
On the other hand, in single-window applications, closing the window indicates an intention to terminate the program. So, the #PB_Event_CloseWindow event could be safely processed exclusively. Furthermore, in single-window applications, the EventWindow() function could also be omitted, as all events are logically meant for it.
That's what we'll be doing, and we'll start with the button:The first If/EndIf block added within the event loop simply checks any events that it receives, and determines if it is a gadget event, #PB_Event_Gadget. If it's not, it continues the loop, and waits for the next event. If it is a gadget event, it will look for the gadget identifier with the EventGadget() function. Then it checks if it is an event from the button (gadget identifier 3), and if it is, executes the code within that second If/EndIf block.
Of course, there's no code there now, so it still does nothing. So let's perform an action:Now, when the button is clicked, the text in the label will change.
Just for the fun of it, let's make use of the text box as well:Now, when the button is clicked, the label will contain the text that has been typed into the text box.
Not exactly Flappy Birds, but I hope that it explains PureBasic's programming structure adequately.
Using PureBasic Forms
The PureBasic form designer simply automates the coding of the user interface. However, the form files that are generated can't be modified, and should thus be called from another module. Furthermore, the structure of these self-generated form files are quite different, in that most of the code are encapsulated within procedures.
However, the program logic remains the same, and the implementation is quite easy.
Let's try to recreate the above example in a form project:
1. From the PureBasic IDE, click on the File menu, and select Preferences.... In the left panel of the dialog that appears, select Form, then in the dialog window, make sure that the Generate event procedure and Generate event loop options are selected. Click Apply then Ok.
2. Again, from the PureBasic IDE, click on the Form menu, and select New Form.
3. Resize the model window to 300 pixels by 200 pixels either by dragging the drag-handle located at its bottom-right corner, or by changing the width and height values in the properties window.
4. Also in the properties window, set the window caption to My Window and select the following constants:
#PB_Window_MaximizeGadget
#PB_Window_MinimizeGadget
#PB_Window_SizeGadget
#PB_Window_ScreenCentered
5. Now, from the Toolbox panel, select the Button gadget by clicking on it once (click and release). Then move the mouse over to the model window and click-hold-down-and-drag the mouse to draw the button at the desired location; release when done. In the properties window, enter the caption for the button as Click Me, and in the Event procedure field, enter buttonEvent.
6. Repeat step number 4 for a String and Text gadget, and enter the caption for the Text gadget as This is a label..., but leave the caption for the String gadget blank. For these two gadgets, leave the Event procedure field blank.
7. Click Ctrl-S (or select Save from the File menu), and save the form under the file name MyForm.pbf.
8. From the Form menu, select Switch Code/Design View, and you should be able to see the auto-generated code, which should look something like this:
9. That's it for the form designer. Now to create the external module that will handle the button procedure, buttonEvent().
10. Click Ctrl-N (or select New from the File menu), for a new PureBasic file, and enter the following code:
11. Click Ctrl-S (or select Save from the File menu), and save this file as MyFormEvents.pb, in the same folder as MyForm.pbf. If it's not in the same folder, you'd have to include the proper file paths to locate the form file:
12. You're done! Click F5 (or select Compile/Run from the Compiler menu) to run the code, and you should see the same results as the earlier manually-coded example.
There are many different ways to implement the PureBasic form file, and this is just one of them. For example, for greater control of the event loop, it could be disabled in the form designer preferences, and implemented in an external module. And for absolute control, even the event procedure option could be disabled and implemented manually. This would mean that the form file is used purely as an interface builder with no higher function.
In any case, really hope it helps.
PS: Here's also an updated video tutorial on the Form Designer:
> PureBasic Form Designer Tutorial 2022
At its very basic, a PureBasic window-based program would look something like this (window-based and not Windows-based - referring to any program on any operating system that uses forms, dialogs, or windows):
Code: Select all
OpenWindow(0, 100, 100, 300, 200, "My Window")
Repeat
event = WaitWindowEvent()
Until event = #PB_Event_CloseWindow
The first part builds the interface, and simply instantiates a 300-pixel wide by 200-pixel high window at position 100, 100 on the screen, and gives it a title of My Window. In order to be referenced later, PureBasic also assigns the window an identifier of 0 - the first parameter.
The second part is known as the event loop, and is the backbone of any window-based PureBasic program. Although the example uses a Repeat-Until loop, any type of loop could be used in its place. The ultimate purpose is to listen for events that the operating system passes to the program, using the WaitWindowEvent() function, and to process those received events by executing any relevant code associated with the said event. This is known as an event-driven paradigm, and is the architecture that PureBasic is built upon.
It should be noted that the event loop is not running in a mad frenzy, like a normal endless loop. The WaitWindowEvent() function actually pauses program execution until it receives an event, at which time the event is passed into the loop to be processed. Then the loop resumes, and pauses again at the WaitWindowEvent() function to await the next event. This process continues until the #PB_Event_CloseWindow event is received, and the program terminates.
In the above skeleton example, the loop simply waits for events, but does not process them in any way. The only event that is processed is the #PB_Event_CloseWindow event, which as you can see, breaks the loop, and essentially ends the program:
Code: Select all
Until event = #PB_Event_CloseWindow
Code: Select all
OpenWindow(0, 100, 100, 300, 200, "My Window", #PB_Window_MaximizeGadget | #PB_Window_MinimizeGadget | #PB_Window_SizeGadget)
Repeat
event = WaitWindowEvent()
Until event = #PB_Event_CloseWindow
Code: Select all
OpenWindow(0, 0, 0, 300, 200, "My Window", #PB_Window_MaximizeGadget | #PB_Window_MinimizeGadget | #PB_Window_SizeGadget | #PB_Window_ScreenCentered)
Repeat
event = WaitWindowEvent()
Until event = #PB_Event_CloseWindow
Another thing you'll notice is that the constants of these window flags are pretty verbose, which could result in lengthy commands. One way to handle this clutter would be to make use of PureBasic's line continuation feature, and break the flags up into separate lines, like this:
Code: Select all
OpenWindow(0, 0, 0, 300, 200, "My Window", #PB_Window_MaximizeGadget |
#PB_Window_MinimizeGadget |
#PB_Window_SizeGadget |
#PB_Window_ScreenCentered)
Repeat
event = WaitWindowEvent()
Until event = #PB_Event_CloseWindow
Code: Select all
wFlags = #PB_Window_MaximizeGadget | #PB_Window_MinimizeGadget |
#PB_Window_SizeGadget | #PB_Window_ScreenCentered
OpenWindow(0, 0, 0, 300, 200, "My Window", wFlags)
Repeat
event = WaitWindowEvent()
Until event = #PB_Event_CloseWindow
There are many other window functions and features, but we'll leave that to the manual.
Handling Gadgets and Events
Now, let's add some gadgets to the window, and see how they work in the scheme of things. To demonstrate this simply, we'll use a button, label, and text box. Here's the code:
Code: Select all
wFlags = #PB_Window_MaximizeGadget | #PB_Window_MinimizeGadget |
#PB_Window_SizeGadget | #PB_Window_ScreenCentered
OpenWindow(0, 0, 0, 300, 200, "My Window", wFlags)
TextGadget(1, 50, 30, 200, 30, "This is a label...")
StringGadget(2, 50, 70, 200, 30, "")
ButtonGadget(3, 50, 120, 200, 30, "Click Me")
Repeat
event = WaitWindowEvent()
Until event = #PB_Event_CloseWindow
Before we begin, here are a few points about how PureBasic manages the events that it receives. Every event carries with it the identifiers of its intended window and gadget. Accordingly, in the case of multi-window applications, we'd first have to determine which window the event is intended for. This is done with the EventWindow() function. Then, we'd check which gadget it's meant for, and process it accordingly. This simplified pseudo-flow might illustrate it better:
Code: Select all
repeat
event = WaitWindowEvent()
window = EventWindow()
if window = window1
if event = #PB_Event_CloseWindow
end the application
gadget = EventGadget()
if gadget = gadget1
execute code for gadget1
if gadget = gadget2
execute code for gadget2
if gadget = gadget3
execute code for gadget3
if window = window2
if event = #PB_Event_CloseWindow
close this window
gadget = EventGadget()
if gadget = gadget4
execute code for gadget4
if gadget = gadget5
execute code for gadget5
if gadget = gadget6
execute code for gadget6
if window = window3
if event = #PB_Event_CloseWindow
close this window
gadget = EventGadget()
if gadget = gadget7
execute code for gadget7
if gadget = gadget8
execute code for gadget8
if gadget = gadget9
execute code for gadget9
forever
On the other hand, in single-window applications, closing the window indicates an intention to terminate the program. So, the #PB_Event_CloseWindow event could be safely processed exclusively. Furthermore, in single-window applications, the EventWindow() function could also be omitted, as all events are logically meant for it.
That's what we'll be doing, and we'll start with the button:
Code: Select all
wFlags = #PB_Window_MaximizeGadget | #PB_Window_MinimizeGadget |
#PB_Window_SizeGadget | #PB_Window_ScreenCentered
OpenWindow(0, 0, 0, 300, 200, "My Window", wFlags)
TextGadget(1, 50, 30, 200, 30, "This is a label...")
StringGadget(2, 50, 70, 200, 30, "")
ButtonGadget(3, 50, 120, 200, 30, "Click Me")
Repeat
event = WaitWindowEvent()
If #PB_Event_Gadget
gadget = EventGadget()
;if the button is pressed (identifier = 3)
If gadget = 3
;whatever code is here will be executed
EndIf
EndIf
Until event = #PB_Event_CloseWindow
Of course, there's no code there now, so it still does nothing. So let's perform an action:
Code: Select all
wFlags = #PB_Window_MaximizeGadget | #PB_Window_MinimizeGadget |
#PB_Window_SizeGadget | #PB_Window_ScreenCentered
OpenWindow(0, 0, 0, 300, 200, "My Window", wFlags)
TextGadget(1, 50, 30, 200, 30, "This is a label...")
StringGadget(2, 50, 70, 200, 30, "")
ButtonGadget(3, 50, 120, 200, 30, "Click Me")
Repeat
event = WaitWindowEvent()
If #PB_Event_Gadget
gadget = EventGadget()
;if the button is pressed (identifier = 3)
If gadget = 3
;sets some new text in the label (identifier = 1)
SetGadgetText(1, "Hello Barry!")
EndIf
EndIf
Until event = #PB_Event_CloseWindow
Just for the fun of it, let's make use of the text box as well:
Code: Select all
wFlags = #PB_Window_MaximizeGadget | #PB_Window_MinimizeGadget |
#PB_Window_SizeGadget | #PB_Window_ScreenCentered
OpenWindow(0, 0, 0, 300, 200, "My Window", wFlags)
TextGadget(1, 50, 30, 200, 30, "This is a label...")
StringGadget(2, 50, 70, 200, 30, "")
ButtonGadget(3, 50, 120, 200, 30, "Click Me")
Repeat
event = WaitWindowEvent()
If #PB_Event_Gadget
gadget = EventGadget()
;if the button is pressed (identifier = 3)
If gadget = 3
;get the text from the text box (identifier = 2)
text$ = GetGadgetText(2)
;and place it in the label (identifier = 1)
SetGadgetText(1, text$)
EndIf
EndIf
Until event = #PB_Event_CloseWindow
Not exactly Flappy Birds, but I hope that it explains PureBasic's programming structure adequately.
Using PureBasic Forms
The PureBasic form designer simply automates the coding of the user interface. However, the form files that are generated can't be modified, and should thus be called from another module. Furthermore, the structure of these self-generated form files are quite different, in that most of the code are encapsulated within procedures.
However, the program logic remains the same, and the implementation is quite easy.
Let's try to recreate the above example in a form project:
1. From the PureBasic IDE, click on the File menu, and select Preferences.... In the left panel of the dialog that appears, select Form, then in the dialog window, make sure that the Generate event procedure and Generate event loop options are selected. Click Apply then Ok.
2. Again, from the PureBasic IDE, click on the Form menu, and select New Form.
3. Resize the model window to 300 pixels by 200 pixels either by dragging the drag-handle located at its bottom-right corner, or by changing the width and height values in the properties window.
4. Also in the properties window, set the window caption to My Window and select the following constants:
#PB_Window_MaximizeGadget
#PB_Window_MinimizeGadget
#PB_Window_SizeGadget
#PB_Window_ScreenCentered
5. Now, from the Toolbox panel, select the Button gadget by clicking on it once (click and release). Then move the mouse over to the model window and click-hold-down-and-drag the mouse to draw the button at the desired location; release when done. In the properties window, enter the caption for the button as Click Me, and in the Event procedure field, enter buttonEvent.
6. Repeat step number 4 for a String and Text gadget, and enter the caption for the Text gadget as This is a label..., but leave the caption for the String gadget blank. For these two gadgets, leave the Event procedure field blank.
7. Click Ctrl-S (or select Save from the File menu), and save the form under the file name MyForm.pbf.
8. From the Form menu, select Switch Code/Design View, and you should be able to see the auto-generated code, which should look something like this:
Code: Select all
;
; This code is automatically generated by the FormDesigner.
; Manual modification is possible to adjust existing commands, but anything else will be dropped when the code is compiled.
; Event procedures needs to be put in another source file.
;
Global Window_0
Global Button_0, Text_0, String_0
Declare buttonEvent(EventType)
Procedure OpenWindow_0(x = 0, y = 0, width = 300, height = 200)
Window_0 = OpenWindow(#PB_Any, x, y, width, height, "My Window", #PB_Window_SystemMenu | #PB_Window_MinimizeGadget | #PB_Window_MaximizeGadget | #PB_Window_SizeGadget | #PB_Window_ScreenCentered)
Button_0 = ButtonGadget(#PB_Any, 50, 120, 200, 30, "Click Me")
Text_0 = TextGadget(#PB_Any, 50, 30, 200, 30, "This is a label...")
String_0 = StringGadget(#PB_Any, 50, 70, 200, 30, "")
EndProcedure
Procedure Window_0_Events(event)
Select event
Case #PB_Event_CloseWindow
ProcedureReturn #False
Case #PB_Event_Menu
Select EventMenu()
EndSelect
Case #PB_Event_Gadget
Select EventGadget()
Case Button_0
buttonEvent(EventType())
EndSelect
EndSelect
ProcedureReturn #True
EndProcedure
OpenWindow_0()
Repeat
event = WaitWindowEvent()
Until Window_0_Events(event) = #False
End
10. Click Ctrl-N (or select New from the File menu), for a new PureBasic file, and enter the following code:
Code: Select all
XIncludeFile "MyForm.pbf"
Procedure buttonEvent(eventType.i)
;get the text from the text box (identifier = String_0)
text$ = GetGadgetText(String_0)
;and place it in the label (identifier = Text_0)
SetGadgetText(Text_0, text$)
EndProcedure
Code: Select all
XIncludeFile "C:\...relevant file paths...\MyForm.pbf"
There are many different ways to implement the PureBasic form file, and this is just one of them. For example, for greater control of the event loop, it could be disabled in the form designer preferences, and implemented in an external module. And for absolute control, even the event procedure option could be disabled and implemented manually. This would mean that the form file is used purely as an interface builder with no higher function.
In any case, really hope it helps.
PS: Here's also an updated video tutorial on the Form Designer:
> PureBasic Form Designer Tutorial 2022