早上看到论坛上有人发问CColorDialog怎么实现非模态,仔细追踪了一下,CColorDialog继承自CCommonDialog,而CCommonDialog在构造时给CDialog类的对话框资源ID是0,
//CColorDialog构造函数 CColorDialog::CColorDialog(COLORREF clrInit, DWORD dwFlags, CWnd* pParentWnd) : CCommonDialog(pParentWnd) { //……省略细节 } // CCommonDialog函数 _AFXDLGS_INLINE CCommonDialog::CCommonDialog(CWnd* pParentWnd) : CDialog((UINT)0, pParentWnd) { }
试着Create(0, NULL)则直接弹出ASSERT,
BOOL AFXAPI _AfxCheckDialogTemplate(LPCTSTR lpszResource, BOOL bInvisibleChild) { ASSERT(lpszResource != NULL); //……省略细节 }
错误也就是资源 不能为空。
用资源编辑器打开comdlg32.dll,可以看到对话框资源中存在"CHOOSECOLOR",正好是颜色对话框的资源,于是试着从comdlg32.dll创建
BOOL CreateColorDialog(CWnd *pParent = NULL) { HINSTANCE hInst = LoadLibrary(_T("Comdlg32.dll")); HRSRC hResource = ::FindResource(hInst, "CHOOSECOLOR", RT_DIALOG); HGLOBAL hTemplate = LoadResource(hInst, hResource); BOOL bResult = CreateIndirect(hTemplate, pParent, hInst); ShowWindow(SW_SHOW); FreeLibrary(hInst); return bResult; }
结果对话框倒是正确显示了,但是没有颜色,也不能操作,这是因为此处仅显示了对话框,但是没有相关的WindowProc来处理这个过程。而comdlg32.dll中的导出函数中也看不出这样的接口, 因此还得从ChooseColor入手,而ChooseColor是个阻塞式的函数,虽然提供一个Hook接口,通过Hook这个函数设置断点,可以找出ChooseColor调用了user32.dll!_DialogBoxIndirectParamAorW@24,但是传递的参数DLGPROC却直接是7632DC1Fh,也就是comdlg32.dll的内部的一个函数,这个地址会随着不同的版本将有差异,因此如果使用绝对地址会对dll的版本依赖性很大。
那么就换个思路吧,新开一个线程,把阻塞式的操作放到新线程中,这样更加容易解决问题,此种方法同样也适用于其他阻塞式操作的非阻塞式实现。废话就不多说了,见具体的实现代码。
//继承自CColorDialog的类 //可模态或非模态运行 class CModelessColorDialog : public CColorDialog { public: INT_PTR iReturn; //记录DoModal的返回值 protected: HANDLE m_ThreadEx; //辅助线程 DWORD m_ThreadID; //线程ID HWND hParentWnd; //接收非模态消息的父窗口指针 protected: //静态的线程函数 //回调函数传入的参数是本类的指针 static DWORD CALLBACK BackDoThread(LPVOID lParam) { CModelessColorDialog *pThis = (CModelessColorDialog *)lParam; //pThis->iReturn = pThis->DoModal(); //不用原始的DoModal, //因为pThis->m_cc.hwndOwner = PreModal();会使ChooseColor仍然变成模态的 //执行阻塞的DoModal pThis->m_cc.hwndOwner = NULL;//PreModal(); pThis->iReturn = (ChooseColor(&pThis->m_cc)==IDOK) ? IDOK : IDCANCEL; //PostModal(); //接收消息的父窗口指针有效, //发送消息(这里模拟发送一个WM_COMMAND消息) //这里也可以使用其他消息或回调函数等 if(pThis->hParentWnd && IsWindow(pThis->hParentWnd)) { ::PostMessage(pThis->hParentWnd, WM_COMMAND, MAKEWPARAM(0x8888U, pThis->iReturn), (LPARAM)pThis->GetColor() ); } pThis->m_ThreadID = 0; CloseHandle(pThis->m_ThreadEx); pThis->m_ThreadEx = NULL; return pThis->iReturn; } public: //构造函数 参数和CColorDialog一样 CModelessColorDialog(COLORREF clrInit=0, DWORD dwFlags=0, CWnd* pParentWnd=NULL) : CColorDialog(clrInit, dwFlags, NULL) { m_ThreadEx = NULL; m_ThreadID = 0; hParentWnd = NULL; if(pParentWnd) hParentWnd = pParentWnd->GetSafeHwnd(); //记录父窗口指针 //如果父窗口指针为空,将不发送消息 } //解析函数 ~CModelessColorDialog() { //退出线程 if(m_ThreadEx && m_ThreadID) { PostThreadMessage(m_ThreadID, WM_QUIT, 0, 0); } } public: //模态执行仍可用 DoModal //非模态执行 BOOL DoModeless(CWnd *pParent) { hParentWnd = pParent->GetSafeHwnd(); //已经在运行中,把窗口提前 if(m_ThreadEx) { return TRUE; } //启动辅助线程 m_ThreadEx = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)BackDoThread, this, 0, &m_ThreadID); return m_ThreadEx ? TRUE:FALSE; } };
//测试代码
// TestDlg1Dlg.h : header file class CTestDlg1Dlg : public CDialog { //类中的声明 CModelessColorDialog colorSelDlg; //……其它细节省略 }; //TestDlg1Dlg.cpp : implementation file //按下ID_CANCEL2按钮消息 void CTestDlg1Dlg::OnCancel2() { // TODO: Add your control notification handler code here //非模态运行 colorSelDlg.DoModeless(this); } //接收非模态发送到消息 BOOL CTestDlg1Dlg::OnCommand(WPARAM wParam, LPARAM lParam) { // TODO: 在此添加专用代码和/或调用基类 UINT wNotifyCdoe = HIWORD(wParam); UINT wID = LOWORD(wParam); if(wID == 0x8888U) { COLORREF cr = (COLORREF)lParam; CString szMsg; szMsg.Format(_T("return = %d color=(R=%u G=%u B=%u)"), wNotifyCdoe, GetRValue(cr), GetGValue(cr), GetBValue(cr)); AfxMessageBox(szMsg); return TRUE; } return CDialog::OnCommand(wParam, lParam); }
效果图