新版的QQ在截图时加入了窗口自动识别的功能,能根据鼠标的位置自动画出下面窗口的轮廓。今天有人在论坛上问起这个问题,下面我们来探讨这个功能的实现原理。

首先我们要明白截图软件的基本原理,截图时实际上是新建了一个全屏窗口,然后将当前桌面的截图画在上面,大部分截图软件,包括QQ都是这么做的。根据鼠标位置获取下层窗口,有好几个类似的API可以用(WindowFromPoint, ChildWindowFromPoint, ChildWindowFromPointEx,RealChildWindowFromPoint)。

这里我们重点关注ChildWindowFromPointEx,因为我们知道截图时有个全屏窗口覆盖在上面,通过鼠标位置去取得窗口,肯定首先取到的是这个全屏窗口,所以我们要把这个窗口过滤掉,而只有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通过调用ChildWindowFromPointEx(hWndDesktop,ptCursor, CWP_SKIPINVISIBLE|CWP_SKIPTRANSPARENT), 这样就可以过滤掉不可见的和Layered窗口,然后通过递归调用该API,就可以获取里面的子窗口了。

为了验证我们猜想,怎么可以自己建立一个Layered Window,然后用QQ截图,可以看到QQ是无法识别该窗口的。

另外我们可以在启动QQ截图后,通过Windows键激活任务栏,然后改变通过任务栏最小化或是关闭某个包含在截图内的窗口,再继续截图就会发现QQ没法识别了。这也说明了QQ截图是实时通过ChildWindowFromPointEx来获取下层窗口的。这也算是QQ截图的一个Bug。

很多截图软件却没有上述问题,我想他们应该是在开始截图时保存了桌面上所有窗口的层次关系和所在区域,后面用的都是当时保存的信息来识别的,这样即使后面下面的窗口变化了,识别也不会受到影响。

另外,有些截图软件能够识别到比窗口粒度更小的元素,比如Toolbar控件上的每个Item,他们用的应该是MSAA(Microsoft Active Accessibility),标准控件一般都支持该接口。

看到有些人对通过枚举方式识别窗口的代码感兴趣 , 下面是我的代码:

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);
 

http://www.cppblog.com/weiym/archive/2012/05/06/173845.html

QQ截图时窗口自动识别的原理(WindowFromPoint, ChildWindowFromPoint, ChildWindowFromPointEx,RealChildWindowFromPoint)的更多相关文章

  1. C#软件开发实例.私人订制自己的屏幕截图工具(九)使用自己定义光标,QQ截图时的光标

    版权声明:本文为 testcs_dn(微wx笑) 原创文章,非商用自由转载-保持署名-注明出处,谢谢. https://blog.csdn.net/testcs_dn/article/details/ ...

  2. qq截图原理

    屏幕截图实现的大体思想是:发起截图时,将当前窗口的图像保存到内存中,然后弹出一个置顶的全屏窗口,将保存的桌面图片绘制到这个全屏窗口上:初始时绘制的是灰化的桌面图像,选择截图区域后,则将选中的区域绘制成 ...

  3. 【转】Android仿QQ截图应用测试

    使用过QQ的同学应该都用过QQ截图,Ctrl+Alt+A进入截图操作,通过拉伸,移动高亮区域的框体可以快速截取我们需要的图片.在android应用中,我们也经常需要截图操作,以下实现了一个类似QQ截图 ...

  4. WPF C#截图功能 仿qq截图

    原文:WPF C#截图功能 仿qq截图 先上效果图 源码下载地址:http://download.csdn.net/detail/candyvoice/9788099 描述:启动程序,点击窗口butt ...

  5. Qt 模仿QQ截图 动态吸附直线

    最近在学Qt.学东西怎么能不动手. 就写了些小程序.看QQ截图能够动态吸附直线的功能挺有意思,所以就模仿了一个. 先上效果图 界面很简单..呵呵 移动鼠标,会把鼠标所在最小矩形选中.把没有选中的地方给 ...

  6. cnblog可以直接黏贴qq截图,但最好不要偷懒

    秋秋(qq)基本上算是ytkah本人开机后要开的软件了,平时也习惯用Ctrl+Alt+A快捷键来进行截图.很早以前ytkah一个无意间发现cnblog可以直接黏贴qq截图,当时截图后可能是忘记黏贴到其 ...

  7. "QQ尾巴病毒"核心技术的实现原理分析

    声明:本文旨在探讨技术,请读者不要使用文章中的方法进行任何破坏. 2003这一年里,QQ尾巴病毒可以算是风光了一阵子.它利用IE的邮件头漏洞在QQ上疯狂传播.中毒者在给别人发信息时,病毒会自动在信息文 ...

  8. 【转】Mac QQ截图保存在哪里?

    原文网址:http://www.pc6.com/edu/67677.html QQ Mac版的截屏图片保存在哪儿呢?可不可以像Windows版本一样设定保存路径呢?当然是可定的.Mac QQ截图保存你 ...

  9. Android窗口管理服务WindowManagerService显示窗口动画的原理分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8611754 在前一文中,我们分析了Activi ...

