About six months ago I made the dramatic step of upgrading to Borland C++. Previously I had been a devout disciple of C and felt the time had come to find out about the future for C programmers (at least that is what the hype would have us believe). Learning how to use C++ did not cause too many problems; I worked through the tutorials, knocked up a few small programs and generally felt this new language could be useful. Confidence boosted I then felt that the time had come to tackle programming Windows applications with the aid of the OWL class libraries supplied with my Borland compiler. This brought me back down to Earth with a bump!
Basically to learn anything new I tend to scan-read the tutorials and manual that hopefully makes sense, and then find some project that will force me to use the skill. This is usually where it comes crashing home that things are never easy, but it does have the advantage that I do learn the task in hand. Windows programming was no different, and the idea I have for this series (I hope) of articles is to narrate the project I have embarked upon to teach myself Windows programming using the OWL class libraries.
The task I chose was to develop a file manager. Yes, I know there are probably trillions of these in existence, but the reason that so many have been programmed only goes to show that what has been done so far does not suit everybody's taste! My favourite file manager to date is probably Norton Commander, probably because it favours the 'power user' and not the 'average user' (I'm too sexy for my shirt!). Being a professional programmer I tend to use applications that are keyboard rather than mouse intensive such as text editors and debuggers. Many of these applications often have the mouse 'bolted' on to them to bring them into the 1990s (Brief?), and it rarely suits them. For this reason I wanted to develop something that is simple and fast to use, and most importantly would avoid me having to find the mouse from amongst the junk and plastic coffee cups on my desk! I know I'm a Luddite (I have even dumped Program Manager for a keyboard-based shell), but I really feel sometimes that Windows applications are aimed at wimps (not WIMPs) and miss out using the power that could be there.
The starting point of any application is to lay down what is really required from the program. It is all too easy to kick the editor off and start programming before really deciding where to go as many of you will know. I decided that the essence of my file manager is that it must be very simple and must, at all costs, avoid the need to use the 'tab' key to move around the child windows (such as file and drive lists, etc.). All input to this application should be seen to be through the one point, the (dare I say it?) 'Command line'. The file list should have a fast search ability. The actions that can be performed on file selections are also to be fairly basic as too much functionality can, I feel, make the application clumsy. Actions I intend to support are: copy, move, delete, view, exec, and attribute change. Any remaining functionality will be served by giving the application the ability to bring selected file names down on to the command line from the file list to act as parameters to other independent programs. A final feature I wish to add (although this may be ambitious) is to allow DOS commands to be passed off to an already running DOS window that has a shell waiting for such tasks. This would get round the problem of DOS windows launched from the 'run' command closing as soon as the program terminates. This would also give the application something like the UNIX '&' command.
Anyway that's enough of the requirements spec and design, lets get on with the interesting bit! Microsoft has provided in the Windows API a very useful control by way of the list box, you can even send it a message to load a file list. When the list box has focus, keyboard entry can be used to a limited degree to locate items, but unfortunately this is limited to the first matching character. I wanted something more for my file manager so I set about creating a super list box with a fast search control.
I took TListBox class supplied with OWL as a starting point and set about building an enhanced list box class. I came up with the following class definition:
_CLASSDEF (TTestListBox) class TTestListBox: public TListBox { public: TTestListBox(PTWindowsObject AParent, int AnId, int X, int Y, int W, int H, PTModule AModule = NULL): TListBox(AParent, AnId.X,Y,W,H,AModule); // Load file list int Load(int attr, LPCSTR fspec); virtual void SetupWindow() { // Default - load current directory into // file window Load(0, "*.*"); }; virtual int FindItemAndMark(RTMessage Msg) = [WM_FIRST + CM_FINDNMARK]; virtual void WMDblClick(RTMessage Msg) = [WM_FIRST + WM_LBUTTONDBLCLK]; virtual void WMChar(RTMessage Msg) = [WM_FIRST + WM_CHAR]; virtual int ActionSelection(); };
Although I could have sent the list box an LB_DIR message that would load a file list, I wanted to be able to display extra details about a file so I decided I needed a 'Load' method to fill the list. At first I placed the first call to Load in the constructor and was surprised to find that the list box did not load. If I created a button and used this to call load everything was fine, so why wouldn't it work in the constructor? The answer was that one of the last things the constructor does is to clear the list box; what a useful feature this is, I thought! Fortunately the designers of the OWL libraries realised that we would probably want to display more than blank windows to our users, so they provided the SetUpWindow method. Overriding this method allowed me to call Load and see the effect.
int TTestListBox::FindItemAndMark(RTMessage Msg) { unsigned result; Pint SelList = NULL; result = GetSelCount(); if (result > 0) { SelList = new int[result]; GetSelIndexes(SelList, result); SetSelIndexes(SelList, result, FALSE); } result = SetSelStrings(&(LPSTR)Msg.LParam, 1, TRUE); return result; }
The 'FindltemAndMark' method does the searching of the list box using the string pointed to by Msg.LParam. If one is found it selects it clearing all other previous selections, otherwise it leaves none selected.
The remaining methods WMDblClick, WMChar and ActionSelection are to handle mouse double clicks and the 'enter' key being pressed. In both cases they will call ActionSelection.
To make this work all we need now is a dialog box with an edit control and OK button? Only snag is that the text entered into the edit control will only be passed to the listbox control when the OK button is pressed, I wanted to be able to dynamically search as text is entered. So I had to create a new edit control:
_CLASSDEF (TTestEdit) class _EXP0RT TTestEdit: public TEdit { public: TTestEdit(PTWindowsObject AParent, int AnId, LPSTR Astr, int X, int Y, int W, int H, WORD len, BOOL IsMulti) : TEdit(AParent, AnId, Astr, X, Y, W, H, len, IsMulti) {}; virtual void HandleChar(RTMessage Msg) = [WM_FIRST + WM_CHAR]; };
The above class that is derived from TEdit allows me to 'catch' characters entered and pass them on to the listbox as they are entered.
void TTestEdit::HandleChar(RTMessage Msg) { LRESULT result; LPSTRStringz[20]; DefWndProc(Msg); GetLine(Stringz, 20, 0); result = SendMessage(((TTestWindow *)Parent-> Parent)->ListBox->HWindow, CM_FINDNMARK,0,(LONG)Stringz); }
I'm not particularly proud of the way in which I send the message to the list box, but it worked! The dialog box that contains this edit control was then attached to an accelerator key to allow it to be invoked. In my next article I hope to take the project further, but in the meantime I welcome any input from yourselves. Over to you.