May 1999

A simple fly-over label component

by Mark G. Wiseman
If you've never written a component for C++Builder, the simple component we create in this article will be a great one to start with. If you're an experienced component writer, you'll be delighted with the usefulness and flexibility of this component.


What's a fly-over label?

Originally, the design of this component called for a label that would change color when the mouse was over it. The final design turned out to be more powerful, allowing you to do just about anything when the mouse passed over the component. This is accomplished by adding two events OnMouseEnter, which fires when the mouse first moves over the label, and OnMouseLeave, which fires when the mouse moves off the label.

Component construction

We'll start by letting C++Builder write some of the code for us. Let's create a new component by selecting the File | New menu item in the IDE. Double-click on Component in the New tab. The New Component dialog box will appear. In the dialog box, select TCustomLabel from the dropdown list for Ancestor Type. Enter TOLabel for the name of our new component and then enter ZDJ for the name of the Palette Page. Click the OK button and C++Builder will write most of the code for TOLabel. The example program shown in Figure A uses several instances of TOLabel.

Figure A: This is the example program using TOLabel.
[ Figure A ]

All we have to do to finish TOLabel is add the two mouse-aware events and publish those properties and events that we want users of TOLabel component to have access to.

So, how do we know when the mouse enters TOLabel's space and when it leaves that space? Inprise has again done most of the work for us. The VCL sends us two messages, CM_MOUSEENTER and CM_MOUSELEAVE. Can you guess what they do?

We need to catch these two messages. We can do this with a message map:

BEGIN_MESSAGE_MAP
	MESSAGE_HANDLER(CM_MOUSEENTER, 
	TMessage, CMMouseEnter)
	MESSAGE_HANDLER(CM_MOUSELEAVE, 
	TMessage, CMMouseLeave)
	END_MESSAGE_MAP(TCustomLabel)

When TOLabel receives the messages CM_MOUSEENTER and CM_MOUSELEAVE, the message map will call the functions CMMouseEnter() and CMMouseLeave(), respectively. These two functions simply call a user-supplied event, if one exists, passing the address of the TOLabel object that received the message:

void __fastcall TOLabel::CMMouseEnter(
	Messages::TMessage &Message)
	{
	if (FOnMouseEnter) FOnMouseEnter(
	this);
	}

void __fastcall TOLabel::CMMouseLeave(
	Messages::TMessage &Message)
	{
	if (FOnMouseLeave) FOnMouseLeave(
	this);
	}

FOnMouseEnter and FOnMouseLeave are data members of TOLabel that hold the user-assigned functions for the events. The user assigns the values to FOnMouseEnter and FOnMouseLeave through two properties published by TOLabel:

__property TNotifyEvent OnMouseEnter = 
	{read = FOnMouseEnter, write = 
	FOnMouseEnter};
__property TNotifyEvent OnMouseLeave = 
	{read = FOnMouseLeave, write = 
	FOnMouseLeave};

Just to be safe, we should set FOnMouseEnter and FOMouseLeave to null in the constructor of TOLabel:

__fastcall TOLabel::TOlabel(TComponent* 
	Owner) : TCustomLabel(Owner)
	{
	FOnMouseEnter = 0;
	FOnMouseLeave = 0;
	}

Finally, we finish TOLabel by publishing the normal properties of a TLabel component. These are declared as protected in TCstomLabel; we need to declare them as published in TOLabel:

__published:
	__property Align;
	// etc. ...

All the code for TOLabel can be found in Listings A and B.

Listing A: TOLabel include

#ifndef OLabelH
#define OLabelH

#include <SysUtils.hpp>
#include <Controls.hpp>
#include <Classes.hpp>
#include <Forms.hpp>
#include <StdCtrls.hpp>


class PACKAGE TOLabel : public TCustomLabel
	{
	private:
		TNotifyEvent FOnMouseEnter;
		TNotifyEvent FOnMouseLeave;

	protected:
		void __fastcall CMMouseEnter(Messages::TMessage 
			&Message);
		void __fastcall CMMouseLeave(Messages::TMessage 
			&Message);

	BEGIN_MESSAGE_MAP
		MESSAGE_HANDLER(CM_MOUSEENTER, TMessage, 
			CMMouseEnter)
		MESSAGE_HANDLER(CM_MOUSELEAVE, TMessage, 
			CMMouseLeave)
		END_MESSAGE_MAP(TCustomLabel)

	public:
		__fastcall TOLabel(TComponent* Owner);

	__published:
		__property TNotifyEvent OnMouseEnter = {read = 
			FOnMouseEnter, write = FOnMouseEnter};
		__property TNotifyEvent OnMouseLeave = {read = 
			FOnMouseLeave, write = FOnMouseLeave};

		__property Align;
		__property Alignment;
		__property AutoSize;
		__property Caption;
		__property Color;
		__property DragCursor;
		__property DragMode;
		__property Enabled;
		__property FocusControl;
		__property Font;
		__property ParentColor;
		__property ParentFont;
		__property ParentShowHint;
		__property PopupMenu;
		__property ShowAccelChar;
		__property ShowHint;
		__property Transparent;
		__property Layout;
		__property Visible;
		__property WordWrap;
		__property OnClick;
		__property OnDblClick;
		__property OnDragDrop;
		__property OnDragOver;
		__property OnEndDrag;
		__property OnMouseDown;
		__property OnMouseMove;
		__property OnMouseUp;
		__property OnStartDrag;
	};


#endif

Listing B: TOLabel source

