用UpdateLayeredWindow实现任意异形窗口

[转]http://qjw.qiujinwu.com/blog/2012/11/03/UpdateLayeredWindow_window/

对于SetWindowRgn,它通过一个Rgn来设置区域,这个Rgn一般可以从图片中读取,在这张图片中,将不需要显示的区域标记为一种特殊的颜色,这里有个问题,必须保证这种颜色没有被正常的区域使用,否则会被误伤。为了解决这个问题,可以考虑用两张图片,增加一张单色的掩码图,这种方案带来了额外的管理开销。SetWindowRgn的好处是效率较高,对于大部分自绘的皮肤,一般只有四个角落有一些不规则,所以用SetWindowRgn是最好的选择。

SetLayeredWindowAttribut es可以将特定的窗口设置为某种透明度,也可以用它来过滤某种颜色,匹配的颜色会变成全透明。也就是类似于SetWindowRgn的效果。SetLayeredWindowAttribut es直接从DC中获得颜色,所以你需要事先绘制DC。

SetLayeredWindowAttribut es过滤颜色后,相关区域虽然不可见,但是不可见的区域可以放置子窗口,这点和SetWindowRgn有所区别。此外若子窗口刷新不及时或其他原因,那么父窗口因为SetLayeredWindowAttribut es被隐藏的DC颜色将被浮出水面。

UpdateLayeredWindow直接根据DC中的Alpha通道来实现透明效果,它很好的处理了和背景的Alpha Blend的问题,所以完美的解决了SetWindowRgn的锯齿问题。
Sample

      template
      class ImageFrameT{
      public:
              BEGIN_MSG_MAP(ImageFrameT)
                      MESSAGE_HANDLER(WM_CREATE, OnCreate)
                      MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
                      MESSAGE_HANDLER(WM_PAINT, OnPaint)
                      MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd)
                      MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLBttonDown)
              END_MSG_MAP()

              ImageFrameT():m_res_(NULL),m_move_flag_(false){}
              virtual ~ImageFrameT(){}

              LRESULT OnCreate(UINT , WPARAM , LPARAM lParam, BOOL& bHandled){
                      bHandled = FALSE;

                      // 通过参数获得资源句柄
                      LPCREATESTRUCT lpCreateStruct = (LPCREATESTRUCT)lParam;
                      if(lpCreateStruct && lpCreateStruct->lpCreateParams)
                              m_res_ = (CImage*)(lpCreateStruct->lpCreateParams);
                      ATLASSERT(m_res_);

                      if(!DLG)
                              this->InitSelf();

                      return 0;
              }

              LRESULT OnInitDialog(UINT , WPARAM , LPARAM lParam, BOOL& bHandled){
                      bHandled = FALSE;
                      if(lParam){
                              // 通过参数获得资源句柄
                              m_res_ = (CImage*)lParam;
                      }
                      if(DLG)
                              this->InitSelf();
                      return 0;
              }

              LRESULT OnPaint(UINT , WPARAM , LPARAM , BOOL& ){
                      // OnPaint不作任何事,转到UpdateLayeredWindow处理
                      T* pT = static_cast(this);
                      CPaintDC dc_(pT->m_hWnd);
                      return TRUE;
              }
              LRESULT OnEraseBkgnd(UINT , WPARAM , LPARAM , BOOL& ){
                      // 屏蔽背景绘制
                      return TRUE;
              }

              LRESULT OnLBttonDown(UINT , WPARAM , LPARAM , BOOL& bHandled){
                      // 是否支持整窗口拖动
                      T* pT = static_cast(this);
                      if(this->m_move_flag_)
                              pT->PostMessage(WM_SYSCOMMAND,0xF012,0);
                      else
                              bHandled = FALSE;
                      return 0;
              }

              void                      SetRes(CImage* res){
                      // 设置资源
                      ATLASSERT(res);
                      if(res)
                              this->m_res_ = res;
              }

              void                      SetMoveFlag(bool flag){
                      // 设置是否可拖动
                      this->m_move_flag_ = flag;
              }
      private:
              void                      InitSelf(){
                      ATLASSERT(m_res_);
                      if(m_res_){
                              T* pT = static_cast(this);

                              // 设置属性WS_EX_LAYERED
                              LONG lWindowLong = ::GetWindowLong(pT->m_hWnd, GWL_EXSTYLE) | WS_EX_LAYERED;
                              ::SetWindowLong(pT->m_hWnd, GWL_EXSTYLE, lWindowLong);
                              // 设置属性WS_POPUP
                              lWindowLong = ::GetWindowLong(pT->m_hWnd, GWL_STYLE) | WS_POPUP;
                              // 去掉一堆其他属性
                              lWindowLong &= ~WS_CHILD;
                              lWindowLong &= ~WS_BORDER;
                              lWindowLong &= ~WS_CAPTION;
                              lWindowLong &= ~WS_SYSMENU;
                              ::SetWindowLong(pT->m_hWnd, GWL_STYLE, lWindowLong);

                              pT->SetWindowPos(HWND_BOTTOM,0,0,
                                              m_res_->GetWidth(),m_res_->GetHeight(),
                                              SWP_NOMOVE | SWP_NOOWNERZORDER);

                              CClientDC dc_(pT->m_hWnd);
                              CDC mem_dc_;
                              mem_dc_.CreateCompatibleDC(dc_);
                              CBitmap mem_bitmap_;
                              mem_bitmap_.CreateCompatibleBitmap(dc_,
                                              m_res_->GetWidth(),
                                              m_res_->GetHeight());
                              mem_dc_.SelectBitmap(mem_bitmap_);

                              m_res_->Draw(mem_dc_,0,0);

                              BLENDFUNCTION pb_;
                              pb_.AlphaFormat = 1;
                              pb_.BlendOp = 0;
                              pb_.BlendFlags =0;
                              pb_.SourceConstantAlpha = 0xFF;

                              CPoint  pt_(0,0);
                              CSize    size_(m_res_->GetWidth(),m_res_->GetHeight());
                              ::UpdateLayeredWindow(pT->m_hWnd,dc_,&pt_,&size_,mem_dc_,&pt_,0,&pb_,ULW_ALPHA );

                              pT->CenterWindow(NULL);
                      }
              }
      protected:
              CImage*                m_res_;
              bool                      m_move_flag_;
      };

      class CAboutDlg :
              public CDialogImpl,
              public ImageFrameT
      {
              typedef ImageFrameT BaseClass;
      public:
              enum { IDD = IDD_DIALOG1 };

              BEGIN_MSG_MAP(CAboutDlg)
                      CHAIN_MSG_MAP(BaseClass)
                      MESSAGE_HANDLER(WM_RBUTTONDOWN, OnClose)
              END_MSG_MAP()

              LRESULT OnClose(UINT , WPARAM , LPARAM , BOOL& )
              {
                      EndDialog(0);
                      return 0;
              }
      };

      CImage          bitmap_bg_;
      BOOL ret_ = File2CImageAndImplAlpha(&bitmap_bg_,_T("res/bk.png"));
      ATLASSERT(ret_);

      CAboutDlg dlg_;
      dlg_.SetMoveFlag(true);
      dlg_.DoModal(this->m_hSubWindow_,(LPARAM)(&bitmap_bg_));

