Till now, we’ve concentrated on switching over to API
calls for creating objects, etc, making as little use of Delphi as possible. Last time, we
managed to create a project (application) which was written using API calls. We now know
of the message loop, and how the application will handle events that happen on a child
object.
One of the most common object that one sees in a Windows
program is a "menu-bar". The menu-bar is what gives all Windows programs the
standard look, and familiarity with the interface. For example, even someone who has
worked in the Windows environment only for a short period, will know that Exit would be
under the File menu.
In this issue, we’ll see how to build a menu in our
application. For the sake of simplicity, we’ll continue with the same application
that we’ve been developing (the text editor), but will now remove the buttons that we
added earlier, and trade them in for the menu. Also, we’ll discuss another important
aspect–resource files.
#define IDM_SAVE 302
#define IDM_EXIT 399
#define IDM_CUT 401
#define IDM_COPY 402
#define IDM_PASTE 403
#define IDM_SELECTALL 404
api MENU
BEGIN
POPUP
"&File"
BEGIN
MENUITEM "&Open", IDM_OPEN
MENUITEM "&Save", IDM_SAVE
MENUITEM SEPARATOR
MENUITEM "E&xit", IDM_EXIT
END
POPUP "&Edit"
BEGIN
MENUITEM "Cu&t", IDM_CUT
MENUITEM "&Copy", IDM_COPY
MENUITEM "&Paste", IDM_PASTE
MENUITEM "Select &All", IDM_SELECTALL
END
END
A resource is binary data that a resource compiler or
developer adds to an application’s executable file. The resource describes an icon,
cursor, menu, dialog box, bitmap, font, etc. In our case, we want to add a menu to our
application. Thus, we’ll first have to create a resource which defines the menu. The
resource is usually written as a plain text file, so it needs to be compiled to a binary
resource file using a resource compiler. All Windows compilers ship with some resource
compiler, and Delphi ships with brcc32.exe (borland resource compiler). The source (text)
for the resource is kept in .RC files, which becomes .RES when compiled. This .RES file
can then be included in the application, and the data in it can be referred from anywhere.
There are several reasons why menus should be put in as
resources. Mainly, it’s much simpler to design complex menu structures when you just
have to write down the structure and let Windows take care of the actual creation of
objects. Also, when the menu is in a separate resource, it’s very convenient to
modify the resource if need be, such as, when the language of the menu items needs to be
changed. Let’s take a look at the resource file for our menu (see the sidebar).
In the first part of the resource file (called
"api.rc" in our case), we define the constants that we’ll be using as
identifiers. If you recall, these identifiers will be used to identify the child objects
when we receive a WM_COMMAND message.
we specify the name of the resource (api), and also the type (MENU). Thus, this line
signifies the start of the definition of a menu called api. Note that the body of the menu
and also of the items is enclosed in the BEGIN-END blocks. This is not because of Delphi,
but because this is the standard way of writing a resource no matter what language or tool
one uses.
The first thing we specify in the menu is the name of the
drop-down or pop-up, as it’s called here. To do this, we say:
POPUP "MenuName"
where "MenuName" is what appears in the menu,
which will be clicked to get the drop down. In our case of the text editor, we’ll
have two pop-ups, namely, File and Edit.
For each pop-up, we then specify the items. For example,
for the pop-up File, we’ll have Open, Save, and Exit as the menu items. When we list
the menu items, Windows knows that all these belong to the File pop-up, as they have been
enclosed in the BEGIN-END statements for the same.
While specifying the menu item, we specify the keyword
"MENUITEM", followed by the string that’ll be displayed (the actual menu
item), and the identifier that our application will use to link this menu item to some
code (separated by a comma). Instead of the menu item’s name, we can include another
keyword "SEPERATOR", which will make Windows draw a horizontal line instead of
the menu item there. This is used to logically group several menu items.
After specifying all the pop-ups and menu items, we close
the menu by putting an END in the end, to match the opening BEGIN, and we save the file.
This will create the source for the resource file. To actually convert it to a Windows
resource, we need to compile it. For that, on the DOS prompt type: brcc32 api.rc.
(This assumes that brcc32 is in the search path, which it
normally is). This will create a compiled Windows resource api.res in the same directory.
We now include the resource in our program, using the $R compiler directive. Our complete
program now looks like this (including the functionality for all the Edit menu items).
Note how simple it’s to link the menu items and perform operations, just as
cut/copy/paste, etc, with just one line of code.
Note that simply including the resource containing the menu
will not suffice. We still have to link the menu to the window that’ll contain that
menu. This is done at the time of defining the window class, in the TWndClass structure:
lpszMenuName:=PChar(‘api’);
This is the name that we specified in the resource file.
This links the menu in the resource, with the window that we are creating. Note that the
processing of the menu items is the same as when we used buttons, by checking the
identifiers when a WM_COMMAND is received.
As you might’ve noticed, making menus is a simple
task, but not very straightforward. Nothing can beat the simplicity of using a WYSIWYG
menu designer that all RAD tools include. However, when you notice that our program still
compiles to a 16 kB executable, one is tempted to go in for API every now and then.
It’s up to you to decide which method to adopt when. Till next time then, happy
coding.
uses
Windows, Messages;
var
eh:HWND;
msg:TMsg;
const
idm_open=301;
idm_save=302;
idm_exit=399;
idm_cut=401;
idm_copy=402;
idm_paste=403;
idm_selectall=404;
{$R api.res}
function windproc (wh:HWND; msg:U INT; wparam,lparam: wparam):
longint;stdcall;
begin
result:=0;
case msg of
WM_CREATE:
begin
eh:=CreateWindow (Pchar(‘Edit’),PChar(‘The stuff
inside...’),
WS_VISIBLE+W0,00,632,352,
wh,0,hInstance,nil);
end;
WM_DESTROY:PostQuitMessage(0);
WM_COMMAND:
begin
if hiword(wparam)=BN_CLICKED then
case loword(wparam) of
idm_open:setwindowtext (eh,PChar(‘Open button
clicked.’));
idm_save:setwindowtext(eh,PChar(‘Save button clicked.’));
idm_exit:sendmessage(wh,WM_CLOSE,0,0);
idm_cut:sendmessage(eh,WM_CUT,0,0);
idm_copy:sendmessage(eh,WM_COPY,0,0);
idm_paste:sendmessage(eh,WM_PASTE,0,0);
idm_selectall:sendmessage(eh,EM_SETSEL,0,-1);
end;
end;
else
result:=DefWindowProc(wh,msg,wparam,lparam);
end;
end;
procedure defineMyWindow;
var
wh:HWND;
wc: TWndClass;
begin
with wc do
begin
style:=0;
lpfnWndProc := @windproc;
cbClsExtra:=0;
cbWndExtra:=0;
hCursor:=LoadCursor(0, IDC_ARROW);
hIcon:=LoadIcon(0, IDI_APPLICATION);
lpszMenuName:=PChar(‘api’);
hbrBackGround:=GetStockObject(LTGRAY_BRUSH);
lpszClassName:=PChar(‘MyEditor’);
end;
wc.hInstance:=sysinit.hInstance;
Windows.RegisterClass(wc);
wh:=CreateWindow(Pchar(‘MyEditor’),PChar(‘My First
Editor’),WS_VISIBLE+WS_OVERLAPPEDWINDOW,
0,0,640,400,
0,0,hInstance,nil);
end;
begin
definemywindow;
while true do
begin
if longint(getmessage(msg,0,0,0))<1 then exit;
translatemessage(msg);
dispatchmessage(msg);
end;
end.