Showing posts with label Ocx Object. Show all posts
Showing posts with label Ocx Object. Show all posts

18 March 2019

StdFont and StdPicture

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

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

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

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

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

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

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

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

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

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

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

29 November 2017

The initialization of the Printer object

Each time you press F5 to run a program the Printer object is reinitialized. As part of the initialization process the program obtains information about the current default printer to prepare the Printer object for use in your program. The same happens in the start-up process of a compiled stand-alone program. When there is no default printer, or a corrupt printer-driver, the Printer’s properties and methods are undefined. You may use one of the Printer‘s properties .DeviceName, .DriverName, or .Port to obtain information about the actual printer associated with the Printer object. For instance, to check for a valid initialization:

Dim fValidPrinter? = Len(Printer.DeviceName)

When the Printer object needs to be associated with the current default printer at all times, you can compare the device-name against the current default printer returned from App.PrinterName(0). Before the printing process actually starts check for a change and re-initialize the Printer object to the new default printer:

' Before printing
If Printer.DeviceName != App.PrinterName(0) ' change of default?
 SetPrinterByName App.PrinterName(0) ' Re-initialize to default 
EndIf
' Start printing

Note - Keeping track of the current default printer this way will not preserve any changes you made to the Printer object.

Available printers and their info
The App object provides properties that enables you to gather information about all the available printers on the system. For instance, to find a printer that has the Orientation set to portrait, starting with the first non-default printer:

' Iterate over available printers
Local i%, PrtName$
For i = 1 To App.PrinterCount ' index = 1
 PrtName = App.PrinterName(i)
 Debug PrtName : Debug " Info: "; App.PrinterInfo(PrtName, "")
 If App.PrinterInfo(PrtName, "Orientation") == 1
 Debug " > Printer in portrait mode"
 EndIf
 Debug
Next

The current default printer is returned by the App property PrinterName(0), with the index = 0. To retrieve all printer devices iterate over App.PrinterName(i) starting with index 1.

The PrinterInfo property retrieves detailed information of the available printers. PrinterInfo is a small wrapper for the GetPrinter() API using the PRINTER_INFO_2 structure to retrieve the information (see SDK). All member names of PRINTER_INFO_2 structure can be passed as an argument to PrinterInfo(PrinterName, membername$), which returns a Variant with the member’s value. Depending on the member-name, must be one of the below, the Variant contains a string or a Long.

'Server','Printer','Share','Port','Driver','Comment','Location',
'Port','SepFile','PrintProcessor','Datatype','Parameters',
'Attributes','Priority','DefaultPriority','StartTime',
'UntilTime','Status','cJobs','AveragePPM'

In addition, PrinterInfo returns a few DEVMODE settings;

'Copies','Orientation','Duplex','Color',
'PaperLength','PaperWidth','Quality','PaperBin','PaperSize'

Finally, in case you pass an empty string - PrinterInfo(PrinterName,””) – it returns one string combining the settings of 'Server', 'Printer', 'Share', 'Port', 'Driver', 'Comment', 'Location', 'Port', 'SepFile', 'PrintProcessor', 'Datatype', and 'Parameters' as key/value pairs.

Printer Object initializing
By default the Printer object is initialized for the default printer. Using the Printer object lets you retrieve or set the settings of the default printer. Any printer output is based on the Printer object’s settings. To change the printer associated with the Printer object GB provides four commands:

Set Printer = cd ' assign a CommDlg Object
SetPrinterByName "PrinterName" ' specify a device name
SetPrinterHDC hdc ' based on a printer device context
SetPrinterHDNHDM hDevName%, hDevMode% ' pass memory handles

The SetPrinterHDNHDM is at the heart of all printer changing commands. The other commands act basically all the same: they retrieve the the DEVMODE and DEVNAME structures from the input parameter and then call SetPrinterHDNHDM. The DEVMODE and DEVNAME structures provide all the information needed to properly initialize the Printer object. The CommDlg object, for instance, holds a DEVMODE and DEVNAME structure after having invoked the ShowPrint dialog box.

The Printer’s default initialization at start up requires these structures as well, and they are obtained by calling the PrintDlg() common dialog API. The PrintDlg() API function only requires two flags:

  • The PD_RETURNDEFAULT flag is used to retrieve information about the default printer without displaying the Print dialog box.
  • The PD_NOWARNING flag prevents the warning message from being displayed when there is no default printer.

At start-up the PrintDlg() is asked to return information about the current default printer without displaying the dialog box and without displaying a warning if the printer is missing. The existence of the PD_NOWARNING flag suggests it is possible (maybe there are no printers at all).
PrintDlg() may also fail because of a corrupted printer driver, or some other reason causing a delay in execution. It isn’t guaranteed that the PrintDlg() will always succeed. Consequently, it isn’t guaranteed that the Printer object is always properly initialized. When PrintDlg() fails GB initializes the Printer object’s private data to zero. This way the global Printer object is at least ‘not Nothing’, but its behavior is undefined because of the lack of actual device information.

Note - The SetPrinterHDNHDM command is useful when combining API functions and the Printer object. It takes 2 handles (hence HDN and HDM) to unlocked global-memory containing the DEVNAME and DEVMODE structures respectively. The hDevName and hDevMode arguments are handles to moveable global-memory objects allocated using GlobalAlloc(GMEM_MOVEABLE, sizeof(DEVMODE/DEVNAME)). They must be unlocked before passing to SetPrinterHDNHDM.

02 August 2017

StatusBar fix (August 2nd Update)

Fixing one bug often leads to the introduction of another. The June update introduced a bug in the runtime in the Panels.Remove method. This is fixed and the most recent update of the runtime – gfawin23.ocx – is now version 2.32.0.1202. Although I like to step away from incrementing build-numbers, I decided to give it a new build because nothing new was added, only a bug is fixed.

StatusBar redraw problem
During testing I noticed the following. In some circumstances the StatusBar isn’t updated or redrawn to reflect the new situation with a panel removed. This is not GB32 bug, but it is related to the common controls. The StatusBar is only completely reset when it receives a WM_SIZE message. Usually, this message is forwarded by the Form, the parent of the control, when it processes the WM_SIZE message. When the Panel is removed the code does not send or post this message and the Form will never receive the message. The following sample code solves this problem, where sb is the name of the StatusBar Ocx.

' Remove a StatusBar Panel:
sb.Panels.Remove "Panel1" ' WM_SIZE recalculates and updates: PostMessage sb.Parent.hWnd, WM_SIZE, 0, 0

The lacking update and redraw is a known problem for more common controls. Usually this can be solved by posting a WM_SIZE message to the parent (without any values in the wParam and lParam). The preferred solution would be invalidating the control followed by a call to the UpdateWindow() API. However, this doesn’t recalculate and reset the panels. The status bar is simply redrawn using the ‘previous’ or current settings. This makes sense when you think about it. If the status bar would recalculate its contents each time it has to redraw, it would provide a serious performance penalty in redrawing a Form when it is manipulated. The common control needs to be told explicitly to adjust to the new situation.

