Home
Archives
About us...
Advertising
Contacts
Site Map
 

ruby in steel

 

Delphi GOODBYE TO THE START MENU

This month , Huw Collingbourne's Program Manager gains the ability to remember its windows and icons from one session to the next
Requirements:
Borland's Delphi 7 or later

 

Download The Source Code:
prog_grps3.zip
Delphi Program Manager 20x20 Huw Collingbourne reads programming manuals for fun. ‘The C++ Programming Language’ by Bjarne Stroustrup provides him with hours of mirthful hilarity.

See also: Part One and Part Two of this series

In this series it has been my aim to create a program manager utility which avoids the cascading menu hell of the Windows Start button. The final project in my last column was an application which displayed one or more cascaded or tiled windows containing the same icons as the Start menu or any of its submenus. The main deficiency of that program manager was that it failed to reload its program groups when from one session to the next. This month I shall fix that.

program manager
The final version of our program manager lets you view the items in the Start Menu, navigate to other directories and display items in tiled or cascading windows, change the view style (as we are doing here) and save and reload all the windows and their contents between sessions.

Load the ProgGroups.dpr project. This is a development of last month’s project in which we have a main form defined in the unit pg.pas and a child form defined in the unit childform.pas. The main form’s FormStyle property has been set to fsMDIForm in the Object Inspector while the child form’s FormStyle property has been set to fsMDIChild. This means that the main form can act as a container for one or more child forms. We can create new child forms at runtime using the New command from the main form’s Windows menu. This simply calls the Tchildfm class’s constructor with the Form1 Self parameter to create a child form of Form1.

Having created a few child forms, you can navigate to different program groups by double-clicking their icons. So, for example, you might open one window on the top level StartMenu directory, another on StartMenu\Programs\ and a third on StartMenu\Programs\Accessories\. As explained last month, the actual location of the StartMenu directory will vary from user to user and our code locates it in the TForm1.Init() procedure.

Having opened windows onto several different program groups we now come to the problem of restoring those groups when we next run our program manager. To do this, you need to write information on the program groups to disk and use this information to recreate the windows subsequently. You could do this in many ways - for example, by writing data to a text file, streaming data to disk or writing information to the Registry. For the sake of simplicity, I have chosen to use INI files. As these are plain text files, they can be loaded into Notepad. This means that INI files have the notable benefit of giving us an easy way to verify the data written to disk.

Saving The Program Groups

Since we want to save the program groups which are active when we exit the application, we need to write to the INI file when the main form closes. You can find the relevant code in TForm1.FormClose(). As long as you have the IniFiles unit in your ‘uses’ list, all you need to do to write entries into an INI file is to create a TIniFile object and pass the file name to its constructor, then use various TIniFile methods to write specific types of data such as integers, strings and Booleans. These methods generally take three parameters, the first is the name of the section header in the INI file, the next is the name of the Key and the last is the value to be written. This is how a section named ‘Form’ might appear in the resulting INI file:

[Form]
Maximized=0
Top=117
Left=207
Height=569
Width=763

Note that the text to the left of the equals sign is a key and is used to access a specific item while the text on the right is the value associated with the key. When the application, an attempt is made to read the data stored in the INI file. You will find the code of this in TForm1.FormCreate(). This time, variables are initialised one at a time by reading the stored entries. For example, here we initialise the Top property of the form (Self.Top) by reading the value associated with the ‘Top’ key from the ‘Form’ section of the INI file. Just in case the entry does not exists, the third parameter specifies a default value of 100:

Self.Top := Ini.ReadInteger( 'Form', 'Top', 100 );

Reading Multiple INI Values

But the program group windows are not fixed. They can be created and destroyed at runtime. This makes it impossible for us to know in advance the number of values we need to restore at startup.

Writing information on the existing program groups is simple enough. We just count down from the maximum MDI child index (obtained by subtracting one from the MDIChildCount property) to zero. In TForm1.FormClose() we iterate through any child forms and write their captions, which contain the path to their ‘root’ directory, into the INI file. Note that, prior to doing this, we delete any existing entries from the INI file using the EraseSection() method. If we did not do so, old entries which haven’t been overwritten with new values would continue exist. For example, if we previously had five entries but we now only have two program groups, we would overwrite only the first two entries. The remaining three entries would remain untouched.