#include <vcl.h>
#pragma hdrstop

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

static inline void ValidCtrCheck(TOLabel *)
	{
	new TOLabel(NULL);
	}


// ---------------------------------------------------------------

__fastcall TOLabel::TOLabel(TComponent* Owner) : 
	TCustomLabel(Owner)
	{
	FOnMouseEnter = 0;
	FOnMouseLeave = 0;
	}

void __fastcall TOLabel::CMMouseEnter(Messages::TMessage &Message)
	{
	if (FOnMouseEnter) FOnMouseEnter(this);
	}

void __fastcall TOLabel::CMMouseLeave(Messages::TMessage &Message)
	{
	if (FOnMouseLeave) FOnMouseLeave(this);
	}


// ---------------------------------------------------------------

namespace Olabel
	{
	void __fastcall PACKAGE Register()
		{
		TComponentClass classes[1] = {__classid(TOLabel)};
		RegisterComponents("ZDJ", classes, 0);
		}
}

Component installation

Let's install our new component into C++Builder so we can use it in a program. First, select Component | Install Component from the IDE menu. Then, select the New Package tab. The Unit file name should be OLabel.cpp. The Package file name should be ZDJ. Enter ZDJ Example Components for the Package description. Then, click the OK button and allow C++Builder to build and install the package.

As a finishing touch, you can add a custom bitmap to represent TOLabel in the component palette of the IDE. You can use a bitmap of your own or use the one included with the source code for TOLabel.


Using TOLabel

To use TOLabel, just drag it from the component palette and drop it on a form. You can then assign functions to the OnMouseEnter and OnMouseLeave events of the TOLabel object. For example, let' refer to the program shown in Figure A.

Each item on the left, a vegetable or meat, is an instance of TOLabel. When the mouse cursor passes over an item, the item's color changes from black to red and the Category of the item, Vegetable or Meat, is displayed by a TPanel object on the right. When the mouse cursor leaves an item, the item's color is set back to black and the Category displayed on the right is cleared.

If you click on one of the items, a message box will announce your selection. You can see how this is done in Listings C and D for the example program.

Listing C: Example program header

#ifndef MainH
#define MainH

#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include "OLabel.h"
#include <ExtCtrls.hpp>


class TMainForm : public TForm
	{
	__published:
		TPanel *Category;
		TOLabel *OLabel;

		void __fastcall OLabelMouseEnter(TObject *Sender);
		void __fastcall OLabelMouseLeave(TObject *Sender);
		void __fastcall OLabelClick(TObject *Sender);

	public:
		__fastcall TMainForm(TComponent* Owner);
	};


extern PACKAGE TMainForm *MainForm;


#endif

Listing D: Example program source

#include <vcl.h>
#pragma hdrstop

#include "Main.h"

#pragma package(smart_init)
#pragma link "OLabel"
#pragma resource "*.dfm"

TMainForm *MainForm;


__fastcall TMainForm::TMainForm(TComponent* Owner) : TForm(Owner)
	{
	}

void __fastcall TMainForm::OLabelMouseEnter(TObject *Sender)
	{
	TOLabel *label = dynamic_cast<TOLabel *>(Sender);
	if (!label) return;

	label->Font->Color = clRed;
	Category->Caption = label->Tag == 1 ? "Meat" : "Vegetable";
	}

void __fastcall TMainForm::OLabelMouseLeave(TObject *Sender)
	{
	TOLabel *label = dynamic_cast<TOLabel *>(Sender);
	if (!label) return;

	label->Font->Color = clBtnText;
	Category->Caption = "";
	}

void __fastcall TMainForm::OLabelClick(TObject *Sender)
	{
	TOLabel *label = dynamic_cast<TOLabel *>(Sender);
	if (!label) return;

	Application->MessageBox(label->Caption.c_str(), "You clicked 
		on...", MB_OK);
	}

TMainForm, the form in our example program, has three functions that are all assigned to different events of our TOLabel objects. OLabelMouseEnter() is assigned to the OnMouseEnter event, OLabelMouseLeave() to OnMouseLeave, and OLabelClick() is assigned to--you guessed it--OnClick.

Notice that these three functions are used for all nine instances of TOLabel. This is much more efficient than writing a separate function for each instance. Let's look at how these functions work.

First, test all three functions to make sure that the Sender argument passed to them is indeed a pointer to a TOLabel object. We do this by testing the result of a dynamic cast:

TOLabel *label = dynamic_cast<TOLabel *>(Sender);
If (!label) return;

If, for some reason, Sender isn't a pointer to a TOLabel, the functions just return without doing anything. Next, OLabelMouseEnter() and OLabelMouseLeave() change the color of the label font:

label->Font->Color = clRed;

and

label->Font->Color = clBtnText;

OLabelMouseEnter() sets the Caption property of the Category TPanel to Vegetable or Meat based on the Tag property of the TOLabel object. During the design of the form, we set the value of Tag to 0 for vegetables and 1 for meats:

Category->Caption = label->Tag == 1 ? "Meat" : "Vegetable";

OLabelMouseLeave() simply clears the Caption property of the Category TPanel:

Category->Caption = "";

OLabelClick() displays a message box showing the Caption of the TOLabel that was clicked:

Application->MessageBox(label->Caption.c_str(), "You clicked on...", MB_OK);

Conclusion

If we count the number of lines of code we had to actually enter while creating TOLabel, we'll find it to be about fifty lines. And, most of those are just exposing the properties from TCustomLabel. The example program required us to enter less than a dozen lines of code!