Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Universal Progress Dialog

0.00/5 (No votes)
21 Jul 2004 1  
A Progress dialog that could be used anywhere any time for any task

Introduction

In any interactive application Progress-Dialogs (modal dialog boxes that display the progress of lengthy operations) play important role in keeping the interface alive by providing the user real-time feedback about the application status. There is almost no context where these dialogs could not be used - ranging from simple file loading operations to complex OS loading operations - we find them useful. However, for each application that we develop, creating a new progress dialog from scratch is a boring task. Under these circumstances we find it useful to have a simple dialog template that could be used in any application any time for any task. Our Universal Progress Dialog that we are about to unleash here is one such modal dialog box that satisfies all these requirements, that is - it could be used any where any time for any operation.

The Universal Progress Dialog

The design goals that have driven the development of this Universal Progress Dialog are:

Simplicity (Usage should be as simple as possible)

In fact, as you would learn soon, you need to add just 2 extra lines to your existing code to start using this dialog in your app!!

Flexibility (Should be able to use it for any task)

This dialog is perfectly suitable to display the progress any kind of operation that you know how to measure the progress. This is facilitated by accepting a Pointer to any user defined Function.

Interactivity (End-user should not suffer from loss of interactivity)

This dialog allows the end-user to cancel the operation anytime before the operation completes on its own. To facilitate this, the dialog internally manipulates a worker thread and manages the communication between the interface thread and the worker thread in a transparent way freeing the developer from unnecessary thread-management issues.

With these design goals in mind, the class CUPDialog (No, it is not CUP dialog - it is to mean CUniversalProgressDialog) that we are about to study provides simple to use, flexible and interactive interface in an elegant way very much similar to the well known CDialog class from MFC. All that we need to do is create the dialog class object and call the method DoModal() on it as shown below.
CUPDialog Dlg(...);         //construct the dialog class object


INT_PTR nResult = Dlg.DoModal();

if(nResult != IDOK) return;

The method CUPDialog::DoModal() displays a modal dialog box that returns IDOK upon successful completion. The dialog template that gets displayed is but a simple dialog box containing one progress bar, one static control and one cancel button as shown in Figure 1. As could be expected easily, the progress bar displays the progress of the underlying lengthy operation while the static control displays any suitable text appropriate for the operation. The cancel button allows the user to cancel the operation at any time before the operation gets completed on its own.

Dialog Template For the Universal Progress Dialog

Figure 1 Dialog Template for the Universal Progress Dialog

When the CUPDialog::DoModal() is invoked, the dialog box automatically creates a background worker thread and schedules a user supplied function for execution in that thread's context. We can specify which function should be executed by supplying a pointer to the function as one of the parameters of the CUPDialog class constructor. The complete prototype of the constructor of CUPDialog is:

CUPDialog(HWND hParentWnd,LP_CUPDIALOG_USERPROC lpUserProc, 
  LPVOID lpUserProcParam,LPCTSTR lpszDlgTitle=_T("Please Wait.."), 
  bool bAllowCancel=true);

The Parameters for the constructor are:

HWND hParentWnd

The application window that is creating the dialog box. This value would be used as the parent window handle for the dialog box.

LP_CUPDIALOG_USERPROC lpUserProc

Pointer to a user defined function. The dialog box internally creates a thread and executes this function in that thread's context. The function should be of the form

bool UserProc(const CUPDUPDATA* pParam);
LPVOID lpUserProcParam

Argument for the user defined function. This value could be accessed from the UserProc by accessing the method CUPDUPDATA::GetAppData()

LPCTSTR lpszDlgTitle

Parameter specifying the caption for the progress dialog. Default value = _T("Please Wait..");

bool bAllowCancel

Parameter indicating if the user can cancel the operation. When set to false the cancel button would be disabled so that user cannot cancel the operation. Default value is true.


For example, the following code fragment executes a function LengthyOperationProc() while displaying a cancelable modal dialog box with default title "Please Wait..".

bool LengthyOperationProc(const CUPDUPDATA* pCUPDUPData);

void CApplicationDlg::OnBnClickedOk()
{
    int nData =0;
    
    CUPDialog Dlg(GetSafeHwnd(),LengthyOperationProc,&nData);
    
    INT_PTR nResult = Dlg.DoModal();    
}

