Finding a Window with WINAPI

By Mike Hanson

Posted March 1 1999

Printer-friendly version

A number of people have described methods for ensuring that only one instance of a particular procedure is active at a time. This prevents users from repeatedly clicking a button on the toolbar, and getting numerous instances of the same procedure. These all work based upon the use of a non-threaded, static, local variable, which is shared by all threads of that particular procedure. When the first instance is started, it saves its thread number in that variable. When others are started while the first is still active, they each see the current thread number, post a message to that thread to tell it to come to the front, then they exit themselves.

However, this "shared variable" technique won't work to ensure that only one copy of your application is running at once. In this article, I will be discussing the solution to this problem. You can also utilize the same method to determine if a completely different APP is running. It's all done with the magic of the Windows API.

If we were creating applications in C/C++ instead of Clarion, we would hate the complexities of the Windows API. As it is, though, Clarion hides most of this complexity from us, leaving us to be productive in our happy little bubble. There are times, however, that we need to break out of that secure haven, and this is one of them.

The MAP and the WINAPI Utility

Whenever you wish to access the WINAPI, you must tell your application about the function(s) that you'll be calling. Adding a section to the global MAP does this. The hard part is to determine what you should add. Once you've decided which functions that you need to call, run a little program called WINAPI.EXE in C:\CLARION5\EXAMPLES\RESOURCE\WINAPI. It will tell you the Clarion equivalents for most of the WINAPI function prototypes, data structures, and constants. The utility will automatically create a WINAPI.CLW for you to include in your APP, but I normally just cut and paste from the preview text box into my editor.

In our situation we will be using "FindWindow" and "SetForegroundWindow". According to the WINAPI utility, the prototype for FindWindow is:

         OMIT('***',_WIDTH32_)
       FindWindow(*LPCSTR, *LPCSTR), HWND, PASCAL, RAW
         ***
         COMPILE('***',_WIDTH32_)
      FindWindow(*LPCSTR, *LPCSTR), HWND, PASCAL, RAW,
NAME('FindWindowA')
           ***

The prototype for SetForegroundWindow is:

SetForegroundWindow(HWND),BOOL,PASCAL

Notice that each FindWindow has a different prototype for 32 and 16-bit, while SetForgroundWindow doesn't. In some cases WINAPI functions have the same prototype for 32 and 16-bit, but in this case SetForegroundWindow is available only to 32-bit programs. Of course, the issue of running concurrent versions of a program is only an issue in 32-bit. If you try to compile this into a 16-bit program, you'll get a linker error (SetForegroundWindow is unresolved).

I normally put the prototypes together into a single module definition:

    MODULE('Windows API')
        OMIT('***',_WIDTH32_)
      FindWindow(*LPCSTR, *LPCSTR), HWND, PASCAL, RAW
        ***
        COMPILE('***',_WIDTH32_)
      FindWindow(*LPCSTR, *LPCSTR), HWND, PASCAL, RAW,
NAME('FindWindowA')
        ***
      SetForegroundWindow(HWND), BOOL, PASCAL
    END!MODULE

Now, you are probably looking at the various elements like "LPCSTR" and "HWD", and wondering, "What in heck are those?" Well they, too, are defined in the WINAPI utility. You have a choice, you can either include the definitions for these equates before the MAP section, or you can manually substitute them yourself. In this case, I choose to do the latter. Here are the new prototypes with the standard Clarion keywords:

    MODULE('Windows API')
        OMIT('***',_WIDTH32_)
      FindWindow(LONG, *CSTRING), UNSIGNED, PASCAL, RAW
        ***
        COMPILE('***',_WIDTH32_)
      FindWindow(LONG, *CSTRING), UNSIGNED, PASCAL, RAW,
NAME('FindWindowA')
        ***
      SetForegroundWindow(UNSIGNED), SIGNED, PROC, PASCAL
    END!MODULE

I've also added the PROC attribute, because I won't be using the return value from the SetForegroundWindow function.

Finding the Window

To find out whether a window is open, we use FindWindow. It will find any top-level window (i.e. program). It looks for the text in the window's caption bar. This means that you must know what it's going to say (including the title of the MDI child window information, appended when an MDI child is maximized in a frame). If you remember from above, the FindWindow procedure requires a CSTRING with the text to be found. Therefore, we must have a local CSTRING variable to hold this phrase (let's call it "wName"). You also need a UNSIGNED variable to record the return value (let's call it "hWnd"). The code looks like this:

WName CSTRING(200)
hWnd UNSIGNED
  CODE
  wName = 'Window Title'
  hWnd = FindWindow(0, wName)

If hWnd is zero, then the window wasn't found. Otherwise, it contains a handle to the desired window. If we are trying to solve the initial problem mentioned at the start of this article (ensuring that only one copy of a program is running), then your code might look like this:

IF hWnd
  SetForegroundWindow(hWnd)
  HALT
END!IF

Conclusion

Of course, we are not limited to this one purpose. One of my customers was calling Acrobat to display a document stored in a temporary file. As soon as Acrobat exited, he wanted to delete the file. Using a timer and the FindWindow function, he achieved his desired results. That's only one example of a plethora of possible applications for this technique.

There are also a variety of other functions that can be used like EnumWindows, which lets you know about all top level (i.e. "program") windows. And FindWindowEx finds a child window, once you know the handle of its parent.

To really benefit from the Windows API, you should have good references on hand. I use two books, Windows API Bible from James Conger and Programming Windows by Charles Petzold, along with the excellent on-line docs included with Microsoft's C++ IDE. These will give you all the information and examples that you'll need. So don't be afraid to experiment. Have fun!

Mike Hanson is affiliated with BoxSoft, which produces the "Super" series of templates, distributed through Mitten Software. He has been creating add-on products for Clarion since his Public Domain Models for CPD 2.0 back in 1988. He's also written articles for every Clarion-related publication, and has spoken at numerous conferences and training seminars. If you have any questions, you can reach him via www.boxsoft.net.

Article comments

Post a comment

You must be logged on to post comments.

Clarion Roadmap

Try the roadmap (beta)

Search ClarionMag

 

Advanced search

From the archives

Sending Clarion Reports as Email Attachments (Part 1)

1/9/2001 12:00:00 AM

The email capability in version 5.5 is a nice addition to the Clarion toolset. What is still missing however, is the ability to easily send a report as an email attachment. In this article David Potter demonstrates one possible solution to this problem. Part 1 of 2.