Working with Windows Messages in .NET - ' Capturing Windows Messages ' (
Page 3 of 4 )
Capturing Windows Messages
If you spent several hours with Spy++, you'd quickly conclude that Windows and
the applications it hosts are throwing messages all over the place. In fact,
there are too many messages for most applications to use, and even Windows
ignores many of them.
ADVERTISEMENT
The point is to monitor the message traffic and to extract
only the messages you need. The .NET Framework already monitors a host of
messages for you. Every time someone clicks a button, the system generates the
four messages shown in Figure 3.
The .NET Framework monitors the message traffic
and automatically generates a WM_COMMAND message for you that results in an
event. If you have an event handler set up for the control, the system directs
the message to it.
Now, think about your .NET code for a moment. The Sender
argument that appears as part of every event handle actually obtains a copy of
the Sender object using the FromHandle() method associated with that control
type. If you look at the .NET documentation, you see that this method accepts a
handle in the form of an IntPtr. The IntPtr value comes from the WM_COMMAND
message, as shown in Figure 3.
Understandably, the .NET Framework doesn't capture every message, so you might
encounter a few messages that you really need for your application that the .NET
Framework doesn't support. For example, you might not always need to know when
the system is going to shut down, but sometimes it's a handy piece of information
when the user has an open document she needs to save. Unfortunately, the .NET
Framework doesn't monitor this message, so you might think you're out of luck.
Fortunately, the .NET Framework does provide a convenient method to add support
to your application for this need, as shown in Listing 1.
Listing 1: Adding Support for the Windows Shutdown Event
// Create a delegate for the custom shutdown event.
public delegate void DoSDCheck(
object sender, System.EventArgs e);
// Create an event based on the delegate.
public static event DoSDCheck ThisSDCheck;
// Define an event handler for the custom event.
public static void HandleShutdown(
object sender, System.EventArgs e)
{
// Display a simple message box.
MessageBox.Show("The system is about to shut down.");
}
The code shows the order in which you should add the code to your application.
Begin by creating a custom delegate to generate the internal event based on the
Windows shutdown message. Once you create the delegate, create an event based on
that delegate. Finally, create an event handler to handle the events generated
by the message. You glue all of these pieces together by adding a single line of
code to the application constructor, as shown here.
// Add the event handler to the event.
ThisSDCheck += new DoSDCheck(HandleShutdown);
Now that you have all of the pieces in place for reacting to the event, you need
some means of generating the event. You need two pieces of information to
perform this task. First, you'll need the messages that the application wants to
monitor. Second, you'll need to override the WndProc() method that performs the
task of monitoring messages for your application. This particular method is part
of the .NET message pump, a component of your application that processes
messages. Listing 2 shows both of these pieces.
Listing 2: Monitoring New Messages in an Application
// We need to know which message to monitor.
public const Int32 WM_QUERYENDSESSION = 0x0011;
public const Int32 WM_ENDSESSION = 0x0016;
// Override the default message processing to add
// the new event.
protected override void WndProc(ref Message ThisMsg)
{
// See which message Windows has passed.
if ((ThisMsg.Msg == WM_QUERYENDSESSION) ||
(ThisMsg.Msg == WM_ENDSESSION))
{
// Fire the event.
ThisSDCheck.Invoke(this, null);
// No more processing needed.
return;
}
// If this isn't an session end message, then pass the
// data onto the base WndProc() method. You must do this
// or your application won't do anything.
base.WndProc(ref ThisMsg);
}
Windows generates the WM_QUERYENDSESSION and WM_ENDSESSION before it ends the
current session. Before you accuse me of grabbing values out of thin air, you
need to consider how to obtain these message values for yourself. Generally, you
need to look through the Visual C++ .h (header) files, because Microsoft doesn't
document the values in MSDN. In this case, you'll find the values in WinUser.h,
which is probably the most important header file for you to check. Of course,
you might wonder why I went to the extra trouble of even defining these values
as shown. You want to make it clear which messages you want to monitor, so it's
important that you go through this extra documentation step.
The code to override WndProc() monitors just the two additional messages we need
for this example. If it detects either message, the code invokes the event, and
you see a message box displayed on screen.
There's more to the ThisMsg object than meets the eye. The object
includes the WParam, LParam, and HWnd values shown in Figure 3. In addition, you
can return a result, in many cases, using the Result property. The Result
property brings up an important difference between the WM_QUERYENDSESSION and
WM_ENDSESSION messages. When working with the WM_QUERYENDSESSION message, you
can return True or False in the Result property. When your application returns
False, it means that it can't terminate, and Windows will stop the shutdown
process. On the other hand, the WM_ENDSESSION message tells you that Windows is
going to shut down whether you want it to or not, and you'd better prepare your
application as best as you can.
Always call base.WndProc() when you can't process a message yourself. Failure to
make this call will cause your application to freeze. Absolutely nothing will
work. Buttons won't push, menus won't operate, nothing will happen besides the
actions you have programmed as part of your override. This is one exceptionally
important line of code and you should always check for its presence before you
begin testing.