See also Part Two
There are many times when you may need to find and run
some external application from one of your programs.
That’s easy to do. But what if you want to display
the icons associated with programs, or documents, and
launch them by clicking a button? This would give you
the ability to create your own ‘program manager’ tool
or, with a bit more effort, an alternative to the Windows
Start Menu. This month I’ll show you the
basic techniques you’ll need to do these tasks.
In the months ahead, I shall be developing more powerful
variations of this program to show you how you might
create all kinds of other file handling applications.
The finished launchpad
is shown towards the top of the screen here. I have
populated it with buttons simply by dragging programs
and documents straight out of the Windows Explorer
and dropping them onto the Launchpad itself. A new
button is created for each item dropped and an appropriate
icon is added automatically. Clicking a button launches
the associated file. Here I have clicked a button
for a JPeg graphic and the file is immediately loaded
into the associated application - which, on my PC,
happens to be the Microsoft Office Picture Manager |
Out To Launch
First I’ll need to write some functions to return
the paths to various important subdirectories. Open the
FileInfo.dpr project (or FileInfo.bdsproj) and view the
filestuff unit. My functions are simple Delphi wrappers
around Windows API routines. By making use of these functions,
any code will be able to avoid the need to declare variables
such as the character arrays and the strings needed by
the API functions. For example, this is my windir() function:
function windir : string;
var
winpath : CHARARRAY;
begin
GetWindowsDirectory(winpath,MAX_PATH);
result
:= winpath;
end;
It supplies the character array variable, winpath,
to the API’s GetWindowsDirectory(). The second
argument defines the size of the directory buffer. Here,
MAX_PATH is a constant in Windows.pas and it is currently
defined to be 260. The winpath array
of characters is assigned by the GetWindowsDirectory() function and it is returned by my windir() function as a string. If the
Windows directory is \WINNT on the C:\ drive, the string
returned by this function will be ‘C:\WINNT’.
Directory Enquiries
My currdir() function is similar, but uses the API’s
GetCurrentDirectory() routine. This returns the path
of the currently active subdirectory. When you run the
program, you can change the current directory by clicking
the ‘Browse’ button to open a file in another
directory. Finally, my appdir() function returns the
home directory of the FileInfo application itself. This
function does not use an API routine. Instead it uses
the Application object’s ExeName property to retrieve
a fully qualified path to the FileInfo.exe program file.
This might be, for example, ‘C:\Delphi\Test\FileInfo.exe’.
It then removes the file name itself using Delphi’s
ExtractFileDir() function, which returns ‘C:\Delphi\Test\’.
The ExecuteFile() function wraps up the complexities
of the API’s ShellExecute() function which you
will find documented in Delphi’s Windows SDK help
system. It lets you run a program or launch a file into
its associated application. This is the ShellExecute() function as documented in the help system:
HINSTANCE ShellExecute(
HWND hwnd,
LPCTSTR
lpOperation,
LPCTSTR lpFile,
LPCTSTR
lpParameters,
LPCTSTR lpDirectory,
INT
nShowCmd
);
Now compare this with the code of my ExecuteFile() function:
Result :=
ShellExecute(Application.MainForm.Handle,
nil,
StrPCopy(ntFileName,
FileName),
nil,
StrPCopy(ntDir,Dir),
LAUNCHSTYLE);
Note that the ShellExecute() function returns something
called a HINSTANCE or ‘instance handle’.
This is a 32-bit value that identifies a Windows resource.
If this function succeeds, it returns a handle to the
application that was run. The first argument to the function
is the handle of the window that calls the function.
In my code this will be the handle of the main form of
the FileInfo program. You can use the MainForm property
of Delphi’s Application object in order to access
this handle:
Application.MainForm.Handle
The ShellExecute() function
now takes four LPCTSTR arguments.
These are pointers to null-terminated strings. A null-terminated
string is equivalent to Delphi’s PChar data type
or an array of chars. Notice that, in my ExecuteFile() function
I declare two variables, ntFileName and ntDir of
the type CHARARRAY. This
is required since you can’t
use Delphi’s native string type when calling Windows
API functions. This is because API functions work with
C-style null-terminated strings.
A null-terminated string is, in essence, an
array of characters with a null character stuck
on the end. This type of string is accessed by
the address of the start of the array. The PChar
(pointer to char) data type is equivalent in Delphi
and you can also create a compatible data type
as an array of char. This is how I have defined
the CHARARRAY type:
CHARARRAY = array[0..MAX_PATH]
of char;
You can convert ordinary Delphi strings
to PChars using the StrPCopy() function. A string is passed
as the second argument to StrPCopy(), a PChar variable
is passed as the first argument. The StrPCopy() function returns a pointer to the PChar which is
what we need for the LPCTSTR arguments in ShellExecute(). |
The first of the four LPCTSTR arguments required by
ShellExecute() is lpOperation. This points to a string
that specifies the operation to perform and this can
be “open”, “print”, “explore” or
NULL (nil in Delphi). If it is NULL, then the default
operation, to Open the file, is performed. This is why
my code uses nil. Next, lpFile points to the file name.
The lpParameters argument optionally specifies one or
more parameters to be passed to the application. Again,
I leave this as nil. Next lpDirectory specifies the default
directory. Finally, the INT argument, nShowCmd, specifies
how the application is shown when it is opened. The API
defines numerous constants that can be used here to display
the application in a normal, maximised or minimised window
amongst other possibilities.
To see how my ExecuteFile() function can be called,
turn to the Fileinfo unit and locate the LaunchBtnClick() method. This contains this piece of code:
ExecuteFile( FNAME, DIRNAME, LAUNCHSTYLE );
Here, FNAME is defined at the top of the unit as 'Notepad.exe'.
DIRNAME is assigned in the FormCreate() method as windir+'\' where windir is the name of my function, described earlier,
which returns the path to the Windows directory. Finally,
LAUNCHSTYLE is one of the possible pre-defined API constants:
SW_SHOWMAXIMIZED, SW_SHOWMINIMIZED or SW_SHOW.
Anything Icon Do
Another thing we’ll want to do is to display the
appropriate icon for each application or document on
its launch button. The DrawIcon() procedure in the filestuff unit does this. This takes two arguments, the icon and
the glyph. The Glyph is a bitmap and it will be supplied
by a BitBtn control. The icon is a TIcon object. The
size of the Glyph is defined to be a 32 pixel square
and the Icon is displayed on the Glyph’s drawing
area or Canvas, using the Draw() method.
To see how to use the DrawIcon() procedure turn to the
Fileinfo unit and find the TForm1.BitBtnClick() method.
This obtains a TSHFileInfo record (which matches the
SHFILEINFO structure documented in the SDK help) by calling
GetFileInfo() in the filestuff unit. It uses a combination
of predefined constants to obtain the icon associated
with the specified file. The available constants are
listed in the SHGetFileInfo entry of the SDK help. An
icon handle is obtained and this is passed, with the
BitBtn’s glyph to my DrawIcon() procedure:
fi := GetFileInfo(DIRNAME+FNAME, SHGFI_ICON or SHGFI_LARGEICON);
Icon.Handle
:= fi.hIcon;
DrawIcon( icon, BitBtn.glyph );
Displaying Icons, Launching
Files...
Our FileInfo program demonstrates how to dispay the
large or small icon of an application and launch
it in a normal, maximized or minimized window.
Notepad.exe is the default application...
Use
the Browse button to select a different application
or document. Here I've checked the Small
Icon box and clicked More
Info to display information
on the type of the file which is selected.
|
Now let’s see how we can put
some of these features to good effect in a real-world
application - namely, a drag and drop launchpad. This
contains a set of buttons that are used to run programs.
Each button contains the name of the source file and
an appropriate icon. The buttons can be created on the
fly by dragging programs or documents out of the Windows
Explorer and dropping them onto the launchpad. It also
lets the user delete selected buttons.
Parental Responsibilities
Before explaining how it works, take time to try it
out. Run the LPad.dpr (or LPad.bdsproj) program. A message
will tell you that no configuration file was found. This
is because this is the first time you have used the program
so no configuration details have yet been saved to disk.
Close the dialog.
On launching, the launchpad is not much more than an empty
form...
Now let’s add some buttons. Open
the Windows Explorer and browse to find some programs
or documents to add. For example, you might find Notepad.exe in
the Windows directory. Select it in the Explorer then
drag it and drop it onto the launchpad. If the launchpad
is not visible, drag onto its button in the Windows taskbar
until the launchpad pops up, then drag onto the launchpad.
Here I've found Notepad.exe in the Windows Explorer and
dragged it right onto the launchpad with my mouse. This
causes a new button to be added to the launchpad, displaying
both the application's name and icon
Carry on adding more application or document files.
If you want you can even mark off several files at once
in Explorer and drop them simultaneously onto the launchpad.
Note that a scrollbar appears
when there are more buttons than can be displayed all
at once
Each time you drop a file a button should appear containing
the file name and an icon. To launch a file, click its
button on the launchpad. To display information about
a file, right-click its button and click ‘File
Info’. To remove the launch button, right-click
and selected ‘Delete’. To save an icon bar
for later use, click File, Save Current IconBar. To clear
the launchpad, select File, Clear All. To reload the
saved bar, select File, Reload Saved IconBar. The saved
configuration will be automatically loaded when you next
run this application.
Use the File menu to save the buttons and reload them later
I won’t go into the inner details of the drag-and-drop
mechanism here. If this is of interest, you can examine
the code at your leisure. Look at TForm1.FormCreate
() where I use the API function DragAcceptFiles() and I
delegate message-handling to my RespondToMessage() method.
You will find the essential drop-handling code in RespondToMessage().
At the moment, the line that interests us is the
call to the AddBtn() method. Find that method now. As
you can see this makes use of routines in my filestuff unit to get and draw the icon based on the filename argument
that’s passed to the method. It creates a graphic
TBitBtn button and adds it to a TStringList object called
ButtonList (you will find this declared in the private
section of the Form definition). This is the internal,
non-visual structure, that maintains the list of buttons.
To make the button visible on the form, I have to make
the Form1 object its Parent:
Parent := Form1;
Now I need to delegate the button’s OnClick event-handling
to a specific method so that each button is assigned
this event-handler when it is created. I also want to
assign a popup menu to each button. This is how I do
this:
OnClick := BtnClick;
PopupMenu
:= PopupMenu1;
This covers all the techniques needed to create a drag-and-drop
program (or document) launching tool. There is, of course,
no reason why you should use such a plain user interface
in your own programs. You might prefer, for example,
to add program-launch icons to a menu system, similar
to the Windows Start Menu or to a drop-down combo box.
You could even create a nested windows system similar
to the good old Program Manager from Windows 3.1. I’ll
be considering some of these possibilities in the months
ahead. Whichever user interface you choose, however,
you are going to need to create controls, with their
own OnClick event-handlers, on the fly. The essential
details of this process are given below…
Simple techniques for adding, deleting and maintaining
controls on the fly.
In my launchpad application, I use a TStringList called
ButtonList to maintain the list of TBitBtn objects. At
first sight it might seem odd to use a TStringList for
this purpose. If you look up its help entry you’ll
see that a TStringList stores and manipulates a list
of strings, not buttons! In fact, each item in a string
list can also store a reference to an object of any type.
The items in my ButtonList are pairs of strings and TBitBtn
objects. The string is the fully qualified file name,
the object is the button that will be used to run that
file. So, in the AddBtn() method, this is how I add a
new file name and TBitBtn to ButtonList:
ButtonList.AddObject(fname, TBitBtn.Create(Self));
Since the buttons are created anonymously, with no variables
to identify them, I must subsequently access each by
its index in ButtonList. The most recently added button
is at position ButtonList.Count-1 and is referenced thus:
TBitBtn(ButtonList.Objects[ButtonList.Count-1])
I can iterate through all the objects from 0 to ButtonList.Count-1,
using an array indexer into the Objects property. You
will find an example of this in the SetLargeIcons() and
SetSmallIcons() methods which apply large and small icons
to each button:
TBitBtn(ButtonList.Objects[i])
When I use the popup menu, I need to know which of the
buttons is currently active. For example, look at the
DeleteMIClick() event-handler which responds to the Delete
item of the popup menu. This method is executed for every
button on the form. The Sender parameter is no help as
this identifies the menu item, not the active button.
In order to find which button the popup menu belongs
to, I need to use the menu’s PopupComponent property
like this:
ButtonList.Delete(ButtonList.IndexOfObject(PopupMenu1.PopupComponent));
Remember that this only deletes the button from the
non-visual ButtonList object. You must always be sure
to remove its visual display from the form by setting
its Parent property to nil. If you forget to do this
the button will still appear on a form and an error will
occur if you click it.
You can also add items to a menu at runtime. Try this,
for example. Create a new application (VCL Forms for
Win32) and drop a PopupMenu onto the form. Use the Object
Inspector to set the Form1 PopupMenu property to PopupMenu1.
With the Form selected, use the Object Inspector to set
the PopupMenu property to the popup menu control, PopupMenu1
Now double-click the form and edit the FormCreate() method to match the following:
procedure TForm1.FormCreate(Sender: TObject);
var
i : Integer;
MI : TMenuItem;
begin
for i := 0 to 10 do
begin
MI := TMenuItem.Create(PopupMenu1);
PopupMenu1.Items.Add(MI);
MI.Caption
:= 'Menu Item ' + IntToStr(i);
MI.Tag
:= i;
MI.OnClick := PopupClick;
end;
end;
Note that if the references to menu items are underlined
in red in the Delphi editor, you will need to add a refernce
to the Menus unit in the uses section at the top of the
file. Just add it at the end, like this:
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms, Dialogs,
Menus;
Notice that each menu item has been assigned a method
called PopupClick() as
its OnClick event-handler. We need to define this method.
Add this code just above TForm1.FormCreate():
procedure TForm1.PopupClick(Sender: TObject);
begin
ShowMessage('Item: ' + IntToStr(TMenuItem(Sender).Tag));
end;
Finally, in the public section
of the TForm1 type definition at the top of the unit,
add this:
procedure PopupClick(Sender: TObject);
Now run the program, right-click the form and select
a menu item. It’s as simple as that!
And here's our popup menu.
The items have been created
in code and each has been assigned an OnClick event-handler
Now go to Part Two
November 2005 |