Focus child window in Python win32gui - python

I'm using Python to send keyboard commands to other programs. I have a working solution but I'm looking to improve it.
The other programs are Genesys CCPulse reports and I have four different instances running at the same time. Each instance has it's own main window and then a number of child windows inside (up to 30).
Thanks to this post Python Window Activation I have been able to switch between the main windows and get the one I need in the foreground. I'm currently using keyboard commands to send menu shortcuts to change focus to the child windows and save them.
I'd like to avoid the menu navigation and just activate each child window, then send the commands to save them. Another post EnumChildWindows not working in pywin32 has got me the list of handles for the child windows.
Ideally I would like to continue using win32gui if possible as the rest of the code is working.
Current code is
import win32gui
import re
class WindowMgr:
#set the wildcard string you will search for
def find_window_wildcard(self, wildcard):
self._handle = None
win32gui.EnumWindows(self.window_enum_callback, wildcard)
#enumurate through all the windows until you find the one you need
def window_enum_callback(self, hwnd, wildcard):
if re.match(wildcard, str(win32gui.GetWindowText(hwnd))) != None:
self._handle = hwnd ##pass back the id of the window
#as a separate function, set the window to the foreground
def set_foreground(self):
win32gui.SetForegroundWindow(self._handle)
#extra function to get all child window handles
def get_child_handles(self):
win32gui.EnumChildWindows(self._handle, add_to_list, None)
#final function to send the commands in
def flash_window(self):
for c_hwnd in child_list:
print((self._handle),(c_hwnd),(win32gui.GetWindowText(c_hwnd))) #prove correct child window found
#send command1#
#send command2#
#send command3#
#seprate fundction to collect the list of child handles
def add_to_list(hwnd, param):
if win32gui.GetWindowText(hwnd)[:3] == "IWD":
child_list.append(hwnd)
child_list=[]
w = WindowMgr()
w.find_window_wildcard(".*Sales*")
w.set_foreground()
w.get_child_handles()
w.flash_window()

Just found the answer to this
def flash_window(self):
for c_hwnd in child_list:
win32gui.BringWindowToTop(c_hwnd)
It's the BringWindowToTop command that will activate each child window in turn. I can then go on to issue whatever commands I want to each window.

Related

Run .py file in system tray rather than in taskbar window

I have a .py file that runs via Task Scheduler when I log into my computer, and it's constantly listening for specific keyboard events to happen. Rather than having it run in the Python Console window on the Taskbar, I'd like to get it to run in the system tray with a Python icon to make it more clean.
I have discovered the pysystray library, and can successfully make a Python icon in the system tray with a right-click menu which will allow me to quit the icon.
from os import path as path
import PIL.Image
import pystray
def on_clicked(icon, item):
if str(item) == "Quit":
icon.stop()
dir_path = path.dirname(path.realpath(__file__))
icon_image = "\\Icon\\Python.ico"
full_path = dir_path + icon_image
image = PIL.Image.open(full_path)
icon = pystray.Icon(
"Testing",
image,
menu=pystray.Menu(pystray.MenuItem("Quit", on_clicked)),
)
icon.run()
However, I can't figure out how to get my existing Python file to run under the icon. It seems like there are two separate events happening; one for launching the icon, and another for running the rest of the code. I would want to make sure that if the "Quit" option is chosen, then the .py file would also stop.
Also, it would be really helpful to have a separate right-click menu item which will display either Python cmd line text since it's constantly being overwritten in my main .py file with a print statement.
Any help/insight would be really appreciated!
The pystray.run function accepts an optional callback function as a parameter. The callback function is run in a separate thread and should be closed normally by ln 9: icon.stop().

tkinter: event binding remains after application terminates?

