青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

新版的QQ在截圖時(shí)加入了窗口自動(dòng)識(shí)別的功能,能根據(jù)鼠標(biāo)的位置自動(dòng)畫出下面窗口的輪廓。今天有人在論壇上問起這個(gè)問題,下面我們來探討這個(gè)功能的實(shí)現(xiàn)原理。

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

這里我們重點(diǎn)關(guān)注ChildWindowFromPointEx,因?yàn)槲覀冎澜貓D時(shí)有個(gè)全屏窗口覆蓋在上面,通過鼠標(biāo)位置去取得窗口,肯定首先取到的是這個(gè)全屏窗口,所以我們要把這個(gè)窗口過濾掉,而只有ChildWindowFromPointEx這個(gè)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)證我們猜想,怎么可以自己建立一個(gè)Layered Window,然后用QQ截圖,可以看到QQ是無法識(shí)別該窗口的。

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

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

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

看到有些人對(duì)通過枚舉方式識(shí)別窗口的代碼感興趣 , 下面是我的代碼:
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;

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

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

FeedBack:
# re: QQ截圖時(shí)窗口自動(dòng)識(shí)別的原理
2012-05-07 16:03 | Richard Wei
更正下,用工具看了下QQ截圖時(shí)的全屏窗口,發(fā)現(xiàn)它沒有WS_EX_LAYERED屬性,也沒有WS_DISABLED屬性,所以QQ截圖在獲取最外層Popup窗口時(shí)應(yīng)該不是用ChildWindowFromPointEx獲取的。 而應(yīng)該是通過Z-Order從上到下枚舉(EnumWindows)所有可見并且非Transparent的Popup窗口, 這樣他們可以過濾掉自己的全屏窗口,找到第一個(gè)鼠標(biāo)所在位置的Popup窗口,然后再用ChildWindowFromPointEx獲取里面的子窗口。  回復(fù)  更多評(píng)論
  
# re: QQ截圖時(shí)窗口自動(dòng)識(shí)別的原理
2012-09-06 11:30 | m775@sina.com
個(gè)人感覺QQ應(yīng)該不是用枚舉窗口做的,用SPY++可以發(fā)現(xiàn),QQ的遮罩層會(huì)定時(shí)收到WM_ENABLE的消息,先FALSE然后在TRUE,比較像先把自己窗口ENABLE,然后抓取其他窗口,然后再恢復(fù),但是使用WindowFromPoint卻發(fā)現(xiàn)無法穿透ENABLE的窗口,目前還沒找方法來實(shí)現(xiàn),你說的枚舉我回頭試下,感謝你的分享  回復(fù)  更多評(píng)論
  
# re: QQ截圖時(shí)窗口自動(dòng)識(shí)別的原理
2012-09-06 12:47 | Richard Wei
@m775@sina.com
其實(shí)我用enum的方式已經(jīng)自己實(shí)現(xiàn)了,參考http://m.shnenglu.com/weiym/archive/2012/08/21/187801.html  回復(fù)  更多評(píng)論
  
# re: QQ截圖時(shí)窗口自動(dòng)識(shí)別的原理
2012-11-17 20:12 | Richard Wei
看到有些人對(duì)窗口識(shí)別的代碼感興趣,在博文最后加上了通過Enum方式查詢窗口的代碼  回復(fù)  更多評(píng)論
  
# re: QQ截圖時(shí)窗口自動(dòng)識(shí)別的原理
2012-12-20 12:42 | 嘿嘿
看了下你的截圖工具;雖然不熟悉C++;但是你的工具應(yīng)該是在截圖前先保存所有rect范圍,然后在截圖使用窗口功能時(shí),再用Pinrect 來判斷是否該rect的吧? /:D 和QQ的原理有些不太一樣,它是實(shí)時(shí)的.   回復(fù)  更多評(píng)論
  