As we shall be writing an unpredictable number of entries each time we save information to the ‘ProgGroups’ section, according to the number of child forms, it does not make sense to attempt to read in a fixed number of items. Instead, we make use of the TIniFile’s ReadSectionValues() method. This reads in all the entries from a named section of the INI file and assigns them to items in a TStrings or TStringList object. Here I’ve assigned the values to a TStringList named paths which is declared in the Form1 class definition towards the top of the unit.

paths
When the program starts, a message box displays the program groups (which will be empty when you first run this program) and the paths from which their contents will be created.

Just so you can see what is going on here, the items are displayed after they are read in. You will note that they include the key, which is an identifier such as ‘Group0’, followed by an equals sign and the string value, which is a path such as ‘C:\Documents and Settings\Huw\StartMenu\’. In fact, we only need the path part. This can be extracted from each string in a string list using the ValueFromIndex[] property. You can view the result of using this by uncommenting the line indicated in FormCreate(). This displays the additional information in the message box (for debugging purposes!) which pops up when the application starts. Notice that I haven’t actually attempted to recreate the program groups from within the FormCreate() method. This is because the main form must already have been created before it is possible to create child forms within it. But the main form will not be fully created until after the FormCreate() method has executed.

Recreating Program Groups

When an application is loaded, the Show event occurs just after the form is created. I have decided, therefore, to create the child forms in the FormShow() event-hander. In fact, the Show event occurs whenever a form is made visible. This explains why I have used a Boolean variable, formjustcreated, which is set to true in FormCreate() and to false in FormShow(). This ensures that the code in FormShow() is only executed when the application is loaded.

The code here iterates through the paths string list which was initialised by FormCreate() from the ‘ProgGroups’ section of the INI file. This creates a new child form for each entry, calling the SetRootFolder() method of each form and passing to it the path. The SetRootFolder() method can be found in the childform.pas unit. It sets the Root property of the ShellListView component on the child form and also places the path in its caption. Once all the child forms have been recreated they will display the icons stored in the specified directories. The FormShow() method then frees the paths string list which is no longer required.

too many groups
A curiosity of the ProgGroups application is that it creates a new child form on loading. As a result, you will end up with more and more groups each time it is run. This error is fixed in the ProgGroupsPlus program.

When I first coded these methods, I was plagued by a strange problem. Each time I ran the program I would end up with one more program group than I expected. Finally I remembered that Delphi creates an instance of all the form classes in a project when that project is executed. Normally this is reasonable behaviour but in the present instance it is undesirable. To get around this problem it was necessary to edit the source of the project file. You can do this by selecting ‘View Source’ from the Project menu. I commented out the line which creates an instance of TChildfm.

Finally, we need to update the caption of each program group form to display the current folder when that has been changed by double-clicking an icon. We do that in  Tchildfm.ShellListViewDblClick(). This simply reads the PathName of the ShellListView’s RootFolder property.

By now we have performed most of the essential tasks needed to save and restore program groups between sessions. There are, of course, a few extra details that need to be taken care of, such as restoring the view styles and opening program groups onto a selected folder. The techniques needed to perform these tasks are explained below.

For a slightly more complete version of this application, try out ProgGroupsPlus.dpr. This is now rather like the old Windows 3.1 Program Manager brought up to date. While it may not be perfect, I have to say I personally think it’s a good deal more civilised than that blasted Start button!


Opening The Selected Folder

How to open a selected folder in its own program group window

By default my Program Manager displays the contents of the selected folder in the current window when you press Enter or double-click a folder icon. It seemed to me that it might also be useful to have the option to open a new window onto the selected folder. I have added this feature in the ProgGroupsPlus.dpr project.

open on group
Select this menu item or press Ctrl+O to open the selected folder in a new window

You will see that the child form now has an ‘Open On Folder’ item in its Groups menu. This has been assigned the Shortcut Ctrl+O so that you can also access it direct from the keyboard. The code that executes can be found in the method, OpenOnFolder(). The first thing this does is to check that an item is actually selected. If not, then the SelectedFolder value will be nil and a message is displayed (this and other messages are there for debugging only and the ShowMessage() code can be removed at your leisure). Now, unfortunately, it turns out that the SelectedFolder property does not make a distinction between a folder icon and an application icon or shortcut. This means that we cannot simply execute the following code to change the folder:

