Retrieve Windows Font Specification

by Karl E. Peterson

  Q. Retrieve System Metrics
How can I retrieve the Windows system fonts specification? I'd like to set the Font properties of numerous controls on my forms to match it.

Ask the VB Pro provides you with free advice on programming obstacles, techniques, and ideas. Read more answers from our crack VB pros here. You can submit your questions, tips, or ideas on the site, or access a comprehensive database of previously answered questions.
A. One simple API call returns information on a host of system metrics, including those for four specific system fonts. You can call SystemParametersInfo, passing SPI_GETNONCLIENTMETRICS as the desired action. This call returns a NONCLIENTMETRICS structure packed with useful data.

The NONCLIENTMETRICS structure holds four LOGFONT structures, representing the Caption, Menu, Message, and Small Caption fonts used by Windows. To use this information, I wrapped this API call in a class module that converts any specified system font to an OLE StdFont object (see Listing 1). You can assign this object directly to the Font property of any control or form:

Dim ncm As CNonClientMetrics
Set ncm = New CNonClientMetrics
Set Me.Font = ncm.MessageFont

LOGFONT structures map neatly to a StdFont object's properties. My CreateFont function creates a new StdFont object and assigns most of its properties directly from the corresponding LOGFONT element. One difficulty is Windows' definition of logical font sizes. Pass the lfHeight element of a LOGFONT structure to the HeightToPixels function, which converts the lfHeight value to a valid point size using a simple formula that considers the number of pixels per logical vertical inch.

Other properties of the CNonClientMetrics class return values for items, such as height and width of both regular and small caption buttons, menu height, and standard scrollbar height and width.

Q. Obtain Alt+Tab Order
I'd like to emulate an Alt+Tab keypress in code, sending focus to the next app in Windows' tab order. I tried doing it with SendKeys, but that only works in NT. I've played with EnumWindows and numerous other APIs, but haven't found the right combination of calls. How can I do this?

A. In a previous column [Ask the VB Pro, February 1999], I told how to obtain the task list using EnumWindows, so I should probably take partial blame for at least one of your dead ends. My demo used a variety of tests to narrow down the list of all windows to just those that Windows displays when you press and hold Alt+Tab. You can use the same set of tests here, but you still need to obtain the correct order to present the list in.

To solve this problem, Jonathan Wood provided me with the necessary clue—a GetWindow loop returns the system Z order list. By putting this fact together with the tests from my previous column, you can write a quick routine that outputs the Alt+Tab order to VB's Immediate window (see Listing 2).

The criteria Windows uses to display "tasks"—a term that means different things in different contexts—aren't documented, but this approach is close. Given an hWnd, test for these conditions:

  • Visible, with IsWindowVisible
  • No parent, with GetParent
  • No owner, with GetWindowLong
  • Caption length greater than zero, with GetWindowText

    These tests rule out most windows encountered in a GetWindow loop, and any window making it this far is probably a valid task. Two special cases need further attention. Tooltip windows created by VB apps pass the above tests, but you can weed them out by checking their class name with GetClassName. And finally—a blast from the past—Windows creates a "Program Manager" task that displays the desktop and Dynamic Data Exchange (DDE) conversations with older apps. The Progman task presents itself as a system listview containing all the desktop icons. Progman.exe still ships with most versions of Windows, and you can also run it as a standalone app. To exclude the system task, check for the absence of the WS_OVERLAPPEDWINDOW style.

    You can now use the data gathered during a GetWindow loop. In your case, you might want to bail from the loop and return the Caption of the first window that meets all the criteria. Emulate an Alt+Tab keypress in code by passing the returned caption to AppActivate or by passing hWnd to SetForegroundWindow.

    Q. Change Your Application Icon
    My application communicates unobtrusively with the user by changing the main form's icon to represent its current state. I've noticed that although the taskbar icon is updated when I change the form's icon, the icon shown in the Alt+Tab dialog isn't similarly updated. How can I force a new icon to be used consistently?

    A. VB applications always include a hidden top-level window that owns all other forms and sinks needed messages from Windows. You must change this window's icon by setting your main form's Icon to the desired image. Then pass that form, either standard or MDI, to my SetAppIcon routine (see Listing 3):

    ' Adjust icon for form
    Set Me.Icon = Image1(Index).Picture
    ' Force app icon update
    Call SetAppIcon(Me)

    SetAppIcon begins by walking up the owner chain until it reaches the top. A SendMessage call then requests your form's icon handle with WM_GETICON. If none is assigned, LoadIcon obtains the handle to a shared copy of the default waving-flag logo icon. hIcon is then assigned to your app's top-level window with WM_SETICON, and SetAppIcon makes another WM_GETICON call to verify the change.

    Karl E. Peterson is a GIS analyst with a regional transportation planning agency and serves as a member of the Visual Basic Programmer's Journal Technical Review and Editorial Advisory Boards. Online, he's a Microsoft MVP and a section leader on the DevX newsgroups. Find more of Karl's VB samples at

    • Karl E. Peterson's Ask the VB Pro column, "Force Your Way to the Foreground" [VBPJ February 1999]
    Original Code
      Get the code for this article here.
    Updated Code