当前位置:C++技术网 > 资讯 > 实现窗口透明(透明窗口和不规则窗口)的原理分析

实现窗口透明(透明窗口和不规则窗口)的原理分析

更新时间:2015-06-23 13:38:13浏览次数:1+次

    曾经为了用C++实现窗口透明,可谓是折腾了很久。用SDK也没有实现,书上都说使用空画刷即可实现背景透明,而我却始终没有实现。我也了解到很多朋友也为之烦恼。原因是,现在的Windows比书上讲的复杂了,实现机制也就复杂了,似乎以前的一些方法不再有效,或者书上的说法在很多地方都没有道出本质,即使我们照做了,或许因为缺少什么,始终没有满足,而作者对此,或许并不知道。不过我想,写书的时候,作者可能是可以实现了,不过,有些书可能真的过时了吧,现在的系统根本就不吃原来那一套了。然后使用MFC,你似乎会觉得更是雪上加霜了吧,MFC层层包被,你已经不知道Windows的很多底层的东西了。放心,既然你看到本文,你就不会再为之烦恼了。我不仅会讲述实现方法,更会道出实现技术的原理,以及平常我们实现时为什么一些不能实现的细节问题。相信你看了,为有所感悟,以后使用也会更加灵活,有了这个基础,再去进一步的探索,至少你不会云里雾里不知去向。
    因为我的经历,我对此感触颇深。曾经为了实现窗口透明,费尽心思,在网上查找资料无数,结果还是不了了之,最后还是求助于同事,同事给了一段代码,叫我放在初始化的函数里面,就能实现了。然后,就试了一下,OK了。那种惊喜,太强烈了。不过也就一瞬间的事情。因为只会贴代码,你就永远是码农,也就是最常见的农民工。所以,我冷静了下来。不过,看到代码,我似乎有点晕了。看上去很复杂的样子。因为那时候,我还是初学者,很多东西真的都没见过,所以,看上去好高级的样子,看不懂。不过,我看到了参数中的数字,我就去改变数字,然后就看看效果,慢慢的,我就看到了透明的效果的深浅。我就是喜欢探索,这种乐趣或许你懂或许你不懂。然而,让我失望的是,只有一个参数可以调节,其他的参数不知道如何调节,也就是说,只有一个调节有效果,其他的调节没效果。以后需要透明效果,就只是复制粘贴即可。虽然代码挺多,但是自己水平有限,也只有这样,想着也只能能水平提高了再来简化代码和弄清本质。
    对于窗口,我始终感觉很朦胧,总感觉看不清窗口的本质,这让我很不舒服。终于有一天,我下定决心看MSDN。英文的,你懂的。所幸我的英语水平不差,看下来也没什么问题,就是慢了点,而慢并不是英语的问题,而是其中的技术知识的问题。而庞大的MSDN,要想不迷路真不是容易的事,或许这是大多数中国程序员不愿意看MSDN的原因吧。不过我告诉你,看MSDN,你的收获绝对是看网上一些资料多得多,代价就是要静下心认真的看,还有要一定的基础。不过MSDN里面的技术都是完整而基础的,所以你既可以在里面获得大量的基础知识,又可以学习高深的知识。只要你认真,你就能搞定。不要觉得这些是废话,我只想告诉你,我对窗口透明技术的了解,之所以深入,就是看的MSDN,网上几乎查不到深入原理的资料。当然是有牛人的,只是没人愿意花时间来告诉你罢了。有MSDN,就是最好的资料,一定要利用起来。不过,你很幸运,我不仅会告诉你如何去获取这些知识,而且还会将这些基础而重要的东西传授于你,让你有更强的基础去深入。如果你问我为什么会无偿的将自己的技术分享还这么认真用心,我只想告诉你,我热爱技术,我鄙视技术私藏,我想让更多的人去学习技术,让更多的人深入技术。我的学习全部来源于网络的无私分享,我也会将我的技术分享给大家,饮水思源。如果你有什么好的技术,不用将你的全部分享,只要把技术基础和思想分享出来,就够了。当然,如果你愿意,可以全部分享。很多人会觉得,分享技术会让自己的优势降低,我想,你就错了。你分享的技术越多,你的收获就越多。事实会证明我这一点。好了,我的这个学习经历就是这样,我想多数人也是这么想的。下面我们进入正题。
    Windows窗口机制,肯定不是从前的那样的了,很多书已经过时,如果你要在新平台实现一些技术,请查阅MSDN。我们先来看看我最初得到的实现窗口的一段代码,然后来分析这段代码。你可以将这一段代码放到你程序实现透明。不过我建议你不要这么做,请你阅读完本文,使用后面更为简洁的代码去实现。学习了原理,你会使用的更为灵活,不过这里只是粗略的讨论,更加深入的,有待你进一步学习研究,这里给你提供一个入口。