放置子控件

若只需要最一个简单的窗口,那么上面的代码可以完成要求。UpdateLayeredWindow有一个问题,那就是它上面不能放置任何子窗口,放置上去的任何窗口都不可见。为了解决这个问题,一种简单的办法是自绘,在单个窗口中模拟各种消息。

若不想搞复杂,有一种变通的办法,那就是在上面放置一个非子窗口,这种子窗口大小位置保持和它一致,同时这个子窗口用SetLayeredWindowAttribut es搞成全透明,接下来我们将所有子控件放到这个全透明的子窗口即可。
Sample

      #define CHAIN_MSG_MAP_ALT_MEMBER_EX(theChainMember, msgMapID, msg) \
              { \
                      if(uMsg == msg && \
                              theChainMember &&\
                              theChainMember->ProcessWindowMessage(hWnd, uMsg, wParam, lParam, lResult, msgMapID)) \
                              return TRUE; \
              }

      // 放置在ImageFrameT上的子窗口
      // 这个类主要处理消息的转发,通过WM_CREATE获得最顶层窗口的指针
      // 并存储在m_message_变量中,然后使用CHAIN_MSG_MAP_ALT_MEMBER_EX
      // 将它子窗口发给它的WM_COMMAND,WM_NOTIFY转发过去
      template
      class SubWindowT{
      public:
              BEGIN_MSG_MAP(SubWindowT)
                      MESSAGE_HANDLER(WM_CREATE, OnCreate)
                      MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd)
                      // 将这个不可见的窗口的WM_COMMAND和WM_NOTIFY消息
                      // 转发给最顶层的窗口
                      CHAIN_MSG_MAP_ALT_MEMBER_EX(m_message_,1,WM_COMMAND)
                      CHAIN_MSG_MAP_ALT_MEMBER_EX(m_message_,1,WM_NOTIFY)
              END_MSG_MAP()

              SubWindowT():m_message_(NULL){};

              LRESULT OnCreate(UINT , WPARAM , LPARAM lParam, BOOL& bHandled){
                      bHandled = FALSE;
                      T* pT = static_cast(this);

                      LPCREATESTRUCT lpCreateStruct = (LPCREATESTRUCT)lParam;

                      // 初始化m_message_
                      if(lpCreateStruct && lpCreateStruct->lpCreateParams)
                              m_message_ = (Y*)(lpCreateStruct->lpCreateParams);
                      ATLASSERT(m_message_);

                      // 设置属性WS_POPUP
                      LONG lWindowLong = ::GetWindowLong(pT->m_hWnd, GWL_STYLE) | WS_POPUP;
                      // 去掉一堆其他属性
                      lWindowLong &= ~WS_CHILD;
                      lWindowLong &= ~WS_BORDER;
                      lWindowLong &= ~WS_CAPTION;
                      lWindowLong &= ~WS_SYSMENU;
                      ::SetWindowLong(pT->m_hWnd, GWL_STYLE, lWindowLong);

                      return 0;
              }

              LRESULT OnEraseBkgnd(UINT , WPARAM , LPARAM , BOOL& ){
                      // 屏蔽背景消息
                      return TRUE;
              }

              // 这个指针指向最顶层的窗口,用它来将紧贴这顶层窗口的不可见窗口的消息
              // 转发给m_message_
              // 注意宏CHAIN_MSG_MAP_ALT_MEMBER_EX
              Y*          m_message_;
      };

      // 这个类在SubWindowT的基础上实现了全透明的效果
      // 利用SetLayeredWindowAttribut es可以对某种特定的颜色实现全透明过滤的特性
      template
      class SubWindow1 :
              public CWindowImpl,CWindow>,
              public SubWindowT,Y>{
              typedef SubWindowT,Y> BaseClass;
      public:
              BEGIN_MSG_MAP(SubWindow1)
                      CHAIN_MSG_MAP(BaseClass) //连接基类的消息处理逻辑,并优先处理
                      MESSAGE_HANDLER(WM_CREATE, OnCreate)
                      MESSAGE_HANDLER(WM_PAINT, OnPaint)
                      REFLECT_NOTIFICATIONS()
              END_MSG_MAP()

              LRESULT OnCreate(UINT , WPARAM , LPARAM lParam, BOOL& bHandled){
                      bHandled = FALSE;

                      // 设置属性WS_EX_LAYERED
                      LONG lWindowLong = ::GetWindowLong(this->m_hWnd, GWL_EXSTYLE) | WS_EX_LAYERED;
                      lWindowLong &= ~WS_EX_TRANSPARENT;
                      ::SetWindowLong(this->m_hWnd, GWL_EXSTYLE, lWindowLong);

                      // 在OnPaint里面将整个窗口刷成RGB(255,0,255)
                      // 在这里将此颜色过滤(编程全透明)
                      ::SetLayeredWindowAttribut es(this->m_hWnd,RGB(255,0,255),0,LWA_COLORKEY);
                      return 0;
              }

              LRESULT OnPaint(UINT , WPARAM , LPARAM , BOOL& ){
                      // 在OnPaint里面将整个窗口刷成RGB(255,0,255)
                      // 以便让SetLayeredWindowAttribut es过滤
                      CPaintDC dc_(this->m_hWnd);
                      CRect client_rect_;
                      this->GetClientRect(client_rect_);
                      dc_.FillSolidRect(client_rect_,RGB(255,0,255));
                      return TRUE;
              }
      };

      template class SUB_WINDOW >
      class ImageFrameExT : public ImageFrameT{
              typedef ImageFrameT BaseClass;
      public:
              BEGIN_MSG_MAP(ImageFrameExT)
                      CHAIN_MSG_MAP(BaseClass)
                      MESSAGE_HANDLER(WM_CREATE, OnCreate)
                      MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
                      MESSAGE_HANDLER(WM_MOVE, OnMove)
              END_MSG_MAP()

              ImageFrameExT(){
                      m_sub_rect_.SetRect(0,0,0,0);
              }

              LRESULT OnCreate(UINT , WPARAM , LPARAM lParam, BOOL& bHandled){
                      bHandled = FALSE;
                      T* pT = static_cast(this);
                      if(!DLG){
                              // 一并创建子窗口
                              // 注意它将pT传给了最后一个参数,这个在
                              // OnCreate传给了SubWindowT
                              m_hSubWindow_.Create(pT->m_hWnd,NULL,NULL,
                                              WS_VISIBLE,0,0U,pT);
                      }
                      return 0;
              }

              LRESULT OnInitDialog(UINT , WPARAM , LPARAM , BOOL& bHandled){
                      bHandled = FALSE;
                      T* pT = static_cast(this);
                      if(DLG){
                              // 一并创建子窗口
                              // 注意这里没有WS_CHILD
                              m_hSubWindow_.Create(pT->m_hWnd,NULL,NULL,
                                              WS_VISIBLE,0,0U,pT);
                      }
                      return 0;
              }

              LRESULT OnMove(UINT , WPARAM , LPARAM , BOOL& bHandled){
                      // 动态更新子窗口
                      bHandled = FALSE;
                      T* pT = static_cast(this);
                      if(m_hSubWindow_.IsWindow()){
                              CRect win_rect_;
                              pT->GetWindowRect(win_rect_);

                              if(m_sub_rect_.IsRectNull()){
                                      m_hSubWindow_.MoveWindow(win_rect_);
                              }else{
                                      CRect tmp_ = m_sub_rect_;
                                      tmp_.OffsetRect(win_rect_.TopLeft());
                                      m_hSubWindow_.MoveWindow(tmp_);
                              }
                      }
                      return 0;
              }

              SUB_WINDOW*          SubWindow(){
                      // 获得子窗口
                      return &m_hSubWindow_;
              }
              void                              SetSubRect(const CRect& rect){
                      // 可以设置让子窗口位于那块区域,而不一定要占满整屏
                      m_sub_rect_ = rect;
              }
      protected:
              SUB_WINDOW            m_hSubWindow_;
              CRect                            m_sub_rect_;
      };

      class CMainFrame :
              public CFrameWindowImpl,
              public ImageFrameExT
      {
              typedef ImageFrameExT BaseClass;
      public:
              DECLARE_FRAME_WND_CLASS(NULL, IDR_MAINFRAME)

              BEGIN_MSG_MAP(CMainFrame)
                      //REFLECT_NOTIFICATIONS()
                      CHAIN_MSG_MAP(BaseClass)
                      MESSAGE_HANDLER(WM_CREATE, OnCreate)
                      MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
                      CHAIN_MSG_MAP(CFrameWindowImpl)
                      ALT_MSG_MAP(1)
                      COMMAND_CODE_HANDLER(BN_CLICKED,OnClick)
              END_MSG_MAP()

              LRESULT OnCreate(UINT , WPARAM , LPARAM , BOOL& )
              {
                      m_close_.Create(this->m_hSubWindow_,
                                      CRect(200,200,50 + 200,26 + 200),
                                      _T("Close"),
                                      WS_CHILD | WS_VISIBLE);
                      m_help_.Create(this->m_hSubWindow_,
                                      CRect(260,200,50 + 260,26 + 200),
                                      _T("Help"),
                                      WS_CHILD | WS_VISIBLE);
                      m_help2_.Create(this->m_hSubWindow_,
                                      CRect(320,200,50 + 320,26 + 200),
                                      _T("Help2"),
                                      WS_CHILD | WS_VISIBLE);

                      return 0;
              }

              LRESULT OnDestroy(UINT , WPARAM , LPARAM , BOOL& bHandled)
              {
                      // 解决wtl不能关闭ws_popup的bug
                      PostQuitMessage(0);
                      bHandled = FALSE;
                      return 1;
              }

              LRESULT OnClick(WORD wNotifyCode, WORD wID, HWND hWndCtl,
                          BOOL& bHandled){
                      if(m_close_ == hWndCtl){
                              this->PostMessage(WM_CLOSE);
                      }else if(m_help_ == hWndCtl){

                      }else if(m_help2_ == hWndCtl){

                      }
                      return 0;
              }
              CButton        m_close_;
              CButton        m_help_;
              CButton        m_help2_;
      };