I'm running tkinter in Python 3.4. A button event seems to remain bound to a command even after the application terminates. Code snippet:
# application class
class DataSel:
def __init__(self,parent):
self.parent = parent
<...>
self.button_sel = tk.Button(self.parent,text='Select')
self.button_sel.grid(row=1,sticky='nesw')
self.button_sel.bind('<Button-1>',self.sel_click)
self.button_quit = tk.Button(self.parent,text='Quit')
self.button_quit.grid(row=2,sticky='nesw')
self.button_quit.bind('<Button-1>',self.quit_click)
def sel_click(self,event):
self.filename = askopenfilename(parent=self.parent)
<...>
def quit_click(self,event):
self.parent.destroy()
# main part of application
root = tk.Tk()
root.lift()
sel = DataSel(root)
root.lift()
root.mainloop()
When I restart the interpreter from scratch and run this application, there is no error message. However, the button_sel button remains pressed (in low relief) after the sel_click method is finished. Then, if I quit the application and rerun it, I get the following message in the shell:
invalid command name ".94227256"
while executing
"$w cget -state"
(procedure "tk::ButtonDown" line 12)
invoked from within
"tk::ButtonDown .94227256"
(command bound to event)
where the number .94227256 changes each time I rerun.
Apart from this message, and the fact that the button remains in low relief, all other functionality is OK. But it seems like the button event somehow stays bound to a stale command!
What is happening is that your binding happens before the button widget is able to process the same event. You are doing this during the processing of the events and you aren't telling Tkinter to stop processing the events further. Therefore, when Tkinter gets around to having the widget process the click event, the window no longer exists and tkinter throws an error.
The root of the problem is that you are putting bindings on a button. You shouldn't do that. If you want to call a function from a button you need to use the command attribute of the button.
If you really think you need to do this via a binding (rather than via the command attribute), you need your function to return '"break"` to tell tkinter to stop any further processing of the event.

Which is the best way to interact with already open native OS dialog boxes like (Save AS) using Python?