02 December 2013

Using methods and properties through IDispatch

This is all about two – more or less – undocumented features; the GFA-BASIC _DispID() function and a special dot-curly operator, the .{dispID} syntax.

An object’s layout in memory is, when using a BASIC Object variable data type:

MemObj vtable (array of function pointers)
AddrOf vtable AddrOf QueryInterface()
RefCount% AddrOf AddRef()
…. AddrOf Release()
…. AddrOf GetTypeInfoCount()
… data AddrOf GetTypeInfo()
AddrOf GetIDsOfNames()
AddrOf Invoke()
AddrOf ComPropertyMethod1()
AddrOf ComPropertyMethod2()
…..

After the following commands the BASIC Object variable oIE holds a reference to an instance of Internet Explorer, an automation server and therefor guaranteed to support a dual interface.

Debug.Show
Dim oIE As Object
Set oIE = CreateObject("InternetExplorer.Application")
Trace Hex(Long{V:oIE})
Set oIE = Nothing

An Object variable is actually an Int32 data type and holds the address returned by CreateObject.
Initially, the Object variable is zero (or Null) and is interpreted as Nothing. The following statement does nothing more then checking if the variable oIE holds a value (is not zero):

If oIE Is Nothing Then _
 Debug.Print "No Object assigned"

To view the contents of the integer oIE we first needs its address. In GB32 you can obtain an address of a variable in several ways. You may use VarPtr(oIE), or V:oIE, or *oIE. After obtaining the variable address it must be read, or peeked, to get the contents. Reading a Long or Int32 from some address is done using Long{addr}. The Trace statement uses the Hex function to convert the value to a hexadecimal format, the usual format for a memory address.

From the previous blog post Using OLEVIEW we learned how to process the calling of a property or method through late binding. To summarize, to execute a method or a property of the object an automation client can:

  1. Call GetIDsOfNames to "look up" the DispID for the method or property.
  2. Call Invoke to execute the method or property by using the DispID to index the array of function pointers.

All BASIC languages support this behavior behind the curtains. They provide the object dot-operator to call properties and methods through late binding. For instance, when you want to check to see if Internet Explorer is visible you may call the Visible property:

If oIE.Visible == True Then _
 Debug.Print "Is visible indeed"

After compiling this code the program will execute oIE’s interface-function GetIDsOfNames to "look up" the DispID for the property. It will then generate code to call Invoke to execute the method or property by using the DispID to index the array of function pointers (vtable). Calling Invoke is a bit of a hassle and is best left to the compiler.

Now what when the server gets new functions and new names. Unfortunately, you would need to recompile and redistribute the client application before it would be able to use the new properties and methods. In order to avoid this, you could use a ‘CallByName’ function to pass the new property and method names as strings, without changing the application.

In contrast with other programming languages GFA-BASIC features a function called _DispID(). This function allows you to call the objects GetIDsOfNames function to "look up" the DispID for the method or property. When you remember the blog post on Using OLEVIEW you might have noticed that every property and method is assigned a unique ID (integer value). Using _DispId(0bject,Name$) we can obtain exactly that unique value. For instance, this will display the ID value 402 in the Debug output window.

Trace _DispID(oIE, "Visible")

Obtaining the dispId of a method or property is only useful when you can use the integer value to call the IDispatch function Invoke(). Specifically for this purpose GFA-BASIC 32 provides us with a might piece of equipment; the dot-curly operator. To call Invoke using the dispId you can simply replace the name of the property or method with ‘{dispId}’.

Global Long dispIdVisible
dispIdVisible = _DispID(oIE, "Visible")
Trace oIE.{dispIdVisible}
Trace oIE.{402}

How does VB6 do it?
If you’re interested, you should compare this elegance to the VB6 function CallByName. This function allows you to use a string to specify a property or method at run time. The signature for the CallByName function looks like this:

Result = CallByName(Object, ProcedureName, CallType, Arguments())

The first argument to CallByName takes the name of the object that you want to act upon. The second argument, ProcedureName, takes a string containing the name of the method or property procedure to be invoked. The CallType argument takes a constant representing the type of procedure to invoke: a method (vbMethod), a property let (vbLet), a property get (vbGet), or a property set (vbSet). The final argument is optional, it takes a variant array containing any arguments to the procedure.

Conclusion
Due to the elegant syntax, GB detects how to invoke the method or property. The parameters of a property or method don’t go in a Variant array. Due to the dot-curly syntax the parameters are specified as any other property or method call. The only thing you need to do yourself is retrieving the dispID, but this is of great advantage since now you are able to store the ID to use it over and over. The CallByName() function each time has to obtain dispID for the name passed.

06 October 2013

Debug is a runtime object

Closer examination of the Debug object reveals some interesting facts. Although the GFA-BASIC IDE offers some alternative actions to open the debug window, it is not an object owned by the IDE.

The Debug object is completely embedded (data and methods) in the GfaWin.Ocx, which is GFA-BASIC’s runtime. The Debug object is not dynamically created like other COM objects. You don’t use the Ocx command or a New clause in a Dim statement. The Debug object is a global (static) object located in the data section of the DLL. Other examples of static runtime objects are App, Screen, Printer, Forms, and Err.

Note – Most COM objects are – by nature – dynamically created at runtime. In GFA-BASIC they are either allocated using the Ocx (or OcxOcx) command, or with the New clause in a Dim statement. These commands create and allocate memory on runtime. Like the String datatype, a COM object is always stored and accessed by a pointer. A COM object variable occupies only 32 bits and points to a block of memory allocated from the free store. When the variable contains the value 0 (not pointing to a memory address) the object is said to be Nothing. There are some more functions that implicitly create dynamic COM objects, like LoadForm and LoadPicture. Each COM object created by a GFA-BASIC command is destroyed by GFA-BASIC, whether it is a static object or dynamic object.

The Debug object manages the window
Thee Debug object isn’t allocated at runtime, it is available all times. You can immediately start invoking properties and methods on the Debug object. Although the purpose of the debug output window is to display state information at runtime, the Debug object is mainly used to control or manage the debug output window. The debug output window is a modeless, top level, non-owned window with one child control; a standard EDIT control. Many properties and methods control the appearance and behavior of the debug output window and/or its edit control. For more information of the properties and methods look at the descriptions in the help-file or press a dot after the Debug keyword in the editor. Most of them speak for them selves.

Like your application can use the Debug object, the IDE can use the output window as well. The IDE uses the same static Debug object as your application. Any changes the IDE makes are reflected in your application, and vice versa. For instance, when the IDE hides the debug output window, your application must re-show it explicitly.
The IDE offers you two alternatives to show the output window.

  1. By selecting the IDE menu bar View | Toggle Output Window [Alt+3] at design time.
  2. By selecting the tray icon popup menu item at run-time.

