|
|
This
month: creating an MDI interface and cascading
or tiling program group windows... |
See also: Part
One and Part Three of this series
In the first part of this series we found out how to
locate the files displayed by the Windows Start Menu.
Having experimented with alternative ways of displaying
these files in my own programs, it’s now time to
start thinking about designing a user interface in order
to create a program manager. This month we shall find
out how to create multiple program groups in tiled and
overlapping windows.
This is the application we’ll
be creating in this column. It lets you create multiple
tiled or overlapping windows containing groups of program
icons which can be launched by double-clicking.
Before getting down to the nitty-gritty details of implementing
the user interface of the program manager, let’s
recap the techniques required to locate the Start Menu
folder on disk. This is the directory in which Windows
stores the actual structure of the menus that branch
off from the Start button. You can find this folder by
passing the constant, CSIDL_STARTMENU (defined in Delphi’s
ShlObj unit) to the Windows SHGetSpecialFolderLocation() function. This returns the actual path to the Start Menu
directory. In my Test.dpr project, you will see that
I have written a function called GetSpecialFolder() which
acts an interface to SHGetSpecialFolderLocation(). This
function tests that the operation is successful and converts
between Delphi strings and null-terminated strings where
necessary.
Displaying Icons in a ListView
In the last project, we displayed a list of Start Menu
files in an ordinary ListView control and preceded each
file name with the appropriate icon. The icons had been
obtained by accessing the system imagelist in FormCreate().
Here I’ve adapted a normal ListView control (on
the left) to display the items in the Windows Start Menu.
On the right, Delphi’s sample ShellListView displays
(more or less the same items.
If you wish, you can use similar techniques to populate
other types of control with file names, with or without
icons. For example, you could add file names to combo
boxes, grids, buttons or (Heaven forfend!) cascading
menus. However, it is worth bearing in mind that Delphi
already comes with a control that is eminently suited
to our purposes. The ShellListView, which is (usually)
located on the Samples tab of the palette, has all the
features of a standard ListView plus a built-in ability
to display file names and icons. In short, it does automatically
what we had to code long-hand into a standard ListView
last month.
Installing
Shell Controls
NOTE: In
my installation of Delphi 2006, I was surprised
to find that the Shell controls had not been installed
by default.
If you have this problem, you will
need to build and install the packages yourself.
These are located
in the \VCLWin32\ShellControls subdirectory nested
beneath Delphi’s \Demos directory.
You can build these by loading the projects into
Delphi, right-clicking the bpl item
in the Project Manager and selecting Install.
|
For the sake of comparison, I’ve used a simple
ListView alongside a ShellListView to display the Start
Menu files in the Test.dpr project. The first difference
you will notice when you load the project is that the
ShellListView displays icons from the active directory
at design time whereas the ListView does not. Now run
the program. You should find that the two controls display
more or less the same items. The order may be slightly
different and the case of the labels may vary but, in
all important details, they are the same. If you select
a file icon and double-click it in each of the two controls,
the matching application will be launched. Finally, use
the radio-box to the right of the form to select alternative
view styles. When you select these, both views are updated
with the exception that the Icon view in our ListView
fails to display large icons. I shall explain this deficiency
in a moment.
Advantages of ShellListView
Close the application and turn to the code. Now the
real difference between a ListView and a ShellListView
should become apparent. All the behaviour of the ShellListView – from
its display of file names and icons to its launching
of an application when double-clicked – is automatic.
All I have had to do is to set its Root folder to the
Start Menu directory with this assignment in the FormCreate() procedure:
ShellListView.Root := startmenudir+'\';
Here, startmenudir is a private variable which is initialised
by my Init() procedure. Now look at all the code I’ve
had to write in order to get the ListView to do what
I need. First, in FormCreate() I have accessed the small
system icons. Then, in ShowFiles() (which is called by
my Init() procedure) I have had to find each file in
the specified directory, get its matching icon and add
it to the ListView. My code has also filtered out the
current and parent directory specifiers, ‘.’ and ‘..’,
which would otherwise be added to the list. Finally,
in order to implement file launching I have had to code
ListView1DblClick(). This calls a routine, ExecuteFile(),
in my filestuff.pas unit. Prior to doing
this I had to do a whole load of checking to make sure
that a valid file name has been selected and that the
specified directory exists.
And even after all that effort, I still haven’t
managed to display the large icons. Adding this feature
would not be difficult. You may want to try it yourself.
For a few hints, take a look at SHGetFileInfo in
Delphi’s
(Win32 SDK) help and also at the code of my file
launchpad application from previous columns. All
the same, why reinvent the wheel? If you decide to
create a different type of Program Manager – using menus or buttons,
say – you will have no alternative but to go through
all the long-winded coding steps I’ve outlined
for adding file icons to the plain vanilla ListView.
However, I personally feel that the ShellListView is
ideal for the job so, from now on, that is what I shall
use.
Creating An MDI Interface
In the old days of Windows 3.1, programs were arranged
as groups of icons displayed within separate windows.
These windows were nested inside a ‘containing’ window
in what is known as MDI (Multiple Document Interface)
style. While this design had some drawbacks – mainly
due to the clutter that resulted from too many nested
windows – I still feel that this was an essentially
neater design than the cascading menus of the current
generation of Windows. For that reason, I would like
to use a Multiple Document Interface for my program manager.
An MDI application comprises a container form and a
child form. Usually many instances may be made of a child
form so that multiple windows appear inside the container
form. When you create an MDI project, you must have at
least two forms. To make the main form, Form1, act as
a container you just set its FormStyle property to fsMDIForm
using the Object Inspector. To let the second form – which
I shall call childform – act as a contained form,
you must set its FormStyle property to fsMDIChild. Open
my Test2.dpr project and you will find that this is precisely
what I have done.
Use The Project Manager
to view the design and code of the ‘child form’ which defines the ‘child’ windows
that will be nested inside the main container window.
I have added a menu to Form1 which includes Window,
New. Clicking this causes a new program group window
to appear. The code of this is simple:
Tchildfm.Create(Self);
This creates a new instance of my child form class and
sets the current form, Self, as its owner. There is nothing
much to be said about the code inside the childform unit
apart from the FormClose() method which specifically
sets the Action parameter to caFree. This causes each
individual child form to be closed when the close button
is clicked. The default close action for a child form
in an MDI application is to minimise the window rather
than close it. Comment out this line of code to see this
for yourself.
Merging Menus
The other thing to notice is that I have given the child
form its own menu. This menu does not, however, appear
on the child form itself. Instead it is merged with the
main menu at runtime. If you double-click the menu and
select Group and View, you will notice that their GroupIndex
properties have been set to 1 in the Object Inspector.
The GroupIndex properties of the top-level items in the
Form1 menu are set to 0. By setting a higher value, I
can ensure that menu items will appear to the right of
the menu bar when two menus are merged.
Turn back now to the main form’s unit, t2, and
look at the event-handlers for the items on the Windows
menu. These simply call the Tile(), Cascade() and ArrangeIcons() methods of the MDI form. The first two methods arrange
multiple open child forms alongside one another or overlapping
while ArrangeIcons() arranges any minimised forms. Note
that Tile() arranges forms horizontally by default. If
you want them to be arranged vertically, use the following
assignment prior to calling the Tile() method:
TileMode := tbVertical;
We have come quite a long way in our development of
an alternative to the Start Menu now. We have a multi-window
program manager in which the windows can be tiled or
cascaded. By default it displays a single group showing
the top level items of the Start Menu itself. You can
navigate easily to the other groups, however, by clicking
the Programs group and other groups branching off it.
You could, in fact, display separate windows of icons
containing the Start, Programs, Accessories and other
groups. The main deficiency is that the groups aren’t
preserved between sessions. It would obviously be much
more useful to be able to save your favourite groups
so that they always appear whenever you run the program.
This is one of the features I ’ll be adding in
my next column.
Delphi 7 can create multi-window applications at the
click of a mouse
I have created the multi-window interface for my application
from the ground up in this month’s project. However,
if you want to save yourself some effort, you can create
a fairly complete MDI application using one of Delphi’s
project wizards.
Delphi creates an MDI application like this with no coding
required!
Select File, New, Other. In the dialog box that pops
up, click the Projects tab or branch. Select MDI
Application and click OK. You will now be prompted for a location.
Browse to a suitable directory or enter a path name then
click OK. Your MDI application is now ready to run. As
you can see, this application is not, as it stands, suitable
for our purposes. It is a multi-window text editor complete
with menus and icons to open, close and arrange windows.
If you wished to create an MDI application of a different
sort, you could adapt this by redesigning the ChildWin
forms. Even if you don’t wish to use this wizard
for your own applications you may find some useful tips
in the code which it generates.
April 2006 |