I was using pywin32 to make a connection between Python and Excel and I was starting to deal with events. It happens that in the project I'm working with, I would need to capture a button click event in Python. I've seen events from Workbooks and Worksheets, but I can't figure out the click ones.
class WorkbookEvents:
def OnSheetSelectionChange(self, *args):
#print(args)
print("You changed the selection")
#print(args[1].Address)
#args[0].Range("A1").Value = "Range :" + str(args[1].Address)
workbook_events= WIN32.WithEvents(wb, WorkbookEvents)
quite late for an answer but I got it working with this piece of code (and using the ActiveX "Command Button" control in Excel)
import win32com.client as win32
import pythoncom
import sys
import time
#The event handlers container
class wsEvents:
def OnClick(self,*args):
print('Button Clicked')
xlApp = win32.Dispatch("Excel.Application")
xlwb = xlApp.Workbooks("Book1.xlsm")
ws=xlwb.Sheets("Sheet1")
xl_events=win32.WithEvents(ws.OLEObjects("CommandButton1").Object,wsEvents)
# define initalizer
keepOpen = True
while keepOpen:
time.sleep(0.1)
# display the message
pythoncom.PumpWaitingMessages()
try:
# if the workbook count does not equal zero we can assume Excel is open
if xlApp.Workbooks.Count != 0:
keepOpen = True
# otherwise close the application and exit the script
else:
keepOpen = False
xlApp = None
sys.exit()
except:
# if there is an error close excel and exit the script
keepOpen = False
xl = None
The ActiveX command button is loaded in Excel as an "independent" COM(OLE) object that is referenced by the worksheet where it is displayed. The Excel API permit to get a reference to OLE objects as explained here and easily done in python code.
Basically the event handlers are not managed by the worksheet but by the Command Button object instead, and of course the events supported by a Command Button are OnClick etc...Turns out that the solution is elegant and that win32com has much respect for COM :)
Credits must be given to Alex Reed blog about his snippet to keep python script waiting for new events.
Related
I made an App to process and display some data in a Tkinter window. I now want to send the contents of the window to a printer for printing on actual paper. However I don't really see any libraries in tkinter or Python to do this. I have to confess I am very new to tkinter and Python.....
Can anyone point me in the right direction?
Thanks.
tkinter is for a graphics user interface and so is all about display. To print data in a tkinter widget you'd have to retrieve that data depending on what the widget is and put it on the clipboard (can do this in tkinter) or file it (in a separate function) and use a separate printing app to print.
(edited as per comment)
This code does much the same as the subprocess module in a minimalist fashion since you have a specific task on Windows. To test it needs a pdf filepath inserted so I've put in an example that brings up notepad just so it can run as it is.
This has an alternative to checking for print status by use of a manual pause. Then Adobe (or any executable that has the appropriate print facility) can be closed automatically. I'd expect the manual pause to be replaced by an automatic timer set for an estimate time for a document to print. Probably need to consider only the time needed to transfer the document into the printing buffer - but that is up to how you want to operate with your system.
"""Process to start external executable.
. default is to let process run until parent pause is complete then process is terminated in parent,
.. this allows time for a process started im the child like printing to finish.
. option to wait until process has finished before returning to parent.
(proc.py)
"""
import _winapi as win
from os import waitpid
from sys import exc_info as ei
def startproc(exe,cmd, wait=False):
try:
ph, th, pid, tid = win.CreateProcess(exe,cmd,None,None,1,0,None,None,None)
win.CloseHandle(th)
except:
print(ei()[1])
ph = 0
return (ph,'error')
if ph > 0:
if not wait:
return (ph,'process still going')
else:
pid, exitstatus = waitpid(ph,0)
return (0,'process done')
#exe = "C:\\Program Files\\Adobe\\Acrobat DC\\Acrobat\\Acrobat.exe"
#cmd = "open <pdf filepath>"
exe = "C:\Windows\System32\\notepad.exe"
cmd = None
print(__doc__)
proc,msg = startproc(exe,cmd)
print(msg)
if 'done' not in msg: # manual pause for printing
input('\n-- carry on --\n') # could be automatic timer
if msg != 'error' and proc != 0:
if win.GetExitCodeProcess(proc) == win.STILL_ACTIVE:
win.TerminateProcess(proc,0)
if 'done' not in msg: print('process closed')
#can delete pdf here
input('\n--- finish ---\n')
I am working with some proprietary business software written in DB/C, a short-lived variant of COBOL over 20 years ago with no access to source code or useful documentation of any kind. I've been trying to automate some of our processes with python, and have written a handler object that can pass commands and send various key codes using pyautogui but I have reached a barrier where sometimes I need to rely on the output of the previous routine to decide when/whether to trigger the next. The software GUI runs in a console window, similar to ncurses.
How can I copy the entire contents of my console window to my clipboard? If there is a way to copy it directly to a local python variable that would be ideal; Once I have the screen text I can just match the part I need with a regex.
Here is my handler so far just to give you an idea:
import pyautogui
import time
import subprocess
class ECISession:
def __init__(self, username, password):
self.username = username
self.password = password
self.openECI()
self.login(username, password)
def openECI(self):
# Open ECI as a subprocess
subprocess.Popen("F:\\ECI\\hp4\\test\\s.bat")
def login(self, username, password):
# Login to ECI Session with username and password
time.sleep(15)
pyautogui.write(username, interval=0.1)
pyautogui.press('enter')
pyautogui.write(password, interval=0.1)
pyautogui.press('enter')
pyautogui.write('Y') # Make sure it ignores duplicate login
#staticmethod
def send(text='', supressEnter=False):
# Send passed text to console and press enter unless specified to supress
if text != '':
pyautogui.write(text, interval=0.1)
if supressEnter:
pass
else:
pyautogui.press('enter')
time.sleep(1)
#staticmethod
def sendF3(n=1):
# Send F3 save key 1 time by default, n times if needed
while n != 0:
pyautogui.press('F3')
n -= 1
#staticmethod
def sendF4(n=1):
# Send F4 search key 1 time by default, n times if needed
while n != 0:
pyautogui.press('F4')
n -= 1
#staticmethod
def readscreen():
# return entire console window text
pass
The readscreen() method is what I need to figure out. I just want to return the console content. I would just use the mouse to highlight the entire thing and press enter, but I have no way to make sure the window starts in the same place every time; It can't be maximized or it will crash since it expects an 80 column display with a specific font size.
FYI, the software in question is HealthPac by Eldorado Computing Incorporated. If any of you have used it you know the struggle. I would post some screenshots but it may be a HIPPA violation or something.
I am working on program automation (program named SEO indexer). I wrote the automation using python's library name pywinauto.
Everything works just great when I am running the automation over RDP connection to the server. But when I am trying to leave the program and disconnect from RDP the "Save AS" windows window is not starting and the program crashes ...
someone knows how can I fix it?
the code that is responsible to save the file is -
def run(self, process_id, link):
controls = self._app[u'TForm1']
text_box = controls.Edit
text_box.set_text(link)
button = controls.ToolBar1
windows = Desktop(backend="uia")
button.click()
self.wait_for_finish(process_id)
result_box = controls.TVirtualStringTree
result_box.RightClick()
sleep(1)
windows_list = windows.windows()
context_menu = windows.window(best_match = "Context")
save_all_button = context_menu.children()[2]
save_all_button.select()
save_as = windows.window(best_match = "save_as")
properties_section = save_as.children()[0]
file_name = "C:\\Windows\\Temp\\indexer_" + str(randint(0, 10000))
file_name_label = properties_section.children()[4].children()[0]
file_name_label.set_text(file_name)
save_button = save_as.children()[2]
save_button.click()
sleep(2)
yes_no_dialog = windows.window(best_match = "GSA SEO Indexer v2.34")
yes_no_dialog.children()[0].click()
return file_name
it crashed on -
save_as = windows.window(best_match = "save_as")
there is a way to force it opening the save as dialog even if there are no screen ?
UPDATE:
I Just notices that the problem is not that the Save as panel is not created, the problem is that when I am without screen and trying to select from the context menu (which is created) - just the text is selected, without clicking on it
The Remote Execution Guide is what you need. This is common problem for any GUI automation tool. So the question is not exactly pywinauto related, but I wrote this guide a year ago to address it for many users.
A brief description of my problem:
1.
My Jenkins job is required to establish an RDP connection to another machine to perform some activities.
2.
Until recently, the default password was maintained between sessions. But now some settings have changed, and the password needs to be reentered by hand each time I creating a new RDP session.
I prepared a short python script interacting with the Windows gui via the win32gui package.
I built a stand alone executable file from this script using the pyinstaller.
And finally I added a call to this executable file directly to the job.
Somethig like that:
while attempts:
security_window_title = "Windows Security"
try:
hwnd_credentials = win32gui.FindWindow(0, security_window_title)
window_controls = []
win32gui.EnumChildWindows(hwnd_credentials, collect_window_control, None)
focus_on_window(hwnd_credentials)
sleep(0.5)
prev_user_login = window_controls[2]["hwnd"]
x = int(window_controls[1]["x"] + 80)
y = int(window_controls[1]["y"] + 20)
click(x, y)
type_message(password)
ok_button = window_controls[6]["hwnd"]
push_button(ok_button)
except win32gui.error:
sleep(1)
attempts -= 1
if not attempts:
raise RuntimeError("Can't interact with window: {}.".format(security_window_title))
else:
break
while attempts:
sleep(timeout)
attempts -= 1
if check_connection_started():
break
if check_certificate_errors():
for control in window_controls[::-1]:
if control["text"] == "&Yes":
push_button(control["hwnd"])
if not attempts:
raise RuntimeError("Connection not established.")
3.
This would not be a problem when script running from the job working with the fully functional Windows ui. I can find a window in which my script is supposed to specify a password using the win32gui python package. I can generate all the appropriate keyboard events to enter a password.
Using RDP via console provides me a very strange set of windows-like objects which I can not interact with using the win32gui python package the same way as with ordinary windows. For example, I do locate a window with non zero hwnd and with text property equal to "Remote Desktop Connection". But I can't focus on such a window using the basic method win32gui.SetForegroundWindow(hwnd). This leads to an unnamed win32gui exception.
Is there any possibility to transfer the password to the desired control of the desired window-like structure, so that the job does not interrupt its execution?
Thank you so much for any help.
I can focus on both "Remote Desktop Connection" and "Windows Security" with win32gui.SetForegroundWindow(hwnd).
Sample code:
import win32api
import win32gui
import win32con
import time
from pynput.keyboard import Key, Controller
def main():
Remote = "Remote Desktop Connection"
Security = "Windows Security"
try:
hwnd_Remote = win32gui.FindWindow(0, Remote)
print(hwnd_Remote)
win32gui.ShowWindow(hwnd_Remote,win32con.SW_SHOWNORMAL)
win32gui.SetForegroundWindow(hwnd_Remote)
keyboard = Controller()
keyboard.type('ipaddress')
keyboard.press(Key.enter)
keyboard.release(Key.enter)
time.sleep(3)
hwnd_Security = win32gui.FindWindow(0, Security)
print(hwnd_Security)
win32gui.ShowWindow(hwnd_Security,win32con.SW_SHOWNORMAL)
win32gui.SetForegroundWindow(hwnd_Security)
keyboard.type('password')
keyboard.press(Key.enter)
keyboard.release(Key.enter)
except win32gui.error:
raise RuntimeError("Can't interact with window: {}.".format(Remote))
if __name__ == "__main__":
main()
Make sure that the foreground process did not disable calls to the SetForegroundWindow function. Add the LockSetForegroundWindow(LSFW_UNLOCK) or AllowSetForegroundWindow(ASFW_ANY) to enable the call of SetForegroundWindow.
I am writing a script intended to be used by members of a project team. As part of the script, I am launching a 3rd party proprietary application run through Citrix. I am going to use the script mostly to send keys to this application, but the first step once it launches is for the user to log in.
Because I would like the user to log in while the script is running, rather than asking for user/pass from some kind of GUI input earlier, and because the time it takes Citrix to launch varies, I would like to include some kind of logic that detects when the user has logged in and then resume the script from there, rather than including an obnoxiously long implicit wait or risking the script timing out.
Is there a way to detect user keystrokes using win32com.client (or to detect a change in state of the application itself)? See below for the relevant code to launch the app:
import win32com.client
shell = win32com.client.Dispatch("WScript.Shell")
shell.Run('C:\Citrix\[rest of path])
EDIT:
Per Vasily's suggestion in the comments below, I attempted to adapt the "hook and listen" code to my scenario, but was unsuccessful. When I launch my file, I don't even get an exception message in my terminal, I get a Windows pop-up that says Python encountered a problem and needs to quit.
This is how I adapted it:
#[omitting import lines for brevity]
def on_timer():
"""Callback by timer out"""
win32api.PostThreadMessage(main_thread_id, win32con.WM_QUIT, 0, 0);
def on_event(args):
"""Callback for keyboard and mouse events"""
if isinstance(args, KeyboardEvent):
for i in range(1,100):
time.sleep(1)
if args.pressed_key == 'Lcontrol':
break
def init():
hk = Hook()
hk.handler = on_event
main_thread_id = win32api.GetCurrentThreadId()
t = Timer(55.0, on_timer) # Quit after 55 seconds
t.start()
hk.hook(keyboard=True, mouse=True)
At the point when the 3rd party Citrix app begins to launch in my main script, I call hookandlisten.init().
As a reminder, my goal is to wait until the user sends a certain keystroke (here I chose Control) before proceeding with the rest of the main script.
Solved this by eliminating the timer and unhooking the keyboard upon the correct keystroke:
import win32api
import win32con
from pywinauto.win32_hooks import Hook
from pywinauto.win32_hooks import KeyboardEvent
from pywinauto.win32_hooks import MouseEvent
def on_event(args):
"""Callback for keyboard and mouse events"""
if isinstance(args, KeyboardEvent):
if args.current_key == 'Lcontrol' and args.event_type == 'key down':
print("Success")
hk.unhook_keyboard()
return
def init():
hk.handler = on_event
hk.hook(keyboard=True, mouse=False)
hk = Hook()