28 November 2010

Download new update

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

Update of Build 1169 (2)

This is the second update of Build 1169. The IDE (GfaWin32.exe) isn't changed and still has build number 1169; however the runtime in the previous build-release 1169 (1) contained a new bug.

The CommDlg property .FileName didn't return the selected folder when CommDlg.ShowFolders was used. This is now fixed and the .FileName property should now return the correct filename(s) or folder. Note that the .FileName property was associated with three bugs. I hope they are all fixed now.
The runtime has build number 1169 now as well.

GOTO DOWNLOAD

22 November 2010

ImageList Ocx Part 2

In the introduction to the GFA-BASIC 32 ImageList Ocx (1) the emphasis was placed on the creation of the image list common control, which – as we saw - is delayed to the moment the first picture is added to the control. All images have the same size. The size is determined by the first picture's height and width, or the size values set using the .ImageWidth and .ImageHeight properties of the ImageList Ocx.  The .ColorFormat property specifies how the pictures are added and whether or not a mask is generated. The color for the mask is  specified using the .MaskColor property.

After the creation
Once the underlying image list common control is created, the .ColorFormat, .ImageWidth and .ImageHeight properties fail to accept a new value. These properties raise the E_FAIL COM error. This is also demonstrated when you use the 'ImageList Data' dialog box to add images to an ImageList Ocx. The upper left controls contain the options to create the image list common control. You may specify the required size and color format. The default settings are zero for both the size properties, and the 0 (device dependent/mask included) ColorFormat property.
After adding an image these control are disabled, because ImageList_Create() has been invoked using the settings from these controls.  
[image17.png]
Using masks in GFA-BASIC 32 is a bit confusing because its setting is the opposite of Windows API.

Masks in GFA-BASIC 32
By default GFA-BASIC 32 creates a mask for each and every picture you add to the ImageList control. Whether or not the mask is added to the underlying image list common control is determined by the .ColorFormat property value.
In contrast with the API specifications, GFA-BASIC 32 uses a reversed setting for the mask option. The flags argument in the ImageList_Create() function specifies the mask using the ILC_MASK bit value. In GFA-BASIC 32 you don't explicitly specify  a mask by setting bit 1 to 1; in GFA-BASIC the .ColorFormat value must hold a zero value in bit 1.

.ColorFormat = 0    '(default) create mask
.ColorFormat |= 1   ' no mask


Just before creating the image list common control GFA-BASIC inverts the first bit and includes it in the flags parameter passed to the ImageList_Create() function.
However, when you explicitly exclude a mask (by setting .ColorFormat |= 1) GFA-BASIC 32 will continue to create masks. However, when picture and the mask are added they are simply ignored by the image list common control.
Note Excluding a mask will result in loss of data when you add an icon to the image list. Therefore GFA-BASIC 32 uses a mask at all times.

Purpose of UseMaskColor and MaskColor Properties
When you add an icon into the list, GFA-BASIC uses its internal mask. If a bitmap or a metafile is added, a mask is created in memory. When .UseMaskColor == 0 the mask is simply a black monochrome bitmap of the same size, all pixel bits are set to zero. However, when you set .UseMaskColor to a nonzero value, the pixels in the image that have the same color value as the .MaskColor setting have a corresponding white color in the monochrome mask bitmap. This opens up the possibility to draw the image transparently. How the image is drawn later depends on more factors than the availability of a mask bitmap only, however.
You can change both UseMaskColor and the MaskColor properties before adding the next image to the list. That way you can have GFA-BASIC generate the correct mask for the image to add. (Each image might have a different transparency color.) 
UseMaskColor and MaskColor are a per image setting when adding a Picture to the list.

The BackColor Property
The only property we didn't discuss so far. The .BackColor property isn't used in the creation of the image list common control and it isn't used in the addition of images to the list. The .BackColor property setting is used when we want to get something out of the image list control. It is used in drawing operations and when a Picture object is retrieved.
The .BackColor is used in the ListImage.Draw method. The .Draw method is simply a wrapper around the ImageList_Draw(himl, i, hdc, x, y, fStyle) API, which draws an imagelist item in the specified device context. The fStyle parameter is used to set the drawing style and only when fStyle = ILD_NORMAL (0) the image is drawn using the background color. You can experiment with this, it speaks for itself.
Note The .BackColor value is passed to the underlying common control using ImageList_SetBkColor() API. 

The .BackColor setting is also used when the ImageList control is used with other controls (that can't be bound to the ImageList control). You can assign the ListImage.Picture object of the ListImage item to the .Picture property of another control. For example, the following code assigns the Picture object of the first ListImage object in a ListImages collection to the Picture property of a newly created StatusBar panel:

Dim pn As Panel
Set pn = sb.Panels.Add() ' Add a new Panel object.
Set pn.Picture = im1.ListImages(1).Picture 

However, how is this Picture created? That is , how are single Pictures  extracted from the image list control? Can this be done by invoking some ImageList_Xxx() API function? The answer is no, the Picture property is a GFA-BASIC 32 specific implementation which uses the .BackColor setting.

The Picture property With .Picture (Get-property) GFA-BASIC allows to create a new bitmap from the image list (and its mask). It creates always a 24 bits DIB memory bitmap to draw the image using ImageList_Draw(). The size of the DIB is ImageWidth x ImageHeight and the background is cleared using the color from .BackColor setting. Then, GFA-BASIC uses the ImageList_Draw() API to draw the specified image using the API background color for the image list (which is also set with .BackColor property!). The net result is a bitmap with a background color .BackColor.
Since the .Picture property returns a 24 bit DIB only, possibly transparent, the new obtained image form the ImageList Ocx might be quite different from what first was added. The .Picture property only returns device independent bitmaps or icons. Any jpg and metafile images are converted to a DIB.  

ExtractIcon
When you require a transparent image for some other control that accepts an icon use the .ExtractIcon property instead. This will return a real Windows icon resource which is transparent by default.

ImageList Ocx (1)

An ImageList control is a collection of images of the same size and type. An image list control helps you manage a collection of images that are the same size, such as bitmaps or icons. Image lists, which are designed for use with list view and tree view controls, manage images but do not display them directly. In BASIC its use is extended to be repository for other controls as well.
Note Part 2 was published before part 1, so might have read ImageList Ocx (2) before.
The API behind the creation
The windowless ImageList Ocx is a bit different from the image list common control. Although the GFA-BASIC 32 Ocx is created using the API function ImageList_Create(), the underlying image list common control is managed using a second layer of ListImages, another Ocx type. The ListImages object is created when the ImageList Ocx is created and both are initially 'empty'; there is not even a real image list common control. GFA-BASIC 32 doesn't create the underlying image list control before the first picture is added to the list. The reason behind this behavior will be clear when you look at the definition of ImageList_Create(). The image list needs the initial size of the images and most often the size isn't known until the first image is added. 
hIml = ImageList_Create(cx, cy, flags, cInitial, cGrow )

This function creates a new image list common control and returns the handle to the image list if successful. Note that the handle isn't a handle to a window (hIml isn't HWND type).
To be able to create a proper ImageList Ocx, the following parameters and Ocx properties need to be set.
API Meaning ImageList property
cx Define the width and height, in pixels, of each image. .ImageWidth
cy Defines the height of each image .ImageHeight
flags Set of bit flags that specify the type of image list to create.
- use a mask (ILC_MASK) 
- color depth  (ILC_COLORnn)
.ColorFormat (sets color depth and sets ILC_MASK)
cInitial Specify the number of images that the image list initially contains. 1 - Determined by GB32
cGrow Number of images by which the image list can grow when the system needs to make room for new images. 1, 2, or 4 - Determined by GB32
Only three properties determine the type of image list common control is created.
  1. .ImageWidth and .ImageHeight are used to set the cx and cy arguments of the API function. When not specified the first image added to the list determines the width and height of all images (cx and cy).
  2. .ColorFormat is used to set flags API parameter, which specifies the required image type (16 colors, true colors, etc.). Default is device dependent with a mask.
