Showing posts with label Library. Show all posts
Showing posts with label Library. Show all posts

10 June 2025

Pointer (1)

With the Pointer command, Pointer() function, and Pointer type, GFABASIC-32 features a powerful tool. With the Pointer command you can assign a Pointer type variable to a memory address.The Pointer type variable is 32-bits integer variable that stores the address of some memory; it is a memory pointer. To see it in practice look at this code:

Dim i As Int = 9
Dim p As Pointer Int
Pointer p = V:i
Debug p                 ' displays 9

The variable p is a pointer to a type Int. From this declaration GB32 knows how to interpret the pointer p; it is pointing to an Int. Initially, the pointer variable p is 0, it is not assigned a memory address yet. You can obtain the memory address it is pointing to with the Pointer() function.

Debug Pointer(p)        ' displays 0

To be useful, it must be assigned a memory address and the contents of the memory address is then interpreted as an Int type. To assign a memory address use the Pointer p = addr command. The Debug command shows that p points to the memory address of the integer variable i, because it displays the value of i.

ByRef & Pointer are identical
Because the pointer variable is exactly the same as a ByRef variable, the auto-complete info shows a Ref indication. A by reference variable/parameter is also a (local) variable without an address. When a variable is passed to a ByRef parameter, the address of that variable is assigned to the by reference (pointer) variable. Because the compiler checks for the type of the passed in variable, you cannot assign a random memory address to a by reference parameter. With the Pointer type you can though.

Practical example: ownerdraw
The Pointer type comes to a practical use with Windows system messages that are handled in the MessageProc() event sub.One example is the ownerdrawn message WM_DRAWITEM. Ownerdrawn is used with controls and menu-items, to draw the items yourself. The WM_DRAWITEM specifies a pointer to a DRAWITEMSTRUCT structure in the lParam. In C/C++ this is written as DRAWITEMSTRUCT* or LPDRAWITEMSTRUCT, meaning Long Pointer to DRAWITEMSTRUCT, where the 'Long' is a holdover from the 16-bits days. This description is easily translated to GB32 to declare a pointer variable:

Dim pdi As Pointer To DRAWITEMSTRUCT

Since the DRAWITEMSTRUCT is defined in the Windows winuser.h header file, you can find the GB32 definition in winuser.inc.lg32. which must be included before the structure can be used:

$Library "winuser.inc"

In the MessageProc event sub the WM_DRAWITEM message is used like this:

Sub Win_1_MessageProc(hWnd%, Mess%, wParam%, lParam%, retval%, ValidRet?)
  Switch Mess%
  Case WM_DRAWITEM
    Local pdi As Pointer To DRAWITEMSTRUCT      // = null
    Pointer pdi = lParam%

    // Do your drawing here

    retval% = 1                 // handled the message
    ValidRet? = True            // retval% is a valid value

    // other messages
  EndSwitch
EndSub

The local pointer variable pdi is 0 initially. By using the Pointer command the pdi variable is set to the DRAWITEMSTRUCT passed by the system in the lParam%. From that point you can use the pdi variable as any other user-defined variable to get access to a member. For instance, to obtain the device context use pdi.hDC.
If you handled the message, the system must be notified by returning 1, which is the C/C++ value for TRUE. To force GB32 to return the value set in retval% you must set the ValidRet? boolean variable to True.

The next time I will explore the use of Pointer with converting a C/C++ source using pointers.

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.

25 April 2021

The gfawinx library

It’s been a while since GFA Software Technologies GmbH released GFA-BASIC 32. At that time (2001) it could not be foreseen how Windows - and PC-use in general - would evolve. Consequently, GB32 misses runtime functions to coop with the latest Windows developments and the introduction of better hardware. To fill that gap gfawinx.lg32 comes with a set of commands and functions to access and use the newer features. The name gfawinx stands for “GFA-BASIC 32 expansion” library, gfawinx contains several categories. When you load the gfawinx.g32 code file in the editor the Procs sidebar shows the current groups:

Screenshot 2021-04-23 112115

Download latest version
Previous gfawinx versions – until and including the version of 16 Feb 2021 - contain an error that may lead to problems when loading the library into a program. If you are planning to use gfawinx make sure you have a version later than 16 Feb 2021. Please check the date of the file in the Include directory.

Download a newer version if necessary here and unzip the file to the Include directory.

