• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            新版的QQ在截圖時加入了窗口自動識別的功能,能根據(jù)鼠標(biāo)的位置自動畫出下面窗口的輪廓。今天有人在論壇上問起這個問題,下面我們來探討這個功能的實(shí)現(xiàn)原理。

            首先我們要明白截圖軟件的基本原理,截圖時實(shí)際上是新建了一個全屏窗口,然后將當(dāng)前桌面的截圖畫在上面,大部分截圖軟件,包括QQ都是這么做的。根據(jù)鼠標(biāo)位置獲取下層窗口,有好幾個類似的API可以用(WindowFromPoint, ChildWindowFromPoint, ChildWindowFromPointEx,RealChildWindowFromPoint)。

            這里我們重點(diǎn)關(guān)注ChildWindowFromPointEx,因?yàn)槲覀冎澜貓D時有個全屏窗口覆蓋在上面,通過鼠標(biāo)位置去取得窗口,肯定首先取到的是這個全屏窗口,所以我們要把這個窗口過濾掉,而只有ChildWindowFromPointEx這個API有窗口過濾功能。
            HWND ChildWindowFromPointEx(      

                HWND hwndParent,     POINT pt,     UINT uFlags );

            Parameters

            hwndParent
            [in] Handle to the parent window.
            pt
            [in] Specifies a POINT structure that defines the client coordinates (relative to hwndParent) of the point to be checked.
            uFlags
            [in] Specifies which child windows to skip. This parameter can be one or more of the following values.
            CWP_ALL
            Does not skip any child windows
            CWP_SKIPINVISIBLE
            Skips invisible child windows
            CWP_SKIPDISABLED
            Skips disabled child windows
            CWP_SKIPTRANSPARENT
            Skips transparent child windows

            所以我們有理由相信QQ的全屏窗口用了WS_EX_LAYERED屬性,然后QQ通過調(diào)用ChildWindowFromPointEx(hWndDesktop,ptCursor, CWP_SKIPINVISIBLE|CWP_SKIPTRANSPARENT), 這樣就可以過濾掉不可見的和Layered窗口,然后通過遞歸調(diào)用該API,就可以獲取里面的子窗口了。

            為了驗(yàn)證我們猜想,怎么可以自己建立一個Layered Window,然后用QQ截圖,可以看到QQ是無法識別該窗口的。

            另外我們可以在啟動QQ截圖后,通過Windows鍵激活任務(wù)欄,然后改變通過任務(wù)欄最小化或是關(guān)閉某個包含在截圖內(nèi)的窗口,再繼續(xù)截圖就會發(fā)現(xiàn)QQ沒法識別了。這也說明了QQ截圖是實(shí)時通過ChildWindowFromPointEx來獲取下層窗口的。這也算是QQ截圖的一個Bug。

            很多截圖軟件卻沒有上述問題,我想他們應(yīng)該是在開始截圖時保存了桌面上所有窗口的層次關(guān)系和所在區(qū)域,后面用的都是當(dāng)時保存的信息來識別的,這樣即使后面下面的窗口變化了,識別也不會受到影響。

            另外,有些截圖軟件能夠識別到比窗口粒度更小的元素,比如Toolbar控件上的每個Item,他們用的應(yīng)該是MSAA(Microsoft Active Accessibility),標(biāo)準(zhǔn)控件一般都支持該接口。

            看到有些人對通過枚舉方式識別窗口的代碼感興趣 , 下面是我的代碼:
            class CSCWinFilter
            {
            public:
                static BOOL IsFilterWindow(HWND hWnd)
                {
                    _ASSERTE(hWnd != NULL);
                    DWORD dwProcessID = GetCurrentProcessId();
                    if(hWnd != NULL && IsWindow(hWnd))
                    {
                        DWORD dwWinProcessId(0);
                        GetWindowThreadProcessId(hWnd, &dwWinProcessId);
                        if(dwProcessID == dwWinProcessId) 
                        {
                            return TRUE;
                        }
                    }
                    
                    return FALSE;
                }
                
                static DWORD GetIncludeStyle()
                {
                    return WS_VISIBLE;
                }
                
                static DWORD GetExcludeStyleEx()
                {
                    return  WS_EX_TRANSPARENT;
                }
                
                static BOOL IsTargetPopupWindow()
                {
                    return FALSE;
                }
            };

            class CSCWinInfo
            {
            public:
                HWND m_hWnd;    
                CRect m_rtWin;    //window rect
                                
                INT m_nLevel;    // 1 - pop up window  ;  2N - child window
            };

            //pop up win 1 (level 1).. first Z order
            //        child11 (level 2)
            //        child12 (level 2)
            //                chilld121 (level 3)
            //                chilld122 (level 3)
            //                
            //        child3 (level 2)
            //pop up win2
            //        child21 (level 2)
            //        child21 (level 2)
            // .
            // .
            //pop up winN . last Z order


            template<typename CWinFilterTraits = CSCWinFilter>
            class CSCWinSpy:  public CHYSingleton<CSCWinSpy>
            {
            public:
                BOOL SnapshotAllWinRect()
                {
                    ClearData();

                    // cache current window Z order when call this function
                    EnumWindows(EnumWindowsSnapshotProc, 1); 
                    
                    return TRUE;
                }
                
                //get from current Z order of desktop
                HWND GetHWNDByPoint(CPoint pt)
                {
                    m_hWndTarget = NULL;
                    
                    EnumWindows(EnumWindowsRealTimeProc, MAKELPARAM(pt.x, pt.y));
                    
                    return m_hWndTarget;
                }
                
                CRect GetWinRectByPoint(CPoint ptHit, BOOL bGetInRealTime = FALSE)
                {
                    CRect rtRect(0, 0, 0, 0);
                    if(bGetInRealTime) //get from current Z order
                    {
                        HWND hWndTarget = GetHWNDByPoint(ptHit);
                        if(hWndTarget != NULL )
                        {
                            GetWindowRect(hWndTarget, &rtRect);
                        }
                    }
                    else //get from snapshot cache
                    {
                        GetRectByPointFromSnapshot(ptHit, rtRect);
                    }
                    
                    return rtRect;
                }
                
            protected:
                static BOOL CALLBACK EnumWindowsRealTimeProc(HWND hwnd, LPARAM lParam)
                {
                    if(!PtInWinRect(hwnd, CPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)))) return TRUE;
                    
                    if(ShouldWinBeFiltered(hwnd))  return TRUE;
                    
                    m_hWndTarget = hwnd;
                    
                    if(CWinFilterTraits::IsTargetPopupWindow()) return FALSE; //this is the target window, exit search
                    
                    EnumChildWindows(hwnd, EnumChildRealTimeProc, lParam);
                    
                    return FALSE;
                }
                
                static BOOL CALLBACK EnumChildRealTimeProc(HWND hwnd, LPARAM lParam)
                {
                    if(!PtInWinRect(hwnd, CPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)))) return TRUE;
                    
                    if(ShouldWinBeFiltered(hwnd)) return TRUE;
                    
                    m_hWndTarget = hwnd;
                    EnumChildWindows(hwnd, EnumChildRealTimeProc, lParam);
                    
                    return FALSE;
                }
                
            protected:
                static BOOL CALLBACK EnumWindowsSnapshotProc(HWND hwnd, LPARAM lParam)
                {
                    INT nLevel = lParam;
                    if(ShouldWinBeFiltered(hwnd))  return TRUE;
                    
                    SaveSnapshotWindow(hwnd, nLevel);
                    
                    if(!CWinFilterTraits::IsTargetPopupWindow())
                    {
                        ++nLevel;
                        EnumChildWindows(hwnd, EnumChildSnapshotProc, nLevel);
                    }
                    
                    return TRUE;
                }
                
                static BOOL CALLBACK EnumChildSnapshotProc(HWND hwnd, LPARAM lParam)
                {
                    INT nLevel = lParam;
                    
                    if(ShouldWinBeFiltered(hwnd)) return TRUE;
                    
                    SaveSnapshotWindow(hwnd, nLevel);
                    
                    ++nLevel;
                    EnumChildWindows(hwnd, EnumChildSnapshotProc, nLevel);
                    
                    return TRUE;
                }
                
            protected:
                static BOOL PtInWinRect(HWND hWnd, CPoint pt)
                {
                    CRect rtWin(0, 0, 0, 0);
                    GetWindowRect(hWnd, &rtWin);
                    return PtInRect(&rtWin, pt);
                }
                
                static BOOL ShouldWinBeFiltered(HWND hWnd)
                {
                    if(CWinFilterTraits::IsFilterWindow(hWnd)) return TRUE;
                    
                    DWORD dwStyle = GetWindowLong(hWnd, GWL_STYLE);
                    DWORD dwStyleMust = CWinFilterTraits::GetIncludeStyle();
                    if((dwStyle & dwStyleMust) != dwStyleMust) return TRUE;
                    
                    DWORD dwStyleEx = GetWindowLong(hWnd, GWL_EXSTYLE);
                    DWORD dwStyleMustNot = CWinFilterTraits::GetExcludeStyleEx();
                    if((dwStyleMustNot & dwStyleEx) != 0) return TRUE;
                    
                    return FALSE;
                }
                
                //find the first window that level is biggest
                static BOOL  GetRectByPointFromSnapshot(CPoint ptHit, CRect& rtRet)
                {
                    int nCount = m_arSnapshot.size();
                    _ASSERTE(nCount > 0);
                    CSCWinInfo* pInfo = NULL;
                    CSCWinInfo* pTarget = NULL; 
                    
                    for(int i=0; i<nCount; ++i)
                    {
                        pInfo = m_arSnapshot[i];
                        _ASSERTE(pInfo != NULL);
                        
                        //target window is found 
                        
            //and level is not increasing, 
                        
            //that is checking its sibling or parent window, exit search
                        if(pTarget != NULL
                            && pInfo->m_nLevel <= pTarget->m_nLevel)
                        {
                            break;
                        }
                        
                        if(PtInRect(&pInfo->m_rtWin, ptHit))
                        {
                            if(pTarget == NULL)
                            {
                                pTarget = pInfo;
                            }
                            else
                            {
                                if( pInfo->m_nLevel > pTarget->m_nLevel)
                                {
                                    pTarget = pInfo;
                                }
                            }
                        }
                    }
                    
                    if(pTarget != NULL)
                    {
            #ifdef _DEBUG
                        if(pTarget != NULL)
                        {
                            HWND hWnd = pTarget->m_hWnd;
                            TCHAR szText[128] = {0};
                            _sntprintf(szText, 127, _T("GetRectByPointFromSnapshot: pt(%d, %d), hWnd(%x)"),
                                ptHit.x, ptHit.y, (UINT)(pInfo->m_hWnd));
                            OutputDebugString(szText);
                        }
            #endif

                        rtRet.CopyRect(&pTarget->m_rtWin);
                        return TRUE;
                    }
                    
                    return FALSE;
                }
                
                static VOID SaveSnapshotWindow(HWND hWnd, INT nLevel)
                {
                    _ASSERTE(hWnd != NULL && IsWindow(hWnd));
                    CRect rtWin(0, 0, 0, 0);
                    GetWindowRect(hWnd, &rtWin);
                    if(rtWin.IsRectEmpty()) return;
                    
                    CSCWinInfo* pInfo = new CSCWinInfo;
                    if(pInfo == NULL) return;
                    
                    pInfo->m_hWnd = hWnd;
                    pInfo->m_nLevel = nLevel;
                    pInfo->m_rtWin = rtWin;
                    
                    m_arSnapshot.push_back(pInfo);
                }
                
                static VOID ClearData()
                {
                    int nCount = m_arSnapshot.size();
                    for(int i=0; i<nCount; ++i)
                    {
                        delete m_arSnapshot[i];
                    }
                    
                    m_arSnapshot.clear();
                }
                
            protected:
                friend class CHYSingleton<CSCWinSpy>;

                CSCWinSpy() { NULL; }
                ~CSCWinSpy() {    ClearData(); }
                
                static HWND m_hWndTarget;
                static std::vector<CSCWinInfo*> m_arSnapshot;
            };

            template<typename T> HWND CSCWinSpy<T>::m_hWndTarget = NULL;
            template<typename T> std::vector<CSCWinInfo*> CSCWinSpy<T>::m_arSnapshot;

            這樣使用, 在截圖開始時保存所有桌面窗口層次:
            CSCWinSpy<CSCWinFilter>::GetInstance()->SnapshotAllWinRect();

            然后就可以這樣查詢某個位置的最上層窗口了:
            CRect rtSelect = CSCWinSpy<CSCWinFilter>::GetInstance()->GetWinRectByPoint(pt, FALSE);
            posted on 2012-05-06 21:34 Richard Wei 閱讀(12240) 評論(9)  編輯 收藏 引用 所屬分類: windows desktop

            FeedBack:
            # re: QQ截圖時窗口自動識別的原理
            2012-05-07 16:03 | Richard Wei
            更正下,用工具看了下QQ截圖時的全屏窗口,發(fā)現(xiàn)它沒有WS_EX_LAYERED屬性,也沒有WS_DISABLED屬性,所以QQ截圖在獲取最外層Popup窗口時應(yīng)該不是用ChildWindowFromPointEx獲取的。 而應(yīng)該是通過Z-Order從上到下枚舉(EnumWindows)所有可見并且非Transparent的Popup窗口, 這樣他們可以過濾掉自己的全屏窗口,找到第一個鼠標(biāo)所在位置的Popup窗口,然后再用ChildWindowFromPointEx獲取里面的子窗口。  回復(fù)  更多評論
              
            # re: QQ截圖時窗口自動識別的原理
            2012-09-06 11:30 | m775@sina.com
            個人感覺QQ應(yīng)該不是用枚舉窗口做的,用SPY++可以發(fā)現(xiàn),QQ的遮罩層會定時收到WM_ENABLE的消息,先FALSE然后在TRUE,比較像先把自己窗口ENABLE,然后抓取其他窗口,然后再恢復(fù),但是使用WindowFromPoint卻發(fā)現(xiàn)無法穿透ENABLE的窗口,目前還沒找方法來實(shí)現(xiàn),你說的枚舉我回頭試下,感謝你的分享  回復(fù)  更多評論
              
            # re: QQ截圖時窗口自動識別的原理
            2012-09-06 12:47 | Richard Wei
            @m775@sina.com
            其實(shí)我用enum的方式已經(jīng)自己實(shí)現(xiàn)了,參考http://www.shnenglu.com/weiym/archive/2012/08/21/187801.html  回復(fù)  更多評論
              
            # re: QQ截圖時窗口自動識別的原理
            2012-11-17 20:12 | Richard Wei
            看到有些人對窗口識別的代碼感興趣,在博文最后加上了通過Enum方式查詢窗口的代碼  回復(fù)  更多評論
              
            # re: QQ截圖時窗口自動識別的原理
            2012-12-20 12:42 | 嘿嘿
            看了下你的截圖工具;雖然不熟悉C++;但是你的工具應(yīng)該是在截圖前先保存所有rect范圍,然后在截圖使用窗口功能時,再用Pinrect 來判斷是否該rect的吧? /:D 和QQ的原理有些不太一樣,它是實(shí)時的.   回復(fù)  更多評論
              
            # re: QQ截圖時窗口自動識別的原理
            2012-12-20 13:11 | Richard Wei
            @嘿嘿
            是的, QQ這種實(shí)時的方式如果啟動截圖后, 后臺窗口一變, 識別出來的就是錯誤的了.
            我這種在啟動截圖時保存所有窗口Rect的做法才是可靠的.  回復(fù)  更多評論
              
            # re: QQ截圖時窗口自動識別的原理
            2013-07-13 11:15 | LzwCracker
            還有一個問題請教一下 QQ 鼠標(biāo)移動到工具欄的時候放大鏡和顏色抓取依然能跟隨這個有時什么原理啊?  回復(fù)  更多評論
              
            # re: QQ截圖時窗口自動識別的原理
            2013-08-09 16:12 | LzwCracker
            這樣還是有點(diǎn)問題..有些被遮住或者看不到的窗口也是可以捕捉的  回復(fù)  更多評論
              
            # re: QQ截圖時窗口自動識別的原理[未登錄]
            2015-03-05 11:02 | SillyRabbit
            @Richard Wei
            你好 你是怎么保持截屏窗口 一直在最前面的呢
              回復(fù)  更多評論
              
            99国内精品久久久久久久| 久久久久久亚洲精品影院| 亚洲一区中文字幕久久| 国产叼嘿久久精品久久| 欧美日韩精品久久免费| 精品免费tv久久久久久久| 欧美伊香蕉久久综合类网站| 亚洲AV无码成人网站久久精品大| 国产精品激情综合久久| 国内精品伊人久久久影院| 久久精品国产精品青草| 亚洲日本久久久午夜精品| 久久99国产精品久久| 久久夜色精品国产噜噜麻豆| 伊人热人久久中文字幕| 伊人久久无码中文字幕| 久久AⅤ人妻少妇嫩草影院| 婷婷久久综合九色综合98| 久久青青草原亚洲av无码app| 少妇无套内谢久久久久| 国产亚洲美女精品久久久| 婷婷综合久久中文字幕| 亚洲va中文字幕无码久久| 日韩电影久久久被窝网| 97热久久免费频精品99| 精品国产青草久久久久福利| 狠狠色婷婷久久一区二区| 狠狠色婷婷久久综合频道日韩 | 国产精品中文久久久久久久| 久久国产视频99电影| 国产午夜精品理论片久久影视 | 中文国产成人精品久久不卡| 精品国产热久久久福利| 午夜福利91久久福利| 办公室久久精品| 丁香久久婷婷国产午夜视频| 国产91久久综合| 久久精品综合一区二区三区| 欧美日韩精品久久久久| 久久久久亚洲国产| 99精品国产99久久久久久97|