通用进度对话框






3.67/5 (25投票s)
一个通用进度对话框,可以随时随地用于任何任务。
引言
在任何交互式应用程序中,进度对话框(显示长时间运行操作进度的模式对话框)通过向用户提供有关应用程序状态的实时反馈,在保持界面活力的方面发挥着重要作用。几乎没有一种场景可以不使用这些对话框;从简单的文件加载操作到复杂的操作系统加载操作,我们都觉得它们很有用。然而,对于我们开发的每个应用程序,从头开始创建新的进度对话框都是一项枯燥的任务。在这种情况下,拥有一个简单易用的对话框模板,可以随时随地用于任何任务,将是非常有用的。我们即将在此发布的通用进度对话框就是这样一个模式对话框,它满足了所有这些要求,即 - 它可以随时随地用于任何操作。
通用进度对话框
驱动此通用进度对话框开发的设计目标是
简单性(使用应尽可能简单) |
事实上,正如你很快就会知道的,你只需要在现有代码中添加两行就可以开始在你的应用程序中使用这个对话框了!! |
灵活性(可用于任何任务) |
此对话框非常适合显示任何你知道如何测量其进度的操作的进度。这通过接受用户定义函数的指针来实现。 |
交互性(最终用户不应遭受交互性损失) |
此对话框允许最终用户在操作自行完成之前随时取消操作。为了实现这一点,对话框在内部操作一个工作线程,并以透明的方式管理界面线程和工作线程之间的通信,从而使开发人员免于不必要的线程管理问题。 |
牢记这些设计目标,CUPDialog
类(不,它不是一个 CUP 对话框 - 它的意思是我们将要学习的CU
niversalProgressDialogDialog)以一种优雅的方式提供了一个简单易用、灵活且交互的界面,非常类似于 MFC 中著名的 CDialog
类。我们只需要创建对话框类对象并调用其 DoModal()
方法,如下所示。
CUPDialog Dlg(...); //construct the dialog class object
INT_PTR nResult = Dlg.DoModal();
if(nResult != IDOK) return;
CUPDialog::DoModal()
方法显示一个模式对话框,该对话框在成功完成后返回 IDOK
。显示的对话框模板只是一个简单的对话框,其中包含一个进度条、一个静态控件和一个取消按钮,如图 1 所示。正如可以轻松预料到的,进度条显示底层长时间运行操作的进度,而静态控件显示适合该操作的任何文本。取消按钮允许用户在操作自行完成之前随时取消操作。
当调用 CUPDialog::DoModal()
时,对话框会自动创建一个后台工作线程,并在该线程的上下文中安排一个用户提供的函数执行。我们可以通过在 CUPDialog
类构造函数的参数之一中提供函数指针来指定应执行哪个函数。构造函数的完整原型是
CUPDialog(HWND hParentWnd,LP_CUPDIALOG_USERPROC lpUserProc,
LPVOID lpUserProcParam,LPCTSTR lpszDlgTitle=_T("Please Wait.."),
bool bAllowCancel=true);
构造函数的参数是
HWND hParentWnd |
创建对话框的应用程序窗口。此值将用作对话框的父窗口句柄。 |
LP_CUPDIALOG_USERPROC lpUserProc |
指向用户定义函数的指针。对话框在内部创建一个线程并在该线程的上下文中执行此函数。该函数应采用以下形式 bool UserProc(const CUPDUPDATA* pParam);
|
LPVOID lpUserProcParam |
用户定义函数的参数。可以通过访问 |
LPCTSTR lpszDlgTitle |
指定进度对话框标题的参数。默认值 = |
bool bAllowCancel |
指示用户是否可以取消操作的参数。当设置为 |
例如,以下代码片段在显示具有默认标题“Please Wait..”(请稍候..)的可取消模式对话框的同时,执行一个名为 LengthyOperationProc()
的函数。
bool LengthyOperationProc(const CUPDUPDATA* pCUPDUPData);
void CApplicationDlg::OnBnClickedOk()
{
int nData =0;
CUPDialog Dlg(GetSafeHwnd(),LengthyOperationProc,&nData);
INT_PTR nResult = Dlg.DoModal();
}
在上面的代码中,我们试图为将在 LengtyOperationProc()
函数中执行的某个长时间运行的操作显示进度对话框。根据原型要求,LengtyOperationProc()
函数接受一个 CUPDUPDATA*
作为参数并返回一个布尔值。CUPDUPDATA
代表 CUniversalProgressDialogUserProcedureDATA
。这个关键结构支持以下重要方法
LPVOID GetAppData() |
提供对用户提供的参数的访问。此值与作为 |
bool ShouldTerminate() |
指示函数是否应终止。仅当此返回值是 |
void SetProgress(LPCTSTR lpszText) |
这使我们能够根据进度适当地设置静态控件的文本。 |
void SetProgress(UINT_PTR dwPbarPos) |
这使我们能够根据进度适当地设置进度条控件的位置。 |
void SetProgress(LPCTSTR lpszText,UINT_PTR dwPbarPos) |
这使我们能够根据进度适当地同时设置静态控件的文本和进度控件的位置。 |
void AllowCancel(bool bAllow) |
用于启用或禁用进度对话框上的取消按钮。 |
void SetDialogCaption(LPCTSTR lpszDialogCaption) |
允许修改进度对话框的标题。 |
为了演示 CUPDUPDATA
的用法,让我们考虑一个伪长时间运行的操作:从 1 数到 100。作为此操作的一部分,我们希望显示计数的每个数字的进度。实现此功能的代码片段如下所示
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;
}
在上面,我们首先通过使用 GetAppData()
方法检索用户提供的数字的指针。然后,我们从将进度条设置为 0 开始,对于每个计数过的数字,我们都会重新定位进度条以反映最新状态。请注意 CUPDUPData::ShouldTerminate()
在主 while
循环中的用法。只有当用户按下取消按钮或关闭系统按钮时,ShouldTerminate()
方法才会返回 true
。通过在循环中放置 ShouldTerminate()
检查,我们确保一旦用户想要取消,我们就会立即停止。对于此设计,应尽可能频繁地进行此类检查,以便在用户取消对话框的瞬间做出响应。考虑到我们是在后台线程上下文而不是主应用程序线程上下文中执行函数,其重要性无需赘述。
最后,完成后,我们将进度设置为 100 并通过返回 true
退出函数。这表示我们已成功完成长时间运行的操作,这会导致 CUPDialog::DoModal()
方法返回 IDOK
。然而,我们有时可能需要指示长时间运行的操作失败。例如,在涉及文件操作的操作中,我们可能会遇到文件加载/读取/写入错误。在这种情况下,我们通过返回 false
退出函数。这将导致 CUPDialog::DoModal()
方法返回由 LOWORD(IDCANCEL)
和 HIWORD(0)
组成的 INT_PTR
。如果用户在操作自行完成之前取消了对话框,则 CUPDialog::DoModal()
方法的返回值将是一个由 LOWORD(IDCANCEL)
和 HIWORD(1)
组成的 INT_PTR
。以下代码片段说明了这种错误检查机制
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 !!"));
}
}
在任何典型的应用程序中,当用户在操作自行完成之前按下取消按钮时,对话框会将与 CUPDUPData::ShouldTerminate()
相关的值设置为 true
。但是,如果我们由于任何原因(例如,被卡在某些耗时的原始操作中,或忘记调用 CUPDUPData::ShouldTerminate()
)而无法立即检查它,对话框将等待一段时间,然后才真正将控件返回给父窗口。在这种情况下,即使对话框已终止,线程仍可能在后台继续执行。它将在对话框类对象变量超出范围时被杀死。也就是说,如果此时线程仍然存在,它将在 CUPDialog
对象的析构函数中被杀死。
你应该做什么
要在你的代码中使用此通用进度对话框,你只需要将演示应用程序中的 UPDialog.h、UPDialog.cpp 和 InitCommonControls.h 文件添加到你的项目中,然后开始使用 CUPDialog
类。UPDialog.h 文件包含 CUPDialog
类声明和其他相关结构,并且可以通过以下方式在你的代码中访问
#include "UPDialog.h"
在任何需要显示进度操作的地方,声明 CUPDialog
类的变量,并调用其 CUPDialog::DoModal()
方法。(不要担心创建对话框资源。CUPDialog
有一个内置模板,可以从内存加载。)要声明 CUPDialog
类的变量,你需要将要执行的函数作为构造函数参数传递。请记住,该函数应采用以下形式
bool UserProc(const CUPDUPDATA* pParam);
在函数体中,分别使用重载方法 CUPDUPDATA::SetProgress()
和 CUPDUPDATA::ShouldTerminate()
来设置进度,并确定用户是否已取消对话框。要访问你提供的数据,请使用 CUPDUPDATA::GetAppData()
方法。
请注意,此函数是在与主应用程序线程不同的线程上下文中执行的。因此,在执行长时间运行的操作时,你的应用程序仍然保持交互性。随时随地、任何次数地使用它!!
你可以做什么
如果你已经设计了自己的对话框模板,其中包含更多控件,或者有一个类似的对话框但控件 ID 值不同,你仍然可以受益于此 CUPDialog
。
你只需要使用 CUPDialog::SetDialogTemplate()
方法,传入你自己的自定义对话框模板资源名称以及静态进度条和取消按钮控件 ID。
inline void SetDialogTemplate(HINSTANCE hInst, LPCTSTR lpTemplateName,
int StaticControlId, int ProgressBarControlId, int CancelButtonId)
CUPDialog::SetDialogTemplate()
在运行时接受控件 ID,而不是在编译时选择它们。这样,CUPDialog
可以同时为多个模板提供运行时逻辑。此方法的第一个参数是对话框资源的 HINSTANCE
,这使得你能够提供可以从其他模块加载的模板。
要处理自定义对话框的任何其他控件消息,你可以使用重载的 CUPDialog::OnMessage()
方法。
INT_PTR OnMessage(HWND hDlg,UINT Message,WPARAM wParam,LPARAM lParam, BOOL bProcessed)
每当对话框收到消息(从 WM_INTIDIALOG
开始)时,都会调用此 CUPDialog::OnMessage()
。你可以在你自己派生的 CUPDialog
类中重写此方法并处理其他消息。请参阅示例演示应用程序以查看其运行情况。
此外,为了细化对话框在发出终止信号后应等待的时间,你可以使用 CUPDialog::SetTerminationDelay()
方法。
#define CUPDIALOG_TERMINATE_DELAY (500) //Time to wait in MilliSeconds
默认情况下,对话框等待 500 毫秒。将其更改为较大的值(例如 1000)将使对话框等待更长时间(1000 毫秒),而更改为较小的值将使其等待更短时间。这两种操作都不被建议,因为它们会导致对话框长时间停滞,或者线程过早终止。对于大多数应用程序来说,500 毫秒的默认值是合适的。请注意,此值仅用于强制终止,例如用户取消对话框,并且仅在取消时发现线程仍在运行时使用。如果在任何中断之前线程自行完成,则此值不会生效。这样,在所有情况下都可以保证效率和交互性达到最佳。
结论
通用进度对话框是一个简单的模式对话框,旨在在大多数需要添加简单进度对话框而无需花费大量时间从头重新设计模板和逻辑的情况下非常有用。你可以随时随地、任何任务地使用它。下次当你觉得某个操作花费的时间太长时,试着看看是否可以插入这个 CUPDialog
。它只需要添加两行代码。