What’s the problem? One of the library functions uses a DLL function declared using the Declare command. However, when a lg32 library uses a declared function it must also export that DLL function using an $Export Decl command.The program that is importing the library must be able to add that declared function to the program’s table of DLL functions. Gfawinx used a declared function, but did not export it. The current version of gfawinx doesn’t use Declare anymore, but uses GetProcAddress() to obtain the function’s address and calls it using StdCall()(). Something to remember when you are developing your own libraries.

The String functions
The Wide and Ansi functions are mainly used with Windows API functions that take a UNICODE string. An example of both functions was discussed in Task Dialog as a replacement for Alert, which demonstrates the use of a Windows API taking UNICODE strings as parameters. This blog post also contained an example of StrToArr, which is used to convert a string, containing sub-strings separated with any character, to an array. StrToArr is an addition or replacement for the Array ar$() = $ command.
The Replace string function is a VB(A) compatible function, much easier to use then the Split command of GB32. The Replace function is optimized for speed and takes the string arguments by reference if the arguments are string variables. Only in case a string literal is passed GB32 will pass the string parameter by value (making a copy first and then pass that copy). This is achieved by making Replace a FunctionVar rather than a Function.

FunctionVar and Sub anonymous String and Variant parameters
When a parameter of a FunctionVar or Sub does not explicitly specify ByVal or ByRef the parameter is anonymous. In this case the compiler decides how to pass the parameter to the FunnctionVar or Sub. If possible, the compiler generates code to pass the String or Variant by reference, otherwise the parameter is passed by value. To be able to pass by reference the caller must pass a variable; a literal is always copied to a temporary locally variable which is then passed by reference. Note that a ByVal parameter always gets a copy of the data that is passed, which requires the compiler to insert additional code to create a copy first. Passing by reference is almost always faster. Using anonymous parameters can speed up the execution of a procedure or function considerably.

An example that shows the difference:

$Library "gfawinx"
Dim src$, find$, new$, result$
result$ = Replace(src$, find$, new$)     ' variables, fast
result$ = Replace("GFA-BASIC", "A", "x") ' literals, slower

ErrStr is a convenient function to obtain the relevant Err-properties as a string. Usually, ErrStr will be used in a Try/Catch handler to present information about the trapped error to the user. The problem is to decide which Err-properties contain the relevant error information. ErrStr examines the properties and only returns the properties that contain the actual error information. ErrStr takes one string parameter that is added to the error information. This parameter is perfectly suited for the current proc-name, which is easily inserted using the keyboard short cut App+P. Here App+P inserts “main” into text:

$Library "gfawinx"Screenshot 2021-04-23 131437
Try
  Error 3
Catch
  MsgBox ErrStr("main")
EndCatch

The program produces the following message box:

The $ = HexDump(addr%, size%) string function returns a memory dump starting from the specified address addr% and with a length of size%. The result of this debugging aid can easily be displayed in the Debug Output window or a MsgBox.

The Windows functions
Here we find the Windows API and form-windows related commands and functions. Let’s first examine the WinVer function which returns the Windows OS version. In the latest version of gfawinx it returns the value obtained using the RtlGetVersion() API in a hexadecimal format $XXYY, where XX represents the major version and YY the minor version:

$Library "gfawinx"
Print Hex(WinVer())  // A00 (Win 10)

There is a lot of information to be found on how to obtain the Windows version. The method used by WinVer() does not depend on the manifest file as other methods do and should be safe to use with future releases of Windows. (Note - only the latest version of gfawinx - later than 16 Feb 2021- contains this method.)

GetWorkArea is a useful command when using multiple monitors where the Screen.Work* properties don’t work. GetWorkArea returns the work area of the monitor containing the largest area of a given window.
GetProcAddr returns the address of a function from a specified DLL. It works a little different than the GetProcAddress() API, because it first checks if the required DLL is already loaded.
The GetClientSize command returns the size of the client area, useful if _X and _Y are not available (in API windows).
ModifyStyle and ModifyExStyle make it easy to change the windows Style or ExStyle on the fly. They allow to both set and remove a windows-style simultaneously.
The Assoc() function returns system information associated with files, extensions or Prog IDs. For instance to obtain the full path of the default application that is associated with the .txt extension use:

$Library "gfawinx"
Print Assoc(".txt") // full path of default app