Is there any efficient way using any Python module like PyWind32 to interact with already existing Native OS dialog boxes like 'Save As' boxes?
I tried searching on Google but no help.
EDIT:
1: The Save As dialog box is triggered when user clicks on a Save As dialog box on a web application.
2: Any suggestion are welcome to handle any native OS dialog boxes which are already triggered using Python.(Need not be specific to Selenium webdriver, I am looking for a generic suggestion.)
(When I was posting the question I thought that by 'interacting with a dialog box' will implicitly mean that it is an existing one as if I am able to create one then surely I can interact with it as it is under my programs control. After reading the first 2 answers I realized I was not explicitly clear. Thats why the EDIT )
Thanks
While looking for a possible solution for this I came across several solutions on SO and otherwise.
Some of them were using AutoIT, or editing browsers profile to make it store the file directly without a prompt.
I found all this solution too specific like you can overcome the issue for Save As dialog by editing the browser profile but if later you need to handle some other window then you are stuck.
For using AutoIT is overkill, this directly collides for the fact I choose Python to do this task. (I mean Python is itself so powerful, depending on some other tool is strict NO NO for any Pythonist)
So after a long search for a possible generic solution to this problem which not only serves any one who is looking to handle any Native OS dialog boxes like 'Save As', 'File Upload' etc in the process of automating a web application using selenium web driver but also to any one who wants to interact with a specific window using only Python APIs.
This solution makes use of Win32gui, SendKeys modules of Python.
I will explain first a generic method to get hold of any window desired then a small code addition which will also make this usable while automating a web application using Selenium Webdriver.
Generic Solution::
import win32gui
import re
import SendKeys
class WindowFinder:
"""Class to find and make focus on a particular Native OS dialog/Window """
def __init__ (self):
self._handle = None
def find_window(self, class_name, window_name = None):
"""Pass a window class name & window name directly if known to get the window """
self._handle = win32gui.FindWindow(class_name, window_name)
def _window_enum_callback(self, hwnd, wildcard):
'''Call back func which checks each open window and matches the name of window using reg ex'''
if re.match(wildcard, str(win32gui.GetWindowText(hwnd))) != None:
self._handle = hwnd
def find_window_wildcard(self, wildcard):
""" This function takes a string as input and calls EnumWindows to enumerate through all open windows """
self._handle = None
win32gui.EnumWindows(self._window_enum_callback, wildcard)
def set_foreground(self):
"""Get the focus on the desired open window"""
win32gui.SetForegroundWindow(self._handle)
win = WindowFinder()
win.find_window_wildcard(".*Save As.*")
win.set_foreground()
path = "D:\\File.txt" #Path of the file you want to Save
ent = "{ENTER}" #Enter key stroke.
SendKeys.SendKeys(path) #Use SendKeys to send path string to Save As dialog
SendKeys.SendKeys(ent) #Use SendKeys to send ENTER key stroke to Save As dialog
To use this code you need to provide a string which is the name of the window you want to get which in this case is 'Save As'. So similarly you can provide any name and get that window focused.
Once you have the focus of the desired window then you can use SendKeys module to send key strokes to the window which in this case includes sending file path where you want to save the file and ENTER.
Specific to Selenium Webdriver::
The above specified code segment can be used to handle native OS dialog boxes which are triggered through a web application during the automation using Selenium Webdriver with the addition of little bit of code.
The issue you will face which I faced while using this code is that once your automation code clicks on any Web Element which triggers a native OS dialog window, the control will stuck at that point waiting for any action on the native OS dialog window. So basically you are stuck at this point.
The work around is to generate a new thread using Python threading module and use it to click on the Web Element to trigger the native OS dialog box and your parent thread will be moving on normally to find the window using the code I showed above.
#Assume that at this point you are on the page where you need to click on a Web Element to trigger native OS window/dialog box
def _action_on_trigger_element(_element):
_element.click()
trigger_element = driver.find_element_by_id('ID of the Web Element which triggers the window')
th = threading.Thread(target = _action_on_trigger_element, args = [trigger_element]) #Thread is created here to call private func to click on Save button
th.start() #Thread starts execution here
time.sleep(1) #Simple Thread Synchronization handle this case.
#Call WindowFinder Class
win = WindowFinder()
win.find_window_wildcard(".*Save As.*")
win.set_foreground()
path = "D:\\File.txt" #Path of the file you want to Save
ent = "{ENTER}" #Enter key stroke.
SendKeys.SendKeys(path) #Use SendKeys to send path string to Save As dialog
SendKeys.SendKeys(ent) #Use SendKeys to send ENTER key stroke to Save As dialog
#At this point the native OS window is interacted with and closed after passing a key stroke ENTER.
# Go forward with what ever your Automation code is doing after this point
NOTE::
When using the above code in automating a web application, check the name of the window you want to find and pass that to find_window_wildcard(). The name of windows are browser dependent. E.g. A window which is triggered when you click on an Element to Upload a file is called 'File Upload' in Firefox and Open in Chrome.
Uses Python2.7
I hope this will help any one who is looking for a similar solution whether to use it in any generic form or in automating a web application.
EDIT:
If you are trying to run your code through command line arguments then try to use the thread to find the window using Win32gui and use the original program thread to click on the element (which is clicked here using the thread). The reason being the urllib library will throw an error while creating a new connection using the thread.)
References::
SO Question
SenKeys
Win32gui
There is a Python module called win32ui. Its found in the Python for Windows extensions package. You want the CreateFileDialog function.
Documentation
Edit:
This is a save dialog example. Check the documentation for the other settings.
import win32ui
if __name__ == "__main__":
select_dlg = win32ui.CreateFileDialog(0, ".txt", "default_name", 0, "TXT Files (*.txt)|*.txt|All Files (*.*)|*.*|")
select_dlg.DoModal()
selected_file = select_dlg.GetPathName()
print selected_file
from time import sleep
from pywinauto import Application, keyboard
app = Application(backend='uia').start('notepad.exe')
app.top_window().set_focus()
main = app.window(title='Untitled - Notepad', control_type='Window')
main.draw_outline(colour='red', thickness=2)
fileMenu = main.child_window(title='File')
fileMenu.draw_outline(colour='red', thickness=2)
fileMenu.click_input()
keyboard.send_keys("{VK_CONTROL}{VK_SHIFT}{S}")
sleep(2)
dlg = main.window(title='Save As')
dlg.draw_outline(colour='red', thickness=2)
savebtn = dlg.child_window(title='Save')
filePath = dlg.child_window(title='File name:', control_type='Edit')
savebtn.draw_outline(colour='red', thickness=2)
filePath.set_text('D:\\aa.txt')
filePath.draw_outline(colour='red', thickness=2)
savebtn.click_input()
I recommend using pywinauto lib.
If you have opened notepad and "Save as" dialog window:
from pywinauto import application
app = application.Application()
app.connect(title='test1.txt - Notepad')
app.SaveAs.Edit1.type_keys("test2.txt")
app.SaveAs.Save.click()

