Miscellaneous Top Level Windows

From Team Developer SqlWindows Wiki
Jump to: navigation, search

Miscellaneous Top Level Windows


Pointer2.png How to prevent a toplevel window stealing focus Pointer.png

Normally, when a top-level window (form, dialog) is created it will be activated and get the focus and
the object which had the focus will loose it.
In most cases this is wanted behaviour, but in some you don't want the window to steal the focus.
For instance, when you create a window to display info (a popup) on top of another window.

This is a solution : create the top-level window as hidden (it will not get the focus). When it is
created, show it using a flag not to activate it using API function ShowWindow.

This is the declaration for ShowWindow

Library name: USER32.DLL
   Function: ShowWindow
      Export Ordinal: 0
         Boolean: BOOL
         Window Handle: HWND
         Number: INT

Also define this constant


Now, here the code to create the top-level window:

   Set hWndPopup = SalCreateWindowEx( frmPopup, hWndForm, 0, 0, 0, 0, CREATE_Hidden )
   Call ShowWindow( hWndPopup, SW_SHOWNOACTIVATE )

Here you can download a sample:
Down.png WIKI_PreventFormStealingFocus.zip

Pointer2.png How to bring up a window to the top of all applications programmatically (even on Windows XP and above) Pointer.png

With introduction of XP, Windows started to prevent other applications from automatically bringing them to the top of the screen. SalBringWindowToTop() oder APIs like SetWindowPos(...) only work reliably when the regarded application is already on top of all applications. Anyway, sometimes we need such a behaviour and the trick is simulating a keyboard or mouse input for the window in question and than bring it to the front.

These are the declarations for the APIs we are going to use...

Library name: USER32.DLL
   Function: keybd_event
      Export Ordinal: 0
         Boolean: BOOL
         Number: BYTE
         Number: BYTE
         Number: DWORD
         Number: LPVOID
   Function: SetForegroundWindow
      Export Ordinal: 0
         Boolean: BOOL
         Window Handle: HWND

...and this is the small piece of code which does the trick:

Function: SetWindowToForeground
      Boolean: BOOL
      Window Handle: p_hWnd
      ! 1. Simulate a keyboard input for our app...
      Call keybd_event( VK_F12, 0, 0, 0 )
      Call keybd_event( VK_F12, 0, 2, 0 )
      ! 2. ...bring our window in front of all GUI threads
      Return SetForegroundWindow( p_hWnd )

Pointer2.png How to set the opacity and transparency color key of a window Pointer.png

Starting from Windows 2000 and up, you can set the opacity and color key of a top-level window using SetLayeredWindowAttributes.

With the opacity you can determine the amount of transparency of a window (0=completely transparent to 255=fully visible)
With the color key you can make a certain color on a window completely transparent, therefore creating holes which reveals windows beneath it.
It is also possible to combine color key and opacity at the same time.

First use these external function declarations:

Library name: USER32.DLL
   Function: GetWindowLongA
      Export Ordinal: 0
         Number: LONG
         Window Handle: HWND
         Number: INT

   Function: SetWindowLongA
      Export Ordinal: 0
         Number: LONG
         Window Handle: HWND
         Number: INT
         Number: LONG

   Function: SetLayeredWindowAttributes
      Export Ordinal: 0
         Number: LONG
         Number: LONG
         Number: LONG
         Number: BYTE
         Number: LONG

and declare these constants :

   Number: GWL_EXSTYLE     = -20
   Number: WS_EX_LAYERED   = 0x80000
   Number: LWA_COLORKEY    = 0x0001
   Number: LWA_ALPHA       = 0x0002

Next a sample to set the opacity (transparency) of a window:

   On SAM_Create
      ! First enable the window to be layered by setting the layered style
      Call SetWindowLongA( hWndForm, GWL_EXSTYLE, GetWindowLongA( hWndForm, GWL_EXSTYLE ) | WS_EX_LAYERED )
      ! Now set the opacity to 200 using the LWA_ALPHA flag
      Call SetLayeredWindowAttributes( SalWindowHandleToNumber( hWndForm ), 0, 200, LWA_ALPHA )

Next a sample to set the color key of a window. In this case all red colored objects are made transparent

   On SAM_Create
      ! First enable the window to be layered by setting the layered style
      Call SetWindowLongA( hWndForm, GWL_EXSTYLE, GetWindowLongA( hWndForm, GWL_EXSTYLE ) | WS_EX_LAYERED )
      ! Now set the color key to red using the LWA_COLORKEY flag
      Call SetLayeredWindowAttributes( SalWindowHandleToNumber( hWndForm ), COLOR_Red, 0, LWA_COLORKEY )

