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 {$180B5F70} %& 1    ' DlgBase Units?
    x% = MulDiv(x%, {$180B5E98}, 4)
    y% = MulDiv(y%, {$180B5E94}, 8)
    w% = MulDiv(w%, {$180B5E98}, 4)
    h% = MulDiv(h%, {$180B5E94}, 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 May 2019

Task Dialog as a replacement for Alert

Since Vista Windows supports the Task Dialog, an extended Message Box with a lot of new features. As the message box the task dialog displays an application-defined message, title, icons and any combination of predefined push buttons. In addition it supports a verification checkbox, command links, and radio buttons. In this post I’ll show you how to use the latest GB update (version 2.56) to easily create a task dialog. At the end of this post we will create the following dialog as an replacement for the famous Alert box.

There are two APIs that create a dialog box: TaskDialog and TaskDialogIndirect. The links will take you to the MS SDK site, to the pages that formally describe the APIs. Starting with GFA-BASIC update version 2.56 the APIs are declared in the include library file commctrl.inc.lg32. To get to the actual declaration you should inspect commctrl.inc.g32 – the source file for the library.

The TaskDialog API
The TaskDialog API is declared as follows:

Declare Function TaskDialog Lib "comctl32.dll" ( _
  ByVal hwndParent As Long, ByVal hInstance As Long, _
  ByVal pszWindowTitle As Long, ByVal pszMainInstruction As Long, _
  ByVal pszContent As Long, _
  ByVal dwCommonButtons As Long, _
  ByVal pszIcon As Long, pnButton As Long) As Long

Note that only the last parameter takes a variable by reference, all others are declared as ByVal. The TaskDialog function returns the selected button through this variable. The return value of the function itself indicates success or failure.

The parameters that take a string expect a wide string, the string must be formatted as an Unicode string. I discussed Unicode strings in Ansi and Unicode. To convert an Ansi string to Unicode I’ll use the function Wide() from gfawinx.lg32, which is located in the Include directory as well. The name of this file does not include the inc part, because it is a library with executable code, which will add to the program’s size (be it minimal). The include files (those that include the inc extension in the filename) only contain declarations and definitions that don’t add to the program’s size.

If you didn’t change the path to the Include directory after installing the update the Extra tab in the GB Properties should contain a valid library-path. This also means that you can include commctrl.inc.lg32 and gfawinx.lg32 as shown in this code:

$Library "commctrl.inc"
$Library "gfawinx"
OpenW 1
Print DlgTask(Me.hWnd, "Prompt", "Content", "Demo" , , -3)
Do
  Sleep
Until Me Is Nothing

Function DlgTask(hOwner As Handle, sMainText$, sContent$, _
  Optional sTitle$, Optional iButtons% = TDCBF_OK_BUTTON, _
  Optional Icon& = 0) As Long
  Local Long RetVal, lIcon
  Local String Title

  sMainText$ = Wide(sMainText$)
  sContent$ = Wide(sContent$)
  Title = Wide( Iif(IsMissing(sTitle$), App.Name, sTitle$))
  lIcon = MakeLongHiLo(0, Icon&)

  If TaskDialog(hOwner, 0, V:Title, V:sMainText$, V:sContent$, _
    iButtons%, lIcon, RetVal) == S_OK
    Return RetVal
  EndIf
EndFunc

This code produces the following dialog box at the center of the parent window:

You can pass Null to the hOwner parameter of the TaskDialog, but that would display the dialog at the center of the main screen. Not very useful when using multiple monitors with high resolutions. The hInstance parameter can be 0 because we don’t use an icon from the EXE’s resources. Instead we pass a predefined value for the icon to display (-3). The wide strings are passed by providing their address. The RetVal variable is passed by reference to receive the button’s number (>1) that is selected. The DlgTask function returns 0 if TaskDialog doesn’t return with S_OK (=0).

Although commctrl.inc defines all constants to be used with the task dialog functions, it doesn’t provide constants for the icons (GFA-BASIC versions <= 2.56). The icons are defined as follows:

Icon Value (Word)
Warning -1
Error -2
Information                    -3
Shield -4

The icon parameter must be an integer resource value created with the macro MAKEINTRESOURCE(). This macro, defined in some SDK header, creates a long where the high word is zero and the low word contains the resource identifier (Word). Here we use MakeLongHiLo() to create the integer resource value. With a newer version of GB (> 2.56) you can use the constants as defined in commctrl.inc without the need to convert with MakeLongHiLo.

The TaskDialogIndirect API
The TaskDialogIndirect function allows further fine tuning of the task dialog. For this to happen you need to fill out a structure of type TASKDIALOGCONFIG.

Declare Function TaskDialogIndirect Lib "comctl32.dll" ( _
  ByVal pTaskConfig As Long, pnButton As Long, _
  pnRadioButton As Long, pfVerificationFlagChecked As Long) As Long

By using TaskDialogIndirect you can create custom buttons rather than using predefined buttons only. Therefor it is a perfect candidate to replace the good old Alert box with a nicer version. The next example shows how to set up the TASKDIALOGCONFIG structure and pass it to the TaskDialogIndirect API to create the dialog box as shown at the beginning of this post. The Alert2 function takes the same arguments as the Alert box function. The IconAndFlag% argument specifies the icon and layout of the alert box. The MainText$ argument can specify multiple lines by using | as a separator. The ButtonText$ specifies the custom buttons and DefButton% the button to preselect. These parameters are translated to the task dialog features.

$Library "commctrl.inc"
$Library "gfawinx"
OpenW 1
Print Alert2(2, "Which procedure should|be executed", 1, "Input|Calculate|Print")
Do
  Sleep
Until Me Is Nothing

Function Alert2(IconAndFlag%, MainText$, DefButton%, ButtonText$) As Long
  Dim Icon As Word, RetVal As Long, VerFlag As Long, i As Long
  Dim sTitle As String, aBtn() As String, sVerificationText As String
  Dim tdc As TASKDIALOGCONFIG, taskBtn() As TASKDIALOG_BUTTON

  ' Provide a title (Unicode)
  sTitle = Wide(App.Name)
  sVerificationText = Wide("Don't ask again")

  ' Determine the icon
  Switch IconAndFlag% %& 7
  Case 1       : Icon = -2    ' Stop: Stop/Error icon
  Case 2, 4, 7 : Icon = -3    ' Question, Information: Information icon
  Case 3       : Icon = -1    ' Exclamation: Warning
  Case 5, 6    : Icon = -4    ' Windowsflag, Application: Shield icon
  EndSwitch

  ' Text lines are separated with |, but we need #10
  MainText$ = Replace(MainText$, "|", #10)
  MainText$ = Wide(MainText$) ' to UNICODE

  ' Copy the button text to the TASKDIALOG_BUTTON array
  StrToArr(ButtonText$, "|", aBtn())
  ReDim taskBtn(0 .. UBound(aBtn()))
  For i = 0 To UBound(aBtn())
    aBtn(i) = Wide(aBtn(i))        ' convert to Unicode
    taskBtn(i).nButtonID = i + 1   ' ID's are base 1
    taskBtn(i).pszButtonText = V:aBtn(i)
  Next

  tdc.cbSize = SizeOf(TASKDIALOGCONFIG)
  tdc.hwndParent = Me.hWnd
  tdc.dwFlags = TDF_ALLOW_DIALOG_CANCELLATION | TDF_POSITION_RELATIVE_TO_WINDOW
  ' Right align text?
  If IconAndFlag% %& 64 Then tdc.dwFlags = tdc.dwFlags | TDF_RTL_LAYOUT
  tdc.pszWindowTitle = V:sTitle
  tdc.hMainIcon = MakeLongHiLo(0, Icon)
  tdc.pszMainInstruction = V:MainText$
  tdc.cButtons = Dim?(taskBtn())
  tdc.pButtons = V:taskBtn(0)
  tdc.nDefaultButton = DefButton%
  tdc.pszVerificationText = V:sVerificationText

  If TaskDialogIndirect(V:tdc, RetVal, 0, VerFlag) == S_OK
    ' VerFlag is 1 if checkbox is checked
    Return RetVal   ' base 1
  EndIf
EndFunc

As with the TaskDialog function all strings must be UNICODE. After setting the tdc.cbSize member to the required size the hwndParent and dwFlags members are used to position the dialog in the center of the parent window.
If the IconAndFlag% parameter specifies right aligned text it is honored by including TDF_RTL_LAYOUT in the dwFlags member.
The main text string must contain LF (#10) characters to separate multiple lines. However, the MainText$ argument will separate multiple lines using the | character. So, before converting MainText$ to a wide string we need to replace all occurrences of | with #10 characters. The Replace function is located in gfawinx.lg32.
Setting up the custom buttons requires a bit more work. First we need to create separate strings for each button text. For this to happen we use the gfawinx procedure StrToArr, which splits a string into multiple array elements. Then, in a loop, each array element is converted to a wide string. In the same loop we assign the button’s ID-value and the string pointer to an array of TASKDIALOG_BUTTONs. After initializing this array, it is assigned it to the tdc members cButtons, pButtons, and nDefaultButton.
Finally, we specify text for an additional checkbox control with the pszVerification member. When pszVerification holds a valid memory address the checkbox control is displayed, but it is enabled only if the pfVerificationFlagChecked parameter of TaskDialogIndirect specifies the address of a return variable. If this parameter is Null the check box is displayed in a disabled state. This is also true for additional option boxes. Note that you may pass Null (0) to a ByRef parameter of a declared DLL function.

18 March 2019

StdFont and StdPicture

GFA-BASIC 32 provides two COM objects for use with fonts and two for use with pictures, Font and StdFont and Picture and StdPicture. The Font and Picture objects are GB specific, they are used with other objects like Form.Font and Form.Picture, etc. So, why would do you need StdFont and StdPicture?

StdFont and StdPicture are standard automation OLE objects, so maybe you can use them with an automation server like Office? In theory it should. VB/VBA uses StdFont and StdPicture for its font and picture objects and they should be compatible with GB’s StdFont and StdPicture. However, when you try to assign a Font object from an Excel cell to a variable of the StdFont datatype, GB complains about incompatible data types.
There are a few situations where you might stumble upon a Std* COM type, for instance when you are converting VB/VBA code. Another use can be found for StdFont: it allows you to create a font object on its own. StdPicture is less useful in this respect.

StdFont and StdPicture are coclasses
StdFont and StdPicture are for use in a GB program mainly. Both types allow the New clause in a Dim statement, because both StdFont and StdPicture are coclasses from which you can create an object instance. (You cannot use New on a Font or Picture object.) The New keyword in the declaration inserts object-creation code into the program. The result of New is a new instance of the StdFont or StdPicture class provided by olepro32.dll. That’s one of the reasons GB requires the presence of this DLL. After an object has been created it has a pointer to an interface - located in olepro32.dll as well – which holds the address of the array of functions (properties and methods). These interfaces are called IFontDisp and IPictureDisp. In fact, IFontDisp and IPictureDisp only expose the IDispatch functions, there is no way to directly access the properties and methods. When you use a StdFont or StdPicture property the compiler inserts late binding code, it cannot early bind to the properties.
For a discussion on IDispatch see CreateObject Caching.

Normally, as with all IDispatch objects (Object type), you can only tell at runtime whether a property is accessed correctly. However, this is not true for the StdFont and StdPicture objects. GFA-BASIC 32 checks the syntax at compile time because it knows quite a lot of the properties that can be accessed through IDispatch. Therefor, the compiler can perform a syntax check on the names and arguments. In addition, the compiler can optimize the late binding code for the properties of these Std* types. The properties’ disp-ids are documented and the compiler can hard-code them into the executable code. This prevents the compiler from inserting code to obtain the disp-id before calling IDispatch.Invoke. Although the compiler can optimize a disadvantage of using the IDispatch interface is the use of Variants when passing arguments to and from properties.

Using StdFont and StdPicture
So there are only disadvantages in using StdFont and StdPicture, it seems. This is certainly true for the StdPicture; it doesn’t provide any useful functionality to actually create a picture. The only way to create a picture object is when you use CreatePicture or LoadPicture. You can assign such a picture to either a StdPicture or Picture data type. However, why would you want to assign it to a (New) StdPicture type? Let’s see how that should work.

Dim p As New StdPicture ' creates a new instance
Set p = LoadPicture(f$) ' assign new instance

The Set command assigns a new object to a variable. When that variable currently holds a reference to another object that object is released first. So, the StdPicture object instance created with New is released before the new picture is assigned.
The New keyword caused the creation of an ‘empty’ StdPicture object. Since all properties of StdPicture are read-only there is no way to manipulate the data of the StdPicture object (same is true for a Picture object). Consequently, the statement Dim p As New StdPicture is not very useful. It doesn’t provide any other functionality as the Picture object and it causes the compiler to insert (slower) late binding code.

The use of a StdFont is more useful. A New StdFont creates a new font that can be assigned to anything with a Font property. (In GB StdFont and Font are compatible types.) This feature is more useful than applying New on a StdPicture as the example shows:

Dim f As New StdFont    ' new instance
f.Bold = True           ' set properties
f.Name = "Arial"
Set frm1.Font = f       ' assign

In contrast with StdPicture the StdFont properties are also writeable and makes the StdFont a very useful object.

Conclusion
StdFont and StdPicture are IDispatch objects. Using New creates a new instance, but this isn’t very useful for a StdPicture object.

13 January 2019

January Update v 2.52

This update – version 2.52 - fixes many small bugs from version 2.5 (December 2018) and introduces several improvements, especially with auto-complete. First of all, auto-complete isn’t as intrusive as it was in 2.5. To select a word from the list you now need to enter the listbox explicitly using <arrow down>. This prevents accidently inserting words you don’t want. Where possible auto-complete is extended with statement completion: the popup listbox shows suggestions determined by the context. Please give it try and if you don’t like it you can disable it in the Properties dialog’s Extra tab, where you can customize more new features.

One of the goals of this update is to bring the GFA-BASIC 32 user interface to current standards, unfortunately the IDE wasn’t built for this. So, within the given limitations a first attempt was made to introduce new features. For instance, auto-complete relies on the status of internal databases of procs and variables, but these databases are updated only when the code is compiled (Shift+F5 or Ctrl+F5). This is especially true for user-defined types and the declaration of a variable of a UDT. To have auto-complete present the UDT-members the Type definitions must be compiled first. (Tip: define UDTs in a library, the $Library command loads pre-compiled UDTs.) In addition, to initialize the entire auto-complete feature the program is best compiled (test) just after loading. Normally, this isn’t a problem unless your program contains errors, that’s where the compiler stops and leaves auto-complete uninitialized.

Although the auto-complete feature is the most obvious addition, many more improvements have been made. I will discuss them briefly, but you can find more information in the readme25.rtf file.

Fixed: destruction of local arrays
The most important improvement of 2.5 and later is the fix of the destruction of local arrays and hashes. Until now it was difficult to use a local array or hash because the compiler didn’t add destruction code to the procedure. For a local array to be destructed you needed to introduce some another dynamic variable – mostly a string – as a workaround. A hash table wasn’t destructed at all, and required an explicit destruction in the code (Hash Erase). Although workarounds existed the problem remained that GB’s standard exception handlers didn’t destruct these data types. An exception (error) in the procedure caused a memory leak because the array and hash weren’t deleted (unless you used Try/Catch and followed the workaround rules). This problem now belongs to the passed, the destruction code is inserted for a normal flow of execution and for exceptions. Now this is fixed, GFA-BASIC 32 shouldn’t cause memory leaks anymore.

Groups in the Proc-tab
For large programs with a lot of procedures there is now a way to group the procedures in the Proc-tab of the sidebar. The following screenshot from the gfawin32.gll project illustrates the result of adding collapsible groups:

The $Group command is placed directly in the front of a procedure that is to be the first in the group. Each $Group command defines a new group of sequential procedures. By default, non-active groups are folded. The grouping feature allows very fast navigating through large programs. To remove a group type $GroupOff on the group line and after the group is removed you can simply delete the line.

New toolbar buttons
The screenshot shows a few more toolbar buttons.

  • The arrow-left and –right allow you to navigate through your edit-history. Since version 2.52 adds more actions to the navigation-history, this increases the chance that you can properly return to the line you previously edited.
  • A Procs button allows for fast access to commonly used procedure related actions like Set to Top, Print, Disassemble, and many more.
  • The validate (V) button is a shortcut for Shift+F5.
  • The window-wipe button performs a cleanup when a window or form remains on the screen after an abnormal exit.
  • The Launch Exe button (here dimmed because the current GLL project cannot be launched) helps in saving, compiling, and running your project in one simple step.
  • Finally, there is a button to quickly toggle the debug output window.

Quick Help
The quick help feature has been completely revised. Now when you hoover over a (key)word a popup with a short help description appears. Although most help topics were available, many (more than 2000) were mapped incorrectly to their help-ids. You should try it out and be surprised by new information never showed before.
Quick help is also available for procs, variables, and Types. For procs and types the quick help can be scrolled to show you the entire proc or type in the quick help window.

Keyboard shortcuts
There are now some really helpful keyboard shortcuts. For instance, App+P inserts the name of the current procedure into the text: an easy way to add a name to a message string.
Shift+Enter inserts the line-continuation character before invoking the Return key.
There are also many shortcuts for procedure related actions, for instance Alt+F12 to show the procedure’s disassembly and Ctrl+F11 to align the current procedure to the top of the editor which helps to focus on the current procedure.

But there is more. Known from Visual Studio is Incremental Search; press Shift+Ctrl+I and start typing letters, while typing the location of your search string is immediately shown. Press the arrow keys to reverse the search direction.

This post doesn’t cover all the new features. That’s why a Tip of the day (only once a day) presents a short note on a new feature. However, for more information please refer to the readme25.rtf file available from the Start Menu.

Bugs, questions and other remarks can be posted at gfabasic32@gmail.com.