Author: Lou Adler
Adding an accelerator key to a TPageControl
Answer:
This tip is an example of extending the functionality of a component by creating a
descendant. While implicit to the discussion at hand, here's where the power of an
object-oriented language such as Delphi lays. As you'll see in the code below, it
doesn't take much to create new functionality of an object by creating a
descendant. The point of this is that had I not been using an object-oriented
language, I would have had to re-write the original code of the TPageControl, then
add the extended functionality. Fortunately, the VCL, which is really an object
hierarchy, allows me to transparently inherit and retain the ancestral
functionality and concentrate on the new functionality. You gotta love it!
For those of you new to Delphi, an accelerator key is a key that is pressed in
combination with the Alt key to execute a command. They're sometimes called
keyboard shortcuts or hotkeys, and you'll typically see them in menus as the
underlined letter of a menu item. For instance, the "F" in the File menu selection
is an accelerator key for that item. So to open up the File menu, you'd press Alt-F.
Accelerator keys aren't limited to just menu items. In fact, for almost any Caption
property or a Caption-like property (e.g. Radio Group items) of a component, you
can define an accelerator key. All you need to do is place an "&AMP" before a
letter to designate it as an accelerator key. This is useful with VCL components
like a TRadioGroup's Items, which allow the user to quickly select the radio button
choice with the touch of a key. However, not all VCL components will respond to
accelerator keystrokes if you define them. TPageControl in Delphi 2.0, which
replaces TTabbedNotebook, is one of those components. And with it, accelerator key
functionality would be particularly useful.
The only method I know for implementing accelerator key functionality in a
TPageControl is to create a new component. There's another way, but you have to
create menu and define hotkeys for menu items with equivalent functionality
(they'll turn your pages for you), and that's a pretty kludgy way of doing things.
Besides, the code to accomplish what we want is actually very simple.
Below is the unit code for a descendant of TPageControl that adds accelerator key
functionality. We'll discuss the particulars after the listing:
1 unit accel;
2 3 interface4 5 uses6 Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
7 ComCtrls;
8 9 type10 TAccelPageCtrl = class(TPageControl)
11 private12 { Private declarations }13 procedure CMDialogChar(var Msg: TCMDialogChar); message CM_DIALOGCHAR;
14 protected15 { Protected declarations }16 public17 { Public declarations }18 published19 { Published declarations }20 end;
21 22 procedureregister;
23 24 implementation25 26 procedure TAccelPageCtrl.CMDialogChar(var Msg: TCMDialogChar);
27 var28 I: Integer;
29 Okay: Boolean;
30 begin31 32 Okay := False;
33 34 inherited; //call the inherited message handler.35 36 //Now with our own component, start at Page 1 (Item 0) and work to the end.37 for I := 0to PageCount - 1do38 begin39 //Is key pressed accelerator key in Caption?40 Okay := IsAccel(Msg.CharCode, Pages[I].Caption) and CanChange(I);
41 //this is the fix42 //It is, so change the page and break out of the loop.43 if Okay then44 begin45 Msg.Result := 1; //you can set this to anything, but by convention it's 146 ActivePage := Pages[I];
47 Change;
48 Break;
49 end;
50 end;
51 52 end;
53 54 procedureregister;
55 begin56 RegisterComponents('BD', [TAccelPageCtrl]);
57 end;
58 59 end.
As you can see from the above. all that's required to add accelerator key response
is a simple message handler procedure. The message we're interested in is
CM_DialogChar, a Delphi custom message type encapsulated by TCMDialogChar, which is
a wrapper type for the Windows WM_SYSCHAR message. WM_SYSCHAR is the Windows
message that is used to trap accelerator keys; you can find a good discussion of it
in the online help. The most important thing to note is what happens when the
TAccelPageCtrl component detects that a CM_DialogChar message has fired.
Take a look at the CMDialogChar procedure, and note that all that's going in the
code is a simple for loop that starts at the first page of the descendant object
and goes to the last page, unless the key that was pressed happened to be an
accelerator key. We can easily determine if a key is an accelerator key with the
IsAccel function, which takes the key code pressed and a string (we passed the
Caption property of the current TabSheet). IsAccel searches through the string and
looks for a matching accelerator key. If it finds one, it returns True. If so, we
set the message result value and change the page of TAccelPageCtrl to the page
where the accelerator was found by setting the ActivePage property and calling the
inherited Change procedure from TPageControl.
I haven't used TPageControl since I created this component because of how easy TAccelPageCtrl makes switching from TabSheet to TabSheet. It's far easier to do a Alt- combination than use the mouse when you're at the keyboard. Play around with this and you'll be convinced not to use the standard VCL TPageControl.