And now combine both

   On SAM_Create
      ! First enable the window to be layered by setting the layered style
      Call SetWindowLongA( hWndForm, GWL_EXSTYLE, GetWindowLongA( hWndForm, GWL_EXSTYLE ) | WS_EX_LAYERED )
      ! Now set the color key to red and opacity to 200 combining the LWA_ALPHA and LWA_COLORKEY flags
      Call SetLayeredWindowAttributes( SalWindowHandleToNumber( hWndForm ), COLOR_Red, 200, LWA_ALPHA | LWA_COLORKEY )

Starting from TD 5.1 and higher, you must use hWndFrame instead of hWndForm when calling SetWindowLongA and SetLayeredWindowAttributes.

Here you can download a sample (containing also hWndFrame version):
Down.png WIKI_OpacityAndColorKeyWindow.zip

A specific version for TD x64 (TD 7.0 and up) is created using GetWindowLongPtr and SetWindowLongPtr:
Down.png WIKI_OpacityAndColorKeyWindow_for_TD(x64).zip

Pointer2.png How to set a toplevel window topmost Pointer.png

TopMost means that the top level window is placed on top of all other windows.
When you click on another window (even other windows of different running applications) the window will stay on top.
(except other windows which are set to topmost).

Declare the WinApi function SetWindowPos

Library name: USER32.DLL
   Function: SetWindowPos
      Export Ordinal: 0
         Window Handle: HWND
         Window Handle: HWND
         Number: INT
         Number: INT
         Number: INT
         Number: INT
         Number: UINT

Also define these constants

   Number: HWND_NOTOPMOST    = -2
   Number: HWND_TOPMOST      = -1
   Number: SWP_NOMOVE        = 0x0002
   Number: SWP_NOSIZE        = 0x0001

Now, here the code to toggle the top-level window topmost:

   ! Next line sets the form topmost
   Call SetWindowPos( hWndForm, SalNumberToWindowHandle( HWND_TOPMOST ), NUMBER_Null, NUMBER_Null, NUMBER_Null, NUMBER_Null, SWP_NOMOVE | SWP_NOSIZE )
   ! Next line sets the form notopmost
   Call SetWindowPos( hWndForm, SalNumberToWindowHandle( HWND_NOTOPMOST ), NUMBER_Null, NUMBER_Null, NUMBER_Null, NUMBER_Null, SWP_NOMOVE | SWP_NOSIZE )

When a window has accessories enabled (eg a toolbar or statusbar), TD internally creates an AccFrame window as parent of hWndForm.
The TopMost must be applied then to the AccFrame window instead of hWndForm.
Also, on TD 5.1 and higher, the AccFrame window can also be created without having accessories enabled.

In all cases, the best way to deal with this is to check if the window has an AccFrame as parent.
When there is an AccFrame, use that window handle to set the window TopMost.
When no AccFrame is present, use hWndForm window handle.

How to implement this is explained in this article:
Get AccFrame window handle

The sample on TopMost has implemented the AccFrame check and works on all TD versions.
Here you can download the sample:
Down.png WIKI_SetTopMost_V2.zip

Pointer2.png How to prevent displaying scrollbars on top level windows Pointer.png

At design time you can set pagewidth and pageheight attributes using the attribute inspector.
This setting determines the boundary for GUI objects. You can see the boundary when the form is displayed in layoutmode presented as a dotted line on the canvas.
When a GUI object gets outside the boundary or the window is resized smaller so the boundary will get out of the window dimensions, scrollbars are displayed.

To prevent this, use the following trick to disable the showing of scrollbars :

Set the page width and page height to 0.01".


Both vertical and horizontal scrollbars will never appear.
If you want to retain the displaying of the horizontal scrollbar and not the vertical scrollbar (and visa versa) just set only one attribute to 0.01".

Pointer2.png How to (re)position windows on multi-monitor setups Pointer.png

End-users can have any combination of monitor (display) set-ups. Though one monitor is mostly used, multi-monitor set-ups are becoming more common.
Even when there is only one monitor, there are things to consider when you build TD applications.

You can not rely on the fact that all users will have the same set-up you use when building applications.
The user can have a different resolution and may have installed multiple monitors to increase the working space of the desktop.
It may happen, windows are partly falling off the visual screen or in worst case completely disappear from view.

The purpose of multi-monitors is you can place windows on different monitors to create more space by dragging
them from one monitor to another.
A user-friendly application will remember where the user placed the window so they do not have to relocate the window again and again.
Also, when a window is partly falling off visual view, the window will relocate itself when opened again to become fully visible.

Remember though, that even the same user at the same system can change their set-ups. They can decrease/increase the screen resolution or attach another monitor or remove one.
This means that screen locations for a window which are stored/remembered could be invalid when created and become (partly) invisible.

First a brief explanation what happens to the desktop dimensions when you have a multi-monitor setup (dualview).