Masks
The .UseMaskColor and .MaskColor properties are useful only when the control has a masked bitmap attached. The masked-bitmap is created when the API argument flags includes ILC_MASK (GFA-BASIC default). If the ImageList Ocx doesn't contain a masked bitmap these two properties have no effect!
  • A non-masked image list includes a color bitmap that contains one or more images. This is a wide bitmap containing small bitmaps. When a non-masked image is drawn, it is simply copied into the target device context (DC); no special processing occurs.
  • A masked image list includes two wide bitmaps of equal size. The first is a color bitmap that contains the images; the second is a monochrome bitmap that contains a series of masks (one for each image in the first bitmap). The mask is created by GFA-BASIC 32 and not by Windows, except for icons. Icons contain both a color bitmap and a mask and GFA-BASIC adds directly to the underlying common control.
On output, when an image is retrieved from the ImageList Ocx, the existence of a mask determines how the image is composed. An image can be drawn using ImageList.Draw or extracted using ImageList.Picture or ImageList.ExtractIcon
With a masked image present, the mask is combined with the image itself. This combination produces transparent areas in the bitmap in which the background color of the target DC shows through. More about this is ImageList Ocx (2).
The Picture input Object
The ImageList Ocx only accepts images contained in a Picture or StdPicture object. A Picture Ocx object is a container for a bitmap, icon, jpg, or metafile image. In both VB and GB32 it is not possible to assign any other type of image than a Picture COM type.
There are three ways to obtain a Picture object.    
1. Use LoadPicture() to load an external image file.
2. Use CreatePicture() to create a picture object initialized with a bitmap or icon.
3. Obtain a Picture object from another COM object.

All Picture objects (jpg, bmp, dib, emf) that are added to the ImageList Ocx are provided with custom created mask. The masked image is created by GFA-BASIC using the color from the .MaskColor property, but only when .UseMaskColor is True. .UseMaskColor and .MaskColor determine the transparent color per image.
Adding Pictures – API details
Once you have an image in a Picture object, you can start adding that image to ImageList Ocx. Internally, GB32 has a choice from three possible API functions to add a Picture image: ImageList_Add(), ImageList_AddIcon(), and ImageList_AddMasked() [not used by GB32]. Obviously, GFA-BASIC uses ImageList_AddIcon() when the Picture object holds an icon resource. In all other circumstances it uses the ImageList_Add() API which takes three arguments:
index = ImageList_Add(himl, hbmImage, hbmMask)

When you add an image into the list, GFA-BASIC gives it a mask. If the image is an icon, its internal mask is reused. If it is a bitmap or a metafile, a mask is created. The end result is a collection of icons of the same size. However, all images in an image list are contained in a single, wide bitmap in screen device format. When an image list includes a mask, it also has a monochrome bitmap that contains masks used to draw images transparently (icon style).
Before invoking this function, GFA creates the hbmImage and the hbmMask. The hbmMask is a black bitmap in case .UseMaskColor = False (0).
Finally
Usually, the ImageList Ocx is used as an image repository for other Ocx controls. When you attach an ImageList to another control, you don’t need to worry about what gets drawn and how. An image list is a collection of images of the same size, each of which can be referred to by its index or key.
A second blog ImageList Ocx (2) discusses more implementation details.

05 August 2010

Initializing an Arrray

An array is a named collection of values of the same type. In BASIC an array is always dynamic, the memory necessary to store the array elements is allocated at runtime. You cannot set the array elements to a constant value before hand. Initializing array elements is a runtime operation performed after the array is created, unfortunately. Let me illustrate.
In C/C++ you can assign initial values to an array by enclosing values within braces {}. For instance

int scores[5] = {1, 2, 3, 4, 5};

In contrast with BASIC, the array is static and not created dynamically at runtime. The C/C++ compiler reserves 5 consecutive integers (each 4-bytes, thus 20 bytes) in the .rdata section of executable and associates the address with the variable name scores. When the executable is loaded the 5 integer values are loaded as well and scores points to the first integer (value=1). Because the array is of type int (32-bits integer) the C/C++ compiler converts the ASCII representation "1" found in the source code to the 32-bits number 1. The executable contains the following array data, assuming the scores is stored at 0x0498760:

variable address value
scores[0] 0498760 1
scores[1] 0498764 2
scores[2] 0498768 3
scores[3] 049876C 4
scores[4] 0498770 5

In C/C++ the initialization is performed at compile time. So, when the C/C++ program is started no time is spent on assigning values to the array elements.
This in contrast with (classic) BASIC. The only method of initializing an array is by explicitly assigning each value to the array elements. Often, the values are stored in a DATA statement and then assigned using READ in a For-Next loop. To accomplish the same in BASIC you will often see the following code.

Dim scores(4) As Int, i As Int
For i = 0 To 4
  Read scores(i)
Next
Data 1,2,3,4,5

At compile time, the compiler doesn't know the data type of the Data-items. This is determined by the datatype of the variable used in the READ command and known at runtime only. After compiling the values in the Data line are stored as ASCII strings. The Read command converts the ASCII "1" to a 32-bit integer (because scores() is declared as an Int). After READ completes the conversion of the first value, it increments the data-pointer _Data. The next Read statement converts the next string ("2") using an ASCII-to-Int function. (The runtime function used is READINT() ). A rather clumsy, inelegant, and (run)time consuming process. Fortunately, GFA-BASIC 32 provides a faster way to initialize an array, thereby skipping the ASCII-TO-INT process for each element. BASIC doesn't allow array access through pointers, so the dynamic allocation remains.

The Array command
To initialize an array with literal constants GFA-BASIC 32 offers the Array command. The Array command takes a string with data in binary format to initialize the elements.

Array scores() =  Mki$(1, 2, 3, 4, 5)

The string is nothing more than a container for the data; a piece of memory containing the values in their binary form. And, most importantly, the string is created at compile-time. The string data is stored in their binary 32-bit format (Mki()) and takes 5 * 4 bytes = 20 bytes in your program's code. The storage is the same as with an initialized C/C++ array. The Array command uses the length of the string to calculate the number of elements to create a one-dimension array. It then copies the data to the array. This process is about 6-8 times faster than the For-Next loop method. 

20 July 2010

Unsigned to signed Byte data type

GFA-BASIC 32 provides two unsigned integer data types, the Card and Byte data type. The Card is a 16-bits integer and allows you to store positive integral values in the range from 0 to 65535. The Byte is 8-bits data type allowing you store a positive value from 0 to 255.

It is not important how many positive numbers the data type can hold. More importantly is the way how these values are handled in case of mathematic operations. The default operation GFA-BASIC 32 allows on integers is signed arithmetic, because all other integer data types are signed types. However, when you include a value stored in a Card or Byte GFA-BASIC expands the value to an unsigned 32-bits integer before using it in a calculation. So, when you store 255 in a Byte, the value 255 is used in calculations.

