A Progress-indicating Status Line in C++ (Part 3)

From EDM2
(Redirected from Progress3)
Jump to: navigation, search

Written by Stefan Ruck

Introduction

Well, it's done. All the basic classes are constructed in part 1 and 2 of this series and it is time to put them together to show the use of them in a sample application. This is what I'm going to do in this last part.

This sample just reads and writes a file. It does not display the contents. And it does not give the ability to edit the file. But reading/writing is performed by a separate thread created by the main application and the progress is indicated by the status line.

Errata

In the description of the AEditFile::write member function, I said the proceed message is only posted if there are bytes left to write. Otherwise the status line will display a percentage greater than 100.

Looking at the source code again when I wrote this sample, I saw that the explanation was right but unfortunately the source was not. In the source shipped with EDM/2 issue 4 volume 6 I first checked if there are any bytes left and then recalculate the number of bytes left to write. That's a big mistake and I apologize. The right sequence is first to recalculate and then to check if there are any bytes left. An updated version of editfile.cpp is included.

An Additional Message Handler

As said above, I want to read and write the file in a separate thread. And because I want to know when this thread has finished without using ICurrentThreat::waitFor (which may block the whole system), I need two additional user messages, and, of course, a handler to handle them.

MYM_OPENFILE_READY is posted when the thread which reads the file has finished. MYM_SAVEFILE_READY indicates the end of the writing thread.

Neither message indicates (by an event parameter) whether the operation was successful or not. But I have to say that this would be a good extension.

The message handler AUserMessageHandler looks like AStatusHandler. It is also derived by IHandler. The member functions called to handle the messages are AUserMessageHandler::openFileReady and AUserMessageHandler::saveFileReady. Both are virtual.

The messages are included in mymsg.h. AUserMessageHandler is defined in usermsgh.hpp and usermsgh.cpp.

The Main Class, AStatusLineSample

I will just say a few words about the member functions which are related directly to the status line. If you have any further questions why I'm doing something the way I did, please contact me by e-mail.

AStatusLineSample is defined in statline.hpp and statline.cpp

The setupStatusLine Member Function

The reason why I set the font of the status line to the font used by the menu bar is quite simple. I don't like the status line to be higher than the menu bar and of course the font should fit in the height. Because I'm using OS/2 on a screen resolution of 1024x768 pixels in conjunction with a 17" monitor I set the system font of the menu bars to a smaller one than standard. Assuming that I'm not the only one who did so I think it's the easiest way to assign a small font to the status line without worrying about a special font which might not exist on the system.

One warning to programmers who want to use upper-case letters with the need of internal leading, e.g. the German 'Umlaute'. Because the height of the status line is just lMaxAscender + 2 * lMaxDescender these letters might not be displayed correctly.

The virtualKeyPress Member Function

When the user presses Esc, I check out if there is not just a thread running (m_pFileThread, the pointer to this thread is set), but also if a file object is created (m_pEditFile, the pointer to the file object, is set). It is possible that Esc is pressed either before or after the file is read/written. In this case I can't stop the process in an elegant way. So both pointers (m_pFileThread and m_pEditFile) must be set. This indicates an action on the file and the process gets stopped by AEditFile::stopProcess.

The startOpenFile Member Function

This member function is not used to open and read the file. This is done in a separate thread which is created at the end of this member. The AStatusLineSample::startOpenFile examines if there is a sub-thread already created (m_pFileThread is not null) and gives the user the ability to enter the name of the file to open. All the work is done by AStatusLineSample::openFile.

The openFile Member Function

//****************************************************
// AStatusLineSample :: startOpenFile - starts
//                      opening a file, starts thread
//****************************************************
Boolean AStatusLineSample :: startOpenFile (void)

{
  if(m_pFileThread) // is a file thread active?
     {
      IMessageBox msgBox (this); // yes
      // load the message title
      if(!loadString (STR_FILE_OPEN))
         m_String = "Open a file";
      // set the title
      msgBox.setTitle (m_String);
      // load the message
      if(!loadString (STR_ALREADY_PROCEEDING))
         m_String = "A file process is still in progress.";
      // show it
      msgBox.show (m_String,
                   IMessageBox::okButton   |
                   IMessageBox::errorIcon  |
                   IMessageBox::applicationModal |
                   IMessageBox::moveable);
      return (false); // abort
     }

  // load the title for the open file dialog
  if(!loadString (STR_FILE_OPEN))
     m_String = "Open a file";

  IFileDialog :: Settings fsettings;

  fsettings. setTitle (m_String); // set the title

  fsettings. setFileName ("*.*"); // set the filename

  // show the open file dialog
  IFileDialog fd( (IWindow*) desktopWindow (),
                  (IWindow*) this, fsettings);

  if (!fd. pressedOK ()) // if not pressed "OK", abort
      return (false);

  m_sFilename = fd.fileName (); // save the filename

  m_pFileThread = new IThread (); // create a thread object

  // start the read-thread
  m_pFileThread -> start(new IThreadMemberFn
                                <AStatusLineSample>
                                (*this,
                                AStatusLineSample::openFile));

  return (true); // return
}