Your application can provide its own means of operation to open the debug output window. The debug window could be used for logging, without actually showing the window.

So, whether your application or the IDE controls the debug output window, they both use the same static Debug object in the runtime. Therefor it is important to know what happens in the background.

The Debug thread
First of all, at the first opportunity – when one of the Debug object’s properties or methods is invoked – a new thread is created. As with all good threads it is not destroyed before the application ends; that is when the DLL runtime unloads from memory. When the GFA-BASIC IDE starts executing the thread isn’t started immediately. Only when your GFA-BASIC application, a GLL editor extension, or one of the IDE commands to show the debug window, are applied the thread is actually started.

The debug thread registers the DebugClass window class and creates an instance of the window class (top-level). Note that the window is displayed in the taskbar. Once the window is created the thread enters an infinite message retrieval loop (inside the thread) and handles the dispatching of messages to the DebugClass window procedure, located in the runtime. The window procedure itself is responsible for responding to the messages of the debug output window. One of the messages the window procedure must handle is the WM_SYSCOMMAND, because the debug window thread adds a system menu item to the window (Always on Top). The window procedure either responds by setting the top-level window to the top of the top-level-windows Z-order (HWND_TOPMOST), or by setting it to the bottom of the Z-order (HWND_BOTTOM), which by the way doesn’t mean that it will disappear immediately.

The position is saved automatically
Another message the thread’s window procedure handles is WM_EXITSIZEMOVE. This saves the (new) position of the ‘DebugClass’ window in the registry. It saves the position as a binary value from a RECT structure. It is saved under the a sub key name of ‘DebPos’ in ‘HKEY_CURRENT_USER/Software/GFA/Basic’. When this key doesn’t exist, it is created. When your customers application uses the debug window, the position will be saved in this registry key of your customers PC. Also note that your customer has the ‘Always on Top’ option available. This setting isn’t stored in the registry. (BTW the window position isn’t saved when the output window is in a minimized or maximized state.)

Give it an owner
I find the debug window - unowned toplevel - completely living on its own quite annoying. I think it should be owned by the main window (and thread) of the application that is using it. In case of GFABASIC this should be the Gfa_hWnd window, in case of a customer application, your main windows handle.

~SetWindowLong(Debug.hWnd, GWL_HWNDPARENT, Gfa_hWnd)
Assert GetWindow(Debug.hWnd, GW_OWNER) == Gfa_hWnd

As an example I added the Assert line. I was a little unsure what exactly setting GWL_HWNDPARENT would do and tested the result for what I expected it to do. Anyway, giving it an owner removes the Debug window from the taskbar and makes it behave like its owner. When the owner (Gfa_hWnd) is minimized, the debug output window is minimized. When the owner is (de-)activated the output window is de-activated.

Give it a different font
The edit control is the one and only child window of the debug output window. It has ID number 1 and its window handle is obtained using:

hEditCtrl = DlgItem(Debug.hWnd, 1)

Since it uses a non mono space font you might want to create your own font using API functions and then assign it to the edit control.

SendMessage hEditCtrl, WM_SETFONT, hFont, MakeWParam(1,0)

25 June 2013

What to do with an OLE_COLOR?

An OCX/ActiveX control color property can be assigned any valid Win32 color reference. However, the OCX/COM libraries provide a special data type for color properties, the OLE_COLOR data type. This new type was invented to give VB-like programs an opportunity to differentiate between a normal Win32 color reference and system colors. These programs could then present a special color-selection dialog box that allowed the selection of system colors. The snapshot of the GB32 Ole-color selection is shown below.

image

RGB colors are of type COLORREF
Like an RGB-color value, an OLE_COLOR data type is internally represented as a 32-bit long integer. An RGB-color is referred to as a COLORREF value and is used with GDI device context functions. A COLORREF is simply defined as a 32-bit value used to specify an red, green, and blue components of a color requiring only 3 bytes. When specifying an explicit RGB color, the COLORREF value has the following hexadecimal form.

0x00bbggrr

The low-order byte contains a value for the relative intensity of red, the second byte contains a value for green, and the third byte contains a value for blue. The high-order byte must be zero. The maximum value for a single byte is 0xFF.
To create a COLORREF color value, you can use the GB32 RGB() function. To extract the individual values for the red, green, and blue components of a color value, use the GetRValue, GetGValue, and GetBValue functions, respectively. For performance reasons these GFA-BASIC 32 functions are directly translated to assembly code; they are not implemented as function calls.

The OLE_COLOR type
When a COM object property (most often .ForeColor and .BackColor only) requires an OLE_COLOR color, you can either pass an RGB value (COLORREF) or a COM specific formatted system color index value. A COM specific format has it’s high-byte set 0x80. An OLE_COLOR can have two formats only:

  • 0x00bbggrr (COLORREF value)
  • 0x800000xx, where xx is the index used by GetSysColor().

The object doesn’t do much with the OLE_COLOR value, it is more a kind of a storage type. For instance, you cannot pass an OLE_COLOR to a GDI device context handle. GDI doesn’t know what to do with a COM formatted system index color (0x800000xx). The OLE_COLOR value is simply saved in the private data of the COM object and remains unchanged until the application explicitly changes the value again. This way, the Get-color-property variant of the Put-color-property will return exactly the same value as was first put in.

How the OLE_COLOR is used
Since the format of the OLE_COLOR data type isn’t known to Win32 drawing functions, they must be converted to RGB colors first. Usually, the OLE_COLOR is converted on a just-in-time basis. Just before a painting operation is performed the OLE_COLOR (32-bit long integer) is tested for a high-byte value of 0x80. When the high-byte is indeed 0x80, the low-byte value is used as a parameter to the GetSysColor() API function, which in turn returns the RGB color value of the specified display element. See the code below.

Aside: Identifying display elements
Display elements are the parts of a window and the display that appear on the system display screen. These elements are identified using the API COLOR_* constants, like COLOR_BTNFACE, COLOR_WINDOW, etc. These API constants may also be used in the GFA-BASIC 32 function SysCol(idx), which is slightly easier to use and a bit faster due to caching.

Dim rgb% = SysCol(COLOR_WINDOW)

The GFA-BASIC32 OLE_COLOR Constants
GFA-BASIC 32 also defines a set of OLE_COLOR constants to identify the system display elements. These constants are very much like the Windows API COLOR_* constants. However, instead starting their names with ‘COLOR_’ they start with ‘col’, like colBtnFace and colWindow. These GB32 OLE_COLOR constants have the format of 0x800000xx, where xx is the index for the display element. See the code below.

Convert an OLE_COLOR
When you want to use an OLE_COLOR value, make sure to convert the OLE_COLOR to a COLORREF (RGB) value first. This is the only color format Win32 understands. In GB32 you get hold of an OLE_COLOR when you use one the col* constants or retrieve a color from one of the Ocx-color properties. For instance, when you want to use the same color as the form’s background, you might very well end up with an OLE_COLOR with the high-byte set to 0x80.