Now, suppose you need to use a Byte to store values like: –1, 0, 1. (The GFA-BASIC 32 editor uses these values to store line indenting information and I used it to implement block folding.) You can simply store these values into a Byte and in case of –1 all bits of the byte are set resulting in $FF (=255).

Dim b As Byte = -1  ' b = 255 ($FF)

When the Byte is used in a calculation, the value 255 is applied, which is not what we want. We need the Byte to behave as a signed value; $FF must be interpreted as –1. For this to happen we must GFA-BASIC 32 tell to interpret the Byte as a signed byte explicitly using the Sbyte() function. The Sbyte(b) function forces a conversion to a signed expansion to 32-bits.

Dim b As Byte = -1  ' b = 255 ($FF)
i% = b           ' i% = 255
i% = Sbyte(b)    ' i% = -1

In fact, the compiler uses a different assembler instruction to move the value from memory to the eax register (movzx eax, mem).

02 July 2010

Accessing a C array

A C-compiler treats arrays as pointers. An array-variable points to the first element. C computes the address of an array  element by multiplying the element index with the size of the data type. This is
automatic behavior of C. A pointer is an 32-bits integer holding an address pointing to a specific type. For instance, the next declaration defines a pointer to an array of 16-bit integers. To be effective it needs to be assigned an address first.

short* parr;
parr = 0x0180980;

The variable parr references the Short (int16) at the given address. To read the value at the address:

short value;
value = *parr;

Incrementing the pointer with one will let the pointer reference the next element, not the next memory location! E.g. parr++ will result in 0x0180982 (+2). Pointer arithmetic includes the size of data type.

parr++; 
value = *parr;
  
Accessing the fifth element in the array:

parr += 5; value = *parr;
value = *(parr + 5) ;      // even shorter
  
Almost the same can be simulated in GFA-BASIC 32 using the Pointer-type. Except GFA-BASIC does not use the size of the data type into pointer arithmetic. A Pointer variable defines the type of the variable without an address, like C.

Local parr As Register Pointer Short
Pointer(parr) = 0x0180980
Local Short value 
value = parr

To obtain the value from the address GFA-BASIC 32 doesn't provide an indirection operator as C does, as in value = *parr. In GFA-BASIC 32 you must first assign the pointer variable an address using Pointer(parr)= addr. Once the pointer variable is assigned an address, it can be used as a normal variable. Note that Pointer()= performs an kind of implicit VarPtr(parr) = addr, because a pointer variables VarPtr == Null.
Incrementing the pointer to point to the next element requires to set the variable's VarPtr to the next address, in this case plus 2 (SizeOf(Short)==2). So, first give the variable a new address and than access the address using normal variable syntax.

Pointer(parr) = Pointer(parr) + SizeOf(parr)
value = parr

The next sample shows how to iterate over a C- array and how to access an element by index.

Dim C_str As String = "Hello"

' Iterating a C-array
Dim parr As Pointer Byte
Pointer(parr) = V:C_str
Trace Hex(Pointer(parr))
While parr <> 0
  Debug Chr(parr);
  Pointer(parr) = Pointer(parr) + SizeOf(parr)
EndWhile
Debug
Trace Hex(Pointer(parr))

' C-array using index
Pointer(parr) = V:C_str + 4 * SizeOf(parr)
Trace Chr(parr)

The code uses the Debug Window and shows the begin and ending address of the variable parr. The Debug commands show the byte (in character format) the parr Byte variable is addressing.

27 June 2010

A GFA-BASIC32 Forum

I'm joining a forum now.

http://gb32.proboards.com/

The administrator Detlef Peters AKA 'Joshy' convinced me of the relevance of this forum. He has created plug-ins, DLLs, libraries to be used with Basic4GL, but he thinks this language is dead and no longer supported. He also thinks that quite some Basic4GL users might switch to GFA-BASIC 32. They are welcome and might need technical support. Joshy seems knowledgeable and serious, and therefore I decided to join, although I don't like forums, I prefer mailing lists, but what the heck I'll give it a try.

I will check in as much as possible, but will limit myself to answering GFA-BASIC 32 related questions and problems. General Windows programming issues should be solved by studying (VB) books and other samples. For me, it is no longer opportune to answer questions like "what is the difference between ByVal and ByRef?" The internet is full with good explanations. I will go into questions of why you should use ByRef explicitly in non-event Sub subroutines

13 June 2010

Exe without runtime?

One of the questions I got is:

"There is a possibility that my program e.g. "name.g32" run as "name.exe" after compiling without using "GfaWin32.Ocx" ?
If yes, can you give me instructions for this please ?

At the first beta introduction of GFA-BASIC 32 back in 1998 we asked the same question. Why wouldn't it be possible to statically link the necessary code into the EXE? That would give us a slightly larger EXE, but the result would be one file. There are two reasons why this isn't possible.

  1. The entire interface of a GFA-BASIC 32 program is encapsulated in to COM objects. All COM objects are related to each other and thus the stand-alone EXE would increase with about 500 KBytes. GFA simply choose to put the code in a single (in contrast with VB!) OCX runtime. Putting OLE/COM stuff in a DLL is common to all programming languages.
  2. The Windows OS is capable of sharing one instance of a DLL with multiple EXEs. Not only your EXE depends on the runtime, but the IDE does as well. All instances of your programs and all instances of GFA-BASIC 32 IDEs share this one DLL. 

Mailinglist off-line

It seems the gfa@liebenstein.de mailinglist is currently offline. I hope this will be fixed soon, otherwise we will have to look for an alternative. For the moment, if you have urgent questions you can ask me at gfabasic32@gmail.com.

10 June 2010

New 'old-GFABASIC 32- samples'

Harald Bonsel posted me the following:

"I found an old directory of GFA ftp://ftp.gfa.net/pub/gfa/

. May be you out the link on your  Google page?"

It turned out that the old site contains some never published samples. so check it out.

 

Be a game-programmer!

I've ported MiniB3d (a library, originally developed for BlitzMax, http://blitzbasic.com/ ) to a DLL that can be used in FreeBasic, or GFA Basic.

Another FreeBasic user, named super-castle, made include files to use it in GFA basic.
http://www.freebasic.net/forum/viewtopic.php?t=15794
http://www.freebasic.net/forum/viewtopic.php?t=15409

MiniB3D features:
- simple access to OpenGL graphic.
- primitives like CreateCube, CreateCone, CreateSphere...
- animated models
- collisions
- quaternion-based rotations (very useful for flight simulation)
- LOD terrains
- and much more.

I hope that you may find it useful.
Bye!
Angelo Rosina
angros47@yahoo.it;

06 June 2010

An introduction by Troy H. Cheek

On Apr 13, 2009, Troy H.Cheek wrote on his blog about GFA-BASIC: "While there are websites just chock full of VB, QB, and even GFA programming goodness, most of them seem to assume a level of knowledge that your average enthusiast (or even an above average one such as myself) just doesn't have yet. It's all well and good to fill a GFA-BASIC 32 Stuff blog full of articles about "ANSI API functions and RichEdit" and "GetFirstVisible method not implemented", but I'll be the first to admit that I'm not sure what any of that stuff means. Heck, much of "Pointer and Gfa_Var object" was over my head. I guess what I'm asking is this: Where are the websites for down and dirty little GFA BASIC programs? You know, the type that the average man on the street (well, geek in the lab) is actually going to write?"

Well, Troy wrote a series on one: "Roll Your Own GFA BASIC Program 1"

Swap two characters

