I have been having a problem with win32gui while taking screenshot of certain application window.
My script (code below) take a screenshot of a window to analyze it. For certain windows it works perfectly, but I have just encountered one application that, while I have my script running the window that it takes screenshot from make the window flickers. (I keep seeing white flash on the whole window)
Anyone ever experienced it and had a solution for it?
def getImage(self,hwnd = None):
if hwnd == None:
hwnd = self.hwnd
self.whereIsWindow(hwnd)
left, top, right, bot = win32gui.GetWindowRect(hwnd)
w = right - left
h = bot - top
self.width = w
self.height = h
hwndDC = win32gui.GetWindowDC(hwnd)
mfcDC = win32ui.CreateDCFromHandle(hwndDC)
saveDC = mfcDC.CreateCompatibleDC()
saveBitMap = win32ui.CreateBitmap()
saveBitMap.CreateCompatibleBitmap(mfcDC, w, h)
saveDC.SelectObject(saveBitMap)
result = windll.user32.PrintWindow(hwnd, saveDC.GetSafeHdc(), 0)
bmpinfo = saveBitMap.GetInfo()
bmpstr = saveBitMap.GetBitmapBits(True)
im = Image.frombuffer(
'RGB',
(bmpinfo['bmWidth'], bmpinfo['bmHeight']),
bmpstr, 'raw', 'BGRX', 0, 1)
win32gui.DeleteObject(saveBitMap.GetHandle())
saveDC.DeleteDC()
mfcDC.DeleteDC()
win32gui.ReleaseDC(hwnd, hwndDC)
I know the culprit are the last 5 lines, if i comment them out it stops flickering but it takes all the memory which is not an option.
I ended up putting more of a delay in my loop and there was no flickering at all.
Related
I think the same error as this thread; Python win32ui.error: CreateCompatibleDC failed
It's been a few months since I took a look at this code because I couldn't get past this error.
I'm using source_needle_images as the artefacts I'm trying to find, and haystack as the image I'm trying to find them in. My needles are currently two jpg's however, in future, this may be dozens of jpg's and my haystack is an application window named wincap
The eventual intention is to "do something" if anything whatsoever from source_needle_images is found within the application window.
This code is from my main.py, and I'm using two functions search and windowcapture
windowcapture captures the application window, search performs the OCR.
from windowcapture import WindowCapture
from search import Search
# the window to capture
wincap = WindowCapture('P')
# load needle images and start the matching process
source_needle_images = glob.glob(r'C:\\\\\\\*.jpg')
search = Search(source_needle_images)
loop_time = time()
while(True):
# get an updated image of the background
haystack_img = wincap.get_screenshot()
# display the processed image
points = search.find(haystack_img, 0.85, 'rectangles')
This is the section of code causing issue;
def get_screenshot(self):
# get the window image data
wDC = win32gui.GetWindowDC(self.hwnd)
dcObj = win32ui.CreateDCFromHandle(wDC)
cDC = dcObj.CreateCompatibleDC()
dataBitMap = win32ui.CreateBitmap()
dataBitMap.CreateCompatibleBitmap(dcObj, self.w, self.h)
cDC.SelectObject(dataBitMap)
cDC.BitBlt((0, 0), (self.w, self.h), dcObj, (self.cropped_x, self.cropped_y), win32con.SRCCOPY)
# convert the raw data into a format opencv can read
#dataBitMap.SaveBitmapFile(cDC, 'debug.bmp')
signedIntsArray = dataBitMap.GetBitmapBits(True)
img = np.fromstring(signedIntsArray, dtype='uint8')
img.shape = (self.h, self.w, 4)
# free resources
dcObj.DeleteDC()
cDC.DeleteDC()
win32gui.ReleaseDC(self.hwnd, wDC)
win32gui.DeleteObject(dataBitMap.GetHandle())
and this is the error in question;
Traceback (most recent call last):
File "c:\\\\\\main.py", line 23, in <module>
haystack_img = wincap.get_screenshot()
File "c:\\\\\\windowcapture.py", line 52, in get_screenshot
dataBitMap.CreateCompatibleBitmap(dcObj, self.w, self.h)
win32ui.error: CreateCompatibleDC failed
Updated with requested code:
# constructor
def __init__(self, window_name=None):
# find the handle for the window we want to capture.
# if no window name is given, capture the entire screen
if window_name is None:
self.hwnd = win32gui.GetDesktopWindow()
else:
self.hwnd = win32gui.FindWindow(None, window_name)
if not self.hwnd:
raise Exception('Window not found: {}'.format(window_name))
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.
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:
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:
Windows 10 1909
Python 3.7.9
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")
So I have this code:
import win32gui
import win32ui
from ctypes import windll
from PIL import Image
hwnd = win32gui.FindWindow(None, "#chat - Discord")
# Change the line below depending on whether you want the whole window
# or just the client area.
left, top, right, bot = win32gui.GetClientRect(hwnd)
#left, top, right, bot = win32gui.GetWindowRect(hwnd)
w = right - left
h = bot - top
hwndDC = win32gui.GetWindowDC(hwnd)
mfcDC = win32ui.CreateDCFromHandle(hwndDC)
saveDC = mfcDC.CreateCompatibleDC()
saveBitMap = win32ui.CreateBitmap()
saveBitMap.CreateCompatibleBitmap(mfcDC, w, h)
saveDC.SelectObject(saveBitMap)
# Change the line below depending on whether you want the whole window
# or just the client area.
result = windll.user32.PrintWindow(hwnd, saveDC.GetSafeHdc(), 1)
#result = windll.user32.PrintWindow(hwnd, saveDC.GetSafeHdc(), 0)
bmpinfo = saveBitMap.GetInfo()
bmpstr = saveBitMap.GetBitmapBits(True)
im = Image.frombuffer(
'RGB',
(bmpinfo['bmWidth'], bmpinfo['bmHeight']),
bmpstr, 'raw', 'BGRX', 0, 1)
if result:
#PrintWindow Succeeded
im.show()
win32gui.DeleteObject(saveBitMap.GetHandle())
saveDC.DeleteDC()
mfcDC.DeleteDC()
win32gui.ReleaseDC(hwnd, hwndDC)
The problem with it is that the screenshot it takes is all black. It does this with all windows, not just Discord. What is wrong with it, and what do I need to do to fix it?
Also as a side question - when I take screenshots the command prompt opens and then close real quick, is there also a way to stop that from happening?
The problem with it is that the screenshot it takes is all black.
I understand that you want to take a screenshot?
from PIL.ImageGrab import grab
image = grab()
image.save('Screenshot.png')
If you have the coordinates of the window, try this instead:
from PIL.ImageGrab import grab
box = (0,0,200,200)
image = grab(box)
image.save('Screenshot.png')
PIL has been discontinued though, so you may be able to use this code only for the next immediate future. Also, the command prompt does not appear with this code.
I've got an (apparently) cross-platform screenshot function using wxPython:
def take_screenshot(x=0, y=0, width=None, height=None):
try:
import wx
except ImportError as e:
return 'Screenshot could not be taken - wx could not be imported: %s' %(e)
import os, datetime
folder_name = datetime.date.today().strftime('%Y-%m-%d')
file_name = datetime.datetime.now().strftime('%H-%M-%S') + '.png'
directory = os.path.join(os.getcwd(), 'screenshots', folder_name)
make_directory(directory)
filename = os.path.join(directory, file_name)
app = wx.App()
screen = wx.ScreenDC()
size = screen.GetSize()
if width == None:
width = size[0]
if height == None:
height = size[1]
bmp = wx.EmptyBitmap(width, height)
mem = wx.MemoryDC(bmp)
mem.Blit(0, 0, width, height, screen, x, y)
del mem
bmp.SaveFile(filename, wx.BITMAP_TYPE_PNG)
return 'Screenshot saved to file: %s' %(filename)
Here is a screenshot I took on Windows 7. This code works fine on Linux. I'm running on Python 2.7.8 and wxPython 3.0.2.0
Has anyone seen any similar problems? Am I doing something wrong?
This was discussed a few years ago on Stack and the wxPython mailing list. At that time, there was no reliable cross-platform method of taking a screenshot of the user's screen. I have not heard of any improvements since. You might try the PyQt method mentioned in the Stack link or you could try out the pyscreenshot project.
Update for wxPython 4 and higher
import wx
app = wx.App(False)
screen = wx.ScreenDC()
size = screen.GetSize()
width = size.width
height = size.height
bmp = wx.Bitmap(width, height)
# Create a memory DC that will be used for actually taking the screenshot
memDC = wx.MemoryDC()
# Tell the memory DC to use our Bitmap
# all drawing action on the memory DC will go to the Bitmap now
memDC.SelectObject(bmp)
# Blit (in this case copy) the actual screen on the memory DC
memDC.Blit(
0, 0,
width, height,
screen,
0, 0
)
# Select the Bitmap out of the memory DC by selecting a new bitmap
memDC.SelectObject(wx.NullBitmap)
im = bmp.ConvertToImage()
im.SaveFile('screenshot.png', wx.BITMAP_TYPE_PNG)
I just finished writing a Tkinter based GUI of the board game Othello for a programming class. Everything seems to be working correctly, but something strange is still happening: any changes that occur on the game board GUI are updated only when I click outside of the window.
This happens not only in my application, but in other Tkinter based GUI applications that my instructor has written--only when running on my machine. I've seen these applications work correctly on other machines, which has led me to believe that there's something specific to my machine that is causing this issue, and for this reason, I haven't included any code in my question.
Does anyone have any insight into this? I'm sorry if I haven't included sufficient details; I'm not exactly sure what to include. I'm using Python 3.3.2 and Tkinter 8.5
edit:
Here's the code for the GUI that my instructor wrote. It's a simple application that creates ovals on a canvas wherever the user clicks. It worked fine on the machines at school, but on my computer, when I click on the canvas, nothing happens until I click outside of the tkinter window. After clicking outside of the window, the spots draw correctly.
Also, I'm running OS X Mavericks (10.9).
import coordinate
import spots_engine
import tkinter
class SpotsApplication:
def __init__(self, state: spots_engine.SpotsState):
self._state = state
self._root_window = tkinter.Tk()
self._canvas = tkinter.Canvas(
master = self._root_window, width = 500, height = 450,
background = '#006000')
self._canvas.grid(
row = 0, column = 0, padx = 10, pady = 10,
sticky = tkinter.N + tkinter.S + tkinter.E + tkinter.W)
self._canvas.bind('<Configure>', self._on_canvas_resized)
self._canvas.bind('<Button-1>', self._on_canvas_clicked)
self._root_window.rowconfigure(0, weight = 1)
self._root_window.columnconfigure(0, weight = 1)
def start(self) -> None:
self._root_window.mainloop()
def _on_canvas_resized(self, event: tkinter.Event) -> None:
self._redraw_all_spots()
def _on_canvas_clicked(self, event: tkinter.Event) -> None:
width = self._canvas.winfo_width()
height = self._canvas.winfo_height()
click_coordinate = coordinate.from_absolute(
(event.x, event.y), (width, height))
self._state.handle_click(click_coordinate)
self._redraw_all_spots()
def _redraw_all_spots(self) -> None:
self._canvas.delete(tkinter.ALL)
canvas_width = self._canvas.winfo_width()
canvas_height = self._canvas.winfo_height()
for spot in self._state.all_spots():
center_x, center_y = spot.center_coordinate().absolute(
(canvas_width, canvas_height))
radius_x = spot.radius_frac() * canvas_width
radius_y = spot.radius_frac() * canvas_height
self._canvas.create_oval(
center_x - radius_x, center_y - radius_y,
center_x + radius_x, center_y + radius_y,
fill = '#ffff00', outline = '#000000')
if __name__ == '__main__':
SpotsApplication(spots_engine.SpotsState()).start()
If I'm not mistaken, the problem we are talking about here is a known bug:
http://bugs.python.org/issue19373
EDIT:
Installing/reinstalling ActiveTcl8.5.15.1.297588 over ActiveTcl8.6.1.1.297588 is working! My Tkinter GUI is responsive again.
NOTE: I'm using Python 3.3.3.