Now, there are two possible ways to convert an OLE_COLOR to a COLORREF value.

1. Manually, in your code. For example this converts to an RGB-value:

Trace Hex(SysCol(colBtnFace)) ' =0 (SysCol: what is a colBtnFace?)
Trace Hex(colBtnFace) ' =8000000F (an OLE_COLOR)
Trace Hex(OleToRGB(colBtnFace)) ' =F0F0F0 (the RGB value)
Trace Hex(OleToRGB(0x667788)) ' =667788 (no conversion)
Function OleToRGB(olecolor As Long) As Long Naked
 If (olecolor And 0xFF000000) == 0x80000000
 Return SysCol(olecolor And 0xFF) ' LoByte
 Else
 Return olecolor ' RGB!
 EndIf
EndFunc

2. Use the system OLE function OleTranslateColor()

Declare Function OleTranslateColor Lib "oleaut32.dll" ( _
 ByVal OleColor As Long, _
 ByVal hPal As Handle, _
 ByRef ColorRef As Long) As Long
Dim rgb As Long Long

If OleTranslateColor(colBtnFace, Null, rgb) == S_OK Then _ Trace Hex(rgb)

The second option using OleTranlateColor() might be preferable because it can also return other GDI color formats, like a palette-index value. In addition in case of an error, it let you investigate the type of the error. See the SDK for more info.

More flexibility
You can use OLE_COLOR types with the GB32 graphic commands, though. All drawing statements, font statements, and color statements accept an OLE_COLOR type. GB32 converts the OLE_COLOR to a COLORREF before it invokes the corresponding GDI function.

04 January 2013

Memory Leak on Image Ocx

At the end of 2012 Peter Heinzig posted a sample showing GFA-BASIC 32 memory leaks with the Form and Image Ocx. In Memory leak with Form.Picture I focused on the Form object and a remedy against the memory leak. In the meantime I investigated the Image Ocx and can confirm a memory leak when the Ocx control is destroyed. Just like the Form Object the Image Ocx doesn't release it's Picture object. The remedy is actually the same. In the parent Form Ocx _Destroy sub event add a command to release the Picture property of the image control.

The solutions presented here are applicable on all versions of GFA-BASIC 32. However I hope to build an update of the runtime as soon as possible. I hope these are the only memory leaks, but it takes time to verify these are the only Picture objects that aren't released after a program has been closed.

29 December 2012

Memory Leak with Form.Picture Object

When you assign an image to the Picture property of a Form Ocx, and don't release the Picture explicitly, a memory leak occurs when the Form is destroyed.

An image can be assigned using the Form-Editor Properties window in the sidebar. For a Form object the Properties window shows a Picture property that can be assigned an image (bitmap, icon, cursor, GIF, JPEG, and metafiles) by double clicking in the value column. Behind the scenes the LoadForm command - that is used to load a Form created with the Form-Editor - will create a COM Picture object on behalf of the Form. The dynamically allocated Picture COM object is then assigned to the Form.Picture property. 
However, a program may also set a Picture object to the Form.Picture property explicitly in code.

The memory leak occurs when the Form is closed. In the process of destroying all resources the Picture object is simply forgotten. The memory for the image is never released and the reference count on the COM object never reaches zero. Not only the memory necessary to store the image, but the memory allocated for the COM object will remain occupied. The memory used to store a bitmap can be of a considerable size and after some time you may even run out of memory. This most likely will present itself after RUNning a program multiple times within the IDE.

The best place to set the Picture Object to Nothing is within the Form_Destroy() event sub. A program cannot release the object when the Form has gone (==Nothing).

The leaked memory can never be reclaimed, because after the Form is destroyed the pointer to the Picture's COM Object is zeroed out. Selecting 'Cleanup Resources' from the Project menu will not work.

11 October 2012

An owned Form

Until of the older set of chm MSDN Library files (<= October 2001) were replaced by the newer MSHELP, the MSDN contained an article called "Win32 Window Hierarchy and Styles" by Kyle Marsh, September 29, 1993. This article explains how top-level windows (aka Forms) relate to each other.

Form is a top-level window
A Form window, whether is is created using OpenW, Dialog, or Form, is a top-level window by default. A top-level window is any window that is not a child window. Top-level windows do not have the WS_CHILD style. All top-level windows are also connected to the desktop window through the parent window handle, GetParent(). Top-level windows are connected to the desktop window as if they were child windows of the desktop, and can be considered children of the desktop in that parent/child navigation techniques can be used to move between a top-level window and the desktop window. All top-level windows are displayed in the client area of the desktop window and thus behave as if they were children of the desktop for display purposes.

The parent window
Each window, being a top-level or a child window, has a parent window. The desktop window occupies the first level of the windows hierarchy and top-level windows occupy the second level. Child windows, which are windows created with the WS_CHILD style, occupy all other levels. Child windows connect to their parent window in the same way top-level windows connect to the desktop window.

The owned window
Top-level windows can own or be owned by other top-level windows. One Form can own another Form. An owned window is always displayed above its owner in the Z order and is hidden when its owner is minimized. Try this and see what happens, you cannot display Form Win_1 on top of Form Win_2:

OpenW 1
Me.Caption = Me.Name
OpenW Owner Win_1 , 2
Me.Caption = Me.Name
Do
 Sleep
Until Me Is Nothing

Win_2 is owned by Win_1. Owned windows are not hidden when their owner is hidden. Thus, if window #1 is minimized, window #2 is hidden as well.
BTW you can set the owner using the designer in the Properties window. A Form has a an Owned property that can be set to True. After loading the Form using LoadForm the current active Form as referenced by Me will be set as the owner.

On the API level an owned window relationship is created by passing the window handle to the owner window in the hwndParent parameter of the CreateWindow() function. GB32 passes the window handle of the Form object specified in the Owner clause in the OpenW statement. To top-level window #2, window #1 is the owner, but not its parent window. The parent window for both Forms is the desktop window.
The GetParent() API function is not used to obtain the owner of a top-level window, that would return the desktop window handle. Insert the following lines before the start of the message loop. Both will return Null (0).

Print GetParent(Win_1.hWnd)
Print GetParent(Win_2.hWnd)

To obtain the owner GetWindow(hWnd, GW_OWNER) API function should be used. Insert these lines as well, and see how their results differ.

Print GetWindow(Win_1.hWnd, GW_OWNER)
Print GetWindow(Win_2.hWnd, GW_OWNER)

Finally, closing the owned top-level Win_2 won't automatically close Win_1. After Win_2 has been destroyed the current Form Me is set to Win_1. The global message loop will not end until Me == Nothing, which will not happen until Win_1 has been closed.

12 April 2012

Ocx background color

Not all OCX controls support a .BackColor property to change the background color, although the control itself allows changing it. Here GB32 conforms to VB too much. Rather than adding a property to the implementation – which would require another patch in executable code – we can use a different approach.