I am a bad typist. I often hit the wrong keys on my keyboard. I never learned to type using 10 fingers, I only use one finger a time. The faster I type the more errors I make. It turns out however, that two kind of errors show up most frequent.
The first error concerns hitting the wrong key. Most often I hit the key next to the key I want to press. I have to go back to the position have to delete the character and retype the correct letter. I cannot speed up this kind of restoring.
The second typing error I often make is hitting keys in an incorrect order. An incorrectly spelled keyword in GFA-BASIC 32 marks the source code line in "error-state" after hitting [Enter]. That is the moment I realize I mistyped something. For instance:
Locla Bool fCommentLine
This kind of error is very easy to correct. Put the caret between the l and the a and then hit Ctrl-T. This swaps the two characters at either side of the caret.

30 May 2010

Initializing Variables

What value does a variable have when you first declare it, and what happens if you try to read it before you initialize it? And how do you get an initial value into a variable? 

Ocx initialization
Take a look at control and form properties. You initialize them as much as possible at design time in the Properties window. In contrast with VB, GFA-BASIC 32 needs extra code to initialize the item properties of every Ocx that supports a Item collection (ToolBar.Buttons; Status.Panels; etc). Initialization is so important to control and form properties that GFA-BASIC should be extended with more property pages to initialize items at design time. For example, we could use a property box to initialize the strings of a ListBox control at design time, a task you have to perform at run time now.

Initializing variables in general
In some languages, uninitialized variables have a semi-random value. In C, for example, local variables (but not global or static variables) are undefined. If you want an initial value, you must give it. Fortunately for C programmers, this is easy to do. An undefined variable is a disaster waiting to happen, and careful C coders initialize their variables as close to declarations as possible. In contrast, GFA-BASIC 32 always initializes all variables whenever they are declared. String variables are initialized to a "Null-String", numeric variables are initialized to 0, Variants are initialized to Empty, and object variables are initialized to Nothing.
This difference fits the philosophies of C and Basic. C doesn’t initialize variables to a default because local variables must be initialized at run time. This has a cost, and C doesn’t do any run-time work unless you ask for it. Undefined variables are dangerous, but that’s your problem. GFABASIC 32 is more concerned with safety. If you declare an array of 5000 Integers, GFA-BASIC 32 will initialize them all to 0 even if it takes extra run-time work to do so.
However, 0 or Empty might not be the initial value the program needs. In C, you can combine the declaration of a variable with its initialization:

int cLastWindow = 20;


Even arrays can combine a declaration and initialization. 

Fortunately, GFA-BASIC 32 allows declaration and initialization in one statement:

[Global] Dim iCountMax As Integer = 23


This usually works fine, and by using the Global statement global variables can be declared in any procedure. GFA-BASSIC 32 doesn't provide a 'Declarations section at the top' like VB, so we must take care when and where we declare global variables. The problem is that initialization-while-declaring requires executable code, the default value must be copied to the variable's memory location.

You need to find some logical place to put the declaration & initialization. That place must be reached only once—either when the program is executed or the first time the variable is accessed.
Global vs Static Variables
A Static variable is a global variable as well. The difference is that a global variable is visible to all parts of your program, while static variables have an implicit Local clause in front of it. They are only accessible inside a procedure. (Note - A Static variable in the main part of the program is local to the Main section only.) The Static variable can be initialized in a combination with a declaration:

Static fFirstTime As Boolean = True

A Static variable is only initialized once the first time the subroutine is executed. Would you use a global variable declaration & initialization inside a subroutine, the global variable would be initialized over and over.

16 May 2010

Virtual-key code to ANSI character

The Ocx_KeyDown event and the function Gfa_KeyGet (GFA-BASIC editor extension) provide a virtual-key code representing the key pressed. However, it doesn't take the shift-state into account. (The Screen_PreView event sub passes WM_KEYDOWN messages as well.)
In all cases you want to process a WM_KEYDOWN message the ANSI character isn't known. To convert a virtual-key code plus a shift state to an ANSI character you need ToAscii() API. This function takes the virtual-key code and the keyboard state and translates it to an ANSI character. I use it as follows:

' Press shift-key than click mouse
Do :   Sleep : Until MouseK
Trace Chr(VkKeyToAscii(65))
Trace Chr(VkKeyToAscii(Asc("8"))) ' –> *

Function VkKeyToAscii(keycode As Int) As Int
  Dim sb As String * 4

  Static Dim keyboardState(256) As Byte
  ~GetKeyboardState(ArrayAddr(keyboardState()))

  If ToAscii(keycode, 0, ArrayAddr(keyboardState()), sb, 0) == 1
    Return Asc(sb)
  Else
    Return 0
  EndIf

EndFunc

13 May 2010

DefMouse

DefMouse is an hold over from previous GFA-BASIC versions, but is still – with a little twist - supported in GFA-BASIC 32. In GFA-BASIC 16 bit DefMouse was used to set the mouse shape for the entire screen. DefMouse changed the mouse globally, for each GB window and control, as well as for the desktop. With GB32 the mouse-cursor shape is set for each OCX object individually, which is much more flexible. Now, the general way of setting a mouse-cursor is by setting the control's MousePointer, MouseCursor, or MouseIcon property, either at design-time or at runtime.

DefMouse 0 is the secret
GFA-BASIC 32 tries to be as compatible as can be with previous versions, so the DefMouse command had to be implemented as well. It works, however the region is now limited to GFA-BASIC windows (and controls) only. An improvement, wouldn't you agree? How does that work? How does GFABASIC prevent corrupting mouse pointers? The solution to the problem is the DefMouse value. When DefMouse == 0 (or basDefault) the individual mouse settings of the Forms and controls are used. However, when DefMouse n (where n <>0) is used to select a new mouse shape the individual OCX mouse settings are ignored. On the plus side, you are guaranteed with the new mouse shape over every(!) OCX control/form. To revert to the individual settings you must invoke DefMouse 0.

A Popup window
A Popup selection menu is a system window executed in the context of the GFA-BASIC 32 application. It isn't an Ocx object (it should have been!) and has no private mouse settings. Especially when a Popup is used from within a TrayIcon Ocx GFA-BASIC 32 has to revert to the DefMouse setting. However when DefMouse = 0 GFA-BASIC does nothing, because it expects to handle the individual Ocx mouse settings. The WM_SETCURSOR message isn't handled properly an a random cursor value is used to tell the system (Windows) which cursor to use. The mouse cursor is drawn incorrectly when showing the popup menu. Consequently, the global mouse must be set to a non-zero value before invoking Popup, for instance DefMouse basArrow. When Popup returns the global mouse setting must revert to zero: DefMouse 0.

Screen Ocx
The Screen Ocx supports the MousePointer, MouseCursor, or MouseIcon properties as well. These operate exactly the same as DefMouse, eg on the global mouse setting. Thus DefMouse can be replaced by Screen.MousePointer = n, where n is a basXX mouse value.
The DefMouse command can also be passed a string containing a bit-pattern to create a custom (monochrome) mouse.
The Screen.MouseCursor on the other hand accepts a MouseCursor object (a real Windows cursor resource in an external file). In addition, Screen.MouseIcon accepts a Picture object. So, there many different options to create a custom – global – mouse shape.

24 April 2010

Replace a Toolbar's button image

Back in 2000 a bug report was posted describing how GFA-BASIC 32 failed to properly remove a Toolbar Ocx button. I say properly, because the button was removed, but the button count remained the same. Now in 2010 another bug was reported. The button's image couldn't be easily replaced, because the button's property .Image isn't implemented. So, the Ocx Toolbar has some flaws ....