SaveFormPos and LoadFormPos save and load the form’s position respectively. For single monitor apps these are simple functions to store and retrieve the form’s current position in and from the registry. They are especially useful when used with multiple displays and DPI-aware applications. For detailed information see the help file.
MemStat is is a helper function to obtain memory statistics. If the argument is omitted (or is 0) it returns the memory used by the application. It is possible to trap a possible memory leak by using MemStat() when the program starts and compare the returned values at each start of the program.

The DPI-related functions
When you want to write DPI-aware applications these helper functions will be very useful. For more information on writing DPI-aware programs please see the English CHM helpfile: in the Contents section see Creating an application. Programming DPI aware programs is beyond the scope of this blog post. The gfawinx library provides the following functions.

WinDpi returns the DPI of the display for a window or form.
DpiAwareness set the DPI mode for the program.
ScaleToDpi(Sng) helps to scale coordinates.
ScaleXYWHToDpi scales ScaleLeft, ScaleTop, ScaleWidth, and ScaleHeight to a form's DPI.

The TimerQ function
The TimerQ function creates a high-resolution timer wrapped in a (minimal) COM object. This is the gfawinx implementation of the discussion in the blog High Resolution Timer . By using a COM object for a resource it can be guaranteed that the resource is freed when the program ends – either normally or through a runtime error.

Conclusion
The gfawinx library provides functions and commands that are not included in the GB32 runtime. The commands and functions are displayed in the GB32 syntax color for statements and GB32 functions. The procedures that implement the additions are optimized for speed, when possible the procedures are Naked. It is recommended that you – sometime - take a look at the code, it contains comments on implementation details you might find interesting. The library will grow over time when more ‘missing’ features will be added.

23 February 2021

The Include directory

After installing GFA-BASIC 32 you’ll find four directories in the installation path: Bin, Doc, Include, and Samples.

image

The \Bin directory contains the GB32 binaries, the \Doc contains the original (German) doc-files that came with GFA-BASIC 32 back in 2001 (now obsolete because everything can be found in the English CHM helpfile), the \Include contains the Windows API include files, and the \Samples directory the samples g32 files, including the new Direct2D example programs.This time we’ll focus on the \Include directory only.

What is the \Include directory for?
The purpose of the \Include directory is to collect all Windows API definitions and declarations in one directory. Because of the huge amount of Windows APIs the definitions and declarations are split into multiple smaller include library files. These GB32 Windows API include-files come both with the source code and the compiled library (lg32) file. The organization of the GB32 include files follows the way the Windows SDK presents the C/C++ include header files. For instance, the C/C++ header file winuser.h has an equivalent GB32 include file winuser.inc.lg32. All include files follow this naming convention: name.inc.lg32.
You import a GB32 include library file using the $Library command, for instance:

$Library "winuser.inc"  ' .lg32 may be omitted

By default, the line doesn’t need to specify the full path, because the location of the \Include directory is pre-selected in the Properties | Extra tab dialog box.

image

You can easily add your own paths that contain your own library files. The entire string with the specified paths is stored in “lg32paths”registry key. To add a path first type a semicolon ; after the existing path and then specify the full path after the semicolon. Once a path is part of Library paths you do no longer need to type the full path in the $Library statement. Note that libraries are searched for (1) in the current program's directory, (2) in My Documents\lg32, and (3) in the paths stored in the registry key "lg32paths".

Note The \Include directory also contains the libraries gfawinx.lg32, direct2d.lg32 and variants.lg32. These are not include files and don’t have the .inc clause in their names. These are libraries that are part of the GFA-BASIC 32 updates and need an easily accessible path.

Why multiple include files?
Each include file contains only a part of the Windows API, that way you don’t need to include a single large file with many APIs just to have a few declarations. The GB32 include files only provide the APIs that are not built-in by GFA-BASIC32. (Note that the Win32API.g32 that originally came with GB is incomplete and full of errors.) Due to the amount of new APIs that come with each new Windows version, the include files in the \Include files aren’t complete either. They do however provide more declarations and definitions than the original Win32API.g32. Some of the include files are updated with the latest APIs (most specifically APIs supported by Win7 and Windows 10) like, for instance, winuser.inc.lg32. However, many haven’t been updated for a while. They tend to get an update on a ad hoc basis; when I need new APIs I add them to the include files. It is a boring job and, because of the translation from C/C++, errors are easily made. If you need an API that isn’t in one of the include library files yet, let me know (gfabasic32@gmail.com).

