July 1998

Message-handling for non-visual components

by Kent Reisdorph

A non-visual component must occasionally respond to either Windows operating system or user-defined messages. However, a non-visual component has no window and, therefore, no window handle. And without a window handle, it can't receive messages. In this article, we'll explain how to create a hidden window so your non-visual components can receive messages.

What you'll need...

In order to create a hidden window for your component, you'll need the following:
bulletA private variable of type HWnd to hold the window handle.
bulletA function to catch the messages that Windows will send to the component (a WndProc).
bulletA call to AllocateHWnd to create the window handle and set up the WndProc.
That's really all there is to it. Since you'll want to see this process in action, let's build a simple component to illustrate this concept.

Creating the component

First, we'll create a new component. Choose File | New... from the C++Builder main menu and double-click the Component icon in order to display the New Component dialog. Change the Ancestor type to TComponent and the Class Name to TTest, as shown in Figure A.

Figure A: Select the ancestor and name of your new class.
[ Figure A ]

The Palette Page field is already set to Samples, so we'll leave that field alone. Click OK to create the component.

Now we have a new unit containing the basic source code for our component. (The code generated for the component will vary slightly depending on whether you're using C++Builder 1 or C++Builder 3. The differences aren't important for this discussion, so we won't go over them here.) Go ahead and save your work. Choose File | Save All to open the Save As dialog box. Save Unit1.cpp and Unit1.h as TestBCB.cpp and TestBCB.h, respectively.

Switch to the component's header file. Place the following declarations in the private section of the class declaration:

HWnd FHandle;
void __fastcall WndProc (TMessage& Msg);
The first line declares an HWnd variable called FHandle. This variable will hold the window handle after the window is created. The second line declares the WndProc function that will receive the messages. The function declaration must have the signature shown here in order to qualify as a WndProc. Next, move down to the public section of the class declaration and add this line below the constructor declaration:
void DoIt();
This is a public function that we'll use to test the component. Your class declaration should now look like this (if you're using C++Builder 1, the declaration won't have the PACKAGE modifier):
class PACKAGE TTest : public 
	TComponent
{
private:
    HWnd FHandle;
    void __fastcall WndProc
	(TMessage& Msg);
protected:
public:
    __fastcall TTest
	(TComponent* Owner);
    void DoIt();
__published:
};
Now switch to the component's source unit. Enter the following line near the top of the unit (just above the ValidCtrCheck function is probably a good place):
#define MY_MESSAGE WM_USER + 1
This line declares a user-defined message that the component will send to itself when the DoIt function is called. At this point, we must allocate a window handle for the component. This handle will provide a hidden window that we can use to catch messages in the component. Locate the component's constructor, and add a line so that the constructor looks like this:
__fastcall Test::Test
	(TComponent* Owner)
	: TComponent(Owner)
{
  FHandle = AllocateHWnd
	(WndProc);
}
That's all there is to that particular step. The AllocateHWnd function creates a hidden window and returns the window handle of the hidden window. Notice we pass the address of the WndProc function so that Windows knows where to send any messages. Speaking of WndProc, let's create that function next. Add this code to your source file:
void __fastcall TTest::WndProc
	(TMessage& Msg)
{
  if (Msg.Msg == MY_MESSAGE)
    MessageBox(0, "Got here!",
	 "Message", 0);
  try {
    Dispatch(&Msg);
  }
  catch (...) {
    Application->
	HandleException(this);
  }
}
Windows calls this function whenever Windows sends a message to the component. The code in this function does two things. First, it checks to see whether the message received is our user-defined message, MY_MESSAGE. If it is, a message box is displayed so you can see that the message was, in fact, received.

In addition, this code passes the message on for processing by the system (or by VCL). The Dispatch function performs this service. The try/catch block is used to ensure that, in the event an exception is thrown, it will be handled in the default manner. In a nutshell, the WndProc function watches for our custom message, while passing all other messages on for default handling.

Now we only have to create the DoIt function, and our component will be finished. Add this code to your source unit:

void TTest::DoIt()
{
  PostMessage(FHandle, 
	MY_MESSAGE, 0, 0);
}
This function simply posts a message to the component's window handle (remember, the window handle was previously saved in the FHandle data member). We've finished creating the component. Select File | Close All to save your work. You'll be prompted to save any outstanding changes.

Testing the component

Naturally, our next step is to test the component. If you're using C++Builder 1, just install the component to the component palette using Component | Install. However, if you're using C++Builder 3, then you must add the component to a package, again using Component | Install. (You can use the DCLSTD35 package for quick tests like this.) In either case, select the TestBCB.cpp file you just saved. Once you've installed the component, it will show up on the component palette. It has a default icon, of course, but that won't stop you from testing it. Drop the TTest component and a regular button component on a form. Double-click the button and enter this code in the OnClick event handler for the button:
Test1->DoIt();
Now, run the program. When you click the button, you should see a message box that says, "Got here!". Sure enough, the component works as advertised. Listings A and B contain the header and the source code for the TTest component. The code provided is for the C++Builder 3 version of the component. You can download both versions from our Web site at www.cobb.com/cpb; click on the Source Code hyperlink.

Conclusion

A non-visual component that can respond to Windows messages has many uses. The most obvious are for components, which encapsulate some aspect of the Windows API. For example, both TAPI and Winsock send messages to inform the user of events. If you write a component that encapsulates one of these APIs, you'll need a way to catch the messages that Windows sends. Adding a hidden window to your component allows you to do just that.

Listing A: TESTBCB.H

//-------------------------
	--------------------
#ifndef TestBCBH
#define TestBCBH
//-------------------------
	--------------------
#include <SysUtils.hpp>
#include <Controls.hpp>
#include <Classes.hpp>
#include <Forms.hpp>
//-------------------------
	--------------------
class PACKAGE TTest : public TComponent
{
  private:
    HWnd FHandle;
    void __fastcall WndProc(TMessage& Msg);
  protected:
  public:
    __fastcall TTest (TComponent* Owner);
    void DoIt();
  __published:
};
//---------------------------------------------
#endif
Listing B: TESTBCB.CPP
//---------------------------------------------
#include <vcl.h>
#pragma hdrstop

#include "TestBCB.h"
#pragma package(smart_init)

// Declare a user-defined message.
#define MY_MESSAGE WM_USER + 1

//---------------------------------------------
// ValidCtrCheck is used to assure that 
// the components created do not have
// any pure virtual functions.

static inline void ValidCtrCheck(TTest *)
{
  new TTest(NULL);
}
//---------------------------------------------
__fastcall TTest::TTest
	(TComponent* Owner)
    : TComponent(Owner)
{
  // Allocate a hidden window for the component.
  // We store the return value in FHandle, and
  // pass the WndProc function as a parameter.
  FHandle = AllocateHWnd(WndProc);
}
//---------------------------------------------
void __fastcall TTest::WndProc(TMessage& Msg)
{
  // Respond to the MY_MESSAGE message, and do
  // default handling for all other messages.
  if (Msg.Msg == MY_MESSAGE)
    MessageBox(0, "Got here!", "Message", 0);
  try {
    Dispatch(&Msg);
  }
  catch (...) {
    Application->HandleException(this);
  }
}

void TTest::DoIt()
{
  // This public function sends a user-defined
  // message to the component's hidden window.
  // This will result in the WndProc function
  // being called.

  PostMessage(FHandle, MY_MESSAGE, 0, 0);
}

// The usual registration stuff
namespace Testbcb
{
  void __fastcall PACKAGE Register()
  {
    TComponentClass classes[1] = 
      {__classid(TTest)};
      RegisterComponents
      ("Samples", classes, 0);}
}
//---------------------------------------------

Kent Reisdorph is a editor of the C++Builder Developer's Journal as well as director of systems and services at TurboPower Software Company, and a member of TeamB, Borland's volunteer online support group. He's the author of Teach Yourself C++Builder in 21 Days and Teach Yourself C++Builder in 14 Days. You can contact Kent at editor@bridgespublishing.com.