30 June 2021

Forms, the base of an app (Part 1)

A Windows program usually has one or more windows to interact with the user. In GB32 the windows are called forms. A Form is actually a normal window created with the Windows API function CreateWindowEx(). However, a GB32 form adds additional features to the window. For instance, a form supports keyboard tabbing from one Ocx control to the other, it’s settings are accessible through properties and methods, and a form is able to execute event subs. A Form is a normal window with a COM-wrapper. A Form is accessible through a COM object variable; you either provide your own name to a form like frmMain, or you use a predefined form variable like Win_x or Dlg_x. The name of the form-variable depends on the command used to create the form.

Creating a Form with OpenW    
There are several ways to create a form. You might want to use one of the ‘good old’ GFA-BASIC commands OpenW, ParentW, and ChildW. Or you could use the new Form statement to create a Form in code. To load a form created in the Form Editor use the LoadForm statement. In addition, you could use the Dialog command to convert dialogs from older GB versions to GB32 forms.

Note The ParentW and ChildW commands allow you to create a multiple-document-interface (MDI) program, but this type of program isn’t very popular anymore. These days, multiple documents programs are single windows that display a TabStrip Ocx control with multiple tabs that have an Ocx Form child window attached to each tab. I won’t discuss these window types in this post.

As in previous versions of GFA-BASIC the OpenW takes a window number from 0 to 31 to identify the window. After creating the window GB32 wraps the window in a Form object and assigns it to one of the predefined form-variables Win_0 to Win_31. This does not mean that you are limited to 32 windows. The OpenW command accept numbers larger than 31 but these forms are accessed using the form-array syntax Form(n), where n is the number used in the window creation command. For example:

OpenW Center 50, 0, 0, 300, 300
Form(50).AutoRedraw = 1

Note that the forms created with a number from 0 to 31 can be accessed using the Form() array syntax as well. So, there are two ways to access the Form object for a window with number 1: Win_1 and Form(1). This is practical in situations where a Form must be accessed through an index rather than through a fixed Form variable like Win_x.

