c++ builder 子窗口长期前置

【Write by wood 小汤——http://blog.csdn.net/tbwood  转载请说明出处,写下来不容易】

要真正的理解和解决这个问题,确实不容易。当我花了好几天时间才搞明白之后(每天都会纠结新的问题),我真想写一本书。

首先说明下让窗口前置的方法非常多,但现有(2011年1月26号)网上(公开中文论坛资料)的99%(还有1%我是没发现)的回答和论点都是不全面或者错误的。所以我觉得写这样一篇文章是由必要的,真理谈不上,就希望给大家点参考,防止走弯路。

以下是我自己钻研出来的经过BCB RAD测试得到的结论。(过程就不说了,两个字:辛酸)

一、原理讨论。(想直接看“实现从属窗体前置”的翻到二部分)

有必要了解Windows 窗口机制的Z 轴坐标的原理才能再实践中更上一层楼。(一部分最后Note里会讲到)

BCB-RAD(其他没有尝试,可能只是部分适用)里要让一个窗体(设为Form)能和任务管理器一样在所有窗口之上,必须具有两个条件:

1.必须是父窗体(主窗体),换句话说Form没有parent form。——这里要说明的是,父窗体可以理解为主窗体MainForm(只是在一定范围内可以,其本质也不相同),但是子窗体不能理解为从属窗体,至于为什么,我只知道MDI子窗体和从属窗体是完全的两回事,没有深究,这里不阐述。

2.必须要用WindowAPI 或者是窗体属性来设置Form的EXSTYLE为TOPMOST。(BCB提供的SetZOrder什么的都没有用)以下是两种方式:

1)API-SetWindowPos:在窗体初始化函数__fastcall TForm::TForm(TComponent* Owner) : TForm(Owner) 里或者Create函数里加上

[cpp]  view plain copy
  1. SetWindowPos(Form->Handle,HWND_TOPMOST,0,0,0,0,SWP_NOSIZE|SWP_NOMOVE|SWP_SHOWWINDOW);  
 

SetWindowPos(带设置窗体的句柄,ExStyle(HWND_TOPMOST),坐标x,y,xx,yy,Style);

2)参数传递法:添加一个参数传递函数:

在.h文件里TForm类__published成员下添加:

 void   __fastcall CreateParams(TCreateParams   &Param) ;

表示这个参数将在窗体创建的时候传递给窗体,使之按这个参数创建窗口;

然后在.cpp中实现之。如下:

[cpp]  view plain copy
  1. void   __fastcall   TForm2::CreateParams(TCreateParams   &Param)  
  2. {  
  3.           TForm::CreateParams(Param);//初始化  
  4.                 //添加最高层属性。  
  5.           Param.ExStyle=Param.ExStyle  |WS_EX_TOPMOST   ;  
  6. }  
 

【Note】:关于1中父窗体的问题,在BCB里,显得十分复杂:

比如,Form->parent  和GetParent(Form->Handle) 结果是不同的。

经过猜测和测试证实后,BCB是这样管理窗口的。

首先生成的Application,也就是exe文件。这个文件它就是一个窗口,当你运行的时候,它是存在的,只不过是隐藏的。而且它是一个父窗体,那么是谁的父窗体呢?(是不是很神奇?继续...)注意看下面的描述:它是该程序中非主窗体(也就是单击首先运行的窗体——不管可见与否)的父窗体。难理解没关系,看下面的例子就行了。

也就是说假如这样一个例子:创建两个窗体Form1、 Form2,其中Form1是主窗体(父窗体)。Form2通过Form1产生。应用程序的名称设置为AppForm(在project选项里设置)。这里有两个主(父)窗体:Form1,和AppForm,其中AppForm是被隐藏的(用ShowWindow可以实现它的最大化显示,这里不详述,不清楚的可以和我讨论)。让人吃惊的是Form2是从属窗体,而且它不是Form1的从属窗体,而是AppForm的。

这里也许大家会问:

为什么子窗体不可以长期前置呢?大家仔细体会下下面这段话,不难理解。

我猜想:我们对于一个窗体考虑它的Z坐标位置,都是相对于其父窗体来说的。也就是说,Form2是一个从属窗体,我们用SetWindowPos或者参数传递法时,设置的Z位置都是指以主窗体为坐标原点的。

而当一个窗体为父窗体时,其实他的“隐含父窗体(实际不存在)”就是这个显示屏,这样的基础上,我们设置的Z位置就是这个显示屏的Z轴了,这样才可以达到在所有窗体之上的效果。

另外需要说明的是,如果多个窗口都是前置的,比如任务管理器,QQ截屏等。那么它们是并列的,也就是,互相能覆盖,但比非前置窗口的Z坐标都要前。其原则是:谁有焦点,谁在上面。

另外,Form2的Style里没有WS_CHILD属性,这验证了我上面说的子窗体不等同于从属窗体,但它却确实存在父(主)窗体AppForm。


二、实现从属窗体的长期前置:

      看完原理部分,相信大家还有很多疑问,接下来大家想了解的就是:如果现在有一个fForm,它是由主窗体Form1上的一个按钮来控制其Show出的,也就是,程序运行,Form1是主窗体(BCB中AppForm隐藏,一部分解释了)首先显示,然后我们按下Form1上的某个按钮时,fForm弹出。

      根据第一部分,我们要先把fForm变为主(父)窗体,如何变呢?

      我提供两种经测试正确的方法:

      (1)运行前参数传递:这个也是理由CreateParams函数来实现的。我们要做的很简单,只需要把fForm的窗体父窗体设置为NULL,也就是成为显示屏的一个“虚拟从属窗体”(第一部分有解释)。如下代码:

[cpp]  view plain copy
  1. //(BCB C++)  
  2. void __fastcall fForm::CreateParams(TCreateParams&Param)  
  3. {  
  4.         TForm::TCreateParams(Param);  
  5.         Param.WndParent=NULL;  
  6. }  
 

      (2)这个比较复杂,不推荐,有兴趣的可以做。在运行时,企图用SetParent函数来修改窗体的父窗体是有条件的。一般情况下会GetError()返回120错误:此操作仅限于系统权限。所以你要在运行时设置,必须要把程序的权限设置到系统权限。


三、某程序所有窗口均前置:

     当然这个方案可以用二中所说的来实现,但是二会产生很多个父窗口,而且实际意义上他们一个代表了一个程序。

     这里我想提出的另一个方案是利用消息机制。这个方案也有些同志的用过。大多数还是不知道原理,纯粹的Ctrl+C/V

     假如,某个程序的窗体情况如下:

     主(父)窗体:Form1

     从属窗体:Form2,Form3  通过Form1上的事件调用触发Show显示。

     先讨论普通情况,假如,我们把Form1,Form2,Form3都 用参数传递,或者SetWindowPos的方法设置为TopMost窗口。在Form2或者Form3覆盖了Form1后(在这个程序中,他们都是TopMost同级窗口),那么在Alt+Tab键切换时,除了Form1之外的窗口,即Form2、Form3都会被其他程序窗口覆盖。因为他们不是主窗体。

     但我们知道一点,如果在Form2,Form3被覆盖之后.Form1重新获得焦点的话,整个这个程序的窗口都会前置。这是最普通情况下也会发生的,就不解释了。所以我们找到一个思路,在程序首次运行后,任何尝试夺走本程序焦点的窗口消息,我们都予以否决。这确实有点霸道,但还是很有用的。

     可以让窗体失去焦点变为不活动窗体的消息是WM_ACTIVATEAP,以下是BCB下的WinProc里添加的代码,很好理解:

在主窗体Form1 .h文件类申明的protected里添加:

[cpp]  view plain copy
  1. void   __fastcall   WndProc(TMessage&   Msg) //override自定义消息处理函数  
  2.   
  3.        if   (Msg.Msg==WM_ACTIVATEAPP   &&   ! Msg.WParam)//Msg.WParam为false时代表是失去焦点,为true时是获得焦点  
  4.                SetWindowPos(Handle,   HWND_TOPMOST,   0,   0,  0,   0,  3);// 3= SWP_NOMOVE|SWP_NOSIZE  
  5.        TForm::WndProc(Msg);//其他的依旧交给原函数处理。  
 


四、防止Win+D键(所有窗口最小化)

      防止用户Win+D组合键把我们前置的窗体给最小化。这个问题我们也可以用消息机制来处理:找到最小化消息WM_SHOWWINDOW ,屏蔽之即可。

//BCB C++代码:

   在主窗体Form1 .h文件类申明的protected里添加override自定义消息处理函数:

[cpp]  view plain copy
  1.      void   __fastcall   WndProc(TMessage&   Msg) //override自定义消息处理函数  
  2. {  
  3.  if(Msg.Msg==WM_SHOWWINDOW   && ! Msg.WParam && Msg.LParam==SW_PARENTCLOSING)//参数见下参数说明  
  4.     )               //当显示或者隐藏窗口时触发  
  5.     {  
  6.        Msg.Result   =   0;     return;  
  7.     }  
  8. }        
  9. /*【参数说明】其中WM_SHOWWINDOW的wParam:1表示为显示窗体,0表示隐藏窗体。 
  10. lParam: 
  11. SW_OTHERUNZOOM:由于其他某个窗口被最小化或者某个最大化的窗体回复原状导致本窗口被显示。 
  12. SW_OTHERZOOM:由于其他某个窗口最大化导致本窗口被覆盖(隐藏)。 
  13. SW_PARENTCLOSING:这个窗体将要被最小化。(这就是我们想要的) 
  14. SW_PARENTOPENING:这个窗体将要被复原。*/  
 



五、以上的各个方案均不是绝对的TopMost,包括消息机制处理的情况下,还是会被任务管理器或者QQ截屏等覆盖。为什么呢?

很简单:因为这些窗口也使用了上面的方法并且用了消息机制来处理失去焦点消息。其中任务管理器没有用四提到的防止最小化。所以任务管理器可以最小化的,但QQ截屏不是。QQ截屏一个都没放过。

       但是QQ解聘所包括以上的方案都不能做到绝对的TopMost,使用同样方案的其他窗体可以互相覆盖。

       要想做到真正真正真正意义上的前置,永不被覆盖。我暂时发现的只能用无线循环线程了。(修改操作系统或者硬件不算)

[cpp]  view plain copy
  1. while (1)  
  2. {  
  3.   sleep(100);//这个值随便设,主要不要让用户感觉出来就好。  
  4.   SetWindowPos(window->handle,HWND_TOPMOST,0,0,0,0,3);//HWND_TOPMOST改成HWND_TOP也行。  
  5. }  
 

这个方案缺点就是一直需要轮设top位置,占用CPU资源。


六、总结:

以上方案都是本人自己参考了MSDN和网上相关资料经自己思考和研究测试得出的,经过了BCB RAD的测试,如果发现问题,我会立马更新,大家有什么疑问也可以一起讨论。

欢迎CB程序员或者编程大师加入C++Builder技术交流群:

QQ群:41410643

【Write by wood 小汤——http://blog.csdn.net/tbwood  转载请说明出处,写下来真不容易】

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值