Contents of an include library
By default, the GB32 Windows include files only provide function declarations (Declare statements), constant definitions, and user-defined type definitions. They don’t contain executable code and besides the Declare’s they do not contribute to the size of the program. You should know that Declare-ed DLL functions are collected in a DLL-import table that do become part of the program (executable). By spreading the Declare’s over multiple include files the DLL-import table can remain small.
There is one “include” file that does contain executable code: winmacros.lib.lg32 (note the lacking .inc clause in the name). This library contains functions for often used Windows macros that are used as functions in C/C++, but are defined as macros in the C/C++ header files. Some of these function macros are collected into winmacros.lib.lg32. Among others, the library provides (Naked) functions for GET_X_PARAM, GET_Y_PARAM, MakeLParam, MakeIntResource, etc. Please take a look at the source code in winmacros.lib.g32 for an overview of the supported functions/macros.

How to locate a specific API
How do you know in which include file a specific API (type, constant, or function) is located? It might be that GB32 already supports the API as a built-in API, only function declarations and constant definitions. GB32 does not have built-in API support for user-defined API type definitions, they always have to be defined by the program or imported from an include library. The easiest way to check if GB32 supports a specific API is by using the auto-complete feature. Just type the first letters of the function or constant and check if the auto-complete pops up with the required name. If it isn’t provided by GB32 you’ll need to check the Windows SDK documentation to see which C/C++ include file provides the declaration or definition for that API and load the equivalent GB32 .inc library file.

An example of using an API
Each topic in the Windows SDK specifies in which C/C++ header file an API function, type, or constant is declared. For instance, if and API is located in the winuser.h C/C++ header file you have a big chance of finding it in the winuser.inc.lg32 file. Let’s look at an example. Suppose your program wants to process the WM_GETMINMAXINFO message. After looking up the documentation for this message, it tells you to obtain the  MINMAXINFO structure from the lParam. However, GB32 itself does not provide a definition for the MINMAXINFO user-defined type, and you need to import the type. When you go to the SDK page that describes this type you’ll find at the bottom of the page the location of the definition of this structure: the winuser.h C/C++ header file. Now you know which GB32 include library you need: winuser.inc and the program can import that library. As it happens this structure contains members of the API type POINT, which is defined in wingdi.inc. However, you won’t need to include wingdi.inc in your program, because it is imported by winuser.inc (otherwise it couldn’t be compiled). After importing winuser.inc in your program the constants and user-defined types from wingdi.inc are available as well. So, the POINT structure is available as a user-defined type in your program.

Note winuser.inc exports the constants and type definitions of wingdi.inc, but not the function declarations. If you need an API function declared in wingdi.inc you’ll need to import wingdi.inc as well. Importing both include files won’t collide with each other.

$Library "winuser.inc"
$Library "wingdi.inc"

Although both include files export the same constants and user-defined types they are added only once to the program. Now if winuser.inc would also export the function declares from wingdi.inc the internal database of GB32 may become corrupted. Therefor, winuser.inc exports all constants and types using the $Export Const * and $Export Type * statements, but the exported $Export Decl name statements must specify each exported declare separately. This is easily done using the App+E shortcut, it inserts $Export Decl lines for each Declare in the library file.

Conclusion
You can use the Windows API inc files to import Windows APIs easily. Because the (compiled) user-defined types are imported from a library the autocomplete function has direct access to their members and is fully operational without first compiling your program.

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.

10 May 2017

Error free using a library

I use libraries a lot, but there are few things that make using them a bit obscure and may lead to the non-descript message "Load lg32 error: filename.lg32" in the status bar and debug window. In addition, a compiled library may produce strange runtime errors.

BUG - Runtime errors
When you run a project which includes a library it may generate strange, seemingly unrelated error messages. In particular the error "Hash Internal Error 1/2 (Version?)" pops up regularly. The reason for runtime errors inside the code of a library is a bug(!) in applying the setting for Branch Optimizations.

For a lg32 file, GFA-BASIC wants to apply the Full Optimization for Exe setting on the compiling process. However, it is never applied at all, because the code applies this setting in the wrong place, after the code is compiled ;). Consequently, the compiler switches to the trackbar/slider setting from Branch Optimizations.
This is a bug from a long time ago and it is simply never tested properly.

In general objectcode generated for a lg32 file is position independent, it differs from code generated for  EXE (and GLL files). Therefor, the lg32-generated code for the jump-tables for Switch/Case statements and On n GoSub/Call statements are wrong (this is also true for a GLL, for which I always use the default settings). 

