轻轻的我走了,正如我轻轻的来——Duilib无焦点窗口的实现

         在Windows编程中,我们已经习惯了一个窗口从创建到显示并获得焦点。我们总感觉一个窗口创建出来获得焦点是理所理所当然的,一个窗口只要显示就必须获得焦点。一个新窗口显示时,会收到到WM_SETFOCUS消息,然后旧的窗口会收到WM_KILLFOCUS消息。可能我们并不关心焦点的切换,因为从视觉角度来看,没有任何影响,但是键盘类的消息却在焦点变化时不断切换响应窗口。我们都知道,鼠标消息到来时,决定哪个窗口响应的是鼠标的坐标点,指哪里打哪里;键盘消息到来时,是谁来决定这个消息由哪个窗口响应呢?答案就是焦点。焦点是键盘消息的“舞台”,任何一个窗口只要登上焦点这个“舞台”,所有的键盘类消息自然会分配到该窗口的窗口过程函数。

         不过话又说回来了,在什么情况下的窗口不需要获得焦点呢?我总结了一下,有两种情况:1.提示类窗口,像我们常见的tooltipWindow,酷狗的音乐列表上的提示窗口,还有我们每天都在使用的QQ,把鼠标移到你的头像上,出现的提示信息窗口,当QQ收到消息时,鼠标指到QQ的任务栏图标上时出现的消息列表提示窗口,等等。当然,你有可能会问了,提示窗口和焦点有什么关系,提示窗口获得焦点又怎样?其实表面上影响不是很大,但是仔细想想这是不符合原生的Window控件机制的,WM_MOUSEMOVE消息只是个过客,键盘消息的“舞台”不能受鼠标移动干扰。比如我们打开QQ正在聊天时,或者正在填写一个比较繁琐的表格时,就晃了晃鼠标,光标没了,是不是很蛋疼?我们还得重新找到原来的编辑区,点一下刚才输入的位置让光标重新出现才能继续输入。提示类窗口的创建和销毁往往是由WM_MOUSEMOVE来控制,所以呢这类窗口不能获得焦点。2.菜单窗口,Windows原生菜单是不获取焦点的,可能你还不信,其实测试一下很容易就能看出来,用Visual Studio建一个带有菜单的Win32项目,在窗口过程函数中捕获WM_KILLFOCUS消息,当菜单弹出时,WM_KILLFOCUS是不会响应的。这里肯定你还会问,菜单的弹出和焦点有毛关系,谁规定了菜单必须是无焦点的?当然没有人规定这些,但是遵循原生的机制绝对是没错的。首先,在自己使用窗口模拟菜单时,焦点的切换是一个很大的障碍,当然,有人还利用了这个特性,用WM_KILLFOCUS消息来控制菜单的窗口的销毁,这点在只有一级的菜单中还能体现出优势,但是在做级联菜单时,问题又来了,子菜单出来,主菜单就会失去焦点,这无形当中增添了不少麻烦。各级菜单的耦合度变高,维护起来相当复杂。其次,焦点的不断切换会影响美观,我一提这个,肯定还会有不少人产生疑问,这从何说起啊?焦点在菜单上看不见摸不着,怎么能影响外观?之前有人用Duilib写过一个MenuDemo,应该很多人都在用,效果看起来也不错。但是我个人觉得还是有问题,可能大家也不会把这个当成问题。当鼠标在主菜单上滑行时,子菜单会轮流弹出,如果这时碰巧你在这个窗口的某个编辑里开启输入法输完东西时,输入法的工具框还保留当前窗口,由于子菜单的切换显示,会导致输入法的工具框不断闪烁,我个人感觉是影响美观,不知道大家的看法。原因是由于焦点的切换,导致输入法消息的响应窗口不断切换,从而导致其闪烁。酷我音乐盒应该大家都用过,如果没猜错的话,用的就是Duilib的MenuDemo来实现的菜单。你可以试一下,切换成中文在搜索框里输入几个字,然后右击弹出菜单,用鼠标在主菜单上滑行,让子菜单轮流弹出,看看那个输入法工具框是不是在不停闪烁。还有酷我音乐盒的所有提示框都是带焦点的,在菜单弹出时,不得不屏蔽某些控件的MouseHover事件,防止提示框的弹出抢走焦点,导致菜单销毁。我想这应该是由于Duilib目前不支持无焦点窗口的原因,为了解决这个问题,Skilla在Duilib的菜单上花了不少时间,因为网上这方面的资料真心太少了,还好最后有了成果,所以还有机会和大家一起分享。

       其实,弄一个无焦点的窗口还是比较简单的,1.在窗口显示时,使用ShowWindow(SW_SHOWNOACTIVE),这样窗口显示时就不会获得焦点了,但是不能点击,点击完还是会获得焦点。 2.在窗口过程中截获WM_MOUSEACTIVATE消息,返回MA_NOACTIVE,这样就完美了,这时在客户区任凭你把鼠标左键右键全点烂了这个窗口也不会获得焦点,但是不能点标题栏,这也无妨,在Duilib中我们的窗口一般是不带原生标题栏的,把标题栏去掉就是了,这时只要你不主动去SetFocus,窗口是永远不会获得焦点的。

       如果仅用Win32或者MFC编程,上面两步就够了,但我们还使用了Duilib框架,关于窗口焦点的控制参与的不仅仅是CWindowWnd,CPaintManagerUI也做了大量的::SetFocus操作,如果不做处理窗口还是照样会获得焦点。这时我们需要对源码做一下小小的修改。1.给CPaintManagerUI添加一个布尔类型属性bool m_bUnfocusPaintWindow;来区分所绘制窗口是否为无焦点窗口,在构造时初始化为false,并添加Get、Set方法,让外界能访问到。2.在CPaintManagerUI的cpp文件中查找所有的::SetFocus操作(注意前面的两个点),给所有的::SetFocus操作加上if(!m_bUnfocusPaintWindow)的判断。这时我们仅需要在将m_bUnfocusPaintWindow设置为true即可防止CPaintManager抢走焦点。这样既不影响原来的功能,又达到了我们想要的效果。3.为了方便使用在CDialogBuilder的cpp文件的219行加上一个判断

else if(_tcscmp(pstrName, _T("unfocus")) == 0)
                    {
                        if(_tcscmp(pstrName,_T("true")))
                        pManager->SetUnfocusPaintWindow(true);
                    }

这样,我们就可以在xml里面设置窗口是否为无焦点窗口了,只需在Window标签上加一个unfocus="true"的属性就可以了。例如


        这只是无焦点窗口在TipWindow上的应用,关于菜单的实现我会尽快写好Demo来和大家一起分享。


        如果大家还有什么不明白的地方,或者对我的修改有什么意见或的法的,可以直接留言,或者联系QQ:848861075(Skilla)

你可能感兴趣的:(DirectUI)