In the above, we are trying to display the progress dialog for some lengthy operation to be executed in the function LengtyOperationProc(). As per the prototype requirements the function LengtyOperationProc() accepts a CUPDUPDATA* as argument and returns a boolean value as result. CUPDUPDATA is meant for CUniversalProgressDialogUserProcedureDATA. This key structure supports the following important methods.

LPVOID GetAppData()

Provides access to the user supplied parameter. This value is same as the third parameter supplied as part of the CUPDialog constructor.

bool ShouldTerminate()

Indicates if the function should terminate. We should execute our lengthy operation only if this return value is false. This would return true when the user has cancelled the dialog - which means we should stop and return. We should check this frequently so as not to stall the app.

void SetProgress(LPCTSTR lpszText)

This facilitates us to set the text for the static control appropriately as per the progress.

void SetProgress(UINT_PTR dwPbarPos)

This facilitates us to set the position for the progress bar control appropriately as per the progress.

void SetProgress(LPCTSTR lpszText,UINT_PTR dwPbarPos)

This facilitates us to set both the text of static control and position of the progress control at the same time as per the progress appropriately.

To demonstrate the use of CUPDUPDATA let's consider some pseudo lengthy operation: counting numbers from 1 to 100. As part of this operation we wish to display the progress for each number that we counted. The code fragment that achieves such thing would look like the following:

bool LengthyOperationProc (const CUPDUPDATA* pCUPDUPData)
{
    int* pnCount= (int*)pCUPDUPData->GetAppData();    
             //Retrieve the App Supplied Data

    
    pCUPDUPData->SetProgress(_T("Counting.."),0);
    
    while(pCUPDUPData->ShouldTerminate()== false && *pnCount != 100)
    {
           pCUPDUPData->SetProgress(*pnCount); //Update Progress Bar

                         
        *pnCount = *pnCount + 1;
    
        Sleep(100);
    }
    
    pCUPDUPData->SetProgress(_T("Done !!"),100);
    
    return true;
}

In the above, we are first retrieving a pointer to the user supplied number by using the method GetAppData(). Then we start by setting the progress bar to 0 and for each number that is counted we are repositioning the progress bar to reflect the latest status. Please observe the usage of CUPDUPData::ShouldTerminate() in the main while loop. The method ShouldTerminate() returns true only when the user presses the cancel button or the close system button. By placing the ShouldTerminate() check in the loop, we are making sure that we would stop immediately as and when the user wants. It is good practice with this design to keep such checking as often as possible so that we could respond immediately the moment user cancels the dialog. Its importance need not be stressed twice given the fact that we are executing the function in a background thread context and not in the main application thread context.

Finally when done, we set the progress to 100 and exit from the function by returning true. This indicates that we have successfully completed the lengthy operation, which results in a return value of IDOK for the method CUPDialog::DoModal(). However, we may sometimes require indicating failure in the lengthy operation. For example, in operations that involve file manipulations we may encounter file loading/reading/writing errors. In such cases we exit from the function by returning false. This would result in a return value of INT_PTR made of LOWORD(IDCANCEL) and HIWORD(0) for the method CUPDialog::DoModal(). Incase of the user canceling the dialog before the operation gets completed on its own, the return value for the method CUPDialog::DoModal() would be an INT_PTR made of LOWORD(IDCANCEL) and HIWORD(1). The following code fragment illustrates such er ror checking mechanism.

bool LengthyOperationProc(const CUPDUPDATA* pCUPDUPData);

void CApplicationDlg::OnBnClickedOk()
{
    CUPDialog Dlg(GetSafeHwnd(), LengthyOperationProc, this);
    
    INT_PTR nResult = Dlg.DoModal();
    
    if(nResult != IDOK)
    {
        if(!HIWORD(nResult))
            MessageBox(_T("Error Occurred !!"));
        else
            MessageBox(_T("User Cancelled the Dialog !!"));
    }
}

In any typical application, when the user presses the cancel button before the operation gets completed on its own, the value related to CUPDUPData::ShouldTerminate() would be set to true by the dialog. However, if we are unable to check it immediately due to any reason such as being stuck in some time consuming primitive operations or forgot to call CUPDUPData::ShouldTerminate(), the dialog would wait for some time before actually returning the control to the parent window. In such case, the thread might still continue to execute in the background even though the dialog box has terminated. It would be killed when the dialog class object variable gets out of scope. That is, the thread would get killed in the destructor of the CUPDialog object, if still found alive by that time.

