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)
11 Mar 2011 17  
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 an 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 would 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 two 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 of 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 a CUP dialog - it is to mean CU that we are about to study provides a 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:niversalProgressDialog)

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 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 the user cannot cancel the operation. The default value is true.

For example, the following code fragment executes a function LengthyOperationProc() while displaying a cancelable modal dialog box with the 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 code, 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 an argument and returns a boolean value as a 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 the 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 the static control and the position of the progress control at the same time as per the progress appropriately.

void AllowCancel(bool bAllow)

Useful for enabling or disabling the Cancel button on the progress dialog.

void SetDialogCaption(LPCTSTR lpszDialogCaption)

Facilitates modifying the progress dialog caption.

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 a 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 a good practice with this design to keep such a check as often as possible so that we could respond immediately the moment the 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(). In case 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 an error 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 forgetting to call CUPDUPData::ShouldTerminate(), the dialog would wait for some time before actually returning the control to the parent window. In such a 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 add the UPDialog.h, UPDialog.cpp, and InitCommonControls.h files from the demo application into your project, and start using the CUPDialog class. The UPDialog.h file contains the CUPDialog class declaration and other related structures, and can be accessed from your code with:

#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. (Do not worry about creating a dialog resource. CUPDialog has a built-in template that it can load from memory.) 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 remains being interactive while performing your lengthy operation. Feel free to use it any where, for any operation, any number of times!!

What you could do

In case you have already designed some dialog template of your own containing more controls, or have a similar dialog but with different control ID values, you can still benefit from this CUPDialog.

All that you need to do is use the method CUPDialog::SetDialogTemplate() passing your own custom dialog template resource name and the static progress bar and cancel button control IDs.

inline void SetDialogTemplate(HINSTANCE hInst, LPCTSTR lpTemplateName, 
       int StaticControlId, int ProgressBarControlId, int CancelButtonId)

CUPDialog::SetDialogTemplate() accepts the control IDs at runtime instead of choosing them at compile time. This way, CUPDialog can provide the runtime logic for more than one template simultaneously. The first parameter to this method is a HINSTANCE of the dialog resource, which makes it possible for you to supply a template that can be loaded from other modules.

To process any additional control messages for your custom dialog box, you can use the overloaded CUPDialog::OnMessage() method.

INT_PTR OnMessage(HWND hDlg,UINT Message,WPARAM wParam,LPARAM lParam, BOOL bProcessed)

This CUPDialog::OnMessage() gets called whenever the dialog receives a message (from WM_INTIDIALOG onwards). You can override this method in a CUPDialog derived class of your own and process the additional messages. Please refer to the sample demo application to see this in action.

Furthermore, to refine the time the dialog should wait after signaling the termination, you can use the method CUPDialog::SetTerminationDelay().

#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 situations.

Conclusions

The Universal Progress Dialog is a simple modal dialog box aimed to be handy in most 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. The next time you feel an operation is taking a long time, try and see if you could plug in this CUPDialog. All it needs is adding 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