四种精巧的 C++ Windows 技巧






4.44/5 (33投票s)
本文介绍了四个实用的代码片段,解决了四个常见的编程问题。
引言
最近整理硬盘时,我发现了一些我很久以前创建的古老文本文件。其中一个包含“编程技巧”和代码片段,都是我在开发某些应用程序时遇到的需要解决的问题。
每当我找到某个问题的解决方案时,我就会将相应的代码粘贴到文本文件中。现在,在开发了数十个自制应用程序之后,我知道这些东西你会一遍又一遍地遇到。
- 如何阻止应用程序显示在任务栏中?
- 如何启动一个应用程序而不出现“闪烁”?
- 如何防止我的应用程序被多次启动?
- 如何摆脱自定义 CListCtrl 中烦人的“网格线滚动”错误?
我在这里给出答案,并希望它们对您和您的应用程序有所帮助;也许这篇文章甚至能帮助您节省宝贵的时间。
那么,我们开始吧……
1. 阻止应用程序显示在任务栏中
多年前,我的一个朋友提出了一个想法,想要一个漂浮在所有其他窗口之上、具有可调透明度的小型数字时钟。我开始编码,很快就遇到了两个主要问题:首先,时钟应用程序(基于 CDialog
)显示在任务栏中,其次,它的窗口也可以通过 ALT-TAB 访问。
我尝试了几种方法来解决这些问题,最终找到了下面介绍的解决方案。要“隐藏”应用程序的技巧非常简单:主对话框窗口被附加到一个“不可见”的父窗口。仅此而已,它完美地阻止了我的时钟应用程序出现在任务栏中或通过 ALT-TAB 访问。
这是 MSVC 为任何新的 CDialog
应用程序生成的默认代码。
// // Default MSVC init: window will show up in taskbar and is accessible // via ALT+TAB // CTestDlg dlg; m_pMainWnd = &dlg; int nResponse = dlg.DoModal();
因此,要使您的对话框“抗任务栏”,只需使用下面所示的初始化方法。如果您还想阻止用户通过 ALT-TAB 任务切换机制访问您的窗口,只需使用 MSVC 属性编辑器将您的对话框设置为 TOOL
窗口。
// // Custom init: Prevent application from showing up in the taskbar // CWnd *pWnd = NULL; if(!::IsWindow(m_Invisible_pWnd.m_hWnd)) { LPCTSTR pstrOwnerClass = AfxRegisterWndClass(0); // Create an invisible parent window if(m_Invisible_pWnd.CreateEx(0, pstrOwnerClass, _T(""), WS_POPUP, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, 0)) { pWnd = &m_Invisible_pWnd; } } CTestDlg dlg(pWnd); // "Attach" dialog to the "invisible" parent window m_pMainWnd = &dlg; int nResponse = dlg.DoModal();
别忘了在您相应的 .h 文件中为 m_Invisible_pWnd
添加适当的定义。
class CTestApp : public CWinApp { public: CTestApp(); protected: CWnd m_Invisible_pWnd; // "Invisible" parent window ...
2. 启动应用程序时不闪烁
要将应用程序最小化启动(例如,直接出现在系统托盘区域),通常的方法是在CDialog::OnInitDialog()
调用后尽快调用 ShowWindow(SW_HIDE)
,最好是直接调用。这肯定能解决问题,但偶尔,您会看到主对话框窗口闪烁一小会儿,然后才最终隐藏或发送到系统托盘区域。
这是因为在 CDialog::OnInitDialog()
返回时,基本窗口已经被创建,并且它的默认行为是出现在屏幕上(实际上,这就是窗口的用途)。
几年后,当计算机运行速度超过光速时,您将自动摆脱这种烦人的闪烁,因为您的操作系统将在您启动相应应用程序之前就隐藏您的窗口。
但现在,只需将此 OnWindowPosChanging()
处理程序添加到您的应用程序中。
// // Avoid flickering of main window when starting minimized // void CTestDlg::OnWindowPosChanging(WINDOWPOS* lpwndpos) { if(m_bDlgVisible == false) lpwndpos->flags &= ~SWP_SHOWWINDOW; CDialog::OnWindowPosChanging(lpwndpos); }
您还可以声明一个可选的布尔成员变量(在本例中为 m_bDlgVisible
),它在构造函数中初始化为 true,并在相应的 SendToTray()
和 RestoreFromTray()
函数中切换。这样,我可以跟踪当前窗口状态(IsWindowVisible()
也可以工作)用于不同的目的。
注意:将此消息处理程序添加到您的代码中不会使您的计算机运行速度超过光速。
3. 避免多次启动您的应用程序
您终于编写了这个终极应用程序,却发现您的 Beta 测试者启动了它上百次,导致您的数据库因您的函数调用不是 MT 安全的而变得无用?那么,试试这个只允许一个应用程序实例同时运行的小代码片段怎么样?
检查您的应用程序是否已在运行的方法不止一种。例如,您可以在启动时读取正在运行的进程列表,看看是否能找到您的应用程序不止一次。但这有点小题大做,更重要的是,这不够酷。
创建一个系统范围唯一的对象(称为“互斥体”)并在程序启动时检查它是否存在,这样效率更高(也更酷)。如果它已存在,那么剩下的就是退出最近启动的实例……大功告成!
作为一种选择,您还可以广播一个自定义消息并获取响应应用程序的 HWND 来执行其他魔法。
我们开始吧!
下面显示的这段代码试图获取正在运行实例的 HWND
以将它的父窗口带到前面。这是可选的,但很可能是您正在寻找的东西,所以我包含了必需的代码行。
如果您不需要这种花哨的功能,您可以悄悄退出最近启动的实例,然后就可以安心了。
但我们想要做得漂亮,所以首先,请创建您自己的自定义消息;您可以在主实例的 .h 文件中完成此操作。
const UINT WM_ANYBODY_OUT_THERE
= RegisterWindowMessage(_T("Is there anybody out there?"));
我们在这里使用 RegisterWindowMessage()
来获取一个可以发送的唯一消息编号。(重复的消息编号只会导致可能不是您想要的结果。)
顺便说一句……您当然可以填写任何适合您需求的其他消息文本!
既然您现在正在编辑 .h 文件,请也添加以下内容。
struct SingleInstance { SingleInstance() { // Create a global (and unique) mutex object HANDLE hMutex = CreateMutex(0, FALSE, _T("My very own mutex")); // Failed to create the mutex object: there's an instance of this
// application running! if (GetLastError() == ERROR_ALREADY_EXISTS ||
GetLastError() == ERROR_ACCESS_DENIED) { //************************************************************ // THE FOLLOWING STUFF IS OPTIONAL. YOU ONLY NEED IT TO GRAB
// THE "HWND" OF THE INSTANCE! //************************************************************ HWND hThisApp = NULL; // Call out for each and every window EnumWindows(ThisAppSearcher, (LPARAM)&hThisApp); // Bring window to front if(hThisApp) SetForegroundWindow(hThisApp); // It's also possible to send a message to the application: // SendMessage(hThisApp, WM_USER+4711, (WPARAM)1,
// (LPARAM)WM_LBUTTONDBLCLK); //*********************** // END OF OPTIONAL STUFF //*********************** CloseHandle(hMutex); // Exit this (recently started) instance exit(0); } } //************************************************************** // Broadcast custom message and check if someone responds to it // This is also optional and only needed if you want to get the // instance's HWND! //************************************************************** static BOOL CALLBACK ThisAppSearcher(HWND hWnd, LPARAM lParam) { DWORD dwAnswer; // Message sent, but no one replied: this is probably the first
// instance if(!SendMessageTimeout(hWnd, WM_ANYBODY_OUT_THERE, 0, 0,
SMTO_BLOCK|SMTO_ABORTIFHUNG, 500, &dwAnswer) || dwAnswer != WM_ANYBODY_OUT_THERE) { return TRUE; } // Application responded to our message: grab the HWND for further
// processing *((HWND *)lParam) = hWnd; return FALSE; } };
看起来很强大,不是吗!而且最棒的是,大部分代码都是可选的……
但是等等!还有最后一步:在调用 InitInstance()
之前,添加 SingleInstance TheOneAndOnly;
。
/////////////////////////////////////////////////////////////////////////////
SingleInstance TheOneAndOnly;
BOOL CMyApp::InitInstance()
{
...
瞧,您的终极应用程序,它将阻止您任何 Beta 测试者多次启动它的企图!
4. 解决 XP 网格线问题
您可能以前都见过这种情况:在使用带有网格线的自定义 CListCtrl
报表模式时(在 XP 上),控件在您开始上下滚动时会弄乱其内容。这是 M$ 确认的一个错误,但看起来不会修复……所以这里是解决方法。
网格线滚动错误仅影响启用了平滑滚动功能的系统,但您可以添加(或修改)相关 CListCtrl
的 OnVScroll()
处理程序,这样不会出错。
void CMyListCtrl::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
CListCtrl::OnVScroll(nSBCode, nPos, pScrollBar);
Invalidate();
UpdateWindow();
}
很简单!我们只是强制进行额外的重绘以纠正问题。如果您认为即使禁用了平滑滚动,重绘控件也是浪费宝贵的 CPU 时间,那么这可能会有所帮助。
// Put this somewhere in your app's init routine SystemParametersInfo(SPI_GETLISTBOXSMOOTHSCROLLING, 0, &bIsEnabled, 0);
这将在 XP 系统上启用平滑滚动时将 bIsEnabled
设置为 true。然后,您可以将此值传递给 OnVScroll()
处理程序,以决定是否应重绘控件。
关注点
这就是我的小技巧合集……我不确定其中的任何主题是否已经在本网站上讨论过。无论如何,现在它们都汇集在这里了,如果您觉得它们有用,那就太好了。
历史
这是本文的第一个版本,很可能也是最后一个版本,而且没有版本号。事实上,从来就没有过。