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()
Related
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()
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?
I am trying to code an application that consists of various windows (e.g., generic message dialog, login dialog, main interface, etc.) and am having trouble getting the gtk.main_quit function to be called: either I get a complaint about the call being outside the main loop, or the function doesn't get called at all.
I am a newbie to both Python and GTK+, but my best guess as to how to get this to work is to have a "root" window, which is just a placeholder that is never seen, but controls the application's GTK+ loop. My code, so far, is as follows:
import pygtk
pygtk.require("2.0")
import gtk
class App(gtk.Window):
_exitStatus = 0
# Generic message box
def msg(self, title, text, type = gtk.MESSAGE_INFO, buttons = gtk.BUTTONS_OK):
# Must always have a button
if buttons == gtk.BUTTONS_NONE:
buttons = gtk.BUTTONS_OK
dialog = gtk.MessageDialog(None, 0, type, buttons, title)
dialog.set_title(title)
dialog.set_geometry_hints(min_width = 300)
dialog.set_resizable(False)
dialog.set_deletable(False)
dialog.set_position(gtk.WIN_POS_CENTER)
dialog.set_modal(True)
dialog.format_secondary_text(text)
response = dialog.run()
dialog.destroy()
return response
def nuke(self, widget, data):
gtk.main_quit()
exit(self._exitStatus)
def __init__(self):
super(App, self).__init__()
self.connect('destroy', self.nuke)
try:
raise Exception()
except:
self.msg('OMFG!', 'WTF just happened!?', gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE)
self._exitStatus = 1
self.destroy()
if self.msg('OK', 'Everything worked fine') == gtk.RESPONSE_OK:
self.destroy()
# Let's go!
App()
gtk.main()
The nuke function never gets called, despite the explicit calls to destroy.
DIFF On #DonQuestion's advice:
- self.destroy()
+ self.emit('destroy')
- App()
+ app = App()
This didn't solve the problem...
UPDATE Accepted #jku's answer, but also see my own answer for extra information...
First, there is a bit of a test problem with the code: You call Gtk.main_quit() from the App initialization: this happens before main loop is even running so signals probably won't work.
Second, you'll probably get a warning on destroy(): 'destroy' handler only takes two arguments (self plus one) but yours has three...
Also with regards to your comment about control flow: You don't need a Window to get signals as they're a GObject feature. And for your testing needs you could write a App.test_except() function and use glib.idle_add (self.test_except) in the object initialization -- this way test_except() is called when main loop is running.
I think #jku's answer identifies my key error, so I have marked it accepted, but while playing around, I found that the MessageDialog does not need to run within the GTK+ loop. I don't know if this is as designed, but it works! So, I broke my generic message dialog out into its own function and then kept the main app altogether in a class of its own, which respects the main loop as I was expecting:
import pygtk
pygtk.require("2.0")
import gtk
def msg(title, text, type = gtk.MESSAGE_INFO, buttons = gtk.BUTTONS_OK):
# Only allowed OK, Close, Cancel, Yes/No and OK/Cancel buttons
# Otherwise, default to just OK
if buttons not in [gtk.BUTTONS_OK, gtk.BUTTONS_CLOSE, gtk.BUTTONS_CANCEL, gtk.BUTTONS_YES_NO, gtk.BUTTONS_OK_CANCEL]:
buttons = gtk.BUTTONS_OK
dialog = gtk.MessageDialog(None, 0, type, buttons, title)
dialog.set_title(title)
dialog.set_geometry_hints(min_width = 300)
dialog.set_resizable(False)
dialog.set_deletable(False)
dialog.set_position(gtk.WIN_POS_CENTER)
dialog.set_modal(True)
dialog.format_secondary_text(text)
response = dialog.run()
dialog.destroy()
return response
class App:
def __init__(self):
# Build UI
# Connect signals
# Show whatever
def appQuit(self, widget):
gtk.main_quit()
def signalHandler(self, widget, data = None):
# Handle signal
# We can call msg here, when the main loop is running
# Load some resource
# We can call msg here, despite not having invoked the main loop
try:
# Load resource
except:
msg('OMFG!', 'WTF just happened!?', gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE)
exit(1)
# n.b., Calls to msg work even without the following code
App()
gtk.main()
exit(0)
I have a script which has a record and stop button, the record button does an infinite loop, but it also blocks the other button (stop button). All I wanted to build is a process which starts at click of record button and stops are click of stop button. Here is the script:
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
### BEGIN LICENSE
# This file is in the public domain
### END LICENSE
from locale import gettext as _
from gi.repository import Gtk # pylint: disable=E0611
import logging
logger = logging.getLogger('recordme')
from recordme_lib import Window
from recordme.AboutRecordmeDialog import AboutRecordmeDialog
from recordme.PreferencesRecordmeDialog import PreferencesRecordmeDialog
class RecordmeWindow(Window):
__gtype_name__ = "RecordmeWindow"
record = False
def finish_initializing(self, builder): # pylint: disable=E1002
"""Set up the main window"""
super(RecordmeWindow, self).finish_initializing(builder)
self.AboutDialog = AboutRecordmeDialog
self.PreferencesDialog = PreferencesRecordmeDialog
# Code for other initialization actions should be added here.
self.button1 = self.builder.get_object('button1')
self.button2 = self.builder.get_object('button2')
def on_button1_clicked(self, widget):
while(not self.record):
print 'button1 clicked'
while gtk.events_pending():
gtk.main_iteration(False)
Any ideas about this problem ?
I encountered similar programs in WX, which is also event based. The best (and possibly only) way I found to solve the problem is to create a function that runs on a timer during the main loop. Mine ran periodically, but you could also just set it to wait and close the loop when you run your function. In GTK, you have to do this with another module, "gobject". Here is an example of a method that runs periodically in GTK.
import gobject
class gtk_object(object):
def __init__(self):
gobject.timeout_add(100, self.my_function)
def my_function(self):
#do something here, like stopping the loop or having a timer to stop the loop
return True
Assuming your record functionality is cpu-intensive and/or may block and/or needs soft realtime assurance, I would recommend moving it off to a separate "worker" thread.
Then, create a window and your buttons.
Here, when "record" is clicked, I signal the worker to start recording; when "stop" is clicked, signal worker to stop; Optionally, when stop is clicked, terminate the main loop if you want your app to exit.
Additional control logic to terminate the app when window is closed and terminate the worker thread correctly is at the very bottom.
#!/usr/bin/env python
import time
import logging
import threading
from gi.repository import Gtk
class Worker(threading.Thread):
should_record = False
quit = False
def run(self):
while not self.quit:
if self.should_record:
logging.warn("recording...")
# cpu-intensive code here
else:
time.sleep(0.1)
class MainWindow(Gtk.Window):
def __init__(self):
super(MainWindow, self).__init__()
self.worker = Worker()
self.worker.start()
hb = Gtk.Box()
self.add(hb)
record = Gtk.Button("Record")
stop = Gtk.Button("Stop")
hb.add(record)
hb.add(stop)
def command(arg):
self.worker.should_record = arg
record.connect("clicked", lambda _b: command(True))
stop.connect("clicked", lambda _b: command(False))
# optional, if you want to quit the app on stop as well
stop.connect("clicked", lambda _b: Gtk.main_quit())
if __name__ == "__main__":
main = MainWindow()
try:
# optional, if you want to support close window to quit app
main.connect("delete-event", Gtk.main_quit)
main.show_all()
Gtk.main()
finally:
main.worker.quit = True
main.worker.join()
old stuff
Ideally you wan to use Gtk.main() instead of Gtk.main_iteration() in Gtk+ 3.
In Gtk+ 2, module name was gtk rather than gi.repository.Gtk.
Then you can quit wit with:
Gtk.main_quit
def main_quit()
The Gtk.main_quit() function terminates the current main loop level
started by the most recent call to the Gtk.main() function. The
nesting level of the main loop is reduced by calling this function.
You can have several nested main loops, in which case, you'd have to quit each of those.
Alternatively you can also use gtk_dialog.run() then default action for a button is to exit the loop.
GTK+ (as most UI toolkits) is event-based. That means it runs internal "event loop" - a loop that collects and processes events, such as handling user input and redrawing windows. All event handlers are dispatched from main loop. In order to process events, loop must be "spinning".
In your example, you are blocking main loop:
def on_button1_clicked(self, widget):
while(not self.record):
print 'button1 clicked'
as long as this function does not finish, control does not return to main loop so it cannot process other events, or redraw windows.
You can add this snippet form PyGTK FAQ in order to allow main loop to process event in the meantime:
while gtk.events_pending():
gtk.main_iteration(False)
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()