What you should do?

To use this Universal Progress Dialog in your code all that you need to do is create a dialog template with a static control, a progress bar and a cancel button. Use the following Id's for them.

Dialog box

IDD_PROGRESS_DIALOG

Static control

IDC_PROGRESS_TEXT

Progress bar

IDC_PROGRESS_BAR

Cancel button

IDCANCEL


Then you should include the header file "UPDialog.h" in your code. This header file contains the CUPDialog class declaration and other related structures.

#include "UPDialog.h"

Wherever you require to display the progress operation, declare a variable for the class CUPDialog and call the method CUPDialog::DoModal() on it. To declare the variable for the class CUPDialog you need to pass the function you want to execute in the background as a constructor parameter. Remember that the function should be of the form:

bool UserProc(const CUPDUPDATA* pParam);

In the function body use the overloaded methods CUPDUPDATA::SetProgress() and CUPDUPDATA::ShouldTerminate() to set the progress, and to determine if the user has cancelled the dialog - respectively. To access the data that you have supplied, use the CUPDUPDATA::GetAppData() method.

Please note that this function is being executed in a different thread context than the main application thread context. So, your application would still be interactive while performing your lengthy operation. Feel free to use it any where for any operation any number of times!!

What you could do?

Incase, you have already designed some dialog template of your own containing more controls (or) have given some other Id values for the same controls, you could still get benefit from this CUPDialog.

All that you need to do is overwrite the #defines in the "UPDialog.h" header file. It contains the following definitions that you could redefine.

#define CUPDIALOG_DIALOG_ID        (IDD_PROGRESS_DIALOG) //Dialog Box Id

#define CUPDIALOG_TEXTBOX_ID        (IDC_PROGRESS_TEXT) //Static control Id

#define CUPDIALOG_PROGRESSBAR_ID    (IDC_PROGRESS_BAR)  //Progress Bar Id

#define CUPDIALOG_CANCELBUTTON_ID    (IDCANCEL)        //Cancel Button Id

You could replace them with your own Ids such as

#define CUPDIALOG_DIALOG_ID        (IDD_DIALOG_101)    //Dialog Box Id

and/or

#define CUPDIALOG_CANCELBUTTON_ID    (IDC_BUTTON1001)    //Cancel Button  

To process your own control messages in the dialog box, you could add additional message responses to the ProgressDlgProc() defined in the "UPDiloag.cpp" file.

Furthermore, to refine the time the dialog should wait after signaling the termination, you could modify the following definition in the "UPDialog.h" file.

#define CUPDIALOG_TERMINATE_DELAY    (500)    //Time to wait in MilliSeconds

By default, the dialog waits for 500ms. Changing it to larger values (such as 1000) would make the dialog wait for more time (1000ms), and to smaller values would make it wait for less time. Both are not suggestible actions as they would either cause the dialog to stall for long times or the thread to get killed prematurely. The default value of 500ms is suitable for most applications. Note that, this value is used only for forced terminations, such as the user canceling the dialog, and that too if the thread is found to be alive at the time of cancellation. If the thread completes on its own before any interruption, then this value would not come into scene. This way both efficiency and interactivity are guaranteed to be at their best in all the situations.

If you are interested in improving this design further, you could do so in plenty of ways. One nice way would be to have more than one dialog template ready with different controls on different templates and selectively displaying appropriate template as per user requirement and request at runtime. This could be facilitated by having the CUPDialog constructor accept the control Ids at runtime instead of choosing them at compile time. This way the same class could provide the runtime logic for more than one template and the end users could be given an option to customize the interface as they feel appropriate. All this comes at no additional cost on part of developer, well almost.

Conclusions

The Universal Progress Dialog is a simple modal dialog box aimed to be handy in most of the cases where we want to add a simple progress dialog without wasting much time redesigning a template and logic from scratch. You could use it any where any time for any task. Next time when you feel some operation is taking long time - try and see if you could plug in this CUPDialog. All it needs is just an addition of two more lines of code.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here