0

I'm trying to understand the win32 api / pywin32 package, and I have code that takes a screenshot of a window with a particular name. I'm having a hard time understanding the win32 api, and the current code effectively searches for the same window two different times. I'm assuming it's possible to do better. Here's the code:

1 - Preliminaries:

from PIL import Image
import win32ui, win32gui, win32con

#name of window to screenshot
name = "Photos"

2- The first time I find the window, I am able to use the result to get information about its shape and location:

window = win32ui.FindWindow(None, name)
left, top, right, bottom = window.GetWindowRect()
width = right - left
height = bottom - top

3 - The second time I find the window, I'm able to feed the result into the GetWindowDC function. The remainder of my code (not shown) uses this device context information to perform the screenshot.

def findit(hwnd,ctx):
    if win32gui.GetWindowText(hwnd) == name: # check the title
        return hwnd
window = win32gui.EnumWindows(findit,None)

#convert this into a device context handle
windowDC = win32gui.GetWindowDC(window)

I'd like to know if there is a way to reproduce left, top, right, bottom and windowDC without using two different methods to find the window I want. In addition, I'd like to get an intuition for what data is being contained in window the first time I compute it vs the second. I'm more interested in this understanding than actually taking the screenshot, so code that does that job in a completely different way probably won't be helpful.

Kevin Bradner
  • 438
  • 4
  • 11
  • 1
    Sorry for my bad English,Why couldn't do them at the same time? – jizhihaoSAMA Apr 12 '20 at 07:03
  • 1
    *"I'm assuming it's possible to do better."* - Indeed, it's near impossible to do any worse. This is a recurring theme, where Python developers write code on assumptions. A common assumption being, that [window classes](https://docs.microsoft.com/en-us/windows/win32/winmsg/window-classes) were some obscure concept, that doesn't serve any practical purpose. However, window class names are much more stable than window titles. The latter is often changed throughout the application's life, and commonly subject to localization. Stop using the window title, use the window class name instead. – IInspectable Apr 12 '20 at 08:39
  • @jizhihaoSAMA the first time I compute window, the result type does not work as an input to `win32gui.GetWindowDC`. If I try to use my first `window` as an argument to that method, the error I get is `TypeError: The object is not a PyHandle object`. The second `window` I compute (based on code you wrote, thanks again) does work in this method. I'm a Python novice and I'm used to a language with a very different type system, so I'm having trouble figuring out the types of my two different `window`s. Any tips for how to figure this out are appreciated. – Kevin Bradner Apr 12 '20 at 20:15

1 Answers1

1

Please use win32gui.FindWindow(None, name).

Minimal code:

import win32gui
import win32ui
import win32con

name = "test.txt - Notepad"
hwnd = win32gui.FindWindow(None, name)
left, top, right, bot = win32gui.GetWindowRect(hwnd)
w = right - left
h = bot - top
print(w,h)
wDC = win32gui.GetWindowDC(hwnd)
dcObj=win32ui.CreateDCFromHandle(wDC)
cDC=dcObj.CreateCompatibleDC()
dataBitMap = win32ui.CreateBitmap()
dataBitMap.CreateCompatibleBitmap(dcObj, w, h)
cDC.SelectObject(dataBitMap)
cDC.BitBlt((0,0),(w, h) , dcObj, (0,0), win32con.SRCCOPY)
dataBitMap.SaveBitmapFile(cDC, "1.bmp")
# Free Resources
dcObj.DeleteDC()
cDC.DeleteDC()
win32gui.ReleaseDC(hwnd, wDC)
win32gui.DeleteObject(dataBitMap.GetHandle())

My guess is that winapi of the same name returns different types in different libraries.

Such as win32gui.FindWindow and win32ui.FindWindow

When taking a screenshot of a specific application, you will encounter some other problems, such as not being able to take a screenshot of the Firefox correctly.

There is another way to solve this problem, refer: Capturing screenshots with win32api python returns black image

As a supplement:

I tested EnumWindows and FindWindow in C ++, they will return the same data, and there will be no errors using GetWindowDC.

Here is C++ code:

#include <Windows.h>
#include <string>
#include <iostream>


static BOOL CALLBACK enumWindowCallback(HWND hWnd, LPARAM m) {
    RECT rc;
    int length = GetWindowTextLength(hWnd);
    char* buffer = new char[length + 1];
    GetWindowText(hWnd, buffer, length + 1);
    if (!strcmp(buffer, "Photos"))
    {       
        HDC hdc = GetWindowDC(hWnd);
        GetWindowRect(hWnd, &rc);
        std::cout <<"EnumWindows: "<< rc.right-rc.left <<" "<< rc.bottom-rc.top<<std::endl;
        std::cout <<"EnumWindows: "<< hWnd << std::endl;
    }
    return TRUE;
}

int main()
{
    RECT rc;
    HWND window = FindWindow(NULL, "Photos");
    GetWindowRect(window, &rc);
    std::cout <<"FindWindow: "<< rc.right - rc.left <<" "<< rc.bottom - rc.top << std::endl;
    std::cout <<"FindWindow: "<< window << std::endl;

    HDC hdc = GetWindowDC(window);

    EnumWindows(enumWindowCallback, NULL);
    return 0;
}

Debug:

FindWindow: 649 640
FindWindow: 00080A6A
EnumWindows: 649 640
EnumWindows: 00080A6A
Strive Sun
  • 5,988
  • 1
  • 9
  • 26
  • Thank you, I have confirmed that this works. I also appreciate the link about screenshots returning black images, since the first application I tested this on had that problem. It seems like the key difference with that answer is that you bring the window of interest to the foreground and then capture from the desktop window directly. Is that correct? – Kevin Bradner Apr 14 '20 at 16:19
  • 1
    @KevinBradner yes. – Strive Sun Apr 15 '20 at 01:15