Let's assume there are two monitors, set as dualview and they are configured the same in a resolution of 1600*1200 pixels.


One of them is the primary. Mostly the primary is the left monitor where the secondary is placed at the right.
This set-up creates a virtual desktop of 3200*1200. All windows on the primary monitor have a location within the boundary of 0..1600 on the X axis and 0..1200 on the Y axis.
Windows displayed at the secondary monitor are placed in the boundary of 1600..3200 on the X axis and 0..1200 on the Y axis.

So the X and Y location of a window determines where it is placed on the virtual desktop and which monitor will display the window.
First, to get the location and dimensions in pixels of a window you can use the WinAPI function GetWindowRect.
To set a window to a specific location, use SetWindowPos function. When you set the X and Y position of a window programatically using this function,
the values 2000 (X pos) and 0 (Y pos) the window will be displayed at the secondary monitor.

Beware that the X and Y locations can have negative values. When the multi-monitor set-up has the primary monitor at the right side and the secondary is at the left, the X values of the secondary monitor are -1600..0 and the primary has a boundary of 0..1600.
So when setting a window in this set-up to -1000 (X) and 0 (Y pos), it will be displayed at the secondary (left side) monitor.

To make things more complicated, in a multi-monitor set-up the individual monitors can have different resolutions.
For instance, you can have a primary set to 1600*1200 and a secondary to 1024*768.

Multimon 1.gif

X and Y values for a window location may be defined beyond the virtual desktop boundary. The window will then be (partially) invisible. So setting a window to 5000 (X pos) and 0 (Y pos) is valid, but will not be displayed by any monitor when the virtual desktop has only a size of 3200*1200.
It is in fact created but not visible.

To manage the (re)location of windows and display them completely in the current multi-monitor environment seems a difficult task, but in fact it is not that hard.

Using two WinAPI functions (user32.dll), you are able to determine what the correct X and Y position values should be for a window to be displayed :

The function MonitorFromRect has a bounding rectangle (left, top, right, bottom) as input. It determines which monitor fits best to display this rectangle.
When no monitor is available to display it (the coordinates are beyond the virtual desktop size) it will find the nearest one.
It does not matter if the system has just one monitor or multiple ones installed, using this function you will get a handle to the monitor which has the best fit for the rectangle.

Now, when having the monitor handle, using the function GetMonitorInfo you can get the bounding rectangle of the display area within the virtual desktop.
Besides the rectangle with the complete size of the monitor settings you get a rectangle giving the work area.
The work area of the monitor is the rectangle where windows are displayed, so minus the eventual task bar.

Using the supplied rectangle coordinates, you have to check if the window lies within the boundaries. If so, the window can be displayed at the wanted position.
When it lies beyond the boundary, you will have to calculate a new position (X and Y) for the window while keeping account of the current width and height of the window.
This new position will be such that the window will be completely displayed on the monitor, it should not be chopped of view.

Here the calculation for the new position :

   Set nNewXPos = SalNumberMax( nWorkAreaLeft, SalNumberMin( nWorkAreaRight - nWidth, nPreviousXPos ) )
   Set nNewYPos = SalNumberMax( nWorkAreaTop, SalNumberMin( nWorkAreaBottom - nHeight, nPreviousYPos ) )

Then the last action to take is to (re)position the window using the calculated X and Y positions using SetWindowPos.

To show an implementation of this, you can download a sample from the Sample Vault using the link below.
It will show a dialog with current system metric info, the current position and size and information of the monitor currently displaying the dialog.


When dragging the dialog around, this info is updated. So when the dialog is dragged to another monitor, you can see the monitor info for that monitor.
When closing the dialog, the current X and Y position is saved (stored on another window from which you can start the dialog).
When opening the dialog again, it will try to position itself at the last remembered location (eg on a specific monitor).
using the described procedure above, it will determine if that last position is correct. When not, it will be positioned at the nearest best fitting monitor location.

The sample stores the last dialog position on a form. Normally you would store it in the registry or in a cgf/ini file in the personal folder. The principle stays the same.
To test if the dialog positions itself correctly, try to place it on a specific monitor and close it. Then change your display settings (eg resolution) or remove the monitor so is not available.
You will see the dialog when created is displayed at the nearest monitor in the new set-up.

Here you can download the sample:
Down.png WIKI_WindowLocationOnMultipleMonitors.zip

Pointer2.png Why are ported top-level windows bigger in TD versions starting from TD3.0 Pointer.png

Some might have overlooked the TD3.0 release notes on changes in width and height attributes of top-level windows.
Here the reason for this change copied from the releasenotes:

