Showing posts with label Forms. Show all posts
Showing posts with label Forms. Show all posts

04 February 2025

The 'Owned' property



You will have noticed the Owned property in the Properties sidebar for a Form. You might also have noticed that there isn't the same property when editing; the auto-complete box doesn't show an 'Owned' property.
A Form's owner
Whenever a Form is created it can be 'assigned' to another Form. When this happens the Form is said to be owned. Using the OpenW command, you can explicitly specify an owner:
// Create the first unowned form
OpenW 1
// Create the second owned form
OpenW Owner Win_1, # 2, 0, 0

When a Form is created using the Form editor, it is impossible to explicitly specify an owner form. All you can do is set the design-property 'Owned' to True.The effect will be that the Form is assigned to the current Me at the time it is created using the LoadForm command. (If you enable the Owned property for the first form, it will be assigned to Me = Nothing.and the form isn't owned. Since Me might not be exactly what you want it to be, you can explicitly set Me before executing LoadForm.

Owned windows
We mostly speak of owned windows when it concerns top-level windows, windows that are put on the desktop. The relation between a window and a child control is called a parent-child relation, it is not an owner-relationship. This concept can be seen from the properties of child Ocxes; they provide a Parent property, not an Owner property, only Forms have an Owner property.

When there is an owner relationship between two windows the first window (the owner) has certain control over the other window (the owned window). This relationship has several important implications:

  1. The owned window is always displayed on top of its owner window
  2. When the owner window is minimized, the owned window is automatically hidden
  3. When the owner window is restored, its owned windows are shown again
  4. When the owner window is closed, all its owned windows are automatically closed

This relationship is commonly used for dialog boxes that should stay above their parent window, but also for tool windows (or palettes) that should minimize/restore with their main application window. For example, when you open a "Save As" dialog from a text editor, that dialog is typically owned by the main editor window. This ensures the dialog stays above the editor and behaves appropriately when the editor is minimized or closed. There for the Dlg Save & Open command require a Form reference as their first parameter.

This is different from a parent-child window relationship, where child windows are confined within the client area of their parent. Owned windows can be positioned anywhere on the screen while maintaining their behavioral connection to their owner.

Conclusion
You should definitely consider using the Owned design-time property when using the LoadForm command, you get behavioral features for free and the application will certainly look more professional.

02 September 2021

The IDE’s Color Dialog

The IDE features a custom Color Dialog box to select the foreground and background colors for Ocx controls and Forms. The Color Dialog box is also used to select the editor’s syntax colors in the GB32’s Properties dialog box. Using the Color Dialog box you can select any color you want. However, the Color Dialog box presents a custom color palette for a quick selection. In addition the dialog box displays the system colors as they are defined on your version of Windows.

The Color Dialog box
The following picture displays the Color Dialog box after selecting the ForeColor property of a Form (frm1) in the form-editor. This dialog box will also be displayed after selecting the BackColor property.

Screenshot 2021年09月01日 ColorDlg

The ForeColor property shows a color value of 80000008,ドル which means that the color is set to the system color with index 8 (COLOR_WINDOWTEXT). The ForeColor property is used as the foreground color for drawing in the client area of the form.

The Color Dialog displays the 25 system colors in the lower two rows of the dialog box and it draws a focus rectangle around the color-cell that shows the system color for COLOR_WINDOWTEXT. The large cell at bottom-right of the dialog box displays the selected color. By clicking on the large box GB32 shows the common dialog’s color dialog box. The top 4 rows display a palette of often used RGB colors to allow a quick selection of a color. The fourth row also contains 8 additional colors otherwise not found in the 4 upper rows of the dialog’s color palette.

The BackColor property defines the color used to draw the (empty) contents of the client area. By default, GFA-BASIC 32 uses the system color COLOR_BTNFACE, the system color that is used to paint the shading of the face of command buttons. However, this system color is used for more UI-elements than just the shadow of the face of a button. By using the color-value 8000000ドルf the window background gets the default color of a dialog box, so that the GB32 form will look consistently with system dialog boxes.

Using system colors
It is a big advantage that GFA-BASIC 32 let you use system colors for the user-interface elements of the form and Ocx-controls. The system colors may change when a custom Windows theme is installed or when Microsoft changes the system colors with a new release of Windows. Whenever the system colors change, the colors of the form change accordingly, providing a consistent look with the OS.

GFA-BASIC 32 distinguishes between a pure RGB color format and a system color by setting the high-bit of the 32-bit color value (Long). When a Long value is displayed in it’s hexadecimal format the high-bit setting is shown as 80000000ドル. Before actually applying a color GB32 tests the high-bit of the color value and selects a system color when it is set:

If rgbcol %& 80000000ドル Then rgbcol = SysCol(rgbcol & $FF)

An application can specify a system color in two ways:

RGBColor 80000000ドル + COLOR_WINDOWFRAME
RGBColor SysCol(COLOR_WINDOWTEXT)

The first statement sets the foreground color to a system color value. The second statement converts the system color to an RGB-value beforehand. Both statements will eventually use the RGB color that belongs to the system’s color element COLOR_WINDOWTEXT. The RGB color value for any of the display elements is obtained using the GB32’s function SysCol or the API function GetSysColor(). Each aspect of the display has its own COLOR_* constant that is used as an index in these functions. These constants are built into GFA-BASIC 32. For more information see the SysCol function in the helpfile. It explains which constant represents what user-interface element. When you hoover over the system colors in the IDE Color Dialog box it shows a short description of the display element it represents.

The Editor colors
The Editor tab in the Properties dialog box (Extra | Properties) displays the same Color Dialog box to select the syntax colors.

Screenshot 2021年09月02日 ColorDlg

This picture is taken after clicking the BackColor button. It shows the selected color for the background of the “Changed/Empty” syntax (ie. the background color of empty lines and the line that is being edited). By default, the background color for all syntax elements is set to system color COLOR_WINDOW (80000005ドル). The foreground colors of the syntax elements are custom RGB values. By keeping the background color to a system color, the GFA-BASIC 32’s editor will adapt to the new COLOR_WINDOW color if a theme is installed.

16 August 2021

Fill up the Form (Part 3)

This is part 3 of my blogs on GFA-BASIC 32 Forms. The other parts focused on creating and interacting with a Form:

In this blog I focus on drawing in the form’s client area.

Putting something on the Form
Basically, a form is used for two purposes. It is either used as a dialog box by populating it with OCX-controls, or it is used to draw graphics. Usually, these basic approaches aren’t combined, although you could draw some graphics in the dialog box form as a background for the controls. For instance, you could assign a Picture to draw in the form’s background or change the background color for the form. However, mostly these things are separated; a form is either used as an input form or as a graphics window.
When a GB32 form contains OCX controls it’s behavior changes and the form starts handling control-navigation keys (Tab and arrow-keys). This means that both the system and GB32 take control of the focus. The focus can only be set to a control, not to the client area of the form. The form’s method SetFocus does not work anymore. A form without OCX controls does not change to this behavior and allows you to set the focus to the form, making it possible to develop a text editor for instance. (BTW it is a good habit to add an OCX-Form child window to the client-area of the base-form. The base-form is the container of the OCX-controls and the OCX-Form control is used for drawing or editing.)

Drawing on a Form
An application should paint in the client area when Windows posts (or sometimes sends) a WM_PAINT message. GB32 converts the WM_PAINT message to an event which can be processed in the _Paint event sub. There are applications (like drawing programs) that need to draw interactively in the client area, for instance after a mouse-event. Even so, an application would store the new drawing coordinates and color and then updates its client area to redraw in the _Paint event sub again. This is achieved by invalidating the portion of the form that needs to be redrawn followed by a form-refresh.

frm.Invalidate x, y, w, h
frm.Refresh

The Refresh method calls the UpdateWindow() API and sends a WM_PAINT message to the form directly, bypassing the message queue. By following this procedure, the form is repainted instantly and the app conforms to the rules of painting in the client-area.

Should you use AutoRedraw?
To make things easy VB naively introduced the AutoRedraw feature. Because of VB-compatibility AutoRedraw was also built into GB32. The AutoRedraw feature allows an application to draw outside the _Paint event sub. All subsequent drawings are copied to an offscreen bitmap which is then drawn to the client-area after the form receives a WM_PAINT message. There are some drawbacks to this approach. The application does no longer follow the UI-rules for drawing and it will be difficult to maintain and expand the application. A second drawback is the drop in performance. Each GB32 graphics command is executed twice, once to draw on the screen and once to draw in the offscreen bitmap. In addition, the drawing is limited to the use of GFA-BASIC 32 commands, only GB32 commands draw in the offscreen bitmap. When you use a Windows API drawing function to draw you must draw both on the screen and on the bitmap. For this, the form provides the hDC2 property, this property returns the handle of the GDI device context for the offscreen bitmap. It is only valid after executing the AutoRedraw command. The use of an API function could look like this:

~TextOut(frm.hDC, 0, 0, "Hello, Windows!", 15)
If frm.hDC2
 ~TextOut(frm.hDC2, 0, 0, "Hello, Windows!", 15)
EndIf

If the form uses scaling, the scaling must be applied by hand. Each coordinate and size value must be converted to the current scaling of the form.
Another thing to note is the size of the offscreen bitmap. The bitmap is created when the AutoRedraw command is executed taking the current size of the client area. When a form is later enlarged (resized) the automatic redrawing of the offscreen bitmap will not entirely fill up the client area of the form. This is demonstrated in the following picture where the picture on the right is a resized version of the left form.

AutoRedraw

AutoRedraw is an option to use for a quick and dirty test program, not for a serious application.

Use Direct2D instead
The GB32 drawing commands are wrappers for the Windows GDI drawing functions. However, GDI isn’t fast and isn’t suited for dpi-aware applications. More and more GDI-drawing is replaced by Direct2D drawing. Your application can use Direct2D as well. The Include directory contains a GB32 library Direct2D.lg32 that provides access to the system’s COM-based direct2d libraries. The commands and functions provide a GB32 compatible approach to drawing, so an application can be ported to Direct2D easily. The library commands and functions start with the letters D2 followed by the GB32 name of the drawing command. To set the colors you would use D2RgbColor instead of GB32’s RgbColor command. Instead of Line you would use D2Line, etc. The usage of the library is explained in the latest (English) CHM helpfile (don’t use the very old German hlp file anymore, it is outdated and does not contain OCX-properties and all the new stuff.)  You’ll find the Direct2D information by going to the Contents-tab and opening the Creating an application chapter.

Conclusion
An application either uses the form for input using OCX-controls or draws in the client-area in a response to a _Paint event. It is recommended to not use AutoRedraw. Even better, use Direct2D for drawing.

04 July 2021

Interacting with Forms (Part 2)

This is the second post in a series about forms. In the first post we discussed the commands to create forms and their window styles. Now we’ll discuss how to interact with forms.

Owner and owned forms
The commands OpenW and Form support the creation of owned forms (windows). They provide the Owner clause to specify the ‘parent’ or owner of the new form. The following example creates an overlapped non-owned window (Win_0) and an overlapped owned window frm.

OpenW 0, 100, 100, 300, 300 : TitleW 0, "Owner"
Form Owner Win_0, frm = "Owned", 150, 170, 300, 200
Do : Sleep : Until Me Is Nothing

There are advantages and disadvantages of using a parent-owner relationship.

  • The owned form (frm) is always displayed on top of the non-owned window (Win_0). It is therefor mostly used for forms that behave as dialog boxes.
  • When one of the windows is activated the other is activated automatically.
  • When the parent window is closed the owned window is closed as well. Not the other way around. When you close form frm the Win_0 form is not closed and Me does not become Nothing. The message loop is not ended. When you close Win_0 the form frm is closed as well and Me will become Nothing.
  • The owned window is not disabled (for input) if the parent is disabled. When a program displays a modal dialog box (a form with Ocx controls that disables all other windows while on screen) all other forms have to be disabled explicitly.

Suppose a program wants to display an About box in a modal dialog box. In GB32 a modal dialog box (form) is simulated by showing an owned form and then disabling input for the other forms using the Disable property. Then, after closing the modal dialog box form the other forms are enabled again using the Enable property. Like this:

OpenW 0, 100, 100, 300, 300 : TitleW 0, "Owner"
Ocx Command cmd = "About", 10, 10, 100, 24
Form Owner Win_0, frm = "Owned", 150, 170, 300, 200
Do
 Sleep
Until Me Is Nothing
Sub cmd_Click
 ' Display frm1 (owned by Win_0) created using the Form-Editor
 LoadForm frm1, Win_0.Left + PixelsToTwipX(50), Win_0.Top + PixelsToTwipY(50)
 Win_0.Disable ' no input allowed
 frm.Disable ' no input allowed
Sub frm1_Destroy ' frm1 is being destroyed
 frm.Enable ' enable input
 Win_0.Enable ' enable input
 Win_0.Activate ' activate

In the form-editor the Owned property is set to True. The form only has one Ocx control; a label with an enlarged font. The LoadForm command uses the current active form as the owner for the About box. The result is this, only the About box is active and enabled:

Screenshot 2021年06月28日 121116

The message loop
To interact with forms the application needs a message loop. The most common message loop uses Sleep and ends with Until Me Is Nothing (Do : Sleep : Until Me is Nothing). When all windows are closed Me – the current active form - becomes Nothing and the program will end.
Sleep waits for a queued message; a message that is posted to the window’s message queue. The OS posts only a limited amount of messages, most messages are sent to the window-procedure directly. In general, the posted messages include the input messages for keyboard and mouse, the WM_PAINT and WM_TIMER message.

After Sleep detects a new message it dispatches (relays) the message to the window’s window-procedure. So, in the end all messages (posted or sent) end up in the window procedure of the window. The window procedure is responsible for executing the event subs.

Sleep is not the only command that reads the message queue, others are the (also new) DoEvents, and the older 16-bit GFA-BASIC compatible commands GetEvent, and PeekEvent. Sleep is the preferred command to wait for and handle a message. When the Sleep command executes it first processes all pending (waiting) messages and then puts the program to sleep using WaitMessage(). When a new message arrives Sleep processes all new message(s) before it returns back to the program. Schematically, from left to right Sleep executes:

If any: Handle All Messages + Sleep(0) WaitMessage Handle All
Messages + Sleep(0)
=> return to program (loop)

As part of Handle All Events the Sleep command invokes the Windows API Sleep(0), giving up the remainder of the time slice that the scheduler assigned to the thread (process).

The other command that waits for a message is GetEvent, but this works a bit different. Upon entering the GetEvent handles one event if there is a message present in the queue. Without any pending messages GetEvent invokes WaitMessage() and halts the program. After obtaining a message it continues by processing exactly one event. If more messages are present GetEvent will process them the next time it is executed. Schematically, there are two situations:

Message In queue? Handle One Message => return to program (loop)
No pending messages? WaitMessage Handle One Message => ret to prog

Because GetEvent handles only one message a time it is able to place GFA-BASIC 16 compatible message-info in the MENU() -array. An application can respond to the values of the MENU() array inside the GetEvent-message loop. The GetEvent command does not invoke Sleep(0). With multiple messages pending the GetEvent loop will run until all messages are handled without giving up time, which is not a real problem. After all, the Sleep command does not call Sleep(0) before it handled all pending messages.

PeekEvent is like GetEvent: it fills the MENU() array, but it doesn’t wait for a message, it returns immediately to the program.

Handle One Message (if any) => return to program (loop)

Because PeekEvent is usually placed in a (message) loop, it is likely that the CPU will be overloaded and will be running at 100%. You will need some mechanism to give up some processor time, for instance by using the Sleep(ms) API or the GB32 command Sleep ms inside the message loop.

DoEvents is a bit like PeekEvent: it doesn’t wait for a message. However, rather than processing only one message at a time, DoEvents handles all pending messages in a row before it returns to the program.

Handle All Messages + Sleep(0) => return to program

This does not mean that DoEvents - when used in a loop - will let the processor run at 100%. Before returning to the program DoEvents invokes Sleep(0) to give up CPU time to other processes. DoEvents is often used in a time-consuming loop to keep the GUI responsive.

All these commands Sleep, GetEvent, PeekEvent, and DoEvents handle the keyboard navigation between Ocx controls on the form. However before the keyboard navigation is applied GB32 invokes the Screen_Preview event sub if it is present. This event sub allows the program to intercept queued keyboard messages (WM_KEYFIRST – WM_KEYLAST) to respond to a key and/or cancel the message if it wants to.

Sub Screen_KeyPreview(hWnd%, uMsg%, wParam%, lParam%, Cancel?)

Set the Cancel? parameter to True when a keyboard message is handled and you don’t want to pass it on to the Ocx-control keyboard-navigator or to the window-procedure.

Event Subs
All message retrieval commands, including GetEvent and PeekEvent, execute event subs. (To be precise, the window procedure invokes the event subs.) Using event subs is the preferred way to respond to a message taking away the need to distinguish between posted and sent messages as in previous versions of GFA-BASIC. To handle a form-message simply add an event sub to the program. Note that when a form is being created it executes event subs while the form’s object variable isn’t initialized yet. In particular, the _Paint and _Resize events are invoked without having access to the form’s variable. This behavior is discussed in the previous blog post: Forms, the base of an app.

The _MessageProc sub event
A powerful event sub is the _MessageProc sub event. If defined, it is called for each and every message that is relayed to the window-procedure, including the posted messages.

' Called for all messages
Sub Win_0_MessageProc(hWnd%,Mess%,wParam%,lParam%,retval%,ValidRet?)

You need to know what to do if you handle a message in the _MessageProc event sub. This knowledge is acquired through the Windows SDK (now available on line). For the sent messages the program needs to specify a return value in the by reference variable retval% and set the ValidRet? variable to True. This is necessary; if the program sets ValidRet? the message is considered handled and any event subs that would otherwise be invoked will not be executed. When ValidRet? is set the form’s window procedure will return to the caller (Windows OS) immediately. To handle a posted message – one that doesn’t require a return value - you needn’t set retval%, but to prevent further handling you should set ValidRet? though.

The _Message event sub is for posted messages
The _Message event sub is intended to process queued (posted) messages only. However, the _Message event sub is called for all messages (like the _MessageProc event sub) except for WM_PAINT and WM_ERASEBKGND (!?). Because of its syntax – it misses retval% and ValidRet? - it can only be used to process posted messages, the messages that don’t require a return value:

' Process posted messages only
Sub Win_0_Message(hWnd%, Mess%, wParam%, lParam%)

Even when a program handles a message in the _Message event sub the event sub for that message is invoked as well. For instance, if WM_LBUTTONDOWN is handled in the _Message event sub and the _Click event sub is defined as well, the _Click event is also executed. If both _Message and _MessageProc subs are used both subs are invoked, first the _Message event sub followed by the _MessageProc. If a program responds to a (posted) message in the _Message event sub, it shouldn’t process it in _MessageProc again, or in some other event sub.
The practical use of the _Message event sub is limited, it is intended to port GB16 code that uses the MENU()-array or _hWnd, _Mess, _wParam, _lParam to GFA-BASIC 32. Because the _Message event sub is called from the window-procedure a program can handle all posted messages when the message loop uses Sleep or DoEvents.

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.

21 August 2019

Unicode controls

In the passed years I’m frequently asked for ‘Unicode support’ in GFA-BASIC 32. The issue here is that GB is an ANSI programming language; the IDE accepts only characters in the range from 0 to 255 and the string functions assume one byte per character. All commands and functions that accept a string parameter only take ANSI strings. However, it is possible to create UNICODE controls and let the user input text in the user’s locale setting and then retrieve the wide character text from the controls. To process the retrieved text the application will most likely use Windows API wide-string functions.

A few notes
An introduction to Unicode strings can be found in a previous post: Ansi and Unicode.

This blog post will discuss the use of Unicode (or wide character) controls on a GFA-BASIC 32 form, specifically on a Dialog form. The code is discussed in bits and pieces, but the code for the entire example program can be downloaded here.

Declaring wide API functions
When an application wants to use wide character controls the Ocx property and sub-event system cannot be used any longer. In addition, a dialog definition has to be set up in code, because the form-editor can no longer be used either. The controls have to be created and handled using Windows W-API functions. Windows defines both ANSI and Unicode variants for API functions that take a string parameter. Many of the ANSI APIs are built-in in GFA-BASIC, but the W variants are missing and have to be declared explicitly. Two wide char APIs an application will definitely use are CreateWindowExW and SendMessageW. They have to be declared explicitly (abbreviated):

Declare Function CreateWindowExW Lib "user32" (ByVal dwExStyle As Long,
Declare Function SendMessageW Lib "user32" (ByVal hWnd As Handle, …

Other possible declares are lstcmpW, lstrcmpiW, CharUpperW, and CharLowerW. To draw Unicode text on the screen the application needs to declare TextOutW and/or DrawTextW, etc.

  • Recommended: A full set of wide-string functions can be found in the Shell Lightweight Utility functions (SHLWAPI) DLL. The Include library does not provide an include file with Wide function declarations though!

Defining controls
We cannot use any predefined control and we cannot use the Control command to create a wide character control. We can however create a procedure ControlW that allows an easy translation of Control statements to Unicode controls. Because we will use a W variant of the Control command, an easy way to add controls is by using a dialog box (which is a Form). This also allows us to use an external dialog box editor. The following piece of code is created using ResHacker, a GUI utility that provides the ability to create a dialog box definition. After copying and pasting the definition into the GB editor the command Control is replaced by ControlW. Note that ResHacker produces a dialog definition with dialog base units rather than pixels. Also, the dimension of the controls may need some editing once the dialog is used in GB. The WS_CHILD | WS_VISIBLE styles can be removed as well.

DlgBase Unit
Dlg 3D On
Dialog # 1, 0, 20, 261, 140, "Controls", WS_SYSMENU | WS_CAPTION
 ControlW "&OK", 1, "BUTTON", BS_DEFPUSHBUTTON | 
WS_CHILD | WS_VISIBLE | WS_TABSTOP, 130, 97, 50, 11 ControlW "&Cancel", 2, "BUTTON", BS_PUSHBUTTON |
WS_CHILD | WS_VISIBLE | WS_TABSTOP, 187, 97, 50, 11 ControlW "Checkbox", 10, "BUTTON", BS_AUTOCHECKBOX |
WS_CHILD | WS_VISIBLE | WS_TABSTOP, 7, 8, 60, 14 ControlW "Group", 0, "BUTTON", BS_GROUPBOX |
WS_CHILD | WS_VISIBLE, 7, 23, 59, 47, WS_EX_TRANSPARENT ControlW "Radio 1", 12, "BUTTON", BS_RADIOBUTTON |
WS_CHILD | WS_VISIBLE | WS_TABSTOP, 12, 36, 43, 14 ControlW "Radio 2", 13, "BUTTON", BS_RADIOBUTTON |
WS_CHILD | WS_VISIBLE | WS_TABSTOP, 12, 51, 43, 14 ControlW "Trackbar", 15, "msctls_trackbar32", TBS_HORZ |
WS_CHILD | WS_VISIBLE | WS_TABSTOP, 7, 77, 60, 18 ControlW "Insert Text:", 0, "STATIC", SS_LEFT |
WS_CHILD | WS_VISIBLE | WS_GROUP, 82, 10, 45, 10 ControlW "", 17, "EDIT", ES_LEFT | ES_MULTILINE |
WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 122, 7, 116, 14 EndDialog

The ControlW procedure creates the wide control. The string input parameters are ANSI strings that are converted to Unicode before the CreateWindowsExW is invoked. Since the dialog definition uses dialog box units the Dlg Base Units command is added. This command initializes a few global variables in the runtime. The ControlW procedure tests if Dlg Base Units is used and if it is used converts the coordinates from dialog box units to pixels.

Proc ControlW(text,ドル id%, class,ドル style%, x%, y%, w%, h%, Optional exstyle%)
 Local hWnd As Handle, pText As Long
 If Len(text$) Then text$ = Wide(text$) : pText = V:text$
 class$ = Wide(class$)
 style% |= WS_CHILD | WS_VISIBLE
 If {180ドルB5F70} %& 1 ' DlgBase Units?
 x% = MulDiv(x%, {180ドルB5E98}, 4)
 y% = MulDiv(y%, {180ドルB5E94}, 8)
 w% = MulDiv(w%, {180ドルB5E98}, 4)
 h% = MulDiv(h%, {180ドルB5E94}, 8)
 EndIf
 hWnd = CreateWindowExW(exstyle%, V:class,ドル pText, style%, x%, y%, w%, h%, Me.hWnd, id%, _INSTANCE, 0)
 If hWnd _
 SendMessageW(hWnd, WM_SETFONT, Me.Font._hFont, 1)
EndProc

The controls are created using pure Windows API, this also means that the controls have to be initialized and modified by sending messages. You will need proper documentation to know which message and how to send it to the controls. The controls are not OCX controls and don’t respond to notification messages through an event sub. The notification messages from the controls come either in WM_COMMAND or WM_NOTIFY message. The application needs to process these messages the ‘API-way’.

Notes on using a dialog box
An advantage of using a Dialog form is the presence of properties and event-subs. To respond to control-messages a Dlg_n_Message sub is all that is needed. A disadvantage of using a Dialog form is the lack of Unicode support for the title of the ANSI-based dialog box. One solution could be to add an informative picture along the top (caption) of the dialog form. This would require a simple LoadPicture and PaintPicture sequence of commands.
In addition, ANSI and Unicode controls can not be used together, that would break the Tab-key navigation. Even worse, the navigation with Unicode controls differs from the navigation with ANSI controls. This means commands like Sleep and PeekEvent will mess up the key-navigation. A work-around is to trap the Tab- and arrow keys in the Screen_KeyPreview event sub and call IsDialogMessage ourselves.

Sub Screen_KeyPreview(hWnd%, uMsg%, wParam%, lParam%, Cancel?)
 Dim msg As MSG
 If GetForegroundWindow() = Dlg_1.hWnd
 msg.hwnd = hWnd%
 msg.MessageVar = uMsg%
 msg.wParam = wParam%
 msg.lParam = lParam%
 Cancel? = IsDialogMessage(Dlg_1.hWnd, msg) == 1
 EndIf
EndSub

Cancel is set to True when IsDialogMessage handled the key. This prevents the handling of the navigation key in commands like Sleep and PeekEvent.
IsDialogMessage is always called as part of the message handling commands and IsDialogMessage processes the key when the form contains at least one control (might be a toolbar or statusbar). It seems the GB application ‘eats’ the keypresses if you’re not aware of this behavior.

Another issue is the way the focus is handled in a form with controls. The application should always explicitly set the focus to a control before entering the main message loop. If it doesn’t the focus might not be set correctly when a navigation key is pressed or when the application is reactivated.

Processing Unicode strings
The ControlW custom procedure takes an ANSI string for the control text. However, the program needs to set the controls text using Unicode instead. Normally, a program assigns hard coded text to a control, but the text in the IDE is limited to ANSI characters. Somehow the text must be obtained from a Unicode source that can be used as literal strings. Because it is (almost) impossible to specify Unicode strings in code directly, strings have to be obtained from an external source. This is possible with the use of an editor that can save Unicode strings. For this example I used NotePad2 that can save Unicode strings by setting the Encoding in the File menu to Unicode. In the GB code I defined constants with the index of the strings after they have been loaded into an array. These lines can be found at the start of the example program:

Dim T$$() ' storage for UNICODE strings
Enum wsHello, wsGFABASIC
LoadWStrings("unicode.txt", T$$())

The procedure LoadWStrings loads the Unicode text lines into the array T$$(). The double $ is used to indicate that the string array variable contains wide character strings.

Now, after the dialog box has been created, but before it is displayed, the text of the wide controls can be modified using a string from T$$(). For this to happen the program includes a SetW procedure which assigns a Unicode string to a window. In the same style a GetW function returns a Unicode string from a window.

Function GetW(ByVal hwnd As Handle, Optional InclTerm As Bool = False) As String
 Local size As Long, sBuf As String
 size = SendMessageW(hwnd, WM_GETTEXTLENGTH, 0, 0)
 If size
 size++ ' also obtain the terminating null bytes
 sBuf = String(size Mul 2, #0)
 SendMessageW(hwnd, WM_GETTEXT, size, V:sBuf)
 GetW = InclTerm ? sBuf : Left(sBuf, Len(sBuf) - 2)
 EndIf
EndFunc
Proc SetW(ByVal hwnd As Handle, ByVal wTxt As String)
 If Right(wTxt, 2) != #0#0 Then wTxt += #0#0
 SendMessageW(hwnd, WM_SETTEXT, 0, V:wTxt)
EndProc

For instance, to set the text of the wide EDIT control in the dialog box to Hello:

SetW Dlg(1, 17), T$$(wsHello)

By default GetW returns a Unicode without the terminating two null bytes. However, if a Unicode string is later to be passed to a Windows function, the string is expected to end with two terminating null bytes. So, it depends on the purpose of the string whether or not the string should include the terminating zeros. GetW can return the Unicode string with the terminating bytes as well.
As an example the program also provides a way to compare Unicode strings using Windows API functions:

Function StrCmpW(ByVal str1 As String, ByVal str2 As String, 
Optional ignorecase As Bool) As Bool If Right(str1, 2) != #0#0 Then str1 += #0#0 If Right(str2, 2) != #0#0 Then str2 += #0#0 If ignorecase StrCmpW = lstrcmpiW(str1, str2) == 0 Else StrCmpW = lstrcmpW(str1, str2) == 0 EndIf EndFunc

The function StrCmpW is wrapper around the declared lstrcmpiW and lstrcmpW APIs. Before these APIs are invoked the strings are tested for the two terminating null bytes. If they are missing the strings are modified. For example, to test if the edit-control holds the word ‘hello’ the following code might be used:

wTxt = GetW(Dlg(1, 17))
If StrCmpW(wTxt, T$$(wsHello), True)
 MsgBox "Edit control specified hello"
EndIf

Summary
A GFA-BASIC application can provide Form-based Unicode controls. The IDE does not allow Unicode literal strings so they must come from an external source. In addition, many other commands like MsgBox, Dlg Open/Save require ANSI strings, so the application must use the appropriate wide Windows API functions. To be fully Unicode the application should be created using wide character API functions entirely.

09 September 2018

Did the mouse leave the window?

There are two mouse-messages that are never received unless you explicitly instruct Windows to track the mouse movement. The first message is WM_MOUSELEAVE that is supposed to report that the mouse has left the client-area. The second is WM_MOUSEHOVER which is posted after hovering a certain amount of time over some area. To obtain one (or both) of these messages you need to call TrackMouseEvent() API which notifies the application when the mouse leaves the window or when the mouse hovers over an area for a while.

The next program illustrates how to implement a mouseover feature by drawing a box that turns black when you move the mouse over it. The basic idea is to use WM_ MOUSEMOVE to know when the mouse has moved in or out of the box. The only problem is that if the user moves the mouse quickly outside the window, you won't get a WM_ MOUSEMOVE. To implement a correct behavior of mouseover, you need to know when the mouse has left the window entirely.

The program doesn’t use the _MouseMove eventsub, but combines the mouseover-logic into the _Message eventsub, which receives all (posted) mouse messages. There are no event subs for WM_MOUSELEAVE and WM_MOUSEHOVER, so they have to be handled in a general event- sub. An alternative would be to handle the messages in _MessageProc, but its use is a bit more complicated. In addition, _Message doesn’t require any return values, so it serves our purpose best.

OpenW Center 1
Do
 Sleep
Until Win_1 Is Nothing
Sub Win_1_Paint
 Box 10, 10, 100, 100
EndSub
Sub Win_1_Message(hWnd%, Mess%, wParam%, lParam%)
 Static Bool fTrackingMouse, fBoxHighLighted
 Dim tme As TRACKMOUSEEVENT
 Local Int mx, my
 Switch Mess%
 Case WM_MOUSEMOVE
 ' Track a mouseleave event. Results in a WM_MOUSELEAVE
 ' message when the mouse leaves the window.
 If !fTrackingMouse ' set it only once
 tme.cbSize = SizeOf(TRACKMOUSEEVENT)
 tme.dwFlags = TME_LEAVE
 tme.hwndTrack = Me.hWnd
 fTrackingMouse = TrackMouseEvent(tme) != 0
 EndIf
 ' If mouse is over the box start timer
 mx = LoWord(lParam%), my = HiWord(lParam%)
 If mx > 10 && mx < 100 && my > 10 && my < 100
 tme.cbSize = SizeOf(TRACKMOUSEEVENT)
 tme.dwFlags = TME_HOVER ' start timer
 tme.hwndTrack = Me.hWnd
 tme.dwHoverTime = HOVER_DEFAULT ' use default time
 TrackMouseEvent(tme) ' now wait for WM_MOUSEHOVER
 Else If fBoxHighLighted ' hilighted and not over box
 Win_1.Invalidate 10, 10, 90, 90
 fBoxHighLighted = False
 EndIf
 Case WM_MOUSELEAVE ' triggered by TrackMouseEvent
 fTrackingMouse = False ' TrackMouseEvent not active anymore
 If fBoxHighLighted ' redraw original box
 Win_1.Invalidate 10, 10, 90, 90
 fBoxHighLighted = False ' box is not highlighted
 EndIf
 Case WM_MOUSEHOVER ' triggered by TrackMouseEvent's timer
 mx = LoWord(lParam%), my = HiWord(lParam%) ' mouse coordinates
 If !fBoxHighLighted && mx > 10 && mx < 100 && my > 10 && my < 100
 PBox 10, 10, 100, 100 ' highlight the box
 fBoxHighLighted = True
 EndIf
 EndSwitch
EndSub
Public Const HOVER_DEFAULT = 0xFFFFFFFF
Type TRACKMOUSEEVENT
 - DWord cbSize
 - DWord dwFlags
 - Handle hwndTrack
 - DWord dwHoverTime
EndType
Declare Function TrackMouseEvent Lib "user32" Alias _
 "TrackMouseEvent" (ByRef EventTrack As TRACKMOUSEEVENT) As Long

The _Message sub declares two static booleans, fTrackingMouse and fBoxHighLighted, that keep track of the current state of the mouseover-logic. (If I would have used the _MouseMove eventsub to initiate the mouse tracking the variables should have been declared global, I always try to avoid global variables as much as possible.)
When the first (of many) WM_MOUSEMOVE message is received, the TrackMouseEvent() API is used to set up a WM_MOUSELEAVE "one-shot" event. Exactly one and only one WM_MOUSELEAVE message will be posted to the window specified in the hwndTrack member of the TRACKMOUSEEVENT structure, when the mouse has left the client area.
Note - The message will be generated only once. The application must call the TrackMouseEvent API again in order for the system to generate another WM_MOUSELEAVE message. In addition, when the mouse pointer is not over the application, a call to TrackMouseEvent() will result in the immediate posting of a WM_MOUSELEAVE message.

When the mouse is over the box, the TrackMouseEvent() is used to start a timer that eventually posts the WM_MOUSEHOVER message. After receiving WM_MOUSEHOVER the box is highlighted if the mouse is still over the box. The fBoxHighLigted variable is set to indicate the state of the box. If the variable is set but the mouse is no longer over the box the area occupied by the box is invalidated so that it is redrawn eventually.

Subscribe to: Comments (Atom)

AltStyle によって変換されたページ (->オリジナル) /