All OCX controls share a user-defined structure to store the current settings of the wrapped control. Setting and getting property values is sometimes nothing more than changing and setting a member of the structure. Setting and obtaining the background value is one of them. The BackColor property stores its value in the member at offset $B8 of the start of object’s data block. The following code changes the background of a StatusBar Ocx.

Form frm1
Ocx StatusBar sb1
Trace SetBkColorControl(sb1, RGB(23, 56, 229))
Do
 Sleep
Until Me Is Nothing
Function SetBkColorControl(Ctrl As Control, Color As Long) As Long
 Const OffBackColor = $B8
 Local Long ObjPtr = Long{*Ctrl}
 SetBkColorControl = Long{ObjPtr + OffBackColor}
 {ObjPtr + OffBackColor} = Color
 ~InvalidateRect(Ctrl.hwnd, 0, 1)
 ' Notes
 ' 1 Using the IDispatch interface does not work
 ' when the GetTypeInfo doesn't support the
 ' the .BackColor property.
 ' BackColor is not a property of StatusBar
 If 0 Then Ctrl.BackColor = Color
 ' 2 When using XP Visual Themes the Visual Style
 ' theme takes over and gets precedence over
 ' GB OCX propery settings.
EndFunc

Note – Not all controls let their background set this way. Just experiment.

02 November 2011

Dim ... As New Interface?

What does New mean when used in a declaration statement? Why is New limited to only 8 so called OCX types? What are OCX types anyway? Confused? Let us continue the story on COM.

OCX Objects
By now you know that in GFA-BASIC 32 OCX windows are, in fact, normal  windows and controls wrapped up in a COM Object. For instance, the very familiar OpenW # command still creates an overlapped application window, but it now also creates a COM wrapper of data type Form. Not only creates OpenW a window using the CreateWindowEx() API, but also allocates an additional block of memory  (using malloc) to store all COM related information for the Form object type.

A GFA-BASIC OCX object is a window wrapped in a COM object. An OCX object requires a window handle.

Information about the OCX 
An OCX object is also called an ActiveX control (hence the window handle). GFA-BASIC OCX objects must behave as proper ActiveX objects as described in the OLE/COM documentation. (Actually, OLE is an old name. On the way it changed to COM.)
To use/control a COM object it must somewhere describe (publish) its functions, called properties and methods. This is done by including a type library with a COM object. The type library also specifies whether the COM object supports the creation of an instance of that object. This literally means that the COM object provides a class (function) doing a malloc() to allocate the memory to store the object.

A type library specifies the properties and methods of a COM object (interface), and whether it can be created on behalf of the client (coclass).

Declaring a COM Object
If you have ever used OleView.exe to examine GFA-BASIC's COM objects, you may have noticed the term interface. The interface specifies the name of the COM type and the functions used to access it. The interface name always starts with a capital I - like IForm, ICollection, etc. When used in code the capital I is lost (VB convention, duplicated by GB32).

All the interfaces defined in the type library from the GFA-BASIC Ocx runtime DLL  are added to the GFA-BASIC 32 internal list of data types. (This list is accessible through the Gfa_Types collection.) The list of data types contains all primary types like Bool, Byte, Card, Int32, Long, Currency, String, etc. All names stored in the type-list can be used in a declaration command like Dim, Global, and Local.

There is small problem though. GFA-BASIC 32 doesn't add the COM types by loading them from the type library. The addition of COM types like Collection, DisAsm, TextBox, etc. is hard-coded into the IDE code. Unfortunately! Otherwise we could have easily added third-party COM references to our applications.

To understand how GFA-BASIC handles a declared COM data type we can compare a COM object type to a String data type. For instance

Dim MyName As String
Dim frm As Form

These declarations don't do much. MyName and frm are variable-names and both are an alias for a 4-byte memory location. Initially, the value stored at the addresses is zero (pointer is Null), meaning no additional memory has been allocated for these variables. The pointers remain Null until something is assigned to the variables. Because MyName is of type String, GFA-BASIC knows how to do that and how to interpret the pointer at the location MyName.

The same is true for COM data types. Declaring a COM data type does not create the memory for it. The frm variable references some memory-address, but it does not actually create a window on the screen. First the window must be created and then the variable can be assigned an object of type Form. For COM objects a special command is invented to assign one COM object to another Set:

OpenW 1
Set frm = Win_1

Most GFA-BASIC 32 COM objects are created implicitly. OpenW creates a Form object, OCX TextBox creates a TextBox object, and so on. In addition, OCX controls that require multiple subitems create collections. A ToolBar OCX creates a Buttons collection of Button items.
For implicitly created objects GFA-BASIC 32 is responsible for memory allocation, initialization, and releasing memory.

Some COM types are created explicitly when some data is loaded. The LoadCursor() function creates a MouseCursor object and LoadPicture() creates a Picture object. These functions can be seen as 'a kind of New'.

The New keyword
The New keyword is used with some COM types that are available at your request. These types are independent of any OCX type. The New keyword allows you to create an instance of the object while declaring it. In GFA-BASIC 32 there are only 8 of these COM types allowed.

Dim f As New Font ' IFont2
Dim sf As New StdFont ' IStdFont
Dim p As New Picture ' IPicture2
Dim sp As New StdPicture ' IStdPicture
Dim cd As New CommDlg 
Dim col As New Collection
Dim dis As New DisAsm
Dim iml As New ImageList
  • Font (IFont2) and Picture (IPicture2) are GFA-BASIC implementations of the standard COM types.
  • StdFont and StdPicture are the types from the standard OLE libraries. Font and StdFont are 'compatible'. A variable of type Font can be set to an instance of StdFont and vice versa. The same is true for Picture and StdPicture.
  • The CommDlg and ImageList objects can be created using the OCX command as well.
  • Once the object created with New goes out of scope the object is automatically released.
  • Objects can be released explicitly by setting the variable to Nothing.

Other objects are single-instance objects created at runtime. GFA-BASIC 32 prohibits the creation of additional, new objects of these types. There is only one instance of App, Screen, Printer, ClipBoard, Code4, Debug, and Err. The single-instance objects are released when the program is terminated.

23 October 2011

Cleanup after an exception

In the previous post Don't use END to terminate your application I discussed why End messes up the memory available to an application. The same is true for Assert and Stop (when you use to terminate the application).

Main exception handler
These commands raise an exception caught by the nearest exception handler. This might be a custom Try/Catch handler in your program, but most often it passed on to the applications main exception handler. The main part of a GB32 application is surrounded with a GB32 generated application-exception-handler that releases file handles and memory associated with GB32 data types. The OCX forms/controls are not part of the cleaning process. To properly release all COM object memory all forms have to be closed by an internal function Destroy_Form(). The forms in turn are responsible for releasing the OCX objects of the child windows (controls).

Custom handler
When you use a custom structured exception handling (Try/Catch) in your application the exceptions might be caught by your application's defined custom handler. Now the exception is not passed on to the main application-handler and no memory and file handles are released. When you are put in this situation, you can select the clean-up command from the IDE's menu to close all forms and file handles.

