Displaying Progress in a Wizard

8 Jan 2004 18:24 mfc

I’m adding a wizard to the program that I’m currently working on. The wizard walks the user through importing some information from a file. I’d like to be able to display the import progress as a seamless part of the wizard.

The import process is long-winded, and the details don’t particularly matter here, so I’ll fake it by using the following:

void Task::Run()
{
	// Pretend to do some work.
	const int MAX_ITERATIONS = 10;
	for (int i = 0; i < MAX_ITERATIONS; ++i)
	{
		DWORD startTicks = GetTickCount();
		ReportProgress(i, MAX_ITERATIONS);
		Sleep(1000);
	}

	ReportProgress(MAX_ITERATIONS, MAX_ITERATIONS);
	ReportComplete();
}

The ReportProgress and ReportComplete methods are standard observer stuff. They end up calling the following methods:

void CProgressPage::OnProgress(int current, int maximum)
{
	PumpMessages();

	m_progressCtrl.SetRange32(0, maximum);
	m_progressCtrl.SetPos(current);
}

void CProgressPage::OnComplete()
{
	PumpMessages();

	static_cast<CPropertySheet *>(GetParent())->PressButton(PSBTN_NEXT);
}

Initially, I tried to get this to work from OnSetActive, but it doesn’t work. The property page is never displayed. I’d assumed that the PumpMessages call would allow the repaints to occur. Worse, because the handler for OnComplete calls PressButton(PSBTN_NEXT) while in the OnSetActive method, it gets called again, and again, and again,…

// This doesn't work...
BOOL CProgressPage::OnSetActive()
{
	if (!CPropertyPage::OnSetActive())
		return FALSE;

	// Disable the buttons.
	static_cast<CPropertySheet *>(GetParent())->SetWizardButtons(0);
	CWaitCursor wait;

	Task task(this);
	task.Run();

	return TRUE;
}

Luckily, it works OK if you call PostMessage to return from OnSetActive and then handle it later:

BOOL CProgressPage::OnSetActive()
{
	if (!CPropertyPage::OnSetActive())
		return FALSE;

	// Disable the buttons.
	static_cast<CPropertySheet *>(GetParent())->SetWizardButtons(0);
	PostMessage(WM_START_TASK);

	return TRUE;
}

LRESULT CProgressPage::OnStartTask(WPARAM wParam, LPARAM lParam)
{
	// Start doing whatever it is that we have to do.
	CWaitCursor wait;

	Task task(this);
	task.Run();

	return 0L;
}

I still need to implement the Cancel button, and there’s other things I should say about running long processes in the foreground thread, but I’ll save that for another article.

Source code is on GitHub.