One of the advantages of using OpenW is the easy way of defining the window-styles. To create an overlapped unowned window OpenW provides the following syntax (without the MDI and Owned options):

  • OpenW [options] [#]n [, x, y, w, h][, attr]

If you omit the attr argument GB32 creates a default window – visible, sizeable, and with a caption and a system menu – using the following window styles for the dwStyle parameter of CreateWindowEx: WS_OVERLAPPED, WS_CAPTION, WS_CLIPSIBLINGS,  WS_CLIPCHILDREN, WS_THICKFRAME, WS_SYSMENU, WS_MINIMIZEBOX, WS_MAXIMIZEBOX, and WS_VISIBLE. Some of these default styles can be disabled by using the options argument as we’ll see.

The attr argument is an integer whose bits determine the window styles. The next table shows the possible attributes. Note that the meaning of the bits are inherited from the very first GFA-BASIC versions, and may look a bit strange or appear missing (bit 8):

Bit    Value Description Property
0, 1 1, 2 Set one of these bits for a vertical scrollbar, sets WS_VSCROLL ScrollBars = basVert
2, 3 4, 8 Set one of these bits for a horizontal scrollbar, sets WS_HSCROLL ScrollBars = basHorz
4 16 Displays a caption bar, sets the WS_CAPTION style Caption = “Title”
5 32 Adds a system-menu, sets the WS_SYSMENU style ControlBox = True
6 64 Displays the minimize button in the title bar, sets the WS_MINIMIZEBOX style. With WS_SYMENU the button is dimmed only. MinButton = True
7 128 Displays the maximize button in the title bar, sets the WS_MAXIMIZEBOX style. With WS_SYMENU the button is dimmed only. MaxButton = True
9 512 Sets the WS_THICKFRAME to make the window sizeable Sizeable = True

When attr = –1 all bits are set and the form is created with the following styles:  WS_OVERLAPPED, WS_VSCROLL, WS_HSCROLL, WS_CAPTION, WS_CLIPSIBLINGS,  WS_CLIPCHILDREN, WS_THICKFRAME, WS_SYSMENU, WS_MINIMIZEBOX, WS_MAXIMIZEBOX, and WS_VISIBLE. In short, the same styles as used when the attr argument is omitted, except it now includes the scrollbar styles. These styles can be changed at runtime using the Form’s properties Visible (or Hide/Show), ScrollBars, Caption, ControlBox, MinButton, MaxButton, and Sizeable.

With the introduction of GB32 the syntax of OpenW is changed so that even more window-styles (options) may be specified. For instance, in the example above the option Center is used to position the form in the center of the (main) screen. GB32 supports the following options that maybe combined:

Option Description Property/Method
Center Centers the form on the main(!) display. Center
Hidden Does not show the form, removes the WS_VISIBLE style from the dwStyle parameter. Hide or Visible = False
Full Creates a maximized window on the main(!) display, excludes Hidden. Sets the WS_MAXIMIZE bit of the dwStyle parameter of the CreateWindowEx() API. FullW
Fixed Removes the WS_THICKFRAME dwStyle  
Client3D Sets the WS_EX_CLIENTEDGE bit of the dwExStyle parameter of the CreateWindowEx() API Appearance, set bit 1
Tool Sets the WS_EX_TOOLWINDOW bit of the dwExStyle parameter of the CreateWindowEx() API  
Top Sets the WS_EX_TOPMOST bit of the dwExStyle parameter of the CreateWindowEx() API OnTop = True
Help Sets the WS_EX_CONTEXTHELP bit of the dwExStyle parameter of the CreateWindowEx() API HelpButton = True
Palette Sets the WS_EX_PALETTEWINDOW bit of the dwExStyle parameter of the CreateWindowEx() API  
NoCaption, NoTitle Excludes a title bar that otherwise would be created. ControlBox = False, Caption = “”
Default Sets the x, y coordinates to CW_USEDEFAULT (-127)  

The different options may also be set at runtime after the form has been created using properties or methods, see table.

There is another command to create a form: the Dialog command, but it is primarily used to convert dialog boxes from older GB sources to GB32 forms. Syntax:

  • Dialog [#]n, x, y, w, h, tit$ [,flag [,height, font$] ]

The Dialog command is followed by a sequence of Ocx control definitions that make up the user-interface of the dialog box. The dialog definition is ended with an EndDialog statement. The meaning of the arguments of Dialog depend on the settings specified with the DlgBase command. There are only 32 dialog definitions allowed (0 <= n <= 31) and the predefined form variables are Dlg_0Dlg_31. When a form is created using Dialog, the IsDialog property returns True.

The Form command
The Form command is an alternative to OpenW and allows you to assign any name to the form.

  • Form [options] fname [= title$, x, y,  w, h ]

There is a disadvantage though. The Form command does not allow you to specify the window attributes (the window styles), you can only set the options as summarized in the previous table. The form gets the same default window styles as OpenW: WS_OVERLAPPED, WS_CAPTION, WS_CLIPSIBLINGS,  WS_CLIPCHILDREN, WS_THICKFRAME, WS_SYSMENU, WS_MINIMIZEBOX, WS_MAXIMIZEBOX, and WS_VISIBLE. Using the options argument some of these can be modified, see table.

The Form command does not support the scrollbar window style, because it does not support the attr argument. To add scrollbars use the ScrollBars property after the form has been created:

Debug.Show
Form Center frm = "Title" , 4, 8 , 200 , 300
frm.ScrollBars = basBoth
Do
  Sleep
Until Me Is Nothing
Sub frm_ReSize
  Debug "size event"
Sub frm_Paint
  Debug "paint event"

Some of the form properties that change a window (ex)Style can not be applied dynamically to an existing window. In this case the form has to be destroyed and then recreated with the new window-styles. So, the window is actually created twice. As a consequence the ReSize and Paint event subs are invoked twice before the main message loop is entered, see below “Hidden DoEvents while creating a form”.

The LoadForm command
This command is used to load a form defined using GFA-BASIC 32’s Form Editor.

  • LoadForm frm [options] [, x, y]    (x and y in Twips)

The options specify the same settings as with the other commands. In addition, you can specify the position of the form in pixel coordinates. All the window styles (attributes and options) can be selected using the Properties listbox in the sidebar. Still, you can override some of these properties using the options argument. For instance, using the option Hidden a form can be loaded without making it visible. 
This command initializes its form object variable during execution; the variable is accessible in the events that are executed while LoadForm loads the form.
This is the only command that initiates a Load event that can be used to initialize the Ocx controls of the form or to do any other initialization.

LoadForm frm1
Do
  Sleep
Until Me Is Nothing
Sub frm1_Load
  ' Do initialization here
EndSub

Hidden DoEvents while creating a form
The commands that create a form invoke a hidden DoEvents message loop to process all pending (posted) messages that are a result of the creation of the window. In particular, if the program contains Paint and ReSize event subs, the subs may be called when the hidden DoEvents is executed. In this case, the form’s object variable is not yet initialized (except for the LoadForm command, see above). For instance, trying to access the Win_1 variable in a Win_1_Paint event sub will cause a runtime error, because the first time the Paint event sub is called the variable is still Nothing. The form’s object variable Win_1 is not initialized before OpenW has finished. Fortunately, The Me form variable is valid as soon as the actual window has been created and can be used in the event subs, even when called from the hidden DoEvents.

What is Me?
Me is a global form-variable that references the current ‘active’ form, or the form that is currently being processed. In the event subs Me references the form for which the event sub is invoked. In the message loop Me references the latest form for which a message is received. With multiple windows Me might reference an inactive window, because Me is set to the window that receives a message and it can not be predicted in what order messages are sent or posted. However, Me always references a form, whether it is the top (active) window or some other window. So, do not expect Me to reference the active form in the message loop. When all windows are closed Me becomes Nothing and the main message loop is ended.

Who has the focus?
When a form contains Ocx controls that can have the focus (Command, TextBox, etc) the focus should be set to the first Ocx control in the tabstop (or creation)-order before entering the message loop. Windows will automatically set the focus to a control when the window is being used (resized, (de-)activated, minimized, etc). GB32 does not set the focus to the first control after creating the form,  but to conform to the behavior of Windows, set the focus explicitly at the start of the application.

Conclusion
OpenW and Form are much alike, although OpenW gives more control over the window styles through the attributes argument. During the creation of a form some event subs will be invoked without the form-variable being initialized, but Me is valid. Only LoadForm invokes the Load event sub. The Dialog command is used for porting older sources.