To address the new bug, we need to find a work-around. This is easy, we must invoke Toolbar.Remove index to remove the button and than Add it again with the new image index. But for this to work properly we also need to know why Toolbar.Remove index failed in the first place. Well, the cause for the failure was quickly found; the .Remove method failed because the button wasn't assigned a key. The Toolbar's Add method takes the following (optional) arguments. Each argument is a Variant, GFA-BASIC converts the argument to a proper data-type internally.

ToolBar.Add index, key, caption, style, image

index - defines the position of the button.
key – specifies a character string used to store and retrieve the button by name.
caption – defines the string to display next to the button.
style – specifies the type of button.
image – is a key specifying the image to use from the ImageList control.

ToolBar.Remove index requires the index (== position) of the button to remove. Subsequently, most of us don't specify a key for the button, we don't need it later, do we?
Well, as a matter of fact, GFA-BASIC 32 uses the key to remove the button from the underlying Buttons collection, if it isn't specified it cannot be removed. Despite the fact that Remove requires the index (or position) only, it requires the accompanying key to actually remove. So, add a key argument to ToolBar.Add and you are good.

Now we know how to remove a button we can insert a new one. To accomplish this we must use the index argument of Add to insert a new button with a new image. The sample code looks like this:

' Add button #1
tb.Add 1, "but1" , , 0, 1
' Replace button #1
Tb.Remove 1
Tb.Add 1, "but1" , , 0, 2

26 March 2010

A Dlg_ Form

Any form with a name staring with ‘Dlg_’ follows the dialog rules of GFA-BASIC 32. When a form is created using the Dialog #n command the form automatically gets a name starting with Dlg_0 .. Dlg_31, where 0 <= n <= 31. However, you are not restricted to the Dialog # command to create dialog-forms. Any form, either created at design time using the form-editor or at runtime, can ‘benefit’ from the dialog settings. The only requirement is a name starting with ‘Dlg_’. For instance Dlg_32, Dlg_InputName, Dlg_Msg, etc.

The most obvious change is the adjustment to the DlgBase settings. At the time of creation a form with a name staring with Dlg_ uses the DlgBase settings rather than specified settings. In addition, a Dlg_ form’s keyboard events are handled a bit differently. The PeekEvent command invokes a special GFA-BASIC IsDialogMessage() function to handle the navigation keys (Tab, arrow keys).

I only discovered this recently and didn’t get a chance to figure out how exactly the navigation differs from a normal Form.

Note that in contrast with the remark in the help-file DlgBase Bold has been repaired.

Sleep, DoEvents, PeekEvent, or GetEvent?

In contrast with VB a GB32 application needs a global message loop to retrieve pending messages from the application’s message queue. In GFA-BASIC 32 the message loop is most often placed in the ‘main’ section of the program and often looks like this:
Do
Sleep
Loop Until Me Is Nothing

Me is a Form object variable holding the current active window of the GFA-BASIC 32 program. The Me variable always represents the current active window should a program open multiple windows. Me always represents a window (Form) unless the last window is closed, then Me is equal to Nothing. That’s the moment the Do loop is ends and the program stops.

GFA-BASIC 32 offers 4 different commands to read the message loop and dispatch the message to the appropriate window procedure. Essentially, all these commands utilize PeekMessage(), TranslateMessage(), and DispatchMessage() - like any other message loop under Windows. They only slightly differ which I will discuss here. Let’s start with PeekEvent because it is the heart of all four commands.

PeekEvent invokes the PeekMessage(V:msg,0,0,0,PM_REMOVE) function, which checks a thread message queue for a message and places the message (if any) in the specified structure. If no messages are available, the return value is zero. The PM_REMOVE flag indicates that messages are removed from the queue after processing by PeekMessage. The following GB32 listing shows pseudo-code for the PeekEvent command.

Procedure PeekEvent()
Dim msg As MESSAGE
// Clear MENU() event array
MemZero(MENU(0), 17)
// Obtain message if available
If PeekMessage(V:msg, 0, 0, 0, PM_REMOVE)
// Copy msg members to MENU() array
MENU(2) = msg.pt.x
MENU(3) = msg.pt.y
MENU(16)= msg.time
// Handle key input messages
If msg.message >= WM_KEYFIRST && _
msg.message <= WM_KEYLAST
// Handle keyboard events at
// the application level.
Exit Sub If Screen_KeyPreview(V:msg)
// If Name starts with 'Dlg_' use
// special IsDialogMessage().
If Me.IsDialog Then
IsDlg_XX_Message(Me.hWnd, V:msg)
Else
~TranslateMessage(V:msg)
EndIf
EndIf
// Send to appropriate window proc
~DispatchMessage(V:msg)
EndIf    // PeekMessage()
EndProc

This pseudo code is rather accurate. I omitted the check for reentrance while executing PeekEvent. Only this, once PeekEvent is executing and is dispatching messages, it cannot be reentered again.

- GetEvent is a simple variation on PeekEvent. Like PeekEvent it only retrieves one message a time and then returns to the application, in fact GetEvent invokes PeekEvent as follows:

Procedure GetEvent()
If Not GetQueueStatus(QS_ALLEVENTS) Then _
~WaitMessage()
@PeekEvent()
EndProc

GetEvent waits for a message when - on entrance - no messages are current in the queue. When a message is available it calls PeekEvent to handle the message and then returns to the application.

-DoEvents is a VB compatible command (used seldom in VB because the message loop is hidden and executed automatically in VB programs). In GB32 it is implement as a loop calling PeekEvent until the message queue is empty.

Function DoEvents%()
Dim Count% = 0
While @PeekEvent()
Count% ++
Wend
~Sleep(Count%)
Return Count%
EndFunc

DoEvents yields execution for a small amount of time after retrieving all messages form the queue. It returns the number of messages obtained. The yielding time depends on the number of handled messages.

- Sleep starts with calling DoEvents. When DoEvents returns 0 then Sleep goes in a wait state and waits for new messages and when they arrive it executes DoEvents again.

Procedure Sleep()
If @DoEvents%() = 0 Then
~WaitMessage()
@DoEvents%()
EndIf
EndProc

Sleep is the preferred command for the main message loop of course.

12 February 2010

Kind messages

Every week I’m happily surprised with a kind message of someone stating his fun with GFA-BASIC 32. Some excerpts:

From a Dutch user:

“Dear Frank, I started my own company in 1982 in plastic things.
After a few years I bought a Atari 520. Later a 1024 and a 20 Mb!! harddisk.
I was interrested and bought a Bookkeeper software thing.
That thing was very slow, so I decided to write my own program.
GFA was the option and what kind of an option !!
I bought the manuals and compiler and didn't sleep many nights because I was concentraded, involved and full power.
I had to develop windows, cursor behavior, and I made myself because of all the stuff a professional bookkeeper.
I've used this program for many years and friends with a own company also, because it was able to do the depreciations automatically.
The taxman never had problems with it. It was clear and understandable.
I've used it until 2001 (with Gemulator) and after that I've sold my company.
Now there are such programs with the same functions of 60Mb, mine (yours) was 93K !!!!
Love you, Henk Reinders (Holland)”
From a German user:

“Hi Sjouke,

I am still alive, no time, hard working. I am now 62 years old and may gain more time for my hobbys in a few years. I started to built up a small homepage for private use only. By the way, I will send you some stuff, step by step, from earlier programming. The prog was coded by my son on a Atari 800XL, we had a lot of fun together with this machine. I presented the gfa32 version to my son on one of his birthdays. It has been very easy to transform the code. All the best for you and all gfa-fans in the future.”

