java - 使用上下文菜单捕获 HWND

标签 java winapi capture

我正在使用 WinAPI 在 Java 中捕获窗口。

public static BufferedImage capture(Pointer hWnd2, String wText) {
   HWND hWnd = new HWND(hWnd2);
   boolean success;
   if(User32Extra.INSTANCE.IsIconic(hWnd))
   {
       success= User32Extra.INSTANCE.ShowWindow(hWnd, 4);
       System.err.println(success);
       System.err.println(wText+Native.getLastError());
   }
   HDC hdcWindow2 = User32.INSTANCE.GetDC(hWnd);
   HDC hdcMemDC = GDI32.INSTANCE.CreateCompatibleDC(hdcWindow2);
   success = User32Extra.INSTANCE.PrintWindow(hWnd,hdcWindow2,1);
   RECT bounds = new RECT();
   RECT bounds1 = new RECT();
   User32Extra.INSTANCE.GetWindowRect(hWnd, bounds);
   User32Extra.INSTANCE.GetClientRect(hWnd, bounds1);

   int extraGap = (bounds.right-bounds.left-bounds1.right);
   int width = bounds.right-bounds.left-extraGap;
   int height = bounds.bottom-bounds.top-extraGap ;
   HBITMAP hBitmap = GDI32.INSTANCE.CreateCompatibleBitmap(hdcWindow2, width, height);

   HANDLE hOld = GDI32.INSTANCE.SelectObject(hdcMemDC, hBitmap);
   GDI32Extra.INSTANCE.BitBlt(hdcMemDC,0, 0, width, height, hdcWindow2, bounds.left+bounds1.right-bounds.right+extraGap, bounds.top+bounds1.bottom-bounds.bottom+extraGap, WinGDIExtra.SRCCOPY);
   GDI32.INSTANCE.SelectObject(hdcMemDC, hOld);

   BITMAPINFO bmi = new BITMAPINFO();
   bmi.bmiHeader.biWidth = width;
   bmi.bmiHeader.biHeight = -height;
   bmi.bmiHeader.biPlanes = 1;
   bmi.bmiHeader.biBitCount = 32;
   bmi.bmiHeader.biCompression = WinGDI.BI_RGB;
   Memory buffer = new Memory(width * height * 4);
   GDI32.INSTANCE.GetDIBits(hdcWindow2, hBitmap, 0, height, buffer, bmi, WinGDI.DIB_RGB_COLORS);

   BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

   GDI32.INSTANCE.DeleteObject(hBitmap);
   GDI32.INSTANCE.DeleteDC(hdcMemDC);
   User32.INSTANCE.ReleaseDC(hWnd, hdcWindow2);

   return image;
}

此代码捕获 HWND 指定的窗口。但是如果用户打开上下文菜单或菜单栏选项。那些没有被捕获。

如何捕获窗口的上下文菜单?

最佳答案

我会使用 GetDesktopWindow() 捕获整个屏幕,然后将窗口坐标传递给 BitBlt 以仅复制目标窗口和所有叠加层(包括上下文菜单)

public static BufferedImage capture(Pointer hWnd2, String wText) {
   HWND hWnd = new HWND(hWnd2);
   boolean success;
   if(User32Extra.INSTANCE.IsIconic(hWnd))
   {
       success= User32Extra.INSTANCE.ShowWindow(hWnd, 4);
       System.err.println(success);
       System.err.println(wText+Native.getLastError());
   }
   // Pass GetDesktopWindow instead of target HWND to GetDC
   HDC hdcWindow2 = User32.INSTANCE.GetDC(User32.INSTANCE.GetDesktopWindow());
   HDC hdcMemDC = GDI32.INSTANCE.CreateCompatibleDC(hdcWindow2);

   RECT bounds = new RECT();
   //RECT bounds1 = new RECT();
   User32Extra.INSTANCE.GetWindowRect(hWnd, bounds);
   //User32Extra.INSTANCE.GetClientRect(hWnd, bounds1);

   //int extraGap = (bounds.right-bounds.left-bounds1.right);
   int width = bounds.right-bounds.left;
   int height = bounds.bottom-bounds.top;
   HBITMAP hBitmap = GDI32.INSTANCE.CreateCompatibleBitmap(hdcWindow2, width, height);

   HANDLE hOld = GDI32.INSTANCE.SelectObject(hdcMemDC, hBitmap);
   GDI32Extra.INSTANCE.BitBlt(hdcMemDC,0, 0, width, height, hdcWindow2, bounds.left, bounds.top, WinGDIExtra.SRCCOPY);
   GDI32.INSTANCE.SelectObject(hdcMemDC, hOld);

   BITMAPINFO bmi = new BITMAPINFO();
   bmi.bmiHeader.biWidth = width;
   bmi.bmiHeader.biHeight = -height;
   bmi.bmiHeader.biPlanes = 1;
   bmi.bmiHeader.biBitCount = 32;
   bmi.bmiHeader.biCompression = WinGDI.BI_RGB;
   Memory buffer = new Memory(width * height * 4);
   GDI32.INSTANCE.GetDIBits(hdcWindow2, hBitmap, 0, height, buffer, bmi, WinGDI.DIB_RGB_COLORS);

   BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

   GDI32.INSTANCE.DeleteObject(hBitmap);
   GDI32.INSTANCE.DeleteDC(hdcMemDC);
   User32.INSTANCE.ReleaseDC(hWnd, hdcWindow2);

   return image;
}