/////////////////////////////////代码段1/////////////////////////////////

SetWindowLong(m_hWnd,GWL_EXSTYLE,GetWindowLong(m_hWnd,GWL_EXSTYLE)^0x80000);
HINSTANCE hInst = LoadLibrary(_T("User32.DLL"));
if(hInst != NULL)
{
    typedef BOOL (WINAPI *MYFUNC)(HWND, COLORREF, BYTE, DWORD);
    MYFUNC pFunc=(MYFUNC)GetProcAddress(hInst,"SetLayeredWindowAttributes");
    if(pFunc!=NULL)
    {
        pFunc(m_hWnd,0,220,2);
    }
    FreeLibrary(hInst);
    hInst=NULL;
}

//////////////////////////////代码段1///////////////////////////////////////

    我拿到的代码就是这么一段,可以实现的。对于初学者来说,你觉得这段代码怎么样?是不是比较长,是不是比较复杂?是不是有点犯晕?我当时是。而经过我的深入研究,我用两行代码就实现了同样的功能。相信你看了绝对再也不会使用第一段代码了。代码如下:

////////////////////////////////////////代码段2///////////////////////

    SetWindowLong(m_hWnd,GWL_EXSTYLE,GetWindowLong(m_hWnd,GWL_EXSTYLE)|WS_EX_LAYERED);
    SetLayeredWindowAttributes(0,220,LWA_ALPHA);