Window size change
Prior to version 3.0 of SQLWindows the attributes Height and Width, for top-level windows, referred to the overall window size, including the client area and the non-client area (title bar, status bar, and borders). In Windows XP, the non-client area elements have a larger default size than in other versions of Windows. Since the overall window size stays the same, this results in less client area, and potential “clipping” of child window elements in your application. So if you developed an application in SQLWindows 2.x or earlier, under Windows 2000, for example, then upgrade to Windows XP but still retain SQLWindows 2.x, you will see this smaller client area. This might introduce scroll bars in client areas that previously didn’t have scroll bars. This effect is related to the operating system, not the version of SQLWindows.

In 3.0, to improve the consistency of SQLWindows applications across versions of Windows, the Height and Width attributes were changed to refer to the client area, so that the client area of a top-level window could remain constant under any Windows version. Functions SalGetWindowSize and SalSetWindowSize are unaffected by this change.

Using the height value in SQLWindows 2.x and earlier (which represented the overall window size), and applying it to the client area in SQLWindows 3.0, would result in a significantly larger client area and a larger overall window, one that might no longer fit the desktop. For this reason, an outline that is migrated to 3.0 undergoes a reduction in the height attribute of every top-level window. SQLWindows 3.0 queries the operating system to get the height of the non-client elements, then reduces the old top-level window height attribute by that amount. The result is a top-level window with exactly the same client area height that it had in the earlier version of SQLWindows.

It is essential to perform this outline migration using the same Windows environment that was used to design the application in version 2.x or earlier. As noted, non-client elements have a different default size in XP than in earlier versions, and it is also possible to set your particular Windows environment to some other, non-default value for sizes of title bars, status bars, etc. When the migration facility of SQLWindows 3.0 queries the operating system to get the heights of the non-client elements, those heights must be consistent with the ones used when the original window height was set. So you must not take an application designed using SQLWindows 2.1 and Windows 2000, and migrate it directly to SQLWindows 3.0 running under XP. Do the migration to SQLWindows 3.0 using the original operating system, then move to the new operating system after the migration is complete.

Remember, the overall goal of the migration facility is to keep the client area of top-level windows consistent with their size in earlier versions of SQLWindows. If your window filled the desktop under Windows 2000, then keeping the client area the same in Windows XP means that your overall window will now be slightly too big for the desktop. So even after a correct migration you may still need to modify the layout of some of your windows.

SalGetWindowSize and SalSetWindowSize deal with the entire window area, including the non-client area. So if you use these functions on a given window under different operating systems, you can expect to see different results, even though the design-time window size specifications were the same in all cases.

Pointer2.png Get AccFrame window handle Pointer.png

When creating a top level window (dialog/form) normally the system variable hWndForm represents the window handle of the top level window.
But when the window has accessories enabled (eg a toolbar or statusbar is shown), TD internally creates an extra layer for
the top level window, named an AccFrame window.
The AccFrame window is the parent of the top level window in this case.

Starting from TD 5.1 and higher, TD uses a 3rd party GUI layer, ProfUIs.
This has introduced that top level windows mostly have this AccFrame layer, so not only when accessories are enabled.
To have a standard way in accessing this new layer, a new system variable is introduced: hWndFrame.

So on TD versions 5.1 and higher, we can use hWndFrame to get the window handle of the AccFrame window.
Another way to get the AccFrame window handle which is compatible with older TD versions is explained here.

The solution without using the new hWndFrame system variable (as is the case in TD versions up to 5.1) is to get the parent window handle
of hWndForm. This parent is the AccFrame window.

The TD function SalParentWindow can not be used here. This function gets the parent of the window as defined in SalCreateWindow/SalModalDialog.
The AccFrame window can only be fetched by using a WinApi function, GetParent:

Library name: USER32.DLL
   Function: GetParent
      Export Ordinal: 0
         Window Handle: HWND
         Window Handle: HWND

So this will get the parent of hWndForm:

   Set hWndAccFrameWindow = GetParent( hWndForm )

But, to be sure that the parent window is in fact an AccFrame, we have to check if that is the case.
By using the TD function SalGetType, we can check if the window is an AccFrame against the constant TYPE_AccFrame.

So this makes:

   Set hWndAccFrameWindow = GetParent( hWndForm )
   If SalGetType( hWndAccFrameWindow ) = TYPE_AccFrame
      ! Yes, the parent is an AccFrame
      ! No, parent does not exist or is another type

Having this, you can determine the window handle of AccFrame.
On TD 5.1 and higher, the hWndFrame system variable will contain the same window handle value as the window handle determined in the code above.

The complete functionality is coded in a custom function :

   hWndAccFrame = PALGetAccFrameWindow( hWndWindow )

   Returns the window handle of the AccFrame level when present.
   When the window itself is an AccFrame, this window handle will be returned

This PAL function will work on all TD versions.

Here you can download the sample, using PALGetAccFrameWindow:
Down.png WIKI_GetAccFrameWindow.zip