Create a python tkinter window with no X (close) button

I'm writing a 'wizard' type Python Tkinter GUI that collects information from the user and then performs several actions based on the user's entries: file copying, DB updates, etc. The processing normally takes 30-60 seconds and during that time, I want to:
Provide the user with text updates on the activity and progress
Prevent the user from closing the app until it's finished what it's doing
I started on the route of having the text updates appear in a child window that's configured to be trainsient and using wait_window to pause the main loop until the activities are done. This worked fine for other custom dialog boxes I created which have OK/cancel buttons that call the window's destroy method. The basic approach is:
def myCustomDialog(parent,*args):
winCDLG = _cdlgWin(parent,*args)
winCDLG.showWin()
winCDLG.dlgWin.focus_set()
winCDLG.dlgWin.grab_set()
winCDLG.dlgWin.transient(parent)
winCDLG.dlgWin.wait_window(winCDLG.dlgWin)
return winCDLG.userResponse
class _cdlgWin():
def __init__(self,parent,*args):
self.parent = parent
self.dlgWin = tk.Toplevel()
self.userResponse = ''
def showWin(self):
#Tkinter widgets and geometry defined here
def _btnOKClick(self):
#self.userResponse assigned from user entry/entries on dialog
self.dlgWin.destroy()
def _btnCancelClick(self):
self.dlgWin.destroy()
However this approach isn't working for the new monitor-and-update dialog I want to create.
First, because there's no user-initiated action to trigger the copy/update activities and then the destroy, I have to put them either in showWin, or in another method. I've tried both ways but I'm stuck between a race condition (the code completes the copy/update stuff but then tries to destroy the window before it's there), and never executing the copy/update stuff in the first place because it hits the wait_window before I can activate the other method.
If I could figure out a way past that, then the secondary problem (preventing the user from closing the child window before the work's done) is covered by the answers below.
So... is there any kind of bandaid I could apply to make this approach work the way I want? Or do I need to just scrap this because it can't work? (And if it's the latter, is there any way I can accomplish the original goal?)
self.dlgWin.overrideredirect(1) will remove all of the buttons (make a borderless window). Is that what you're looking for?
As far as I know, window control buttons are implemented by the window manager, so I think it is not possible to just remove one of them with Tkinter (I am not 100% sure though). The common solution for this problem is to set a callback to the protocol WM_DELETE_WINDOW and use it to control the behaviour of the window:
class _cdlgWin():
def __init__(self,parent,*args):
self.parent = parent
self.dlgWin = tk.Toplevel()
self.dlgWin.protocol('WM_DELETE_WINDOW', self.close)
self.userResponse = ''
def close(self):
tkMessageBox.showwarning('Warning!',
'The pending action has not finished yet')
# ...

Python: win32gui.SetForegroundWindow

I have just written simple script to launch an applciation and I am trying to use "SendKeys" module to send keystrokes to this application. There is one "Snapshot" button, but I cant get Python to click "Snapshot" button, as the new window is not in focus. So I am planning to use Win32gui module's win32gui.FindWindow and win32gui.SetForegroundWindow functionality. But it gives me error- invalid handle. My app name is "DMCap"
Here is code snippet in Python:
handle = win32gui.FindWindow(0, "DMCap") //paassing 0 as I dont know classname
win32gui.SetForegroundWindow(handle) //put the window in foreground
Can anyone help me? Is this Python code correct? Can I send handle directly like this?
Your code should run just fine as-is, IF there is truly a window titled "DMCap." To get a list of handles and titles, run the code below:
import win32gui
def window_enum_handler(hwnd, resultList):
if win32gui.IsWindowVisible(hwnd) and win32gui.GetWindowText(hwnd) != '':
resultList.append((hwnd, win32gui.GetWindowText(hwnd)))
def get_app_list(handles=[]):
mlst=[]
win32gui.EnumWindows(window_enum_handler, handles)
for handle in handles:
mlst.append(handle)
return mlst
appwindows = get_app_list()
for i in appwindows:
print i
This will produce a list of tuples containing handle, title pairs.

Categories