# re: QQ截圖時(shí)窗口自動(dòng)識(shí)別的原理
2012-12-20 13:11 | Richard Wei
@嘿嘿
是的, QQ這種實(shí)時(shí)的方式如果啟動(dòng)截圖后, 后臺(tái)窗口一變, 識(shí)別出來的就是錯(cuò)誤的了.
我這種在啟動(dòng)截圖時(shí)保存所有窗口Rect的做法才是可靠的.  回復(fù)  更多評(píng)論
  
# re: QQ截圖時(shí)窗口自動(dòng)識(shí)別的原理
2013-07-13 11:15 | LzwCracker
還有一個(gè)問題請(qǐng)教一下 QQ 鼠標(biāo)移動(dòng)到工具欄的時(shí)候放大鏡和顏色抓取依然能跟隨這個(gè)有時(shí)什么原理啊?  回復(fù)  更多評(píng)論
  
# re: QQ截圖時(shí)窗口自動(dòng)識(shí)別的原理
2013-08-09 16:12 | LzwCracker
這樣還是有點(diǎn)問題..有些被遮住或者看不到的窗口也是可以捕捉的  回復(fù)  更多評(píng)論
  
# re: QQ截圖時(shí)窗口自動(dòng)識(shí)別的原理[未登錄]
2015-03-05 11:02 | SillyRabbit
@Richard Wei
你好 你是怎么保持截屏窗口 一直在最前面的呢
  回復(fù)  更多評(píng)論
  
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
            午夜国产精品视频免费体验区| 欧美国产一区二区在线观看| 国产模特精品视频久久久久| 久久久久久久久一区二区| 夜夜爽99久久国产综合精品女不卡| 欧美日韩91| 欧美国内亚洲| 国产精品视频| 亚洲理论在线观看| 久久久久久一区| 国语自产精品视频在线看| 蜜桃久久精品乱码一区二区| 久久男人av资源网站| 久久精品综合| 欧美视频第二页| 亚洲综合成人在线| 久久精品国产清高在天天线| 亚洲美女电影在线| 久久综合电影| 亚洲品质自拍| 最新日韩在线视频| 快播亚洲色图| 亚洲欧美精品在线观看| 欧美日韩大片一区二区三区| 久久理论片午夜琪琪电影网| 免费成人高清在线视频| 欧美激情亚洲激情| 亚洲国产导航| 欧美日韩国产综合视频在线| 欧美成人69av| 亚洲欧美99| 美国成人直播| 91久久黄色| 欧美日韩一区二区在线| 免费成人你懂的| 亚洲一区二区三区精品动漫| 欧美亚洲综合另类| 好吊一区二区三区| 国产精品一区二区三区四区五区| 日韩视频免费在线| 亚洲欧美久久| 久久se精品一区二区| 在线观看欧美成人| 一区视频在线| 国产精品h在线观看| 久热re这里精品视频在线6| 欧美成人免费网站| 亚洲第一黄色网| 一区二区三区在线观看视频| 久久中文在线| 亚洲第一二三四五区| 巨乳诱惑日韩免费av| 欧美一区成人| 亚洲手机成人高清视频| 久久久久久久999精品视频| 在线免费观看一区二区三区| 欧美日韩1区2区| 亚洲第一黄色| 久久精品日韩| 99re6热只有精品免费观看| 国精品一区二区| 99热在这里有精品免费| 亚洲第一页中文字幕| 国产日韩精品一区二区| 久久综合导航| 久久久久国产精品一区二区| 日韩写真在线| 亚洲日本一区二区三区| 亚洲缚视频在线观看| 国产一区二区久久久| 国内精品久久久久久影视8| 亚洲麻豆视频| 一区二区三区日韩| 91久久精品国产91久久性色tv | 亚洲自拍偷拍麻豆| 欧美一级久久久久久久大片| 久久久五月天| 99国产精品久久久久久久成人热| 久久精品一级爱片| 亚洲东热激情| 欧美在线免费视屏| 久久视频一区二区| 麻豆精品在线观看| 欧美日韩不卡一区| 久久影院亚洲| 亚洲精品综合| 国产在线一区二区三区四区| 欧美高清不卡| 久久婷婷麻豆| 欧美午夜在线视频| 亚洲影院免费观看| 欧美色中文字幕| 亚洲一区二区影院| 亚洲主播在线| 国产精品成人v| 亚洲乱码国产乱码精品精98午夜| 一本色道久久综合精品竹菊| 一区二区日韩伦理片| 日韩视频不卡| 国产精品免费观看在线| 亚洲精选国产| 久久超碰97人人做人人爱| 亚洲精品免费观看| 美日韩精品视频| 亚洲免费高清| 一本久道久久久| 国产精品video| 亚洲精品欧美专区| 欧美高清视频一区二区三区在线观看| 国产精品呻吟| 午夜精品久久久久久| 久久亚洲春色中文字幕久久久| 亚洲国产婷婷综合在线精品| 亚洲一区视频在线| 激情成人在线视频| 欧美在线视频一区| 亚洲麻豆av| 久久精品日韩| 亚洲欧洲日产国产综合网| 亚洲欧洲另类| 加勒比av一区二区| 欧美中文字幕在线观看| 国产日产高清欧美一区二区三区| 亚洲综合成人在线| 欧美极品影院| 久久国产精品久久w女人spa| 日韩一级精品| 欧美日韩一区视频| 久久人人97超碰国产公开结果| 久久亚洲精品中文字幕冲田杏梨| 国产精品美女久久久免费| 亚洲精品欧美日韩专区| 99国产精品久久久久久久久久 | 亚洲欧美在线一区| 亚洲欧美视频在线观看| 亚洲国产小视频在线观看| 久久久九九九九| 亚洲一区二区在线观看视频| 亚洲精品视频一区| 欧美专区第一页| 亚洲免费观看高清完整版在线观看熊| 欧美成人一区二区三区片免费| 欧美aa在线视频| 国产主播喷水一区二区| 中文精品视频一区二区在线观看| 亚洲国产成人av好男人在线观看| 一区二区不卡在线视频 午夜欧美不卡'| 亚洲免费在线看| 老司机成人在线视频| 激情久久中文字幕| 欧美精品色一区二区三区| 亚洲自拍偷拍麻豆| 欧美一区日韩一区| 一区二区在线观看视频| 欧美激情片在线观看| 亚洲人成毛片在线播放| 一区二区三区高清视频在线观看 | 国产乱子伦一区二区三区国色天香| 久久躁狠狠躁夜夜爽| 久久精品99久久香蕉国产色戒| 影音先锋久久资源网| 国产精品日韩一区二区| 欧美77777| 免费看成人av| 亚洲午夜高清视频| 久热精品在线视频| 久久精品综合网| 欧美日韩大片一区二区三区| 亚洲欧美日韩国产成人| 亚洲免费综合| 欧美日韩精品伦理作品在线免费观看| 亚洲欧美激情视频| 久久手机免费观看| 亚洲欧洲一区二区三区久久| 亚洲三级免费| 欧美系列一区| 久久午夜av| 亚洲美女淫视频| 欧美一区二区三区四区高清| 国产综合久久久久久| 欧美成人午夜激情| 欧美一区视频| 欧美大片在线看| 亚洲欧美卡通另类91av| 极品中文字幕一区| 欧美日韩一区二区三区四区五区| 欧美一区二区三区成人| 亚洲国产精品高清久久久| 亚洲欧美日韩一区在线观看| 狠狠综合久久| 欧美午夜欧美| 欧美福利电影在线观看| 久久精品二区| 亚洲午夜精品17c| 欧美成人免费全部观看天天性色| 亚洲影视中文字幕| 日韩视频在线你懂得| 亚洲国产午夜| 韩国v欧美v日本v亚洲v | 一区二区三区日韩在线观看|