The only setting that work flawlessly is the None setting of the slider in 'Branch Optimizations' and uncheck the 'Full Optimization' check box. 


A lg32-file has to be compiled using the default settings for Branch Optimizations.
The slider must be set to the first position (None) and the checkbox Full Optimization for Exe must be unchecked.

Note - The slider setting is applied to compiling code in memory, independent of the required output file type (EXE, LG32, or GLL). The most right position (Full) is exactly the same as checking the Full Optimization for Exe - box. This way you can test fully optimized code inside the IDE.

Note - The branch optimizations of the compiler do not lead to remarkable performance results. These days with fast CPUs and large caches performance increase is hard to provide, the only real performance increase is accomplished by using Naked procedures. Remember however, Naked procedures do not include termination code and do not allow exception handlers.

The $Library statement
The $Library statement loads a lg32 file into memory. But sometimes it cannot locate the lg32 file. The IDE code to find a lg32 file is a bit complicated. In some conditions you may omit the extension and in others you cannot. It depends on the inclusion of a path in $Library statement. For instance, you may include a relative path (relative to the current directory, mostly the g32-file directory, but not necessarily), but than the extension must be provided. It's all a bit incoherent. But there is a solution that always works correctly. That is - the library is always located properly.

Solution for load errors
This solution adds more functionality to the $Library statement and so it complements the current functionality. You must add a (new) register entry to the GFA/BASIC key in the HKEY_CURRENT_USER/Software setting. The key must be named "lg32path" and the value can contain multiple full paths separated by commas. (The value uses the same syntax a the PATH environment variable).

New key: "lg32path", REG_SZ
Value: "C:\GFA\Include, D:\GFA\MyLibs"

Have fun with lg32 file.

28 November 2010

Download new update

General note on updates. Go to the Download Page (button on the top of the blog).

29 October 2009

Modify the Windows style

For a project I needed to change many window style bits in one session. For this I developed two procedures, one to change the GWL_STYLE and one to change the GWL_EXSTYLE window styles.
$Export
$Export Proc Modify*

Procedure ModifyStyle(hWnd As Handle, lRemove As Long, lAdd As Long)
Local Long Style = GetWindowLong(hWnd, GWL_STYLE)
Style &= ~lRemove
Style |= lAdd
~SetWindowLong(hWnd, GWL_STYLE, Style)
~SetWindowPos(hWnd, 0, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOMOVE | SWP_DRAWFRAME)
EndProc

Procedure ModifyExStyle(hWnd As Handle, lRemove As Long, lAdd As Long)
Local Long ExStyle = GetWindowLong(hWnd, GWL_EXSTYLE)
ExStyle &= ~lRemove
ExStyle |= lAdd
~SetWindowLong(hWnd, GWL_EXSTYLE, ExStyle)
~SetWindowPos(hWnd, 0, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOMOVE | SWP_DRAWFRAME)
EndProc

As you can see, I collected these in a $Library (.lg32). I'm a big fan of libraries and whenever possible I put general subroutines in a compiled module.

I'm aware of some problems with loading of LG32 files, I had them in the past as well. However, and I don't know why, I don't have them anymore ...

14 October 2009

Loading a library: fail or success?

When the $Library directive is parsed (triggered when the line is changed) the specified library is loaded and the exported items are added to the Imports tab in the sidebar. When the file is indeed located and opened a message of success is written to Debug Output Window saying "Loading lg32filename".
Any errors caused by the $Library are then due to a corrupt lg32 file.

The "Load lg32 error" message
Programs that contain references to libraries using the $Library directive may occasionally show a "Load lg32 error: filename.lg32" in the status bar. This message is generated when the specified library can't be located at the specified path or, when a path is omitted, in the current active directory, 'My Documents\lg32', or in directories that are searched using the SearchPath API function.
This message should therefore be called: "Lg32 file not found".

Reload project to reset current directory
When a lg32 can't be found the most obvious reason is an unexpected active directory. Sometimes when working at a project the active directory is changed and the project's directory isn't the current any longer. To check for this, simply reload the project, because this sets the current active directory back to the project's folder.

Set default path for Libraries
An undocumented feature is the possibility to set a default path for libraries in the registry. The registry key HKEY_CURRENT_USER\Software\GFA\Basic must contain the key "lg32Path" specifying the default path for the libraries. The key is not present at default, so you must add the key yourself.