QT将窗口嵌入桌面实现类似便笺的效果

2020年11月8日 7888点热度 45人点赞 3条评论

实现思路

众所周知qt中,要将一歌窗口嵌入另一个窗口只需要调用setParent()函数就可以了。但是setparent()参数必须是qwidget对象的指针,很遗憾的是桌面并不是一个qwidget对象,所以并不能这么做。

在查阅了一些资料之后,发现实现类似的效果都是通过winapi实现的,所以我记录下来的也只是在windows平台上的实现方法。

在winapi中提供了一个SetParent()的函数,功能与qt的setParent函数如出一辙,它的原型是这样的。

HWND SetParent(HWND hWndChild,HWND hWndNewParent);

它有两个参数,hWndChild 是子窗口的句柄,hWndNewParent 是父窗口的句柄。qwidget对象提供了函数获取窗口句柄,所以我们只需要找到桌面的窗口句柄就可以实现这个功能了。

寻找桌面窗口句柄

然而我并不熟悉winApi,在网上我找到了这么一段代码

BOOL enumUserWindowsCB(HWND hwnd,LPARAM lParam)
{
    long wflags = GetWindowLong(hwnd, GWL_STYLE);
    if(!(wflags & WS_VISIBLE)) return TRUE;

    HWND sndWnd;
    if( !(sndWnd=FindWindowEx(hwnd, NULL, L"SHELLDLL_DefView", NULL)) ) return TRUE;
    HWND targetWnd;
    if( !(targetWnd=FindWindowEx(sndWnd, NULL, L"SysListView32", L"FolderView")) ) return TRUE;

    HWND* resultHwnd = (HWND*)lParam;
    *resultHwnd = targetWnd;
    return FALSE;
}


HWND findDesktopIconWnd()
{
    HWND resultHwnd = NULL;
    EnumWindows((WNDENUMPROC)enumUserWindowsCB, (LPARAM)&resultHwnd);
    return resultHwnd;
}

将代码复制到qtcreator中,确实是可以实现我想要的功能的,本着求知的精神,我仔细看了一下实现的原理。

首先这里主要使用了三个winApi的中的函数

  • EnumWindows( );

函数原型

BOOL EnumWindows( WNDENUMPROC lpEnumFunc, LPARAM lParam );

功能:遍历屏幕上所有的顶层窗口,并调用回调函数

参数:

  1. 第一个参数的是回调函数的指针,回调函数原型 BOOL CALLBACK EnumWindowsProc(HWND hwnd,LPARAM lParam);
  2. 第二个参数是用户定义的值,用于传入回调函数中进行操作
  • GetWindowLong

函数原型

Long GetWindowLong(HWND hWnd,int nlndex);

功能:获取窗口的一些参数,参数类型由函数的第二个参数决定

参数:

  1. 窗口句柄
  2. 参数类型
  • FindWindowEx()

函数原型

HWND FindWindowEx(HWND hwndParent,HWND hwndChildAfter,LPCTSTR lpszClass,LPCTSTR lpszWindow);

功能:查找指定窗口句柄

参数:

  1. 父窗口的句柄,如果该参数为空,则查找屏幕上的所有顶层窗口
  2. 指定一个子窗口句柄,从这个窗口开始查找。可以为null
  3. 窗口的类名
  4. 窗口名称(窗口标题名称)

那么这个三个函数的功能搞清楚了,原理也基本明了了。首先是遍历屏幕的所有的顶层窗口,然后再每一个窗口下寻找桌面的图标层,直到找到之后返回。不过这里需要注意的是在回调函数中,不仅判断了窗口的可见状态,还窗口中查找了两层子窗口。之前一直搞不太懂这两层子窗口的类名是怎么确定的,在网上搜索了一番也无果。后来我尝试用spy++直接查找桌面,然后得到了这样的结果。

一下让我豁然开朗,spy++真是个好东西。

使用spy++可以直接看到最开始的顶层窗口的类名和窗口标题,也就是说可以不必遍历所有顶层窗口,而是直接使用标题跟类名查找就可以找到图表层的句柄了。我尝试了一下确实是可以的,但是查阅资料后看到有人说之所以不直接查找是因为在有的windows系统里图标层并不在这个窗口下(我使用的win10),所以使用遍历所有窗口的方式更稳妥一点。

示例

#include "mainwindow.h"

#include <QApplication>
#include <windows.h>
#include <qlabel.h>
BOOL enumUserWindowsCB(HWND hwnd,LPARAM lParam)
{
    long wflags = GetWindowLong(hwnd, GWL_STYLE);
    if(!(wflags & WS_VISIBLE)) return TRUE;

    HWND sndWnd;
    if( !(sndWnd=FindWindowEx(hwnd, NULL, L"SHELLDLL_DefView", NULL)) ) return TRUE;
    HWND targetWnd;
    if( !(targetWnd=FindWindowEx(sndWnd, NULL, L"SysListView32", L"FolderView")) ) return TRUE;

    HWND* resultHwnd = (HWND*)lParam;
    *resultHwnd = targetWnd;
    return FALSE;
}


HWND findDesktopIconWnd()
{
    HWND resultHwnd = NULL;
    EnumWindows((WNDENUMPROC)enumUserWindowsCB, (LPARAM)&resultHwnd);
    return resultHwnd;
}
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    QLabel label;
    label.setText("hell desk");
    label.setMinimumSize(QSize(100,100));

    HWND desktopHwnd = findDesktopIconWnd();
    if(desktopHwnd)
        SetParent((HWND)label.winId(), desktopHwnd);

    label.show();

    return a.exec();
}

 

大脸猫

这个人虽然很勤快,但什么也没有留下!

文章评论

  • broin

    点击内嵌窗口后就消失了,感觉是藏到桌面后面了 :lol:

    2023年2月27日
    • 大脸猫

      @broin 如果跟我一样用的是qt,那应该是qt的BUG,再某些情况下窗口不会绘制,如果对窗口设置了透明背景+无边框,那么基本上100%可以复现这个bug :cool:

      2023年2月28日
      • broin

        @大脸猫 确实是这样 :mrgreen: 之后有空研究下源码

        2023年3月4日