Do you want to share something with the GFA-BASIC community? Leave a comment!

07 February 2010

GFA-BASIC Update Build 1166

The internal linker error caused a "File write error" when creating a stand-alone EXE or a Gfa Editor Extension GLL. The bug occured when one or more $Libraries are linked to the compiled code of the program currently editted. When none of the Lg32 libraries contained subroutines (Sub, Proc, or Func) the linker misses a pointer and reads from address NULL. The entire process of creating the output file is guarded in a Try-Catch exception handler that only reports a "File write error in filename". It doesn't specify what went wrong.
Anyway, this bug is solved in Build 1166, which you can download at http://gfabasic32.googlepages.com/update

03 February 2010

Debug.Print to OutputDebugString

To me the Debug Output Window is the most valuable tool of GFA-BASIC 32. The Debug Output Window is controlled (wrapped) by the COM interface IDebug. I always have the debug output window open just below the main window of the IDE.

There is only one instance of the Debug object (just like Printer, Me, Win_, Dlg_, Error, App, Screen, ClipBoard, Forms, and Code4). It is not up to you to create a Debug object. As soon as you use one of the properties or methods of the Debug object, the GfaWin23.Ocx runtime checks for its existence and creates the thread global object for you.

Both Trace and Print display text in the EDITTEXT control that occupies the client area of the Debug Output Window. The central routine responsible simply sends the WM_SETTEXT message to the control. The subclass procedure of this edit control checks for an overrun of the text limit.

In addition, the central text output routine invokes the OutputDebugString() API with the same text string. The OutputDebugString function sends a string to the debugger for the current application. However, GFA-BASIC 32 doesn’t have a Win32 application debugger as meant in this context. Therefore, if the application has no debugger, the system debugger displays the string. If the application has no debugger and the system debugger is not active, OutputDebugString does nothing.

However, when you are debugging GFA-BASIC 32 in a Win32 debugger (OllyDbg, IDA Pro Disassembler, etc) you’ll see the Debug.Print text in their output windows as well.

19 January 2010

ListView Subitems Custom Draw

Just before Christmas 2009 Peter Heinzig mailed me a copy of his "listView_CellColor" program. He was experimenting with Custom Draw for common controls to provide cell coloring for subitems in a ListView in report-mode. To be honest, I never used custom draw, so I had some catching up to do. After reading the SDK I checked the GB32 disassembly to check if GB32 uses custom draw. Bingo! It does use it with the ListView Ocx control to implement the ListItem's properties ForeColor, BackColor, and Font. Anyone trying to implement custom draw for a ListItem object could get in problems with GB32's implementation, so it is time to implement a custom draw routine for ListView controls taking the GB32 implementation into account.

We step right into the drawing stage of custom draw Ocx controls. I discussed the message handling of custom draw for Ocx controls in a previous entry Custom Draw for Ocx controls. Note this is GFABASIC 32 specific implementation, other languages might use a different method.

Using the correct Ocx type
We left off with the function that invokes a specific subroutine when the NM_CUSTOMDRAW message has come from a ListView, TreeView, ToolBar, or Slider Ocx control. This function, which I called OcxCustomDraw() to emphasize we are processing GB32-Ocx specific custom drawing, passes the general Control variable as the first parameter, by value. In the process of passing, the type of COM variable is changed to the required COM interface.
Note - In VB/GB32 the interface name is the name of a COM object without the leading I. Here we have an Control variable, which is a pointer (4-bytes) addressing the memory occupying a structure for an Control type. When we pass the variable by value, we put the contents of the Control variable on the stack and interpret it in the called subroutine as an address of a (I)ListView type (interface ). Any actions performed on the local Ocx variable will instruct the compiler to use COM syntax for the type (I)ListView. Confusing? Probably worth a new blog entry ...

The drawing process
I'll be short about interpreting the NM_CUSTOMDRAW draw message data passed in the lParam argument. You'll find it in the SDK or MSDN. First we need to cast the pointer in lParam to the custom draw structure for a ListView control.

' ListView custom drawing
Function CustomDrawListView(Lv As ListView _
, lParam%, ByRef retval%, ByRef ValidRet?) As Bool
Dim nmcdr As Pointer NMLVCUSTOMDRAW
Pointer nmcdr = lParam

Now we can access the structure-members using the dot-operator syntax. For a ListView's subitem to draw, we must respond to three draw stages; CDDS_PREPAINT, CDDS_ITEMPREPAINT, and CDDS_ITEMPREPAINT | CDDS_SUBITEM. That is it.

1 CDDS_PREPAINT
Let us start with CDDS_PREPAINT. This notification is sent at the beginning of the drawing of the row. Each row sends it once only. You must response with the correct value to specify what you want next. Well, both GB32 as ourselves want more (sub)item draw messages, so we must respond with CDRF_NOTIFYITEMDRAW. Since GB32 already returns this value, our custom draw function may skip this message (although it wouldn't hurt if you didn't).

Switch nmcdr.nmcd.dwDrawStage
Case CDDS_PREPAINT
'retval = CDRF_NOTIFYITEMDRAW
'ValidRet? = True
Return True

We return from this function with True to let the Form's event sub _MessageProc() know that we handled this message and that it can leave the event sub without further processing.

2 CDDS_ITEMPREPAINT
The next drawing stage is CDDS_ITEMPREPAINT. When GB32 receives this message it fills in the appropriate NMLVCUSTOMDRAW  members on return. The common control library than uses the selected colors and fonts for drawing. After it processes this message, GB32 returns with a value indicating it is ready with this list item (row). We trap this message before GB32 handles it an specify that we want additional custom draw messages for each subitem of the row.

Case CDDS_ITEMPREPAINT
retval = CDRF_NOTIFYSUBITEMDRAW
ValidRet? = True
Return True

Because CDDS_ITEMPREPAINT returned CDRF_NOTIFYSUBITEM the common library will start sending notification messages, in the form CDDS_ITEMPREPAINT,  before it starts drawing each of the subitems.

3 CDDS_ITEMPREPAINT + CDDS_SUBITEM
To indicate subitem drawing the control library adds CDDS_SUBITEM to the value. So, in the next stage we must process, we must fill in the members of NMLVCUSTOMDRAW that the control library will use to draw. More precisely, we can fill in the .clrText and .clrTextBk fields for the colors and select a new font in the hDC. This is exactly the behavior of GB32. So, we will let GB32 process the message first.

Case CDDS_ITEMPREPAINT | CDDS_SUBITEM
' Set back to the ListView control default colors for each subitem.
nmcdr.clrText   = CLR_NONE
nmcdr.clrTextBk = CLR_NONE

  ' Let GB32 copy the ListItem properties of the row (color _and_ font)
' Go through the ListView window procedure, so forward the WM_NOTIFY message
' (as OCM_NOTIFY) to the ListView's window procedure.
nmcdr.nmcd.dwDrawStage = CDDS_ITEMPREPAINT
retval = SendMessage(Lv.hWnd, WM_NOTIFY + $2000, 0, *nmcdr)
ValidRet? = True

  ' Finally, we get a chance to overrule the color settings:
Local Int Row = nmcdr.nmcd.dwItemSpec + 1   ' 0 to 1 -based
Local Int Column = nmcdr.iSubItem + 1       ' 0 to 1 -based
If lv4_Colors(Row, Column).Fore <> CLR_NONE
nmcdr.clrText = lv4_Colors(Row, Column).Fore
EndIf
If lv4_Colors(Row, Column).Back <> CLR_NONE
nmcdr.clrTextBk = lv4_Colors(Row, Column).Back
EndIf