但是,如果其他窗口与我们的目标窗口重叠,则此方法将捕获其他窗口。 如果您知道该窗口处于 Activity 状态或打开了上下文菜单(因为它会强制将其置于前台),这可能就足够了。

另一种方法有点 hacky,但它适用于其他场景。我们可以检查是否有类名称为 #32768 的窗口(弹出菜单的类)。如果有,我们检查我们的目标窗口是否是前景窗口,然后我们可以将它复制到我们窗口顶部的目标位图中。

public static BufferedImage capture(Pointer hWnd2, String wText) {
    HWND hWnd = new HWND(hWnd2);
    boolean success;
    if(User32Extra.INSTANCE.IsIconic(hWnd))
    {
        success= User32Extra.INSTANCE.ShowWindow(hWnd, 4);
        System.err.println(success);
        System.err.println(wText+Native.getLastError());
    }
    HDC hdcWindow2 = User32.INSTANCE.GetDC(hWnd);
    final HDC hdcMemDC = GDI32.INSTANCE.CreateCompatibleDC(hdcWindow2);

    RECT bounds = new RECT();
    RECT bounds1 = new RECT();
    User32Extra.INSTANCE.GetWindowRect(hWnd, bounds);
    User32Extra.INSTANCE.GetClientRect(hWnd, bounds1);

    int extraGap = (bounds.right-bounds.left-bounds1.right);
    int width = bounds.right-bounds.left-extraGap;
    int height = bounds.bottom-bounds.top-extraGap ;
    HBITMAP hBitmap = GDI32.INSTANCE.CreateCompatibleBitmap(hdcWindow2, width, height);

    HANDLE hOld = GDI32.INSTANCE.SelectObject(hdcMemDC, hBitmap);
    GDI32Extra.INSTANCE.BitBlt(hdcMemDC,0, 0, width, height, hdcWindow2, bounds.left+bounds1.right-bounds.right+extraGap, bounds.top+bounds1.bottom-bounds.bottom+extraGap, WinGDIExtra.SRCCOPY);

    // Popup menu doesn't have parent, so we could encounter situation when context menu from another window is overlapping our target window
    // Since context menu enforces our window to focus, we can check here if our window is foreground window (otherwise it is not possible to have opened context menu)
    if (User32.INSTANCE.GetForegroundWindow() == hWnd)
    {
        final RECT windowBounds = bounds;
        // Enum all windows and check if they have class #32768
        User32.INSTANCE.EnumWindows(new WNDENUMPROC() {
            @Override
            public boolean callback(HWND hMenuWnd, Pointer arg1) {
                byte[] windowClass = new byte[8];
                User32.INSTANCE.GetClassNameA(hMenuWnd, windowClass, 8);
                String wClass = Native.toString(windowClass);
                // #32768 is class name for popup menus.
                if (wClass.equals("#32768"))
                {
                    HDC hdcWindowMenu = User32.INSTANCE.GetDC(hMenuWnd);
                    RECT menuBounds = new RECT();
                    User32Extra.INSTANCE.GetWindowRect(hMenuWnd, menuBounds);
                    GDI32Extra.INSTANCE.BitBlt(hdcMemDC, menuBounds.left - windowBounds.left, menuBounds.top - windowBounds.top, menuBounds.right-menuBounds.left, menuBounds.bottom-menuBounds.top, hdcWindowMenu, 0, 0, WinGDIExtra.SRCCOPY);
                    User32.INSTANCE.ReleaseDC(hMenuWnd, hdcWindowMenu);
                }
                return true;
            }
        }, null);
    }

    GDI32.INSTANCE.SelectObject(hdcMemDC, hOld);

    BITMAPINFO bmi = new BITMAPINFO();
    bmi.bmiHeader.biWidth = width;
    bmi.bmiHeader.biHeight = -height;
    bmi.bmiHeader.biPlanes = 1;
    bmi.bmiHeader.biBitCount = 32;
    bmi.bmiHeader.biCompression = WinGDI.BI_RGB;
    Memory buffer = new Memory(width * height * 4);
    GDI32.INSTANCE.GetDIBits(hdcWindow2, hBitmap, 0, height, buffer, bmi, WinGDI.DIB_RGB_COLORS);

    BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

    GDI32.INSTANCE.DeleteObject(hBitmap);
    GDI32.INSTANCE.DeleteDC(hdcMemDC);
    User32.INSTANCE.ReleaseDC(hWnd, hdcWindow2);

    return image;
}

对于 EnumWindows,您必须添加以下导入:

import com.sun.jna.platform.win32.WinUser.WNDENUMPROC;

和定义:

boolean EnumWindows(WinUser.WNDENUMPROC lpEnumFunc, Pointer arg);
int GetClassNameA(HWND hWnd, byte[] lpString, int nMaxCount);

关于java - 使用上下文菜单捕获 HWND,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31043798/

相关文章:

delphi - 向在辅助登录用户帐户上运行的应用程序发送消息

c++ - imwrite opencv 会影响捕获时间吗?

java - eclipse 火星 : ANT Task references missing SWT library

java - 使用PreparedStatements时SQL Server死锁

Java JApplet 将不再显示在任何浏览器中

java - 如何在 Eclipse 中捕获控制台输出

c++ - 使用不匹配单词的正则表达式链捕获

java - Twitter4j TwitterStream 或 BufferReading 导致 java.lang.OutOfMemoryError : Java Heap Space

c - c程序中链接的隐藏例程

c++ - 转换后年份值错误