一些方便的对话框技巧、提示和解决方法






4.94/5 (138投票s)
隐藏的模态对话框、窃取焦点、总在最前的对话框、全屏显示、展开和收缩对话框、移除任务栏图标、上下文相关帮助以及许多其他有用的提示和技巧。
引言
这些是我在使用对话框(无论是在基于对话框的应用程序还是在非基于对话框的应用程序中)时发现的一些技巧。其中一些可能看起来很简单,但却非常方便。我想与大家分享其中的一些技巧。
以隐藏状态启动模态对话框
你经常听到有人抱怨,尽管在他们的 OnInitDialog
函数中加入了 ShowWindow(SW_HIDE)
,但他们的模态对话框启动时仍然是可见的。问题在于,当 CDialog::OnInitDialog()
完成时,它会调用 ShowWindow(SW_SHOW)
。因此,你的对话框又会变得可见。但是,正如所料,人们已经找到了解决方法。你需要这样做。
在你的对话框类中添加一个 BOOL
成员,并给它起个名字,比如 visible
。
现在,在你的对话框构造函数中,将 visible
设置为 false
。
visible = false;
接下来,你需要重写 WM_WINDOWPOSCHANGING
。你可能需要更改消息筛选选项,才能让这个消息出现在类向导中。
void CTest_deleteDlg::OnWindowPosChanging(WINDOWPOS FAR* lpwndpos) { if(!visible) lpwndpos->flags &= ~SWP_SHOWWINDOW; CDialog::OnWindowPosChanging(lpwndpos); }
就是这样。现在你的模态对话框实际上是以隐藏状态启动的。当你想要让它可见时,你需要这样做。
visible = true;
ShowWindow(SW_SHOW);
全屏对话框
有时你可能需要制作一个全屏对话框——即一个填满整个显示器的对话框。这其实很容易实现。你需要移除标题栏和边框,我们可以通过移除 WS_CAPTION
和 WS_BORDER
样式来做到。然后我们使用 HWND_TOPMOST
调用 SetWindowPos
,并将对话框的大小调整到填满整个屏幕。只需将以下代码放入你的 OnInitDialog
函数中即可。
BOOL CFullScrDlgDlg::OnInitDialog() { CDialog::OnInitDialog(); //... int cx, cy; HDC dc = ::GetDC(NULL); cx = GetDeviceCaps(dc,HORZRES) + GetSystemMetrics(SM_CXBORDER); cy = GetDeviceCaps(dc,VERTRES) + GetSystemMetrics(SM_CYBORDER); ::ReleaseDC(0,dc); // Remove caption and border SetWindowLong(m_hWnd, GWL_STYLE, GetWindowLong(m_hWnd, GWL_STYLE) & (~(WS_CAPTION | WS_BORDER))); // Put window on top and expand it to fill screen ::SetWindowPos(m_hWnd, HWND_TOPMOST, -(GetSystemMetrics(SM_CXBORDER)+1), -(GetSystemMetrics(SM_CYBORDER)+1), cx+1,cy+1, SWP_NOZORDER); //... return TRUE; }
如何在 2K/XP 上窃取焦点
我敢打赌,你有时会怀念过去的日子,那时一个简单的 SetForegroundWindow
就能让你的对话框获得焦点。唉!现在在 2K/XP 系统中,情况有所改变,如果你尝试一个简单的 SetForegroundWindow
,最终只会让任务栏图标闪烁几次(我没数过,但感觉是闪烁三次)。这可不是你想要的效果,对吧?幸运的是,有办法能让你的对话框显示到前台。
诀窍是使用 AttachThreadInput
将拥有当前前台窗口的线程附加到我们的线程,然后调用 SetForegroundWindow
,再使用 AttachThreadInput
分离已附加的线程。很酷,是吧?
//Attach foreground window thread //to our thread AttachThreadInput( GetWindowThreadProcessId( ::GetForegroundWindow(),NULL), GetCurrentThreadId(),TRUE); //Do our stuff here ;-) SetForegroundWindow(); SetFocus(); //Just playing safe //Detach the attached thread AttachThreadInput( GetWindowThreadProcessId( ::GetForegroundWindow(),NULL), GetCurrentThreadId(),FALSE);
让你的对话框保持在最前
你是否见过那些带有“总在最前”选项的程序?嗯,令人难以置信的是,你只需一行代码就能让你的对话框保持在最前。只需将下面这行代码放入你的对话框类的 OnInitDialog()
函数中。
SetWindowPos(&this->wndTopMost,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE);
我们基本上是使用 SetWindowPos
函数来改变我们对话框窗口的 Z 顺序。我们通过将对话框移动到 Z 顺序的顶部,使其保持在所有其他窗口之上。现在即使你激活了其他窗口,我们的窗口也会保持在最前。但我建议你在这样做之前,要确保你清楚地知道自己在做什么,因为如果人们在想把你的窗口移开时却做不到,这可能会惹恼他们。
展开和收缩你的对话框
我敢打赌你见过这样的程序,它们的对话框开始时是一个尺寸。它们会有一个叫做“展开视图”或者“高级”的按钮,当你点击那个按钮时,对话框会漂亮地展开,显示出一些之前隐藏的子控件。它们可能还有一个叫做“隐藏详情”的按钮,当你点击它时,对话框会收缩回原来较小的尺寸,隐藏那些展开时显示的额外控件。这可以很容易地通过使用 SetWindowPos
来实现,正如你所见,这是一个非常有用的函数。
假设你有两个按钮,“MakeSmall”和“Expand”。那么你需要把这些代码放到它们的点击处理函数中。当然,你需要用你自己的值替换 cx
和 cy
参数。
void CTest_deleteDlg::OnMakeSmall() { SetWindowPos(NULL,0,0,200,200,SWP_NOZORDER|SWP_NOMOVE); } void CTest_deleteDlg::OnExpand() { SetWindowPos(NULL,0,0,500,300,SWP_NOZORDER|SWP_NOMOVE); }
另外,请记住在你的对话框的 OnInitDialog() 函数中调用 OnMakeSmall()
,这样你的对话框窗口就会以收缩后的尺寸启动。相反,你也可以在 OnInitDialog()
中调用 OnExpand()
使其以展开状态启动。有时你想使用同一个按钮,相应地改变按钮标题,并使用一个布尔标志来决定何时展开和何时收缩。
顺便说一下,这里有一个来自 Thomas Freudenberg 的额外提示。它涉及到这样一个事实:即使在收缩后,隐藏的控件仍然可以通过键盘访问,因此你可能需要使用 EnableWindow
来禁用这些控件。我要感谢 Thomas 提出这个建议。
关于“展开和收缩你的对话框”,你忽略了一些事情。看起来你更喜欢使用鼠标而不是键盘(你是一个所谓的 Mausschubser(德语,意为鼠标推手之类))。当对话框收缩后,你仍然可以使用 Tab 键切换到对话框外的控件。我建议对所有相应的控件调用 EnableWindow
(fExtracted)。
让你的对话框回到桌面内
有时用户可能会在屏幕上移动对话框,直到它部分移出桌面。而你可能希望将对话框完全带回视野。也可能出现这样的情况:你在较高的分辨率下进行开发,在你的机器上对话框显示得很好很完整,但最终用户可能使用较低的屏幕分辨率,从而导致对话框的一部分超出了屏幕。同样,你真的希望确保对话框是完全可见的。嗯,信不信由你,这只需要一行代码就可以完成。
SendMessage(DM_REPOSITION);
很流畅吧?请记住,这个消息只对顶级对话框有效,对子对话框无效。
为你的对话框添加最小化/最大化按钮
如果你想在设计时这样做,你只需要相应地设置属性。但如果出于某种原因你需要在运行时这样做,那么你需要这样做。重写 OnCreate()
并在其中添加这段代码。请注意,将此代码放在 OnInitDialog()
中会有一个奇怪的副作用。按钮确实会显示出来,包括最大化和最小化按钮;但它们是假的,意味着它们不能正常工作。我的猜测是,在 OnInitDialog()
中更改对话框的样式已经太晚了。
int CTest_deleteDlg::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CDialog::OnCreate(lpCreateStruct) == -1) return -1; // TODO: Add your specialized creation code here SetWindowLong(this->m_hWnd,GWL_STYLE, GetWindowLong(this->m_hWnd,GWL_STYLE) | WS_MINIMIZEBOX | WS_MAXIMIZEBOX); return 0; }
另外请注意,lpCreateStruct
只是传入的原始 CREATESTRUCT
的一个副本。因此,更改那里的样式位没有效果。并且出于某些令人费解(至少对我来说)的原因,PreCreateWindow
对于模态对话框永远不会被调用,因此我们也不能像对视图窗口或框架窗口那样在那里更改样式位。
更改鼠标光标 - 来自 Andrew Peace
嗯,我要感谢 Andrew Peace 的这个建议。有时你可能需要在对话框中更改默认的鼠标光标。你需要做的是重写 OnSetCursor
,设置一个新的光标,然后返回,而不调用基类函数,如下所示。我曾尝试过这样做但失败了,因为我一直在调用基类。再次感谢 Andrew Peace 为我指明了正确的方向。
BOOL CTest_deleteDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) { // TODO: Add your message handler code here and/or call default SetCursor(AfxGetApp()->LoadStandardCursor(IDC_UPARROW)); // Now we return instead of calling the base class return 0; // return CDialog::OnSetCursor(pWnd, nHitTest, message); }
更改对话框的背景和控件文本颜色
有一个经常被忽视的 CWinApp
成员函数,SetDialogBkColor
,它允许我们做到这一点。该函数接受两个 COLORREF
作为其参数。第一个 COLORREF
是对话框的背景色。第二个 COLORREF
将是静态文本、复选框和单选按钮控件的颜色。它不会影响编辑框和按钮控件。应用程序弹出的所有对话框和消息框都将全局使用这些颜色。将此代码放入你派生自 CWinApp
的类的 InitInstance()
中。记住要在实例化你的 CDialog
派生对象之前调用该函数。
//Red background with Green colored controls SetDialogBkColor(RGB(255,0,0),RGB(0,255,0));
这个函数现在已经过时了!它甚至可能无法工作!我将在下次更新中用正确的方法更新这个技巧!
移除基于对话框的应用的任务栏图标
有时你可能出于各种原因想要创建隐形的对话框应用程序。嗯,它们在严格意义上并非隐形,因为对话框是可见的。但让我们假设你出于某种原因不希望它们有任务栏图标。要实现这一点,你需要这样做。我们首先创建一个不可见的顶层框架窗口。以下是需要放入你派生自 CWinApp
的类中的代码。
CFrameWnd *abc=new CFrameWnd(); abc->Create(0,0,WS_OVERLAPPEDWINDOW); CNoTaskBarIconDlg dlg(abc); m_pMainWnd = &dlg; int nResponse = dlg.DoModal(); if (nResponse == IDOK) { } else if (nResponse == IDCANCEL) { } delete abc;
现在我们需要修改对话框窗口的样式。所以把这段代码放到你派生自 CDialog
的类的 OnInitDialog
中。我们需要移除 WS_EX_APPWINDOW
样式。
BOOL CNoTaskBarIconDlg::OnInitDialog() { CDialog::OnInitDialog(); ModifyStyleEx(WS_EX_APPWINDOW,0); SetIcon(m_hIcon, TRUE); // Set big icon SetIcon(m_hIcon, FALSE); // Set small icon // TODO: Add extra initialization here return TRUE; // return TRUE unless you set the focus to a control }
上下文相关帮助 - P J Arends
这个来自 Pete Arends 的技巧向你展示了如何在对话框中实现上下文相关帮助。首先你需要做的是确保对话框的标题栏上有一个问号。为此,你需要在 OnInitDialog
中这样做,如下所示。
BOOL HelpDialog::OnInitDialog() { //blah blah blah //blah blah blah ModifyStyleEx(0, WS_EX_CONTEXTHELP); return CDialog::OnInitDialog(); }
OnHelpInfo(...)
可能会在两种不同情况下被调用。第一种情况是用户按下 F1 键。这通常应该会弹出对话框的主帮助窗口。另一种情况是用户点击问号,然后点击对话框中的某个控件。或者,用户也可以右键单击一个控件,并从弹出菜单中选择“这是什么?”选项。当然,你必须自己处理弹出菜单的逻辑。因此,我们需要处理这两种情况,如下所示。
BOOL HelpDialog::OnHelpInfo(HELPINFO* pHelpInfo) { short state = GetKeyState (VK_F1); if (state < 0) // F1 key is down, get help for the dialog return CDialog::OnHelpInfo(pHelpInfo); else { // F1 key not down, get help for specific control if (pHelpInfo->dwContextId) WinHelp (pHelpInfo->dwContextId, HELP_CONTEXTPOPUP); return TRUE; } }
请记住,控件必须有关联的帮助 ID。这可以通过 [属性 --- 常规选项卡 --- 勾选帮助 ID] 来完成。当然,你还需要为你的程序编写帮助文件。谢谢。
主对话框关闭后显示 MessageBox
有时,需要在基于对话框的应用程序中主对话框被关闭后显示某种消息框。但你会发现你的消息框从未显示。有趣的是,如果你在那里设置一个断点,程序在调试时确实会中断。问题在于,在基于对话框的应用程序中,CWinThread::m_pMainWnd
就是对话框窗口本身,当对话框被关闭时,主窗口被销毁,程序退出。解决方法是注释掉将 m_pMainWnd
设置为对话框窗口的那一行代码。
BOOL CTestApp::InitInstance() { // .... CTestDlg dlg; /* Comment out the following line */ //m_pMainWnd = &dlg; int nResponse = dlg.DoModal(); if (nResponse == IDOK) { // TODO: Place code here to handle // when the dialog is // dismissed with OK } else if (nResponse == IDCANCEL) { // TODO: Place code here to handle // when the dialog is // dismissed with Cancel } MessageBox(NULL,"Some message","Title",0); return FALSE; }
当然,你绝不能在你的应用程序的任何地方调用 AfxGetMainWnd
。因为 AfxGetMainWnd
会盲目地返回你的 CWinApp
派生类的 m_pMainWnd
成员。否则,你可以做的是将你的 CDialog*
保存到另一个成员变量中,比如说 m_pMainWnd2
,然后编写一个函数 AfxGetMainWnd2
,它只返回 m_pMainWnd2
,如果你真的非常想使用 AfxGetMainWnd
的话。
最后更新
由于我非常频繁地更新这篇文章,我没有保留完整的历史记录,但我会在这里提及最后一次更新的信息。以下一个或多个技巧是在2002年9月18日的最后一次更新中添加的。
- 全屏对话框
- 如何在 2K/XP 上窃取焦点