Figure 1) AStatusLineSample::openFile

This member function is executed by the sub-thread created by AStatusLineSample::startOpenFile. It is not called directly by any member functions of AStatusLineSample.

Let's have a look at the code.

First of all, I set the priority of the current thread, here the sub-thread to read the file, to regular. This may slow down other applications running while you read a file. But if you set the priority to idle-time, reading a large file (say, 90 MB) really takes too long.

Next, I set an error-indicator to false. I need it because I use the same exit-point for all cases of ending, successful or not.

Well, some line of code later, there it is. The evil goto statement.

Oh yes, I can hear you scream and shout the moment you see it in this C++ code. But, please, let me tell you why I am using this forbidden command.

For a long time I was also a big enemy of any goto statement. But after thinking a lot about it I came to the conclusion that using goto at the right place and just to end a function, is better than writing n times the same code for every error-handling. It saves space (I don't get paid per line of code) and I think it makes the program more readable and easier to maintain. But I think you should just use it to get to the end of a function immediately and you should never leave a function by a goto statement. Now that's my personal opinion and whether you agree or not, I don't bother. Re-write this member function if you don't like it the way I did it.

As you can see, the error handling inside of this sample is not what I call user-friendly. But this program is made to show you the use of a few classes and not how to write user-friendly applications, whatever that means.

Any time an error occurs, the error flag is set and the goto statement is used to leave the function. AEditFile::allocateDataBuffer is used inside of a try/catch block to handle the exceptions thrown by this function.

The structure which holds the start parameters passed to the status line should not be a local one. If it is a member of the (in this case) main object, it is certain that the pointer given as a parameter to the status line points to a valid address when it is used. If you use a local structure the pointer might point to an absolutely unusable address because at the moment the status line uses it, the memory block might be allocated for a totally different object. How is that? Remember, the program is using multiple threads and all messages are posted, not sent! The status line is running under the main thread. The AStatusLineSample::openFile is a sub-thread. When you post a message, it is added to the queue and the program does not wait till the message is processed. So our sub-thread posts the MYM_STATUS_START_PROCESS message. Trying to read the file, it perhaps runs into an error and terminates. At the time, the sub-thread is already terminated (and all local data is destroyed) the status line inside of the main-thread is going to process the MYM_STATUS_START_PROCESS message. If the pointer to the start parameters was a pointer to local data, it might point to some invalid memory contents and I think even God does not know what could happen in this case.

At the end of this member, two messages are posted. One to tell the status line that the process has finished. The other to tell the application about the end of the thread.

The openFileReady Member Function

This is the member function which handles MYM_OPENFILE_READY messages. I wait for the sub-thread to finish (because of some system overhead it might not be done at the moment the program reaches this line of code) and then destroy the thread object.

Saving the File

This is almost identical to reading it. Inside the AStatusLineSample::saveFile member function, I use AFile::reOpen instead of AFile::open because the filename is known.

The Makefile

The makefile included looks a little bit different than mak files created by IBM C Set++. It's a hand-edited file with lots of portions copied from a makefile I found in a sample. It gives you the ability to change the compiler and linker options very easily from debug to release mode.

There are two compiler switches I think you should know about. These are /Fi+ and /Si+.

/Fi+ creates precompiled headers of every source header file used during compilation. They are placed in a subdirectory called CSET2PRE under the directory containing the original header.

To use these precompiled headers, you have to set the /Si+ option.

So if you want to save some space on your hard disk, delete them.

For further information about the switches, please refer to "C/C++ Tools, Version 2.0, Programming Guide", IBM, 1993.

Conclusion

Well, that's all. I hope that this plain sample shows you how you can use the status line. As said in part 1, indicating file processing is not the only way to use it. There are a lot of different situations where it might be helpful to get closer to something more user-friendly than many programs are.