Cleanup opened Forms and files
GB32 includes a library function that can cleanup non-closed forms and files after an application terminates abruptly. The DLL-function is located in the runtime and is exported by ordinal #49. The function is automatically invoked when you RUN a program inside the IDE. But you can also invoke it by selecting 'Clean up' (shortcut Ctrl-L) from the IDE's menubar. The menuitem  executes the Gfa_CleanUp command that invokes the DLL cleanup function.

Why would you use Gfa_CleanUp when the same function is executed at the next RUN of your code? Well, programs that are terminated improperly (END, ASSERT, or any exception the program causes) leave with invalid memory-pointers and Gfa_CleanUp might fail (mostly likely it will).

Gfa_CleanUp executes within a exception handler itself and it first calls DLL function #49 to close the forms. If it returns properly it calls exported function #119 which cleans the file handlers. In short, this is what Gfa_CleanUp looks like:

Try
  G49_CleanUpForms()
  G119_CleanUpFiles()
Catch
EndCatch

When the G49_CleanUpForms() DLL function causes an exception the G119_CleanUpFiles() is never executed!

The G49_CleanUpForms() simply enumerates over the Forms collection to call each Destroy_Form() internal function. This function is responsible for deleting form-specific COM objects for Fonts, MouseCursors, Pictures, Icons, AutoRedraw bitmaps, Menus, controls, graphic settings, end other related COM objects. This is a very large function responsible for properly clearing (in the right order) all created COM objects. When something didn't work correctly in your application and it was terminated in an unstable state, it is most likely this function might fail. This might leave behind all kinds of allocated COM memory. In addition, the function might have generated an exception, most likely of an invalid memory pointer, and from the pseudo code above you then see that the file handles aren't released at all.

In conclusion,

  1. When you set up a new application use a proper message loop and try to created one main-form that owns other forms (set the Form.Owned property for the second and later forms in the Properties window). Closing the main-form will close all other forms.
  2. Don't use END and be careful with ASSERT. When an application terminates improperly try using invoke Gfa_CleanUp.
  3. Fix an exception error and make sure it won't happen again. Then restart the IDE to get rid of memory left-overs.

05 September 2011

CreateObject peculiarities

Why CreateObject isn't fully compatible to VB(Script).

Introduction
The GFA-BASIC 32 function CreateObject() creates and then references a COM automation object. Usually the function is used to control a target application (or documents or other objects supported by the target application). Controlling a target application through COM (or OLE) is called OLE Automation.

Understanding OLE Automation
OLE Automation is an OLE service for integrating applications. It enables an application to expose its functionality (MS Word), or to control the functionality of other applications on the same computer or across networks. As a result, applications can be automated and integrated with programming code, creating virtually endless possibilities. OLE Automation is the umbrella term for the process by which an OLE Automation controller sends instructions to an OLE Automation server (using the functionality exposed by the OLE Automation server), where they are run.

CreateObject returns a reference to an object determined by the class argument.

Syntax: Set object = CreateObject(class)

Depending on class, the reference may be to an existing object or to a newly created object. The object reference returned by CreateObject is (usually) assigned to an object variable of type Object. Assigning a COM object to a variable differs from assigning a value or variable to another variable. It always requires the Set statement (as opposed to the Let statement that is implicitly used by other assignments).

Note - Using CreateObject (or GetObject) to set the Object-variable to the target application, or some object that the target application supports, establishes a reference to the target application's Application object. Then the object variable in the sourcecode can use the methods and properties of the corresponding object in the target application.

The ProgID of an automation object
To create an automation object the class argument of CreateObject(class) can be a string of the form "appName.objectType". A string using this format is called a ProgID.
A ProgID is a string that gets converted to a CLSID (a 128-bits GUID value) by looking up the string in the register. Note that the automation object can only be found when it has registered itself using the same string format "appName.objectType". This happen,s for instance, with MS Excel which registers itself as "Excel.Application". And Internet Explorer as "InternetExplorer.Application". These applications can be referenced through automation as follows:  

Dim obj As Object
Set obj = CreateObject("Excel.Application")

The class argument
In GFA-BASIC 32 the class argument may also specify CLSID either as a string (1) or as GUID constant (2). For instance:

Dim obj As Object
Set obj = CreateObject("7b55b57b-cd72-449d-82eb-a02e0964e3eb")

GUID gd = 7b55b57b-cd72-449d-82eb-a02e0964e3eb Set obj = CreateObject(gd)

