早上看到论坛上有人发问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);
}
效果图