Hardcore Clarion - Sub-Classed Windows

by Paul Attryde

Published 1998-09-01    Printer-friendly version

Download the code here

How many times have you tried to turn your computer off when there's a Clarion program running, only to be told "Application still active. Quit the application before quitting Windows"? You can be sure that if it's happened to you then it's probably happened to your clients as well.

There is a solution, however, and it's called sub-classing. Sub-classing involves writing a procedure that gets events before the Clarion Accept() loop. If you're working with some special software, hardware, or just want more control over your application, you can get the events that Windows is sending to your application, instead of having the Clarion Accept() loop throw them away.

You can sub-class both windows and application frames, but only once for each window. If you try to sub-class the same window or application frame twice things turn pear-shaped rather rapidly.

To sub-class a window, you first need a procedure to receive the Windows events. That procedure takes four parameters - the handle of window, a message identifier, the first message parameter, and the second message parameter. The actual prototype of this procedure is different between 16-bit and 32-bit applications, and that's an easy thing to forget when you change your existing 16-bit application to 32-bit. If it stops working, check the prototype. It's a mistake everybody makes, no matter how good they are.

Next you'll need to prototype 2 Windows API functions, SetWindowLong and CallWindowProc. Again, the 16-bit and 32-bit prototypes are the same in the sense that they accept the same number of parameters, but it's the type of the parameters that differs.

The SetWindowLong function is used to change an attribute of the given window. One of the attributes is the address for the window procedure IE the procedure that receives the events that are sent to that window.

To actually install your new procedure, you need to insert some code between where the window is opened and the Accept() statement. It doesn't matter where, just anywhere in between will do.

Assuming the window is called TestWindow, and the new sub-class procedure is called SubClassProc, the code would be:

OPEN(TestWindow)
ReturnAddr = SetWindowLong(TestWindow{PROP:Handle},GWL_WNDPROC,
ADDRESS(SubClassProc))
ACCEPT
  CASE EVENT()

(GWL_WNDPROC is just an equate that Windows uses to determine which attribute of the window is changing).

In later versions of Clarion there are properties that can accomplish the same thing - Prop:WndProc and Prop:ClientWndProc. To use those you'd simply do the following:

OPEN(TestWindow)
ReturnAddr = TestWindow{Prop:WndProc}
TestWindow{Prop:WndProc} = ADDRESS(SubClassProc)
ACCEPT
  CASE EVENT()

You have to remember to save the current procedure first, otherwise you can't pass on any events that you don't want to handle in your sub-classed procedure. Now, the smart ones amongst you will have noticed that there are 2 properties, and you're eager to know what the difference is. The honest answer - I haven't a clue. I started using the Windows API and sub-classed procedures before the properties were available, and I'm the kind of guy who thinks that if it ain't broke don't fix it.

The other Windows API function, CallWindowProc, is used by your new procedure to pass any events that you don't want to handle to the previously installed window procedure - in this case, the Clarion Accept() loop.

In our example, we want the application to close down automatically when we turn the computer off. When we try to do that, Windows will send an event to every application that is currently running. The event is WM_QUERYENDSESSION, and to quote from the API documentation:

  • "The WM_QUERYENDSESSION message is sent when the user chooses to end the Windows session or when an application calls the ExitWindows function. If any application returns zero, the Windows session is not ended. Windows stops sending WM_QUERYENDSESSION messages as soon as one application returns zero."

So, our sub-class procedure will have to handle WM_QUERYENDSESSION events. It will also have to handle WM_ENDSESSION events:

  • "The WM_ENDSESSION message is sent to an application after Windows processes the results of the WM_QUERYENDSESSION message. The WM_ENDSESSION message informs the application whether the Windows session is ending."

So, our sub-classed procedure will look for both types of event, and return TRUE if it receives either one. We don't want to worry about all the other types of events we may receive, so we pass those to the previous window procedure in the chain.

Our actual procedure looks rather similar (exactly the same, in fact) to the following:

SubClassProc       FUNCTION(Prm:hWnd,Prm:wMsg,Prm:wParam,Prm:lParam)
WM_QUERYENDSESSION EQUATE(00011H)
WM_ENDSESSION      EQUATE(00016H)
  CODE
  Case Prm:wMsg
  OF WM_QUERYENDSESSION
    ! If you get a query end session message, it means
    ! Windows wants to close the app.
    ! So, if you return TRUE, you will receive a WM_ENDSESSION event.
    ! If you return FALSE, Windows will not shut down.
    RETURN(TRUE)
  OF WM_ENDSESSION
    ! Windows wants to shut the application down.
    RETURN(TRUE)
  ELSE
    ! Pass the event and its paramters to the
    ! previous window procedure in the chain
    RETURN(CallWindowProc(ReturnAddr,Prm:hWnd,Prm:wMsg,Prm:wParam,Prm:lParam))
  END

Caveats

Now, after having announced how wonderful sub-classed procedures are, there are a few things you should definitely try to avoid:

Don't use Message or Stop in a sub-class procedure - if you're receiving events every 10 seconds but the user takes a minute to see and close the message before the next event arrives, you will have problems.

(This applies to any procedures the sub-class procedure can call, not just the procedure itself). Maybe the problems won't be immediately obvious, but eventually it'll fall over with a resounding crash.

The sub-class procedure should execute as quickly as possible - again, if you receive events every 10 seconds, but your code takes 30 seconds to execute, then you've got problems. The code should process only what it needs to, just as quickly as possible, before returning to the OS.

Watch out for local data declarations. Every time the procedure runs (and you won't know when that is) it'll have to declare, and possibly initialize, all its local data. If you declare variables left and right and don't think it through, pretty soon it'll be declaring 10K of garbage every time. Try to minimise the number of local variables - if you dislike global variables, declare them at the module level instead.

Summary

We've seen how you can use the Windows API to sub-class a window and receive events that you wouldn't normally receive. All you need to remember is to prototype the functions properly, and that the prototypes nearly always change between 16-bit and 32-bit applications.

As for sub-classed procedures - well, this is only a quick introduction to the subject. In certain situations they can be very useful. But also remember that whenever they are called, they are out of context from the rest of your application, and you must take care to code them properly.

Printer-friendly version

 
 

Search

 

Advanced Search
Topical Index

Related Articles

Subscribe to
ClarionMag

One year: $189

(includes all back issues since '99)

Renewals from $139

Two years: $289

Renewals from $239

More Info

Subscribe Now!

ClarionMag Blog

RSS Feeds

Updates via Email

Enter your Email


Powered by FeedBlitz

Quick Links