newchildfm.ShellListView.Root := ShellListView.SelectedFolder.PathName;

Were we to do so, the code would attempt change the path to the folder containing an application when an application icon is selected. This would produce unpredictable results and could crash the program. What we really want to do is to change the folder only when a folder icon is selected and to do nothing when any other type of icon is selected. This is why I have used the DirectoryExists() function to check that the PathName property of the selected item is a valid directory before attempting to assign this value to ShellListView.Root.

Having done this, opening the folder in a new window is trivial. All we have to do is to create another instance of the Tchildfm class and make the folder assignment to its ShellListView.


Saving and Restoring ViewStyles

When a type can’t be saved to file, you may need to string it alone

Not every type can be easily written into an INI file. While the TIniFile class has methods for writing integers, strings, Booleans and a few other types, it does not have any special methods for handling odd types such as TViewStyle which is an enumerated type comprising four constants.

Now, you could get around this problem by casting the ViewStyle property to an integer and saving this to disk. While this is an acceptable solution, it would mean that you would end up with largely meaningless numbers in the INI file. It would be better, I think, to be able to write descriptive entries into the INI file. So if the ViewStyle is vsReport, the entry in the INI file will read ‘vsReport’. All this takes is two functions to convert between ViewStyles and strings. You will find my implementation of these functions, ViewStyleToStr() and StrToViewStyle() in the pg.pas unit of the ProgGroupsPlus.dpr project. There is nothing complicated about these functions and I think the clarity they give to our INI files makes them well worth the effort.


Restoring Sizes and Styles

String lists gives us the ability to restore unknown quantities of data

While the ability to restore information on the number of windows and their root folders is pretty much essential for a program manager, you may also want to restore various other details too. In the ProgGroupsPlus.dpr project I have saved information on the size and position of the main form and the ViewStyle of each of each child form.

Here ViewStyle is the property of the ShellListView on each child form and it determines which of four alternative icon styles and layouts is displayed. A different view style can be applied to each child form using the View menu. We write the ViewStyles into the INI file in the form of strings (see ‘Saving and Restoring ViewStyles’ above). Each time the application is run, the view styles are read in by the FormShow() method. This is done in precisely the same way that we read in the paths as explained in the main text above.

We have already assigned the values of the stored view styles to the items of a string list, viewstyles, in the FormCreate() method. Now, in FormShow(), we iterate through the string list items and assign their matching TViewStyle value, returned by our StrToViewStyle() function, to each successive child window. In principle, the number of view styles read from the INI file should always be identical with the number of paths. However, we have to allow for the possibility that they may not be (after all, maybe some idiot has edited the INI file by hand). For that reason, we always check that the index of viewstyles string list is valid (i.e. the iterator variable, i, is less than or equal to viewstyles.Count-1) before attempting to assign a ViewStyle to the current child form. If the index is invalid, we assign the default view style, vsReport.


Finishing Touches

As a finishing touch to my Program Manager, I have added the ability to save and restore the size and positions of the child windows As with the paths and view styles, I have done this by reading a whole section into a string list. It would be quite messy to have each of the four integer coordinates stored, one entry at a time, for each child form, so I have decided to store the coordinates in comma-delimited strings, with a single entry for each window (e.g. Win0=0,0,160,820). Then when the values are read in by FormShow(), I use a string routine called firstRestStr() which I wrote previously in a unit called huwStrUtils.pas. This takes a string, s, as the first parameter and returns the first token as it second parameter and the remainder of the string as its third parameter. The tokens are split on any of the DELIMS characters defined in huwStrUtils.pas. Each token parsed is converted to an integer and assigned to the relevant property of each child form. Note that the first argument to firstRestStr() is a const and cannot be modified. This is why we assign the values of rest to the variable wp rather than pass ‘rest’ itself in the first parameter.


Where Next…?

The first thing that needs to be done to improve this program manager is to add a great deal more error checking and recovery. For example, FormShow() in the ProgGroupsPlus.dpr project does not verify that each coordinate parsed from a string in the childpositions string list is valid and can also be converted to an integer. If an invalid token is read, this could cause the program to crash. You might also want to add a Browse menu item so that the user can create program groups from any directory on any disk.

May 2006

 


Home | Archives | Contacts

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