////////////////////////////////////////代码段2//////////////////////

    这段代码也实现了这个功能,现在看起来感觉如何?你现在知道看MSDN的好处了吧。
    看看代码段1,第一句是设置窗口风格,第二句加载库,后面就是执行库函数设置窗口透明。最后释放库。
    看看代码段2,第一句设置窗口风格,第二句设置窗口透明。
    其实两段代码执行的是同一个函数,而第一段代码却饶了一个大圈,第二段就直奔主题。你应该知道那个效率高了吧。细细分析一下,加载的User32.DLL库,在带窗口的程序中,都是已经加载了的,用来执行窗口的绘制等。既然已经加载过了,我们就可以直接执行这些函数。或许,你想在API下执行,那也是可以的,只要使用设置窗口透明的API版本即可。当然这里是MFC版本。仅此而已,API版本在MFC成员函数中,要加::表示全局API函数,否则就是类的成员函数。第一段的代码的意思就是,加载User32.DLL库,然后找到SetLayeredWindowAttributes函数的入口,得到函数执行,然后通过函数指针就可调用这个函数,传入参数就可以实现这个功能了。当然,这段代码要放在初始化窗口也就是OnInitDialog中,因为这个只是设置窗口的透明度属性而已,并没有重绘,所以放在初始化这里,窗口之后重绘时自然就应用了透明的属性。当然如果你想随时设置随时应用,你需要使用其他的函数来使窗口更新应用透明属性。这里就不说这些了,留作大家深入研究。这里将透明实现讲述清楚。那么,代码段2就省去加载库,因为也没必要,直接执行就可以了。
    我们现在来仔细的研究一下设置窗口属性的函数。起作用的就这两句代码。第一句设置窗口风格,第二句设置属性。
    第一句很重要,如果窗口没有这种风格,就无法使用这种方法实现透明。WS_EX_LAYERED是层叠窗口风格,有这种窗口风格的窗口,才能实现窗口透明。至于其他高深的自绘窗口什么的,我不清楚,不过,标准的窗口都是需要这样做的,这是微软MSDN的说法。层叠窗口,是一种扩展风格,如果你使用Create*等方法动态创建窗口时,可以指定这种风格,如果直接在资源管理器里添加一个窗口,在属性里不能直接设置,需要通过代码后期设置,就如同第一句代码那样。但是这里要注意,指定窗口风格时不能指定为WS_CHILD风格,子窗口不具备层叠窗口风格,因此一般的控件你是不能使用这种方法实现透明的。如果你要实现控件的透明,如果是和主窗口一起透明,那你不用管,因为主窗口透明了,子窗口跟着透明。这也是子窗口风格不能透明的原因之一,因为内部已经有这种机制,让其随父窗口一起透明。如果你想控件(控件就是子窗口)不随主窗口透明,那么你就要重载控件,让其成为一个非子窗口风格即可。非子窗口并不代表就不是子窗口,只是一个风格而已,有父窗口创建的窗口还是子窗口,只不过“这个窗口长大了而已,不能当做小孩子看待了。”
    上面说明了实现窗口的必须的条件。我们来看看怎么设置窗口扩展风格。
    SetWindowLong这个函数是设置窗口的属性的。第一个参数是指定窗口的句柄,第二个是指定的要设置的属性,第三个是属性值。窗口句柄在窗口对象的m_hWnd成员里保存,GWL_EXSTYLE这个是窗口扩展风格属性的标记,如果你想看其他的,可以在这个宏上面右击,然后转到定义就可以查看其他的了。代表的意思就查看MSDN即可。这个风格的前缀GWL_ 就是GetWindowLong的缩写,这样说帮助你记忆这个标志。前缀后面就是具体的属性。然后后面我们要设置的就是一个扩展风格值,如果你想保留现有的各种风格,那么就使用GetWindowLong(m_hWnd,GWL_EXSTYLE)| WS_EX_LAYERED,GetWindowLong(m_hWnd,GWL_EXSTYLE)获得一个已有的扩展风格值,然后使用位或将层叠风格WS_EX_LAYERED加进去。因为各种风格都是位标识的,只要其中的位为1就表示有这个风格,因此,WS_EX_LAYERED表示1去操作相应的位,如果是位或就表示用1去位或相应的位,这样就设置进去了。如果要取消,就使用&~WS_EX_LAYERED,这个表示将层叠风格变为0,然后用0去位与这个风格,自然这一位就为0了,即取消了这个风格。GetWindowLong得到原有的各种扩展风格,最好使用这种方式,否则如果直接在第三个参数中将层叠风格传进去,那就只有一个层叠的扩展风格了,可能会出现你不希望出现的情况。取得原有的扩展风格,然后位或即可设置相应的风格了。函数返回一个设置后的风格的值,然后将这个值传给第三个参数,就设置了这个风格。希望你好好把这一段看明白,在这是窗口风格时就很灵活了,任何窗口风格都可以通过这些位操作取消或者设置,比如最大化按钮,最小化按钮,标题栏等都可以,这样你可以动态的设置窗口的样式,如单击按钮使窗口没有边框了,再单击一下又有边框了。
    设置好窗口风格后,开始第二句,设置窗口属性。SetLayeredWindowAttributes(0,220,LWA_ALPHA);这一句和代码段1有一点区别。代码段1使用的是API函数,而这里使用的是MFC的函数。这里MFC也只不过是简单的封装,将第一个窗口句柄参数放在了窗口对象的m_hWnd成员中了而已,仅此而已。如果你想使用API函数,使用::放在函数前面即可。这个函数的第一个参数是透明色,第二个参数是透明度值,第三个是透明标志。透明色的意思就是,指定的这个颜色在窗口显示时就是透明的,其他颜色就不透明。通过这个你可以实现不规则的窗口。如,你将透明色设置为蓝色,那么窗口中凡是蓝色的都透明的,其他颜色不透明。这个透明是完全看不见,而不是半透明。设置颜色的方法就是使用RGB宏,如RGB(0,0,255),这就是纯蓝色。颜色值为0-255.RGB的顺序就是红绿蓝。最终的颜色就是这三个颜色合成的颜色。RGB宏返回的值就是COLORREF值,这个值就是第一个参数需要的。第二个参数是透明度,取值是0-255,0就完全透明,255就完全不透明,中间值就是半透明。越靠近255就越不透明。这里的透明是整个窗口的透明,子窗口也跟随主窗口透明。第三个参数,有两个取值:LWA_COLORKEY和LWA_ALPHA。前一个表示使用透明色来设置透明,后一个使用透明值来设置透明。如果使用前者,那么透明值就无效,实现的就是透明色全部完全透明,其他颜色不透明;如果是有后者,那么主窗口和其子窗口全部按照透明值来设置透明,透明色无效。LWA_ 就是LayeredWindowAttributes的缩写。
    我想,到这里,设置窗口的透明,你应该完全熟悉了吧。哪样的可以实现,哪样的不可以实现等等,你应该不会再有问题了,至于如何实现,既有代码又有详细原理解释,就怕你想不懂都难。
    如果还有疑问,直接在后面留言,我们一起研究一起学习。