<menu id="w8yyk"><menu id="w8yyk"></menu></menu>
  • <dd id="w8yyk"><nav id="w8yyk"></nav></dd>
    <menu id="w8yyk"></menu>
    <menu id="w8yyk"><code id="w8yyk"></code></menu>
    <menu id="w8yyk"></menu>
    <xmp id="w8yyk">
    <xmp id="w8yyk"><nav id="w8yyk"></nav>
  • 網站首頁 > 物聯資訊 > 技術分享

    [用UpdateLayeredWindow實現任意異形窗口]

    2016-09-28 00:00:00 廣州睿豐德信息科技有限公司 閱讀
    睿豐德科技 專注RFID識別技術和條碼識別技術與管理軟件的集成項目。質量追溯系統、MES系統、金蝶與條碼系統對接、用友與條碼系統對接

    前面提到,我們可以用SetWindowRgn或SetLayeredWindowAttributes實現不規則以及半透明的效果

    對于SetWindowRgn,它通過一個Rgn來設置區域,這個Rgn一般可以從圖片中讀取,在這張圖片中,將不需要顯示的區域標記為一種特殊的顏色,這里有個問題,必須保證這種顏色沒有被正常的區域使用,否則會被誤傷。為了解決這個問題,可以考慮用兩張圖片,增加一張單色的掩碼圖,這種方案帶來了額外的管理開銷。SetWindowRgn的好處是效率較高,對于大部分自繪的皮膚,一般只有四個角落有一些不規則,所以用SetWindowRgn是最好的選擇。

    SetLayeredWindowAttributes可以將特定的窗口設置為某種透明度,也可以用它來過濾某種顏色,匹配的顏色會變成全透明。也就是類似于SetWindowRgn的效果。SetLayeredWindowAttributes直接從DC中獲得顏色,所以你需要事先繪制DC。

    SetLayeredWindowAttributes過濾顏色后,相關區域雖然不可見,但是不可見的區域可以放置子窗口,這點和SetWindowRgn有所區別。此外若子窗口刷新不及時或其他原因,那么父窗口因為SetLayeredWindowAttributes被隱藏的DC顏色將被浮出水面。

    UpdateLayeredWindow直接根據DC中的Alpha通道來實現透明效果,它很好的處理了和背景的Alpha Blend的問題,所以完美的解決了SetWindowRgn的鋸齒問題。

    Sample

        template<typename T,bool DLG>
        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 /*uMsg*/, WPARAM /*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 /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled){
                bHandled = FALSE;
                if(lParam){
                    // 通過參數獲得資源句柄
                    m_res_ = (CImage*)lParam;
                }
                if(DLG)
                    this->InitSelf();
                return 0;
            }
    
            LRESULT OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/){
                // OnPaint不作任何事,轉到UpdateLayeredWindow處理
                T* pT = static_cast<T*>(this);
                CPaintDC dc_(pT->m_hWnd);
                return TRUE;
            }
            LRESULT OnEraseBkgnd(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/){
                // 屏蔽背景繪制
                return TRUE;
            }
    
            LRESULT OnLBttonDown(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled){
                // 是否支持整窗口拖動
                T* pT = static_cast<T*>(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<T*>(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<CAboutDlg>,
            public ImageFrameT<CAboutDlg,true>
        {
            typedef ImageFrameT<CAboutDlg,true> 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 /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
            {
                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有一個問題,那就是它上面不能放置任何子窗口,放置上去的任何窗口都不可見。為了解決這個問題,一種簡單的辦法是自繪,在單個窗口中模擬各種消息。

    若不想搞復雜,有一種變通的辦法,那就是在上面放置一個非子窗口,這種子窗口大小位置保持和它一致,同時這個子窗口用SetLayeredWindowAttributes搞成全透明,接下來我們將所有子控件放到這個全透明的子窗口即可。

    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<typename T,typename Y>
        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 /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled){
                bHandled = FALSE;
                T* pT = static_cast<T*>(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 /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/){
                // 屏蔽背景消息
                return TRUE;
            }
    
            // 這個指針指向最頂層的窗口,用它來將緊貼這頂層窗口的不可見窗口的消息
            // 轉發給m_message_
            // 注意宏CHAIN_MSG_MAP_ALT_MEMBER_EX
            Y*      m_message_;
        };
    
        // 這個類在SubWindowT的基礎上實現了全透明的效果
        // 利用SetLayeredWindowAttributes可以對某種特定的顏色實現全透明過濾的特性
        template<typename Y>
        class SubWindow1 :
            public CWindowImpl<SubWindow1<Y>,CWindow>,
            public SubWindowT<SubWindow1<Y>,Y>{
            typedef SubWindowT<SubWindow1<Y>,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 /*uMsg*/, WPARAM /*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)
                // 在這里將此顏色過濾(編程全透明)
                ::SetLayeredWindowAttributes(this->m_hWnd,RGB(255,0,255),0,LWA_COLORKEY);
                return 0;
            }
    
            LRESULT OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/){
                // 在OnPaint里面將整個窗口刷成RGB(255,0,255)
                // 以便讓SetLayeredWindowAttributes過濾
                CPaintDC dc_(this->m_hWnd);
                CRect client_rect_;
                this->GetClientRect(client_rect_);
                dc_.FillSolidRect(client_rect_,RGB(255,0,255));
                return TRUE;
            }
        };
    
        template<typename T,bool DLG,template<typename X> class SUB_WINDOW >
        class ImageFrameExT : public ImageFrameT<T,DLG>{
            typedef ImageFrameT<T,DLG> 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 /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled){
                bHandled = FALSE;
                T* pT = static_cast<T*>(this);
                if(!DLG){
                    // 一并創建子窗口
                    // 注意它將pT傳給了最后一個參數,這個在
                    // OnCreate傳給了SubWindowT
                    m_hSubWindow_.Create(pT->m_hWnd,NULL,NULL,
                            WS_VISIBLE,0,0U,pT);
                }
                return 0;
            }
    
            LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled){
                bHandled = FALSE;
                T* pT = static_cast<T*>(this);
                if(DLG){
                    // 一并創建子窗口
                    // 注意這里沒有WS_CHILD
                    m_hSubWindow_.Create(pT->m_hWnd,NULL,NULL,
                            WS_VISIBLE,0,0U,pT);
                }
                return 0;
            }
    
            LRESULT OnMove(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled){
                // 動態更新子窗口
                bHandled = FALSE;
                T* pT = static_cast<T*>(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<T>*      SubWindow(){
                // 獲得子窗口
                return &m_hSubWindow_;
            }
            void                SetSubRect(const CRect& rect){
                // 可以設置讓子窗口位于那塊區域,而不一定要占滿整屏
                m_sub_rect_ = rect;
            }
        protected:
            SUB_WINDOW<T>       m_hSubWindow_;
            CRect               m_sub_rect_;
        };

        class CMainFrame :
            public CFrameWindowImpl<CMainFrame>,
            public ImageFrameExT<CMainFrame,false,SubWindow1 >
        {
            typedef ImageFrameExT<CMainFrame,false,SubWindow1 > 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<CMainFrame>)
                ALT_MSG_MAP(1)
                COMMAND_CODE_HANDLER(BN_CLICKED,OnClick)
            END_MSG_MAP()
    
            LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
            {
                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 /*uMsg*/, WPARAM /*wParam*/, LPARAM /*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<typename Y>
        class SubWindow2 :
            public CWindowImpl<SubWindow2<Y>,CWindow>,
            public SubWindowT<SubWindow2<Y>,Y>{
            typedef SubWindowT<SubWindow2<Y>,Y> BaseClass;
        public:
            BEGIN_MSG_MAP(SubWindow2)
                CHAIN_MSG_MAP(BaseClass)
                MESSAGE_HANDLER(WM_PAINT, OnPaint)
                REFLECT_NOTIFICATIONS()
            END_MSG_MAP()
    
            LRESULT OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/){
                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<CAboutDlgEx>,
            public ImageFrameExT<CAboutDlgEx,true,SubWindow2>
        {
            typedef ImageFrameExT<CAboutDlgEx,true,SubWindow2> 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 /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/){
                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 /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/){
                EndDialog(0);
                return 0;
            }
    
    
            ImageButton<ButtonRes,true>     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 );

    參考

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

    RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成
    最近免费观看高清韩国日本大全