Channels ▼
RSS

Tools

Interruptible-Task Cursor


David Wincelberg runs FileJockey Software and can be reached at www.FileJockeySoftware.com/ or filejockey@compuserve.com.


In one of my programs, there is an Import Favorites or Bookmarks feature. Users could type in the pathname of a bookmark file, press the Browse button to select one such file, or press the Find button to search for these files. Since users may want to cancel the searching before the selected disk is fully searched, the Find button becomes a Stop button. This brings up two questions: How do I make the dialog respond to a stop command and which cursor should be displayed?

The first question has been addressed in several articles. In Wicked Code Jeff Prosise presents a class called CWaitDialog. Using this class would put a dialog box on top of the current dialog (or window) that contains a progress bar and a Cancel button. You would include a BOOL flag in your long-running routine that is tested periodically. You would also add code to update the progress bar and to pump messages. The last part is needed so that clicking on the Cancel button sets this flag to FALSE while this routine is running.

For file searching, you don't know how far you are into the task (unless you have information from a previous search). In addition, I'd rather not cover the import dialog box since it displays which files have been found.

In C++ Q&A Paul DiLascia presents two approaches. The first involves a class called CCancelDlg. This is similar to the CWaitDialog class in Prosise's article. When using this class, a dialog would appear above your dialog (or window). Unlike when using CWaitDialog, testing for the stop signal involves a function call that also pumps messages; see Example 1.


// 1998 Microsoft Systems Journal. 
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual C++ 5.0 on Windows 95.
// July 1998, pp. 83-91, http://www.microsoft.com/msj/0798/c0798.aspx

// Test for abort. This is my chance to run peek/pump message loop;
// ie, to process any messages that may be waiting for me
// or--in Windows 3.1--for other apps as well.
BOOL class_name::Abort()
{
	MSG msg;
	while (::PeekMessage (&msg, NULL, NULL, NULL, PM_NOREMOVE)) {
		AfxGetThread()->PumpMessage();
	}
	return m_bAbort;
}	// Abort

Example 1: Abort() function from Paul DiLascia.

The other approach uses a class called CThreadJob. With this class, the long-running routine runs in a separate thread. This allows for a Stop button to be included in the dialog that contains the routine's start button. Another advantage is that the dialog is more responsive to being dragged since mouse messages don't have to wait until they are pumped.

For the file-searching feature, I used part of the first approach of DiLascia's so that a Stop button could be in the same dialog box. The routine for this feature periodically calls Abort() to pump messages and check if m_bAbort has changed as a result of the user pressing the Stop button.

Now that you have seen several ways to allow for a routine to be interrupted, how do you signal to users that your program is working on a long task but could be stopped? Having a Stop or Cancel button helps. To complete the picture, the cursor should convey both messages. The wait cursors in Figure 1 don't imply that a task could be interrupted.

Figure 1: Wait cursors for Windows Vista (left) and earlier Windows versions (right).

The standard arrow cursor doesn't indicate that a long task is running. Instead, a combination of these cursors is needed. Windows comes with such cursors (see Figure 2).

Figure 2: Mixed cursors for Windows Vista (left) and earlier Windows versions (right).

To make using this cursor convenient, I wrote a class called CInterruptibleCursor, which is in Listing One.


// InterruptibleCursor.cpp
// Copyright (c) 2008 by David Wincelberg
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "InterruptibleCursor.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

// Action:  Sets the cursor to one with an arrow and a wait indicator.
CInterruptibleCursor::CInterruptibleCursor()
{
    m_curNormal = ::LoadCursor (NULL, IDC_ARROW);
    m_curMixed  = ::LoadCursor (NULL, IDC_APPSTARTING);

    if (m_curNormal == NULL)
        m_curMixed = NULL;
        // changes cursor only if normal one is loaded

    Restore();
}   // CInterruptibleCursor


// Action:  Restores the normal cursor.
CInterruptibleCursor::~CInterruptibleCursor()
{
    if (m_curNormal != NULL)
        ::SetCursor (m_curNormal);
}   // ~CInterruptibleCursor


// Action:  Sets or restores the interruptible cursor.
void CInterruptibleCursor::Restore()
{
    if (m_curMixed != NULL)
        ::SetCursor (m_curMixed);
}   // Restore

Listing One

To use this class, create an instance of CInterruptibleCursor on the stack. Inside a loop (or other long process), call ic.Restore() after each call to Abort(). This is necessary since Abort() resets the cursor to the default one. When the CInterruptibleCursor object goes out of scope, the cursor is set back to the normal one; see Example 2.


#include "InterruptibleCursor.h"
CInterruptibleCursor ic;
do {
    ic.Restore();
    // long task
} while (!Abort() && other_conditions);

Example 2: Using the CInterruptibleCursor class along with Abort().

The demo program StopTaskCursor shows how a long process looks when using various cursors -- the normal one, the wait cursor, and the mixed one; see Figure 3. The complete source code and related files are available accompanying this article here.

Figure 3: Demonstrates three cursor choices.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video