其他问题

若上面放置普通的矩形控件,并且不支持透明,那么啥问题都没有,然后若要实现不规则或者透明控件,那么全透明窗口被过滤的颜色将被显示出来

一个变通的办法就是将需要填充子控件的区域抠出来,并交给这个子窗口来画(这就要求放置控件区域的地方没有半透明,大部分需求都只是希望能够处理简单的异形,并且消除锯齿)。

      // 将父窗口中间抠出来,交给子窗口来画
      template
      class SubWindow2 :
              public CWindowImpl,CWindow>,
              public SubWindowT,Y>{
              typedef SubWindowT,Y> BaseClass;
      public:
              BEGIN_MSG_MAP(SubWindow2)
                      CHAIN_MSG_MAP(BaseClass)
                      MESSAGE_HANDLER(WM_PAINT, OnPaint)
                      REFLECT_NOTIFICATIONS()
              END_MSG_MAP()

              LRESULT OnPaint(UINT , WPARAM , LPARAM , BOOL& ){
                      CPaintDC dc_(this->m_hWnd);
                      ATLASSERT(m_res_);
                      if(m_res_)
                              m_res_->Draw(dc_,0,0);
                      return TRUE;
              }

              inline void SetRes(CImage* res){
                      ATLASSERT(res);
                      if(res)
                              this->m_res_ = res;
              }
      private:
              CImage*        m_res_;
      };

      class CAboutDlgEx :
              public CDialogImpl,
              public ImageFrameExT
      {
              typedef ImageFrameExT BaseClass;
      public:
              enum { IDD = IDD_DIALOG1 };

              BEGIN_MSG_MAP(CAboutDlgEx)
                      CHAIN_MSG_MAP(BaseClass)
                      MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
                      MESSAGE_HANDLER(WM_RBUTTONDOWN, OnClose)
                      ALT_MSG_MAP(1)
              END_MSG_MAP()

              LRESULT OnInitDialog(UINT , WPARAM , LPARAM , BOOL& ){
                      bool ret_ = m_res_.InitFromFile(
                                      _T("res/imgbtn.png"),
                                      _T("res/imgbtn_h.png"),
                                      _T("res/imgbtn_p.png"),
                                      _T("res/imgbtn_d.png"));
                      ATLASSERT(ret_);

                      m_btn_.Create(this->m_hSubWindow_,NULL,NULL,0,0,0U,&m_res_);
                      m_btn_.MoveWindow(120,120,0,0,TRUE);

                      return 0;
              }
              LRESULT OnClose(UINT , WPARAM , LPARAM , BOOL& ){
                      EndDialog(0);
                      return 0;
              }


              ImageButton        m_btn_;
              ButtonRes            m_res_;
      };