Return True     ' Notify the Form_MessageProc
EndSwitch  ' dwDrawStage

After overruling the colors the subitem is drawn. Unfortunately, the function uses a global array named lv4_Colors() of type SubItemColors. The following is from the frm1_Load() sub event, lv4 is the Ocx ListView in report mode.

Type SubItemColors
- Int Fore, Back
EndType
Local Int Rows = lv4.ListItems.Count
Local Int Columns = lv4.ColumnHeaders.Count

Global lv4_Colors(Rows, Columns) As SubItemColors

' The array MUST be initialized with the default ListView system colors CLR_NONE (= -1).
' Whenever a subitem cell color must be reset to the default value,
' simply set the Fore and Back members to CLR_NONE.
MemLFill ArrayAddr(lv4_Colors()), ArraySize(lv4_Colors()), CLR_NONE

' Cell (1,1) is left-top-most cell
lv4_Colors(1, 1).Fore = $23FFFF
lv4_Colors(1, 1).Back = $777777
lv4_Colors(2, 1).Fore = $FF
lv4_Colors(2, 1).Back = RGB(0, 255, 0)

The CLR_NONE value indicates the use of the ListView default colors. It is the clue to all drawing.

The integration in GB32 custom drawing might be complicated. If so ask me.

13 January 2010

Swap Arrays by ArrPtr()

Didn't GFABASIC for Atari ST support swapping (or passing) arrays by pointer? I picked up my copy of the book 'GFABASIC' by Frank Ostrowski (1987) to check if I remembered correctly. Indeed, some kind of swapping by pointer is supported. The QuickSort program FO presented on page 22 passes a string array to the sort routine by using *a$(), which is equal to ArrPtr(a$()). To access the array locally in the procedure, the array descriptor passed is swapped with a the descriptor of array a$(). Given a pointer to an array descriptor the Atari ST Swap command supported the following syntax:

Swap *ardescriptor%, a$()   ! Atari ST GFABASIC

Unfortunately, the Swap command of GB32 isn't that flexible. The arguments of the Swap command need to be of the same kind. For instance:

Swap a%(), b%()
Swap f#, g#
Swap udt1, udt2

Even a user-defined type can be swapped! Swap works well as long as the data type of both operands is the same. But I have a need to store a pointer (Long Integer) to an array of data and later on use that pointer to access the array's data. So, I want to store an ArrPtr() and swap that pointer with an (empty) array. For instance:

Swap arrdesc%, d()    // not supported

After disassembling the code GB32 generates for Swap a(), b() it is easy to see, that GB32 implements the array swap by passing the pointers of the descriptors of these arrays. Internally, a Swap a(), b() is actually a Swap ArrPtr(a()), ArrPtr(b()).

Also, the disassembly reveals the Asm scall name for the library function. Strangely, the name is pgMinBottom, obviously an error in the naming. It is now easy to create a custom routine to swap two arrays by descriptor.

Proc ArrPtrSwap(ArrPtr1%, ArrPtr2%)
  . push [ArrPtr1%], [ArrPtr2%]
  . scall pgMinBottom   ' GB32 naming error
EndProc

Given some arrays, the procedure might be invoked like this:

Dim g%(2), a%(5)
Dim ptrToa% = *a()
Dim ptrTog% = ArrPtr(g())

ArrPtrSwap(ptrToa%, *g%())
ArrPtrSwap *a%(), *g%()
ArrPtrSwap *a%(), ptrTog%

Now you can initialize an array, store a pointer to it in the .WhatsThisHelpID property of an OCX control, and use it later on by swapping it with an empty array. I now can finally conclude my series on custom drawing of single SubItems of a ListView.Listitem object.

11 January 2010

The .Tag property

In COM all strings are UNOCODE BSTRs. GB32 converts a string to UNICODE before it calls the set_Tag() function. When the property .Tag is set with a literal string constant, the compiler optimizes string assignment by converting the literal string to an UNICODE string before hand. For instance, the assignment Ocx.Tag = "Hello" is stored as:

Ocx.Tag = "H" #0 "e" #0 "l" #0 "l" #0 "0" #0 #0

When the .Tag property assignment is actually executed during runtime, the UNICODE string is passed to the set_Tag() function of the Ocx/COM object, and the BSTR is stored with the COM object for later access. But something is breaking the process ...

GB32 stores ANSI code
The rationale behind GB32's implementation of UNICODE vs. ANSI isn't very clear. IMHO the most simple, effective, and fastest procedure would be to store the pointer to the UNICODE string as it is passed to the Tag property. But GB32 converts it (back) to ANSI and stores a pointer to the ANSI-string memory. Of course, GB32 strings might be as large as physical memory and storing a UNICODE string doubles the memory necessary. But that couldn't be the reason for this behavior, could it?

Anyway, so far so good. It is a fact of life. If you like you can store binary data in a string and  assign it to a .Tag property. But can you get it out of the Tag property? The answer is No! When the BSTR passed to the set_Tag() function is copied to ANSI, GB32 only stores the pointer to the converted ANSI string (a char* in C/C++). It doesn't save the length of the passed BSTR. So, how does GB32 know how many characters to return from get_Tag()? It doesn't and simply invokes a call to the C-library function strlen(char*), which returns at first occurrence of '\0'.

Binary data cannot be stored in a .Tag property.

08 January 2010

The OCX() return type

