MFC控件的不创建窗口也允许激活的选项查探

最近在写一个控件,其包含两部分的接口,一部分是无需要窗口即可调用的,一部分是必需和窗口关联的调用。

无需要窗口调用的,是指这个控件的全局性接口,在我的工程中它们不需要特定的某个控件实例。比如在一个工程中,可以在一个窗口上拖放多个此控件,那无需窗口就可调用的接口是对每个实例都有效的这种接口。比如传入一个字符串,然后把它保存到文件中。

必需和窗口关联的接口,是指和窗口有关系的情况,比如传入一个用户名,而这个用户名必需在一个窗口上显示。


为了更好地表现无需要窗口就可调用的接口,在建立控件工程的向导时注意了一个相关的选项,其中刚好发现了一个选项:Windowless activation,解释为:允许不创建窗口来激活控件,看似不错,虽然没有用过,还是把它勾上了,先看看再说。完成向导,编译成功。创建demo工程测试,也没有问题。

后向控件中添加接口,其中包含了一个事件,在控件的CxxCtrl::OnCreate()中触发这个事件,在demo中处理这个事件。

运行demo发现这个事件无法触发,由于这个写法之前用其它工程已经测试过了,完全没有问题,而这个控件却不行,有点奇怪。

在控件的OnCreate中下断点然后运行demo,结果OnCreate没有进来!

后对CxxCtrl添加OnClose()函数,并下断点,在关闭demo时发现,CxxCtrl的m_hWnd为0!

知道了,原来这就是所谓的不创建窗口也允许激活控件的功能。工程中对应的代码:

<pre class="cpp" name="code">DWORD CxxCtrl::GetControlFlags()
{
	DWORD dwFlags = COleControl::GetControlFlags();


	// The control can activate without creating a window.
	// TODO: when writing the control's message handlers, avoid using
	//		the m_hWnd member variable without first checking that its
	//		value is non-NULL.
	dwFlags |= windowlessActivate;
	return dwFlags;
}

 就是dwFlags |= 的这一行。 

不过,在控件显示后,还是会有一个椭圆显示在上面!

画这个东西就是在一个窗口上画的,那说明还是有个窗口在的,但不是CxxCtrl。在CxxCtrl::OnDraw()中下断点,然后运行demo,在断点处停住后,查看调用堆栈:

CocxdemoDlg::OnPaint()
CDialog::OnPaint()
CWnd::PaintWindowlessControls(CDC *pDC)
COleControlContainer::OnPaint(CDC* pDC)
COleControl::XViewObject::Draw(DWORD dwDrawAspect, LONG lindex, void* pvAspect, DVTARGETDEVICE* ptd, HDC hicTargetDev, HDC hdcDraw, LPCRECTL lprcBounds, LPCRECTL lprcWBounds, BOOL (CALLBACK* pfnContinue)(DWORD_PTR), DWORD_PTR dwContinue)
COleControl::DrawContent(CDC* pDC, CRect& rc)
CxxCtrl::OnDraw(CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)

注意,以上是按照调用顺序来贴上去的,实际调试时,vs中是从下往上列出这些调用函数的。

最终发现,这个窗口竟然是装控件的窗口!为了证明确实是这个窗口,对CxxCtrl的OnDraw修改如下:

void CxxCtrl::OnDraw(CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)
{
	if (!pdc)
		return;

	// TODO: Replace the following code with your own drawing code.
	CRect rcDraw(rcBounds);
	pdc->FillRect(rcDraw, CBrush::FromHandle((HBRUSH)GetStockObject(WHITE_BRUSH)));
	rcDraw.right += rcBounds.Width();
	rcDraw.bottom += rcBounds.Height()/2;
	pdc->Ellipse(rcDraw);
}

控件背景的大小没有修改,只对椭圆的宽度增加一倍,高度增加1/2。再运行demo,出现了预期中的效果,即控件背景的区域大小没变,但是椭圆增大了指定的尺寸。如果这个pdc不是这个窗口,则肯定无法看到超过控件区域的图像。

原来如此。效果如下图所示:


为了查看windowlessActivate的影响,把DWORD CxxCtrl::GetControlFlags()中的dwFlags |= windowlessActivate;一行注释掉:

DWORD CxxCtrl::GetControlFlags()
{
	DWORD dwFlags = COleControl::GetControlFlags();


	// The control can activate without creating a window.
	// TODO: when writing the control's message handlers, avoid using
	//		the m_hWnd member variable without first checking that its
	//		value is non-NULL.
	//dwFlags |= windowlessActivate;
	return dwFlags;
}

然后再编译运行,发现此次的CxxCtrl::OnDraw()是通过CWnd来调用的,显示和之前走的就不是同一个分支了。同时,增大了的椭圆就只显示了控件区域中的那一部分了,超出的部分没有显示。另外,CxxCtrl::OnCreate()也进来了。效果如下图所示:


当时建立工程时,从网上也搜索了一下Windowless activation选项,结果都只进行了简单的翻译,没啥解释。今天运行时这个控件的demo时,发现OnCreate()没有进来,于是去网上搜索,顺便搜索出来了其它的一些问题,比如:

http://bbs.csdn.net/topics/200016476

http://blog.csdn.net/gxulg/article/details/311057

于是就想把它搞明白,然后进行了以上的跟踪尝试,终于发现了问题所在。当然以上的跟踪比较浅显,还没有仔细查看有关 CWnd::PaintWindowlessControls(CDC *pDC) 的分支是如何整体进行工作的。以后如果需要时再进行分析


结论:
Windowless activation选项被选中后,只会创建CxxCtrl类的实例, 并不会创建这个窗口,实际展现控件的窗口,还是这个控件的父窗口,而不是CxxCtrl窗口。所以此时把CxxCtrl当做一个窗口来对待肯定是不合适的,比如在OnCreate()中处理一些初始化,处理WM_TIMER、WM_SIZE等等和窗口相关的这些消息,都是不成功的,对应的消息函数根本就进不来,更别说在其中做些事情了。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页