Remarkable features of GFA-BASIC's CreateObject() are:

  • The class$ argument can specify a GUID value as a string.
  • The parameter can specify the address of GUID user-defined type. (The GFA-BASIC statement GUID name = xxx..-..-.._..-...x defines a constant (Long) as the address to a GUID value.
  • The syntax does not support the second optional parameter server as is described in Help documentation.
  • The language locale of the target application can be set prior to invoking CreateObject() using Mode(Lang) = .
  • The CreateObject() function is not 100% VB(Script) compatible.

CreateObject 's compatibility
In COM terms CreateObject() connects to a coclass at run time using the IDispatch interface (late binding). The Object variable type performs property and method operations using the IDispatch interface functions. Once you’ve created a new object and returned a reference to it, you can work with the object in GFA-BASIC in the same way you would work with any other object. That is, you can set and retrieve the object’s properties and apply its methods. To be an automation object the object must support IUnknown and IDispatch. All COM objects support IUnknown, because that defines them as a COM object. See this post on COM interfaces.
The IDispatch interface defines late binding and allows a client to call the methods and properties on the server. Consequently, without supporting an IDispatch interface a server cannot be called an automation controller. GFA-BASIC 32 consequently asks the automation object for an IDispatch interface and only connects to the coclass when it acknowledges that it supports an IDispatch interface. Otherwise it generates a runtime error.

VB(Script) are wrong
However VB and VBScript implement CreateObject differently, more flexible maybe, but essentially wrong. At the heart of creating an object is the OLE function CoCreateInstance(). It takes a pointer to the interface the program wants to use to communicate with the object. Since 'OLE automation' is required, it seems logical to pass the GUID for the IDispatch interface (as GFA-BASIC 32 does). But VB(Script) doesn't. The VB-CreateObject passes the GUID for IUnknown which every COM object supports, because IUnknown is what an object makes a COM object in the first place! So in VB CreateObject always works, even when it shouldn't. But the object reference returned by VB's CreateObject is not guaranteed to support IDispatch.

Porting VB to GB
This causes trouble when porting a VB program to GB. Implicitly CreateObject calls the object's QueryInterface(), asking it if it supports IDispatch. A correct behavior for QueryInterface(0 would be to reply for each interface it supports. Badly written OLE automation objects sometimes forget to reply for all interfaces it supports (especially IDispatch). One of them is Microsoft's type-library-reader-tool  "TLI.TypeLibInfo". CreateObject fails with GB where VB does return a reference:

Set TLInfo = CreateObject("TLI.TypeLibInfo")

When GB's CreateObject doesn't work where VB(Script) works, don't blame GFA-BASIC 32, but the COM programmer instead. BTW in these cases you must use the CoCreateInstance() API.

15 July 2011

COM (OOP) in GB32 - IUnknown (1)

In this part I'll show you the layout of a COM class as it could be implemented in GFA-BASIC 32. Note that we restrict ourselves to a minimalist COM object that supports the IUnknown interface only. We will not yet create properties and methods.
The GFA-BASIC 32 approach is loosely based on the article series COM in plain C by Jeff Glatt, specifically Part 1 and Part 2. However, in the first step we are not going to use type libraries, we are not going to store the COM object in a DLL, and we are not going to define another COM object that creates the one we define. COM is merely a binary standard; it dictates how to layout an array of function pointers and where to put the address of this array (of function pointers). It also dictates how to add reference counting. To comply to this standard a COM object must at least contain three pointers to pre-defined functions, also known as the IUnknown interface. In GFA-BASIC 32 this could look like this:
// IUnknownBlog.g32 17.07.2011
Debug.Show
// Our COM object: an implementation of IUnknown
GUID IID_IUnknownImpl = 9578fdab-97cb-4322-99e4-699abd26be1d
Type IUnknownImpl
 lpVtbl As Pointer IUnknownImplVtbl
 RefCount As Int
EndType
// VTABLE (an array of function pointers)
Type IUnknownImplVtbl
 QueryInterface As Long
 AddRef As Long
 Release As Long
EndType
Static IUnknownImplVtbl As IUnknownImplVtbl
With IUnknownImplVtbl
 .QueryInterface = ProcAddr(IUnknownImplVtbl_QueryInterface)
 .AddRef = ProcAddr(IUnknownImplVtbl_AddRef)
 .Release = ProcAddr(IUnknownImplVtbl_Release)
EndWith
// First create a heap allocated instance
// of our implementation of IUnknownImpl
Dim pIUnk As Pointer IUnknownImpl
Pointer pIUnk = mAlloc(SizeOf(IUnknownImpl))
Pointer pIUnk.lpVtbl = *IUnknownImplVtbl
pIUnk.RefCount = 1
Dim obIUnk As Object
{V:obIUnk} = V:pIUnk
Trace obIUnk
Dim o As Object // assign to other
Set o = obIUnk // AddRef() call
// two calls to Release
/*** END ***/
GUID IID_IUnknown = 00000000-0000-0000-c000-000000000046
Global Const E_NOTIMPL = 0x80004001
Global Const E_NOINTERFACE = 0x80004002
Declare Function IsEqualGUID Lib "ole32" (ByVal prguid1 As Long, ByVal prguid2 As Long) As Bool
Function IUnknownImplVtbl_QueryInterface( _
 ByRef this As IUnknownImpl, riid%, ppv%) As Long Naked
 Trace Hex(*this)
 {ppv} = Null
 If IsEqualGUID(riid, IID_IUnknownImpl) || _
 IsEqualGUID(riid, IID_IUnknown)
 {ppv} = *this
 IUnknownImplVtbl_AddRef(this)
 Return S_OK
 EndIf
 Return E_NOINTERFACE
EndFunc
Function IUnknownImplVtbl_AddRef(ByRef this As IUnknownImpl) As Long Naked
 Trace Hex(*this)
 this.RefCount++
 Return this.RefCount
EndFunc
Function IUnknownImplVtbl_Release(ByRef this As IUnknownImpl) As Long Naked
 Trace Hex(*this)
 this.RefCount--
 If this.RefCount == 0 Then ~mFree(*this)
 Return this.RefCount
EndFunc
Copy it to a new GFA-BASIC 32 application and save as IUnknownBlog.g32. Try to run it; it should compile and run flawlessly. Our first minimalist COM object/class has been defined and is up and running. Of course it doesn't do anything, but the we have an object that integrates with GFA-BASIC 32 and fully complies to the COM binary standard.
Assign to Object
Let us take a brief look at the code. Since this project isn't meant for the beginner, I'll walk you through it in big steps.
The Object data type holds a pointer to a piece of memory of at least 4 (lpVtbl) bytes. Mostly it contains another integer for a reference count. In GFA-BASIC 32 the Ocx variables always define their count in the second slot, we will use that as well.
To put a memory address in an Object variable we usually use Set obj2 = obj2. GFA-BASIC 32 checks for two proper COM object types at compile-time. Since we have mAlloc-ed address only this syntax wouldn't work. We must write it to Object directly. For the same reason we set the RefCount to 1 by hand.
The array of functions (VTABLE) is stored in a Type and shared with all instances of our custom COM object. Therefor a static (global) variable is used and initialized once. Each new COM object should hold the vtable-address in its lpVtbl member.
_QueryInterface, _AddRef, and _Release
The application COM functions _QueryInterface, _AddRef, and _Release are never called directly, but always by GFA or another COM object. They clutter up our application code and are in fact just boiler plate code. We must do something about that, for instance put them in a $Library. Also note the Naked attribute. These functions are never executed in the context of the application and need no TRACE code and Try/Catch-exception handler. They should be as naked as possible to gain the best performance.
The Trace *this commands in the code are to verify the address passed. Also note the type of the this pointer. COM passes the address of the COM object by reference and by adding the correct type into the function declaration we can access its members directly. All other parameters are simple placeholders for addresses we don't use or pass on.
In the next part we will implement an IDispatch object and try to integrate the COM code more into GFA-BASIC 32.

14 May 2011

Visual Styles, Ocx Button, and ForeColor

The BUTTON window class is the base windows class for the Command, Frame, Option, and CheckBox Ocx controls. These different OCX controls are created by passing a special BUTTON window style (BS_) in the style parameter of the CreateWindowExA() Windows API.

GFABASIC 32 Ocx BUTTON window Style
Command BS_PUSHBUTTON, BS_DEFPUSHBUTTON
CheckBox BS_CHECKBOX, BS_AUTOCHECKBOX, BS_3STATE, and BS_AUTO3STATE
Option BS_RADIOBUTTON and BS_AUTORADIOBUTTON
Frame BS_GROUPBOX

As you can see the names of the Ocx controls conform to VB control names, which was one of the main changes in the GFA-BASIC 32 development. Another thing adapted from VB is the ability to change the controls behavior and appearance through properties. These are either changed in the Properties sidebar in the form-editor or in source code using the COM properties interfaces. In this case it doesn't matter how the appearance is changed, but how GFA-BASIC 32 implements the VB compatible behavior.

First of all, the implementation is VB compatible, but totally different. In most case (if not all) the GB32 implementation of properties and methods is much faster and cleaner. VB and VB.NET are snakes compared to the fast performance of GB32 applications. Currently, performance doesn't count as strong as in previous decades, but still. Maybe with Windows 7 for Tablets performance and size counts again. Who knows?

BUTTON drawing
The BUTTON control sends the WM_CTLCOLORBTN message to the parent window of a button when the button is about to be drawn. By responding to this message, the parent window can set a button's text and background colors. In GFA-BASIC 32 applications without the Visual Styles enabled this works as documented. However, when an application is accompanied with a manifest the new Visual Styles are applied and take precedence over the GFA-BASIC implementation.

When the Windows OS encounters a manifest file, the OS uses a different version of the common control library and globally subclasses all Windows controls, including the 'old standard' controls like the BUTTON class. GFA-BASIC 32 wasn't developed when the Visual Styles were around and uses a speedy drawing algorithm based on the classic behavior of the BUTTON class drawing. As a result, the Visual Styles improvement is not applied to the text color of the BUTTON class.

ForeColor cannot be set
Thus, the text color of Command, CheckBox, Option, and Frame Ocx cannot be changed by changing their ForeColor property when using a manifest file.
When you really need to change the text color I'm not sure how to proceed, so you are your own there...

10 May 2011

ShowFolders Sample

The ShowFolders method of the CommDlg Ocx object keeps raising questions. Therefor an example of an extended browse for folders dialog box.

The API behind the dialog box of the ShowFolders method is the SHBrowseForFolder() Shell function. Actually, there are two styles of dialog box available. The older style is displayed by default and is displayed using the BIF_ constants specified in the previous blog CommDlg.ShowFolders. However, the list of constants is a subset of the total number of flags. To specify a dialog box using the newer style, you should pass the BIF_USENEWUI flag in the ShowFolders method.

image

The newer style provides a number of additional features, including drag-and-drop capability within the dialog box, reordering, deletion, shortcut menus, the ability to create new folders, and other shortcut menu commands. Initially, it is larger than the older dialog box, but can be resized by the user.
The dialog box can be displayed using the following code.

Public Const BIF_RETURNONLYFSDIRS = 0x0001
Public Const BIF_DONTGOBELOWDOMAIN = 0x0002
Public Const BIF_STATUSTEXT = 0x0004
Public Const BIF_RETURNFSANCESTORS = 0x0008
Public Const BIF_EDITBOX = 0x0010
Public Const BIF_VALIDATE = 0x0020
Public Const BIF_NEWDIALOGSTYLE = 0x0040
Public Const BIF_USENEWUI = (BIF_NEWDIALOGSTYLE | BIF_EDITBOX)
Public Const BIF_NONEWFOLDERBUTTON = 0x0200
Public Const BIF_BROWSEFORCOMPUTER = 0x1000
Public Const BIF_BROWSEFORPRINTER = 0x2000
Public Const BIF_BROWSEINCLUDEFILES = 0x4000
Global cd As New CommDlg
cd.Title = "GFA-BASIC32 ShowFolders Demo"
cd.ShowFolders BIF_USENEWUI
If Len(cd.FileName) Then _
 MsgBox "Folder Selected: " & cd.FileName

21 April 2011

CommDlg.ShowFolders

The ShowFolders method of the CommDlg Ocx isn't documented properly. The method is included to provide an easy way to display the Shell's SHBrowseForFolders dialog box allowing the user to select a folder rather than a file. It is - of course - not part of the the Common Dialog Box Library and should  have been implemented as a separately function. Or shouldn't it?

The FileName and Title properties
The CommDlg.ShowFolders(Optional flag As Variant) method shares two properties with other CommDlg methods that show dialog boxes: FileName and Title. In case these are used with ShowFolders their meaning is as follows:

- CommDlg.Title  (R/W)
Sets the string that is displayed above the tree view control in the dialog box. This string can be used to specify instructions to the user. Returns the same value as was put into it.

- CommDlg.FileName  (R/W)
Sets the browse dialog box's initial selection folder (has nothing to do with filenames!). Note, by default, the SHBrowseForFolder API lets the user start at the desktop to browse the shell's namespace and pick a folder. GFA-BASIC 32 overrides this behavior by starting the browse dialog box at the folder specified in the .FileName property. To let the dialog box start at the desktop set the FileName property to an empty string. To bring up the browse dialog box with the current directory selected, set FileName = CurDir.

After closing the ShowFolders dialog box the FileName property contains the name of the selected folder. That is if you select the Ok button; after Cancel the FileName is undefined.

CommDlg code was/is buggy
The GFA-BASIC 32 library code that implements CommDlg Ocx and specifically the Show Folder function, has always been buggy and I've been fixing it many times in the past. I was confident I fixed all bugs in Build 1169. At the time of writing this blog I tried it again and I was shocked to see that it still has a bug. When you press the browse dialog box's Cancel button the FileName property is undefined and present an Access Violation error. I will fix it as soon as possible and release Build 1170, which also contains fixes for the Dlg Open/Save commands.

The optional parameter
The ShowFolders(flag) method accepts one optional argument that specifies the options for the dialog box. When you omit the argument, the default behavior is BIF_RETURNONLYFSDIRS. The flag can include a combination of the following values:

BIF_BROWSEFORCOMPUTER (0x1000) Only return computers. If the user selects anything other than a computer, the OK button is grayed.
BIF_BROWSEFORPRINTER (0x2000) Only return printers. If the user selects anything other than a printer, the OK button is grayed.
BIF_BROWSEINCLUDEFILES (0x4000) The browse dialog will display files as well as folders.
BIF_DONTGOBELOWDOMAIN (0x2) Do not include network folders below the domain level in the tree view control.
BIF_EDITBOX (0x10) Version 4.71. The browse dialog includes an edit control in which the user can type the name of an item.
BIF_RETURNFSANCESTORS (0x08) Only return file system ancestors. If the user selects anything other than a file system ancestor, the OK button is grayed.
BIF_RETURNONLYFSDIRS (0x01) Only return file system directories. If the user selects folders that are not part of the file system, the OK button is grayed.
BIF_STATUSTEXT (0x04) Include a status area in the dialog box. The callback function can set the status text by sending messages to the dialog box.
BIF_VALIDATE (0x20) Version 4.71. If the user types an invalid name into the edit box, the browse dialog will call the application's BrowseCallBackproc with the BFFM_VALIDATEFAILED message. This flag is ignored if BIF_EDITBOX is not specified.

The BrowseCallbackProc
Although the ShowFolders method will pass most of the flags without questions asked, it doesn't honor them all. Some of the flags require an application-defined callback function to handle application defined behavior. The underlying SHBrowseForFolder API function calls BrowseCallbackProcto notify it about events. The address of the callback function is passed in the BROWSEINFO structure that contains information used to display the dialog box. The GFA-BASIC 32 ShowFolders method uses the callback proc to select the folder set with CommDlg.FileName in response of the BFFM_INITIALIZED message. GFA-BASIC 32 does not provide a way to interact with its BrowseCallbackProc, making ShowFolders a simple folder selection mechanism.

28 November 2010

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
Subscribe to: Comments (Atom)

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