Home
Archives
About us...
Advertising
Contacts
Site Map
 

ruby in steel

 

A DELPHI PROGRAM LAUNCHPAD #1

Sick of the Windows Start menu? Follow Huw Collingbourne’s directions to create an alternative application launchpad.

Requirements:
Borland's Delphi 7 or Delphi 2005 (tested); or earlier versions of Delphi (untested)

Download The Source Code:
lpad1.zip

 

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, // hwnd
  nil,                                      // lpOperation
  StrPCopy(ntFileName, FileName),           // lpFile
  nil,                                      // lpParameters
  StrPCopy(ntDir,Dir),                      // lpDirectory
  LAUNCHSTYLE);                             // nShowCmd

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…


Creating Controls At Runtime

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.

Creating Menus At Runtime

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; // assign an event-handler method
  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);
// the event-handler method we assigned to the menu items
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

 


Home | Archives | Contacts

Copyright © 2006 Dark Neon Ltd. :: not to be reproduced without permission