zoukankan      html  css  js  c++  java
  • Layered Window 透明窗体的实现总结

    此片文章是以前写的, 刚刚新开了博客, 就发出来跟大家分享下。

    这篇文章主要讲得是vc中各种分层、透明、不规则窗口的实现, 基本囊括GDI、GDI+能使用的所有方法。

    本文讲述了三种方法,其中第一种方法有两种不同效果,第三种方法有两种不同的实现方式。文中有方法使用了GDi+,关于GDI+的使

    用请自行查询资料,本文不进行细述。

      

    方法一:窗体整体透明,支持子控件透明,支持OnPaint重绘。

    这个方法比较简单,使用win32 Api 中SetLayeredWindowAttributes

    函数即可,关于该函数可查询MSDN,用这种方法有两种效果:

    效果1:窗体整体透明,子控件也透明,可以实现半透明效果

    //第一步要修改窗体属性,WS_EX_LAYERED支持透明

    LONG lWindowStyle = ::GetWindowLong(hwnd, GWL_EXSTYLE) |

    WS_EX_LAYERED;

    //设置Alpha不透明度

    BYTE byteAlpha = 150;

    //注意最后一个参数为LWA_ALPHA,第二个参数颜色掩码(透明

    //色无用)

    SetLayeredWindowAttributes(m_hwnd, 0/*any*/, byteAlpha,

    ----------------------- Page 2-----------------------

    LWA_ALPHA )

    效果2:窗体整体透明,子控件不透明,实现不规则窗体,区域透明。

    首先需要一张背景位图,需要透明的地方用单一颜色填充,然后将其

    贴在背景上,代码如下

    第一步跟效果一中一样需修改窗体属性

    ::SetWindowLong(hwnd, GWL_EXSTYLE, lWindowStyle);

    //将红色设为透明色, 注意透明区域鼠标并不能穿透RGB(255, 0, 0)

    //为透明色

    //注意最后一个参数为LWA_COLORKEY,第三个参数透明度无用

    ::SetLayeredWindowAttributes(hwnd, RGB(255, 0, 0), 111/*any*/,

    LWA_COLORKEY);

    需要注意的是效果1和效果2可以结合起来使用,最后一个参数改成

    LWA_COLORKEY | LWA_ALPHA即可。使用

    SetLayeredWindowAttributes函数实现不规则形状简单易行,但是通常

    会有锯齿很难处理。

    方法二:根据位图进行区域裁剪 ,关键函数CombineRgn和SetWindowRgn。

    该方法跟方法一一样,需要将背景位图需要透明的

    地方填充为单一颜色,该方法的原理是遍历位图中的每个像素,将需

    要透明的像素过滤,将其他不需要透明的像素所在区域用

    CombineRgn函数连接起来形成一个区域,然后用SetWindowRgn将贴

    好背景图的窗体放进这个区域。此方法好处是可以实现镂空,即鼠标

    穿透透明区域。缺点是遍历每个像素对于大的位图算法时间复杂度高,

    效率很低。代码如下:

    void CMeterHeadDlg::SetupRegion(CDC & pDC, HBITMAP cBitmap,
    
    COLORREF TransColor)
    
    {
    
    CDC memDC;
    
    HBITMAP pOldMemBmp=NULL;
    
    BITMAP bit;
    
    CRect rect;
    
    GetWindowRect(rect);
    
    CRgn wndRgn;
    
    //创建于传入dc兼容的临时dc
    
    memDC.CreateCompatibleDC(pDC);
    
    //取得位图参数,要用其长和宽¨ª
    
    ::GetObjectA(m_hBkBitmap, sizeof(bit), &bit);
    //将位图选入临时dc
    
    pOldMemBmp= memDC.SelectBitmap(m_hBkBitmap);
    
    //创建总的窗体区域
    
    wndRgn.CreateRectRgn(0,0,rect.Width(),rect.Height());
    
    for(int y=0;y<rect.Height()+1;y++)
    
    {
    
    CRgn rgnTemp; //保存临时区域
    
    int iX = 0;
    
    do
    
    {
    
    //等于透明色跳过找到下一个非透明色
    
    if (memDC.GetPixel(iX, y) == TransColor)
    
    {
    
    rgnTemp.CreateRectRgn(iX,y,iX+1,y+1);
    
    //合并region,注意ComebineRgn最后一个参数为“异或”
    
    wndRgn.CombineRgn(wndRgn, rgnTemp, RGN_XOR);
    
    //删除临时region
    
    rgnTemp.DeleteObject();
    
    }
    
    iX++;
    
    }while(iX <rect.Width()+1);
    
    iX = 0;
    
    }
    
    if(pOldMemBmp)
    
    memDC.SelectBitmap(pOldMemBmp);
    
    SetWindowRgn(wndRgn,TRUE);
    
    SetForegroundWindow(m_hWnd);
    
    DeleteDC(memDC);
    
    }

    方法三:使用透明png贴图,并实现透明区域的透明。

    此方法的优点是可以实现不规则形状贴图,鼠标能穿透透明区,并且边缘无锯齿。

    该方法根据实现方式可分为两种方法

    1、使用CImage(ATL和MFC中都有该类,直接用win32 api没有CImage,会麻烦点可能要用CreateFIle函数加载)绘制。

    为什么我们正常的的使用CImage加载png透明区总是有白色背景呢?查了很多资料才

    发现这其实是微软GDI+的设计问题,PNG 图片是ARGB,使用GDI+

    载入图片的时候,GDI+会默认已经进行了预剩运算(PARGB),即

    每象素的实际值是已经和ALPHA值按比例相乘的结果,实际上它根

    本就没有做预乘, 在使用透明图片的象素ALPHA通道的时候,

    CImage 内部正是调用的AlphaBlend,没有预乘的图当作预乘的图片

    处理的结果就是这相当于一张和纯白背景进行了预剩, 所以图象总

    是出现白色背景。所以我们只需要对症下药,载入图片前与处理下即

    可:

    if (Image.GetBPP() == 32) //确认32位包含alpha通道
    
    for (i = 0; i < Image.GetWidth(); i++)
    
    {
    
    for (j = 0; j < Image.GetHeight(); j++)
    
    {
    
    byte *pByte =(byte*)Image.GetPixelAddress(i, j);
    
    pByte[0] = pByte[0] * pByte[3] / 255;
    
    pByte[1] = pByte[1] * pByte[3] / 255;
    
    pByte[2] = pByte[2] * pByte[3] / 255;
    
    }
    
    }
    
    }

    最后调用CImage中Draw方法就可。

    2、使用GDI+贴图,利用UpdateLayeredWindow

    函数实现png透明区域透明。该函数请查询MSDN。这种方法不支持子

    控件透明, 不支持OnPaint重绘代码如下:

    首先在OnInitDialog中修改窗体属性

    ModifyStyleEx(0, WS_EX_LAYERED | WS_OVERLAPPED);

    下面为贴图函数,注意由于不支持OnPaint,所以需重绘是手动调用

    贴图函数,贴图中使用到GDI+

    //在OnInitDialog中初始化m_pBkImage ,m_pBkImage为

    //Gdiplus::Image指针,Image::FromFile是从外面文件夹中导入png文

    //件,若果是从本地资源文件中导入,需使用其他方法。

    m_pBkImage = Image::FromFile(g_strResPath+_T("main_.png"));

    下面为贴图函数

    // 初始化时该函数放在OnInitDialog中调用,后面需要刷新时,手动
    
    //调用,该方法贴的背景图不能响应WM_PAINT消息,也不能够在
    
    //OnPaint函数中调用该绘图方法。
    
    void CMainPanel::DrawAlphaPng()
    
    {
    
    CRect rcClient;
    
    GetClientRect(&rcClient);
    
    CClientDC dc(m_hWnd);
    
    CDC memDc;
    
    memDc.CreateCompatibleDC(dc.m_hDC);
    
    CBitmap bmp;
    
    bmp.CreateCompatibleBitmap(dc.m_hDC, rcClient.Width(),
    
    rcClient.Height());
    
    memDc.SelectBitmap(bmp);
    
    //用GDI+显示图片
    
    Graphics graph(memDc.m_hDC);
    
    graph.DrawImage(m_pBkImage, 0,0 ,rcClient.Width(),
    
    rcClient.Height());
    
    BLENDFUNCTION _Blend;
    
    _Blend.BlendOp = 0;
    
    _Blend.BlendFlags = 0;
    
    _Blend.AlphaFormat = 1;
    
    _Blend.SourceConstantAlpha = 255;
    
    SIZE sz = {rcClient.Width(), rcClient.Height()};
    
    //::UpdateLayeredWindow(m_hWnd, hDC, &ptWinPos,&sizeWindow,
    
    //hdcMemory, &ptSrc, 0, &stBlend, ULW_ALPHA);
    
    UpdateLayeredWindow(m_hWnd, dc, &CPoint(0, 0), &sz, memDc,
    
    &CPoint(0, 0), 0, &_Blend, ULW_ALPHA);
    
    bmp.DeleteObject();
    
    graph.ReleaseHDC(memDc.m_hDC);
    
    ReleaseDC(dc.m_hDC);
    
    }

    需要注意的是使用方法三中第二种方法虽然不会出现锯齿,但是

    会导致界面上的子空间全部透明,这样我们在界面上添加的控件都没

    用。我试过在界面上用Create方法创建及对控件重绘,但是没用。

    决这个问题的方法是结合方法一中的效果二。需要两个窗口A (背景

    窗口),B(用于放置控件)。用方法三第二种方式实现窗口A,使用

    方法一种第二种效果创建B,MFC中可以在OnCtlColor函数中将背景

    颜色设为单一颜色,ATL中则没有OnCtlColor,最简单的做法是在

    OnEraseBKGND消息函数中返回TRUE(背景会变成白色),然后将背

    景颜色设为透明色(这时B窗体会全透明,但是其上控件不会透明),

    将A上所需要的控件放在B上相应位置,B窗口覆盖在A上面与其重合

    (由于B透明所以B上控件看着像放在A上)。在移动B窗口时同时移

    动A窗口。这样就能达到我们想要的效果。

    除了这三种方法之外,使用GDI中TransparentBlt函数也可以实现

    透明,该函数可以将一张有背景的贴图消除背景贴在窗体上。关于该

    函数使用就不再介绍了,可以查询MSDN

    以上所述三种方法本人都尝试过,都是可行的。如有疑问请向我

    咨询。

  • 相关阅读:
    [转]苦逼男和女神之间的经典对话,亲身经历过的有木有啊,必须转。。。
    你是不是对异步Socket 很迷惑? 看完本文的一小类 你就知道大体该做什么,怎么做了....
    解决 由于本机的限制,该操作已被取消。请与系统管理员联系
    总是忘记ARGB8888的排列顺序。。。记一下,以后可以查看
    键盘 钩子 的代码
    MVC 4 诡异的 HTTP ERROR 404.20
    解决Silverlight在ChildWindow中进行DragDrop操作问题
    VS2012 未能正确加载Custom Doc Well Package包
    WPF XAML之bing使用StringFormat
    WPF MVVM之INotifyPropertyChanged接口的几种实现方式
  • 原文地址:https://www.cnblogs.com/just-bg/p/3788412.html
Copyright © 2011-2022 走看看