import win32ui
import win32gui
def WindowExists(windowname):
try:
win32ui.FindWindow(None, windowname)
except win32ui.error:
return False
else:
return True
hwndName = "Main Window Program"
if WindowExists(hwndName):
print ("Program is running")
hwnd = win32gui.FindWindow(None, hwndName)
else:
print ("Program is not running")
child_handles = []
def all_ok(hwnd, param):
child_handles.append(hwnd)
win32gui.EnumChildWindows(hwnd, all_ok, None)
print (child_handles)
The code above is working perfectly fine for me to be able to detect if "Main Window Program" is running, I am also able to enumerate all the child windows off of that. My problem however is I dont know how to use handles instead of captions, so instead of hwnd = "Main Window Program" I could use hwnd = 0xC0D57 (or whatever the handle is).
Also whats the best input method do a control? postmessage, sendmessage etc?
Related
I am calling macros from VBA using xlwings (python 3.7). When run, the macro populates a message box
I was wondering if there's a way to suppress that (e.g. not showing the message box at all or click ok automatically) from the xlwings end (can't change macro, locked). My current setting looks like this:
app = xw.apps.active # open application instance
app.visible = False # Excel application not visible
app.display_alerts = False # supress alert messages
app.screen_updating = False # supress screen updates
Thanks!
As you can't change the macro, the left option is to click the OK button automatically. Obviously, you can't do it in the main process since it gets stuck once the message box occurs. So you need to create a child thread to do it concurrently, getting the main process back from stuck. In summary, you need two things:
a child thread, e.g. threading.Thread
a GUI automation library to catch the message box, e.g. pywin32, pywinauto or PyAutoGUI.
As you're using xlwings, pywin32 should be installed already as a dependency. So, use it here for example.
The whole process looks like:
import xlwings as xw
from listener import MsgBoxListener
# start child thread
listener = MsgBoxListener('Message-box-title-in-your-case', 3)
listener.start()
# main process as you did before
app = xw.apps.active # open application instance
app.visible = False # Excel application not visible
app.display_alerts = False # supress alert messages
app.screen_updating = False # supress screen updates
...
# stop listener thread
listener.stop()
Where MsgBoxListener is the child thread to catch and close the message box:
title is the title of message box, as hidden in your screenshot
interval is the frequency of detecting if exists a message box
# listener.py
import time
from threading import Thread, Event
import win32gui
import win32con
class MsgBoxListener(Thread):
def __init__(self, title:str, interval:int):
Thread.__init__(self)
self._title = title
self._interval = interval
self._stop_event = Event()
def stop(self): self._stop_event.set()
#property
def is_running(self): return not self._stop_event.is_set()
def run(self):
while self.is_running:
try:
time.sleep(self._interval)
self._close_msgbox()
except Exception as e:
print(e, flush=True)
def _close_msgbox(self):
# find the top window by title
hwnd = win32gui.FindWindow(None, self._title)
if not hwnd: return
# find child button
h_btn = win32gui.FindWindowEx(hwnd, None,'Button', None)
if not h_btn: return
# show text
text = win32gui.GetWindowText(h_btn)
print(text)
# click button
win32gui.PostMessage(h_btn, win32con.WM_LBUTTONDOWN, None, None)
time.sleep(0.2)
win32gui.PostMessage(h_btn, win32con.WM_LBUTTONUP, None, None)
time.sleep(0.2)
if __name__=='__main__':
t = MsgBoxListener('Microsoft Excel', 1)
t.start()
time.sleep(10)
t.stop()
We have an app that is built with openframeworks. When started, it first opens a console window that does some work (and stays open) and then starts two more child processes that each open a window in fullscreen, one on each monitor. According to the guy that is building the app, it is impossible to give those two windows titles.
My job is to build a script that:
Checks if the app has crashed and reopens it
Verifies that the windows are in the foreground and one of them is in focus and fixes them if they aren't
I want to reuse an old python script of mine that did exactly this and altered it to fit the bill.
from time import sleep
import subprocess
import psutil
import re
import win32gui
import win32con
client_path = "C:\\path_to_app.exe"
window_name = ""
class WindowMgr:
"""Encapsulates some calls to the winapi for window management"""
def __init__(self, ):
"""Constructor"""
self._handle = None
def find_window(self, class_name, window_name=None):
"""find a window by its class_name"""
self._handle = win32gui.FindWindow(class_name, window_name)
def _window_enum_callback(self, hwnd, wildcard):
'''Pass to win32gui.EnumWindows() to check all the opened windows'''
if re.match(wildcard, str(win32gui.GetWindowText(hwnd))) is not None:
self._handle = hwnd
def find_window_wildcard(self, wildcard):
self._handle = None
win32gui.EnumWindows(self._window_enum_callback, wildcard)
def set_foreground(self):
"""put the window in the foreground"""
win32gui.SetForegroundWindow(self._handle)
def maximize(self):
win32gui.ShowWindow(self._handle, win32con.SW_MAXIMIZE)
def is_minimized(self):
return win32gui.IsIconic(self._handle)
def client_crashed():
for pid in psutil.pids():
if psutil.Process(pid).name() == "app.exe":
return False
return True
if __name__ == "__main__":
w = WindowMgr()
w.find_window_wildcard(window_name)
print("Checking")
while True:
if client_crashed() is True:
print("Reopening app.exe")
subprocess.Popen([client_path])
else:
print("Not crashed")
if w.is_minimized:
print("Maximizing")
w.set_foreground()
w.maximize()
else:
print("Not minimized")
print("Sleeping for 10")
sleep(10)
Now the checking for crashing and restarting works just fine. But since the windows have no title, the best I've come up with so far is to check for windows with no name, which apparently opens random programms like the Windows 10 movie programm (or at least brings them to the foreground which is weird because they should not be running).
Is there a better way to bring a window into focus without knowing its name? One thought of mine was to get the parent process and then access the children from there and bring them into focus somehow, but I've not been able to figure out how.
If there are better ways to achieve what I want than using python, I would also be glad for any pointers in that direction.
If you have unique window class name you can try GetClassName() https://msdn.microsoft.com/en-us/library/windows/desktop/ms633582(v=vs.85).aspx
Or get the window process GetWindowThreadProcessId() and check whether it's an instance of your app. See How to get the process name in C++
One thought of mine was to get the parent process and then
access the children from there and bring them into focus somehow, but
I've not been able to figure out how."
I've been through the very similar issue, I wanted to find a child proccess with random title name, I only had the name of the executable. Here is what I've done.
import win32gui
def windowFocusPassingPID(pid):
def callback(hwnd, list_to_append):
list_to_append.append((hwnd, win32gui.GetWindowText(hwnd)))
window_list = []
win32gui.EnumWindows(callback, window_list)
for i in window_list:
print(i) # if you want to check each item
if pid == i[0]:
print(i) # printing the found item (id, window_title)
win32gui.ShowWindow(i[0], 5)
win32gui.SetForegroundWindow(i[0])
break
# Here we will call the function, providing a valid PID
windowFocusPassingPID(INSERT_PID_HERE_IT_MUST_BE_AN_INTEGER)
So, this is a function that will call win32gui.EnumWindows, which handles all top-level windows. Those windows will be appended to the window_list, letting us know all the ID's and window names...
In the loop, we will iterate over the list and match the PID you want to bring to focus...
You can comment both print(i) if you want.
Here is the documentation:
http://timgolden.me.uk/pywin32-docs/win32gui__EnumWindows_meth.html
I am trying to write a program that finds a window by searching for its title. Once it has found the window, it will attempt to bring it to front. I am using win32gui API to achieve this. I am able to get it to work for the most part, but for some reason it does not work if the taskmanager is in front. I have the follow sample code.
import win32gui, win32con
import re, traceback
from time import sleep
class cWindow:
def __init__(self):
self._hwnd = None
def BringToTop(self):
win32gui.BringWindowToTop(self._hwnd)
def SetAsForegroundWindow(self):
win32gui.SetForegroundWindow(self._hwnd)
def Maximize(self):
win32gui.ShowWindow(self._hwnd, win32con.SW_MAXIMIZE)
def setActWin(self):
win32gui.SetActiveWindow(self._hwnd)
def _window_enum_callback(self, hwnd, wildcard):
'''Pass to win32gui.EnumWindows() to check all the opened windows'''
if re.match(wildcard, str(win32gui.GetWindowText(hwnd))) != None:
self._hwnd = hwnd
def find_window_wildcard(self, wildcard):
self._hwnd = None
win32gui.EnumWindows(self._window_enum_callback, wildcard)
def main():
sleep(5)
try:
wildcard = ".*Building Operation WorkStation.*"
cW = cWindow()
cW.find_window_wildcard(wildcard)
cW.Maximize()
cW.BringToTop()
cW.SetAsForegroundWindow()
except:
f = open("log.txt", "w")
f.write(traceback.format_exc())
print traceback.format_exc()
main()
I pieced this together from multiple online sources. It seems to work for the most part but for some windows like the task manager, it'll work sometimes but fails the rest. When it doesnt work properly, all I notice is the application icon blinks yellow. Is there a proper way of doing this to make sure the window that I am interested in is set to foreground 100% of the times? I am not sure if this is relevant but I am using Windows 7 Professional (32-bit) with Service Pack 1.
I found a solution: if taskmanager, then kill it. I added a method to cWindow:
def kill_task_manager(self):
# Here I use your method to find a window because of an accent in my french OS,
# but you should use win32gui.FindWindow(None, 'Task Manager complete name').
wildcard = 'Gestionnaire des t.+ches de Windows'
self.find_window_wildcard(wildcard)
if self._hwnd:
win32gui.PostMessage(self._hwnd, win32con.WM_CLOSE, 0, 0) # kill it
sleep(0.5) # important to let time for the window to be closed
Call this method just after cW = cWindow().
Another bug trap is to prevent this exception in SetAsForegroundWindow:
error: (0, 'SetForegroundWindow', 'No error message is available')
just send an alt key before the win32gui call:
# Add this import
import win32com.client
# Add this to __ini__
self.shell = win32com.client.Dispatch("WScript.Shell")
# And SetAsForegroundWindow becomes
def SetAsForegroundWindow(self):
self.shell.SendKeys('%')
win32gui.SetForegroundWindow(self._hwnd)
Last, if I may, do not compare != None but is not None. More pythonic ;)
This is the full code:
# coding: utf-8
import re, traceback
import win32gui, win32con, win32com.client
from time import sleep
class cWindow:
def __init__(self):
self._hwnd = None
self.shell = win32com.client.Dispatch("WScript.Shell")
def BringToTop(self):
win32gui.BringWindowToTop(self._hwnd)
def SetAsForegroundWindow(self):
self.shell.SendKeys('%')
win32gui.SetForegroundWindow(self._hwnd)
def Maximize(self):
win32gui.ShowWindow(self._hwnd, win32con.SW_MAXIMIZE)
def setActWin(self):
win32gui.SetActiveWindow(self._hwnd)
def _window_enum_callback(self, hwnd, wildcard):
'''Pass to win32gui.EnumWindows() to check all the opened windows'''
if re.match(wildcard, str(win32gui.GetWindowText(hwnd))) is not None:
self._hwnd = hwnd
def find_window_wildcard(self, wildcard):
self._hwnd = None
win32gui.EnumWindows(self._window_enum_callback, wildcard)
def kill_task_manager(self):
wildcard = 'Gestionnaire des t.+ches de Windows'
self.find_window_wildcard(wildcard)
if self._hwnd:
win32gui.PostMessage(self._hwnd, win32con.WM_CLOSE, 0, 0)
sleep(0.5)
def main():
sleep(5)
try:
wildcard = ".*Building Operation WorkStation.*"
cW = cWindow()
cW.kill_task_manager()
cW.find_window_wildcard(wildcard)
cW.BringToTop()
cW.Maximize()
cW.SetAsForegroundWindow()
except:
f = open("log.txt", "w")
f.write(traceback.format_exc())
print(traceback.format_exc())
if __name__ == '__main__':
main()
Sources: how do I close window with handle using win32gui in Python and win32gui.SetActiveWindow() ERROR : The specified procedure could not be found.
Note: The following deals only with making sure that always-on-top windows such as Task Manager are hidden before activating the window - it assumes that the activation part itself works fine, which may not be the case. The conditions under which a process is allowed to call the SetForegroundWindow Windows API function are listed here.
Task Manager is special in two respects:
By default, it is set to display always on top, i.e., above all other windows.
Even when that is turned off (Options > Always on Top unchecked), you can still make it display on top of other always-on-top windows (something that ordinary windows seemingly cannot do).
Your code:
is working - in my tests - in the sense that the target window does become the active window.
is not working in the sense that the Task Manager window still stays on top of the (maximized) window.
even trying to make your window an always-on-top window as well wouldn't help, unfortunately.
Specifically checking for the presence of the Task Manager window and minimizing it is an option, but note that there may be other always-on-top windows, so for a robust solution you have to identify all open always-on-top windows and minimize them:
The following tries hard to identify all always-on-top windows except the Task Bar and Start button, and minimizes (effectively hides) any such windows.
The new methods are hide_always_on_top_windows and _window_enum_callback_hide.
import win32gui, win32con
import re, traceback
from time import sleep
class cWindow:
def __init__(self):
self._hwnd = None
def SetAsForegroundWindow(self):
# First, make sure all (other) always-on-top windows are hidden.
self.hide_always_on_top_windows()
win32gui.SetForegroundWindow(self._hwnd)
def Maximize(self):
win32gui.ShowWindow(self._hwnd, win32con.SW_MAXIMIZE)
def _window_enum_callback(self, hwnd, regex):
'''Pass to win32gui.EnumWindows() to check all open windows'''
if self._hwnd is None and re.match(regex, str(win32gui.GetWindowText(hwnd))) is not None:
self._hwnd = hwnd
def find_window_regex(self, regex):
self._hwnd = None
win32gui.EnumWindows(self._window_enum_callback, regex)
def hide_always_on_top_windows(self):
win32gui.EnumWindows(self._window_enum_callback_hide, None)
def _window_enum_callback_hide(self, hwnd, unused):
if hwnd != self._hwnd: # ignore self
# Is the window visible and marked as an always-on-top (topmost) window?
if win32gui.IsWindowVisible(hwnd) and win32gui.GetWindowLong(hwnd, win32con.GWL_EXSTYLE) & win32con.WS_EX_TOPMOST:
# Ignore windows of class 'Button' (the Start button overlay) and
# 'Shell_TrayWnd' (the Task Bar).
className = win32gui.GetClassName(hwnd)
if not (className == 'Button' or className == 'Shell_TrayWnd'):
# Force-minimize the window.
# Fortunately, this seems to work even with windows that
# have no Minimize button.
# Note that if we tried to hide the window with SW_HIDE,
# it would disappear from the Task Bar as well.
win32gui.ShowWindow(hwnd, win32con.SW_FORCEMINIMIZE)
def main():
sleep(5)
try:
regex = ".*Building Operation WorkStation.*"
cW = cWindow()
cW.find_window_regex(regex)
cW.Maximize()
cW.SetAsForegroundWindow()
except:
f = open("log.txt", "w")
f.write(traceback.format_exc())
print(traceback.format_exc())
main()
I see there's win32process.GetWindowThreadProcess() that gets a window handler and returns it's process id. Is there a way to do the opposite: get the window handler of a running process by it's process id? Something like win32gui.GetWindowHandler(processId) ?
Specifically What I'm trying to do:
I have a python script that runs an external program, lets say notepad.exe.
Notepad is fired when runProgram() method is called. I want to prevent this method from running Notepad more than once. I accomplish this in the following way, using win32process:
import win32process as process
import sys
PORTABLE_APPLICATION_LOCATION = "C:\\Windows\\system32\\notepad.exe"
processHandler = -1
def runProgram():
global processHandler
#don't run a process more than once
if (isLiveProcess(processHandler)):
#Bring focus back to running window!
return;
try:
startObj = process.STARTUPINFO()
myProcessTuple = process.CreateProcess(PORTABLE_APPLICATION_LOCATION,None,None,None,8,8,None,None,startObj)
processHandler = myProcessTuple[2]
except:
print(sys.exc_info[0])
def isLiveProcess(processHandler): #Process handler is dwProcessId
processList = process.EnumProcesses()
for aProcess in processList:
if (aProcess == processHandler):
return True
return False
runProgram()
This works as expected, but if the process is found to be already alive, I'd like to bring it's window back to front with win32gui
I dont think that Windows API provides a method for this , but you could iterate over all open windows , and find the one that belongs to you .
I have modified your program so it looks like this :
import win32process
import win32process as process
import win32gui
import sys
PORTABLE_APPLICATION_LOCATION = "C:\\Windows\\system32\\notepad.exe"
processHandler = -1
def callback(hwnd, procid):
if procid in win32process.GetWindowThreadProcessId(hwnd):
win32gui.SetForegroundWindow(hwnd)
def show_window_by_process(procid):
win32gui.EnumWindows(callback, procid)
def runProgram():
global processHandler
#don't run a process more than once
if (isLiveProcess(processHandler)):
#Bring focus back to running window!
show_window_by_process(processHandler)
return;
try:
startObj = process.STARTUPINFO()
myProcessTuple = process.CreateProcess(PORTABLE_APPLICATION_LOCATION,None,None,None,8,8,None,None,startObj)
processHandler = myProcessTuple[2]
except:
print(sys.exc_info[0])
def isLiveProcess(processHandler): #Process handler is dwProcessId
processList = process.EnumProcesses()
for aProcess in processList:
if (aProcess == processHandler):
return True
return False
runProgram()
When using win32api.setConsoleCtrlHandler(), I'm able to receive shutdown/logoff/etc events from Windows, and cleanly shut down my app.
However, this only works when running the app under python.exe (i.e., it has a console window), but not under pythonw.exe (no console window).
Is there an equivalent way in Windows to receive these events when you have no console and no window to receive them? Or, is there a programmatic way to hide the console window?
To be clear - my goal is to be able to successfully receive Windows shutdown/logoff/etc events, without having any kind of console window showing.
EDIT:
I've been playing around, and I've gotten quite a bit further. I wrote a piece of test code for this. When I do a taskkill /im pythonw.exe - it will receive the message.
However, when I do a shutdown, restart, or logoff on Windows, I do not get any messages.
Here's the whole thing:
""" Testing Windows shutdown events """
import win32con
import win32api
import win32gui
import sys
import time
def log_info(msg):
""" Prints """
print msg
f = open("c:\\test.log", "a")
f.write(msg + "\n")
f.close()
def wndproc(hwnd, msg, wparam, lparam):
log_info("wndproc: %s" % msg)
if __name__ == "__main__":
log_info("*** STARTING ***")
hinst = win32api.GetModuleHandle(None)
wndclass = win32gui.WNDCLASS()
wndclass.hInstance = hinst
wndclass.lpszClassName = "testWindowClass"
messageMap = { win32con.WM_QUERYENDSESSION : wndproc,
win32con.WM_ENDSESSION : wndproc,
win32con.WM_QUIT : wndproc,
win32con.WM_DESTROY : wndproc,
win32con.WM_CLOSE : wndproc }
wndclass.lpfnWndProc = messageMap
try:
myWindowClass = win32gui.RegisterClass(wndclass)
hwnd = win32gui.CreateWindowEx(win32con.WS_EX_LEFT,
myWindowClass,
"testMsgWindow",
0,
0,
0,
win32con.CW_USEDEFAULT,
win32con.CW_USEDEFAULT,
win32con.HWND_MESSAGE,
0,
hinst,
None)
except Exception, e:
log_info("Exception: %s" % str(e))
if hwnd is None:
log_info("hwnd is none!")
else:
log_info("hwnd: %s" % hwnd)
while True:
win32gui.PumpWaitingMessages()
time.sleep(1)
I feel like I'm pretty close here, but I'm definitely missing something!
The problem here was that the HWND_MESSAGE window type doesn't actually receive broadcast messages - like the WM_QUERYENDSESSION and WM_ENDSESSION.
So instead of specifying win32con.HWND_MESSAGE for the "parent window" parameter of CreateWindowEx(), I just specified 0.
Basically, this creates an actual window, but I never show it, so it's effectively the same thing. Now, I can successfully receive those broadcast messages and shut down the app properly.
If you don't have a console, setting a console handler of course can't work. You can receive system events on a GUI (non-console) program by making another window (doesn't have to be visible), making sure you have a normal "message pump" on it serving, and handling WM_QUERYENDSESSION -- that's the message telling your window about shutdown and logoff events (and your window can try to push back against the end-session by returning 0 for this message). ("Windows Services" are different from normal apps -- if that's what you're writing, see an example here).