经测试,这种方案在拖动时,两个窗口交接处有明显的刷新不一致存在。
Bug

在一些非32位图像模式下,该函数会有些问题,解决办法就是强制创建32位的Bitmap

CBitmap bitmap;
BITMAPINFOHEADER bmih;
bmih.biSize                                  = sizeof (BITMAPINFOHEADER) ;
bmih.biWidth                                = 384 ;
bmih.biHeight                              = 256 ;
bmih.biPlanes                              = 1 ;
bmih.biBitCount                          = 32 ;  //注意32位
bmih.biCompression                    = BI_RGB ;
bmih.biSizeImage                        = 0 ;
bmih.biXPelsPerMeter                = 0 ;
bmih.biYPelsPerMeter                = 0 ;
bmih.biClrUsed                            = 0 ;
bmih.biClrImportant                  = 0 ;
bitmap.CreateDIBitmap(dc,&bmih);

为了避免绘图汇到左上角,需要在调用UpdateLayeredWindow时,明确指定左上角坐标

CRect rect_;
GetWindowRect(rect_);

CPoint topleft(rect_.left,rect_.top);
CPoint  pt_(0,0);
CSize    size_(m_res_->GetWidth(),m_res_->GetHeight());
::UpdateLayeredWindow(pT->m_hWnd,dc_,&topleft,&size_,mem_dc_,&pt_,0,&pb_,ULW_ALPHA );

参考

      http://www.wuroom.com/post/100/

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值