phibao37
phibao37

Reputation: 2350

(C++/Python) Capture window screenshot without caret indicator

I want to capture the screenshot of the existing running window via Python.
Following is my sample code for capture Notepad window:

import time
import win32gui
import win32ui
import win32con
import pyautogui


def getWindowScreenshot(hwnd, output_file):
    r = win32gui.GetWindowRect(hwnd)
    width = r[2] - r[0]
    height = r[3] - r[1]
    wDC = win32gui.GetWindowDC(hwnd)
    dcObj = win32ui.CreateDCFromHandle(wDC)
    cDC = dcObj.CreateCompatibleDC()
    dataBitMap = win32ui.CreateBitmap()
    dataBitMap.CreateCompatibleBitmap(dcObj, width, height)
    cDC.SelectObject(dataBitMap)
    cDC.BitBlt((0, 0), (width, height), dcObj, (0, 0), win32con.SRCCOPY)

    # save the bitmap to a file
    dataBitMap.SaveBitmapFile(cDC, output_file)

    dcObj.DeleteDC()
    cDC.DeleteDC()
    win32gui.ReleaseDC(hwnd, wDC)
    win32gui.DeleteObject(dataBitMap.GetHandle())


# Press the green button in the gutter to run the script.
if __name__ == '__main__':

    window_title = "Untitled - Notepad"
    winHandle = win32gui.FindWindow(None, window_title)
    if winHandle == 0:
        raise RuntimeError(f"Window '{window_title}' is not opening!")

    # Bring the window to foreground in case it is overlap by other windows
    win32gui.SetForegroundWindow(winHandle)
    # Do something with Notepad
    time.sleep(3)

    # Capture the window
    getWindowScreenshot(winHandle, "output.bmp")

However, sometimes the captured image will have the blinking caret indicator on the input textbox. enter image description here
My question is, how to capture the screenshot without getting the caret indicator?


I already tried some solution, but not suitable:

Solution 1:

# Bring the window to foreground in case it is overlap by other windows
    win32gui.SetForegroundWindow(winHandle)
    # Do something with Notepad
    time.sleep(3)

    # Make the "Desktop window" focus
    win32gui.SetForegroundWindow(win32gui.GetDesktopWindow())

    # Capture the window
    getWindowScreenshot(winHandle, "output.bmp")

Before capturing the screenshot, try to set "Desktop" as the foreground window.
However, the command win32gui.SetForegroundWindow(win32gui.GetDesktopWindow()) will raise an error:

pywintypes.error: (0, 'SetForegroundWindow', 'No error message is available')

Solution 2:

Replace win32gui.SetForegroundWindow(win32gui.GetDesktopWindow()) with win32gui.ShowWindow(winHandle, 4), where 4 is SW_SHOWNOACTIVATE. The code run OK, but the Notepad window doesn't lose focus after execution.

Solution 3:

# Bring the window to foreground in case it is overlap by other windows
    win32gui.SetForegroundWindow(winHandle)
    # Do something with Notepad
    time.sleep(3)

    # Click outside of window
    r = win32gui.GetWindowRect(winHandle)
    pyautogui.click(r[2]+1, r[3]+1)

    # Capture the window
    getWindowScreenshot(winHandle, "output.bmp")

With this solution, I tried to click outside the Notepad window (click on the desktop). This code seems worked, but will not work if another window is behind the Notepad window like this:

enter image description here

When the code is executed, it will click on the Windows Explorer window. So, the code can perform clicking on something unexpectedly (example: deleting the system file unexpectedly?).

Solution 4 (from @SongZhu-MSFT)

if __name__ == '__main__':

    window_title = "Untitled - Notepad"
    winHandle = win32gui.FindWindow(None, window_title)
    if winHandle == 0:
        raise RuntimeError(f"Window '{window_title}' is not opening!")

    # Bring the window to foreground in case it is overlap by other windows
    win32gui.SetForegroundWindow(winHandle)
    # Do something with Notepad
    time.sleep(3)

    win32gui.EnableWindow(winHandle,False)
    # Capture the window
    getWindowScreenshot(winHandle, "output.bmp")
    time.sleep(0.5)
    getWindowScreenshot(winHandle, "output1.bmp")
    time.sleep(5)
    win32gui.EnableWindow(winHandle,True)

When I run the code, I still see that the caret displaying all time. And output.bmp + output1.bmp still exists one image with the caret.

System information:

Upvotes: 1

Views: 262

Answers (1)

Zeus
Zeus

Reputation: 3890

After my test, I can use SendMessageW to send WM_KILLFOCUS to make the caret disappear.

This is the code that worked for me:

import time
import win32gui
import win32ui
import win32con
import pyautogui
from ctypes import windll

def getWindowScreenshot(hwnd, output_file):
    r = win32gui.GetWindowRect(hwnd)
    width = r[2] - r[0]
    height = r[3] - r[1]
    wDC = win32gui.GetWindowDC(hwnd)
    dcObj = win32ui.CreateDCFromHandle(wDC)
    cDC = dcObj.CreateCompatibleDC()
    dataBitMap = win32ui.CreateBitmap()
    dataBitMap.CreateCompatibleBitmap(dcObj, width, height)
    cDC.SelectObject(dataBitMap)
    cDC.BitBlt((0, 0), (width, height), dcObj, (0, 0), win32con.SRCCOPY)

    # save the bitmap to a file
    dataBitMap.SaveBitmapFile(cDC, output_file)

    dcObj.DeleteDC()
    cDC.DeleteDC()
    win32gui.ReleaseDC(hwnd, wDC)
    win32gui.DeleteObject(dataBitMap.GetHandle())


if __name__ == '__main__':

    window_title = "Untitled - Notepad"
    winHandle = win32gui.FindWindow(None, window_title)
    if winHandle == 0:
        raise RuntimeError(f"Window '{window_title}' is not opening!")

    # Bring the window to foreground in case it is overlap by other windows
    win32gui.SetForegroundWindow(winHandle)
    # Do something with Notepad
    windll.user32.SendMessageW(winHandle, win32con.WM_KILLFOCUS, None, None)
    # Capture the window
    getWindowScreenshot(winHandle, "output.bmp")
    time.sleep(0.5)
    getWindowScreenshot(winHandle, "output1.bmp")

Upvotes: 2

Related Questions