随机推荐

  1. 生成excel内存溢出问题的解决方式

    常用的excel生成工具包括jxl.poi.但二者都存在一个问题:生成excel需要大量的消耗内存.如果一次性往excel中写入的数据足够的多将导致内存溢出. 1.数据写入excel为什么会大量的消耗 ...

  2. npm 模块安装机制简介

    npm 是 Node 的模块管理器,功能极其强大.它是 Node 获得成功的重要原因之一. 正因为有了npm,我们只要一行命令,就能安装别人写好的模块 . $ npm install 本文介绍 npm ...

  3. Python 2 到 Python 3的变化

    Python 2.x到Python 3.x变化还是挺大的,具体的变化,参考官方文档: https://docs.python.org/3.0/whatsnew/3.0.html

  4. (转)pem, cer, p12 and the pains of iOS Push Notifications encryption

    转自:http://cloudfields.net/blog/ios-push-notifications-encryption/ The serious pains of setting up a ...

  5. 忘记mysql 5.7的密码

    for windows: http://blog.chinaunix.net/uid-27570589-id-3511820.html 一.将net stop mysql; 二.在命令行中 C:\Us ...

  6. wpf-DataTemplate应用

    在WPF中,决定数据外观的是DataTemplate,即DataTemplate是数据内容的表现形式,一条数据显示成什么样子,是简单的文本还是直观的图形,就是由DataTemplate决定的.下面通过 ...

  7. C#、.NET和ASP.NET三者之间的区别

    刚毕业后出去找工作面试的时候就遇到这个问题!.回答不上来.回来网上查的如下: 那么 .NET.C#和ASP.NET这三者之间区别不清楚,到底它们之间有什么联系呢? 1..NET是一个平台,一个抽象的平 ...

  8. Task与Thread间的区别

    通过查找一些文章,得知,Task与Thread不可比.Task是为了利用多CPU多核的机制而将一个大任务不断分解成小任务,这些任务具体由哪一个线程或当前线程执行由OS来决定.如果你想自己控制由哪一个T ...

  9. [c#]asp.net开发微信公众平台(2)多层架构框架搭建和入口实现

    上篇已经设计出比较完善的数据库了,这篇开始进入代码.  首先把上篇设计的数据库脚本在数据库中执行下,生成数据库,然后在VS中建立项目,为了方便理解和查看,我设计的都是很直白的类名和文件名,没有命名空间 ...

  10. 常见Oracle数据库问题总结及解决办法(一)

    开发中常使用Oralce数据库,使用中也许会碰到形形色色的各类错误提示,如:ORA-00933:SQL命令未正确结束.ORA-009242等等,为此记录积累对于自己来说还是很有帮助的,今天就记录以前出 ...