What exactly does the OCX() function return? It got me thinking when I was developing the custom draw program. Since GB32 seems to name all COM objects Ocx objects, I expected it to return a general Object type (which isn't exactly wrong). But it doesn't return an Object type. Remember that a DisAsm or Collection COM object aren't created using the Ocx command, but using a New clause in the Dim command. These are not Ocx objects; Ocx objects are COM wrappers for controls only, that is anything with a window handle. Consequently, strictly spoken, the OCX() function returns a Control object.

Any COM type can be cast back to an Object. The only actions performed on an Object are late bound properties and methods. An Object is an IDispatch interface only.
Next in hierarchy is a Control object. It embeds the IUnknown and IDispatch interfaces, but it also includes data for GB32 to maintain the windows/controls. Every Ocx ( = control) includes (embeds) a Control object. Since there is no interface type-info for a Control object (use OleView.exe to inspect COM types), the properties and methods are invoked through IDispatch, like with the Object type.
Finally, there is an interface definition for an Ocx control. The Ocx object is based on the Control object. In addition, each Ocx object has its own control specific data. A Control object is an OCX without the specific control information and without an interface.

Object -> Control -> Ocx
Control = OCX(hWnd)

As you can see, there is nothing wrong to store the return value of OCX() in an Object variable, but strictly speaking it should be a Control type variable.

For a non-Ocx COM object the hierarchy is

Object -> GB32 COM Object type (Collection, DisAsm, etc).

For these types there is no OCX() function (because it depends on a window handle) and should not be cast to a Control object.

06 January 2010

Custom Drawing Ocx Controls

Together with the TreeView common control, the ListView common control is one of the most important control used to present organized data. Since displaying data in columns is probably the most used features of the ListView control, I will discuss how to implement subitem coloring of cells in a report-view mode ListView Ocx control. In this installment I will discuss the necessary steps to start custom drawing a GB32 Ocx control. The ListView cell coloring is discussed in another entry.
Custom drawing
The common control library supports a technique called 'custom draw' to change the appearance of the elements of some of the controls. The custom draw technique was developed to eliminate the need to fully owner-draw entire controls. Custom draw allows trapping only a small set of the drawing operations, rather than perform all the drawing.
The common controls send custom draw messages as a WM_NOTIFY messages to the parent window. The WM_NOTIFY message specifies  a pointer to a NMHDR structure in the lParam argument. The NMHDR structure specifies the (notification) code, the window handle and ID of the control from which the message originates.
Type NMHDR
  hwndFrom As Long
  idfrom As Long
  code As Long
End Type
The _MessageProc event
In GB32 a parent-window is usually a Form. The WM_NOTIFY message is a sent message and can only be handled in Form's event sub Form_MessageProc(). The _MessageProc is invoked from inside the Form's window procedure (defined in the GfaWin23.Ocx) before  GB32 processes the message itself. The two additional arguments of the _MessageProc (retval% and ValidRet?) determine whether GB32 will actually continue processing this message in its own window procedure once it returned from _MessageProc, which could look like this:
Sub frm1_MessageProc(hWnd%, Mess%, wParam%, _
 lParam%, retval%, ValidRet?)

  Switch Mess
  Case WM_NOTIFY
    ' use Pointer to cast a lParam to a type!
    Dim nmhdr As Pointer NMHDR
    Pointer nmhdr = lParam
    ' Custom draw message.
    If nmhdr.code == NM_CUSTOMDRAW
      ' (do something)
    EndIf
  EndSwitch
EndSub
Noticeable is the elegant way GB32 allows you to cast a pointer to a structure. I explained Pointers in the past.
It is important to realize that NM_CUSTOMDRAW is sent always, for every common control that supports it. As soon as a common control starts it painting process it starts sending this message. It is up to the developer to tell the common control library to send follow-up messages for each stage in the painting process, or to stop sending notifications after the first one arrived. For performance reasons, the library only sends the message that signals the start of the paint-cycle (CDDS_PREPAINT) and then only sends more messages when you respond with a value "send-me-more".
Custom drawing Ocx controls
What to do once you intercepted the NM_CUSTOMDRAW message is described in the MSDN. However, the documentation always discusses C-style handling and provides sample code in case one control wants custom drawing. What if the control is a GB32 Ocx and multiple Ocxs want custom drawing? The question arises how to differentiate between the various Ocx controls when we process NM_CUSTOMDRAW?
Option #1 is to store the window handles of the Ocx-controls in global variables and compare these in a (possibly long) If-Else statement with nmhdr.hwndFrom. Once you find a match you know what to do. This option is certainly valid, but it is not my intention to show you how to solve this problem the API-way, but to show you the GB32- COM way.
The GB32-COM way
This is option #2. We will use nmhdr.hwndFrom to get an Ocx object and then use the object to perform general custom drawing for that object type. Since GB32 supports COM interfaces for ListView, TreeView, ToolBar, and Slider, not all common controls (Header, ToolTip, and ReBar) that support custom drawing can be handled this way. The ReBar common control is not implemented, unfortunately, and Header and ToolTip controls are part of other controls and are not support on their own. So, we need a function that separates the good from the bad.
Function OcxCustomDraw(hWndFrom%, lParam%, _
  ByRef retval%, ByRef ValidRet?) As Bool
  ' Non Ocx controls are rejected immediately, Only OCX() is invoked.

  Dim Ob As Object
  Set Ob = OCX(hWndFrom)

  If IsNothing(Ob)
    Return False        ' Not an OCX control
ElseIf TypeOf(Ob) Is ListView ' Most often used in custom drawing
    Return CustomDrawListView(Ob, lParam%, retval%, ValidRet?)    ' Big chance we have a hit.
  ElseIf TypeOf(Ob) Is TreeView
    Return CustomDrawTreeView(Ob, lParam%, retval%, ValidRet?)
  ElseIf TypeOf(Ob) Is ToolBar
    Return CustomDrawToolBar(Ob, lParam%, retval%, ValidRet?)
  ElseIf TypeOf(Ob) Is Slider
    Return CustomDrawSlider(Ob, lParam%, retval%, ValidRet?)
  EndIf

  ' Not a custom draw Ocx control, 
' continue processing
  ' NM_CUSTOMDRAW in Form_MessageProc()
  Return False
EndFunc
The window handle in nmhdr.hwndFrom identifies the common control, so we pass this value in the first parameter hwndFrom of  the OcxCustomDraw() function. The second parameter is the lParam value as it is passed to _MessageProc, holding a pointer to NMHDR. The out-parameters retval% and ValidRet? are passed as well, since they must be set once we processed the message. In addition, we make this a Boolean function to signal the caller (_MessageProc()) to continue processing WM_NOTIFY or to exit the event sub immediately. (The _MessageProc should not perform any action when the message is handled in OcxCustomDraw(), but It might do more processing when the hwndFrom passed in isn't an Ocx object or when NM_CUSTOMDRAW originates from a GB32 Ocx that doesn't support custom drawing. In these cases OcxCustomDraw() returns False.)
To check the Ocx type for a given window handle we need a two step process. First, obtain the address of the object that wraps the control using the OCX(hWnd) function. This is a simple addrObject = GetWindowLong(hWnd, GWL_USERDATA) call. When no object is attached to the window handle Ob Is Nothing and we can skip the rest of the function. When the Ob contains a valid Ocx type we test whether it is of type ListView, TreeView, ToolBar, or Slider using the TypeOf(O) Is Interfacetype construction. When True, we execute either one of four Ocx custom draw operations. These functions are defined as follows:
' ListView custom drawing
Function CustomDrawListView(Lv As ListView _
  , lParam%, ByRef retval%, ByRef ValidRet?) As Bool
  Dim nmcdr As Pointer NMLVCUSTOMDRAW
  Pointer nmcdr = lParam
Return False
EndFunc
' TreeView custom drawing
Function CustomDrawTreeView(Treeview As TreeView, _
  lParam As Long, ByRef retval%, ByRef ValidRet?) As Bool
  Dim nmcdr As Pointer NMTVCUSTOMDRAW
  Pointer nmcdr = lParam
  Return False
EndFunc
' ToolBar custom drawing
Function CustomDrawToolBar(Toolbar As ToolBar, _
  lParam As Long, ByRef retval%, ByRef ValidRet?) As Bool
  Dim nmcdr As Pointer NMTBCUSTOMDRAW
  Pointer nmcdr = lParam
  Return False
EndFunc
' Slider custom drawing
Function CustomDrawSlider(Slider As Slider, _
  lParam As Long, ByRef retval%, ByRef ValidRet?) As Bool
  Dim nmcdr As Pointer NMCUSTOMDRAW
  Pointer nmcdr = lParam
  Return False
EndFunc
For a ListView control Ocx the function calls CustomDrawListView(). When invoking the function the general Ob variable of type Object is cast to a ListView type implicitly. Each of these CustomDrawType() functions casts the Object variable Ob to the appropriate COM type. Inside each function the lParam is cast to the correct API custom-draw structure (see SDK).
Note - The types are all defined in the commctrl.inc.lg32 included in the GB32 package. You can either load this library using $Library or copy-paste the relevant Types.
Final note
The TypeOf(O) Is Interfacetype construction is a library function that checks the object against the IID value of InterfaceType. Therefore it calls the QueryInterface() for that type. This decreases performance of course, although the GB32 implementation of Is TypeOf is pretty fast.
Next time I discuss how to color subitems in report ListView.