Cancel Excel's close event using python and win32com - python

Currently I try to cancel Excel's close event using Python and win32com. I have already managed to handle this issue with IronPython some month ago. But for further purposes of my companies department this should also be able with Python. Followed you will see two snippets. The first will contain the working IronPython Code
import clr
clr.AddReference("Microsoft.Office.Interop.Excel")
clr.AddReference("System.Windows.Forms")
from Microsoft.Office.Interop import Excel
from System.Windows.Forms import Form, Application, MessageBox, MessageBoxButtons, MessageBoxIcon, DialogResult
class CloseEventTry(Form):
def __init__(self):
excel = Excel.ApplicationClass()
excel.Visible = True
excel.DisplayAlerts = False
self.workbooks = excel.Workbooks.Add()
self.Text = "Dummy GUI Window"
#link "BeforeCloseEvent" to the "beforeClose" method
self.workbooks.BeforeClose +=Excel.WorkbookEvents_BeforeCloseEventHandler(self.beforeClose)
def beforeClose(self, cancel):
print type(cancel) #Type: 'StrongBox[bool]
choice = MessageBox.Show("Close Excel", "Close", MessageBoxButtons.YesNo, MessageBoxIcon.Information)
if choice == DialogResult.Yes:
cancel.Value = False #do't cancel the close action
self.Close()
elif choice == DialogResult.No:
cancel.Value = True #prevent excel from closing
Application.Run(CloseEventTry())
The second one will contain the version with Python and win32com. This one is based on my IronPython snippet and the sample of that link
https://win32com.goermezer.de/microsoft/office/events-in-microsoft-word-and-excel.html
import clr
clr.AddReference("System.Windows.Forms")
from System.Windows.Forms import Form, Application, MessageBox, MessageBoxButtons, MessageBoxIcon, DialogResult
import win32com.client as win32
#workbook event handler class. Needed according to this example https://win32com.goermezer.de/microsoft/office/events-in-microsoft-word-and-excel.html
class WorkBookEvents(object):
def OnBeforeClose(self, cancel):
print(type(cancel)) #Type: class 'bool'
choice = MessageBox.Show("Close Excel", "Close", MessageBoxButtons.YesNo, MessageBoxIcon.Information)
if choice == DialogResult.Yes:
#do't cancel the close action => raises AttributeError: 'bool' object has no attribute 'Value' Exception
cancel.Value = False
self.Close()
elif choice == DialogResult.No:
#prevent excel from closing => raises AttributeError: 'bool' object has no attribute 'Value' Exception
cancel.Value = True
class CloseEventTry(Form):
def __init__(self):
excel = win32.DispatchEx('Excel.Application')
excel.Visible = True # makes the Excel application visible to the user
excel.DisplayAlerts = False
self.Text = "Dummy GUI Window"
self.workbooks = excel.Workbooks.Add()
#define event handler according to this example https://win32com.goermezer.de/microsoft/office/events-in-microsoft-word-and-excel.html
self.workbooks = win32.DispatchWithEvents(self.workbooks, WorkBookEvents)
Application.Run(CloseEventTry())
As you will see I could connect to the "OnBeforeClose" event, but cannot cancel the close event as I've done it with the IronPython version. As mentioned in the last code snippet's comment, the Python version raises an AttributeError exception. Further you can also see, that the types of the needed "cancel" variable of the event handlers have two different types. In the IronPython version its a "StrongBox[bool]". On the other hand the Python version's type is a common "class 'bool'" type (which explains the exception). Thats way I tried to just type
cancel = True #prevent excel from closing
But using this way, excel closes anyway.
I also did some research but was not able to find a solution for this issue. My Assumption is that there is some kind of wrapper needed?

you can achieve the same behaviour as the IronPython version by returning the desired cancel value, e.g.
return True
in case you want to abort the Close event.
Regards

Related

Pywinauto unable to find/close pop-up window

Source code
def is_admin():
try:
return ctypes.windll.shell32.IsUserAnAdmin()
except:
return False
if is_admin():
app = Application(backend='uia').start("C:\\Program Files (x86)\\Advantech\\AdamApax.NET Utility\\Program\\AdamNET.exe")
win = app['Advantech Adam/Apax .NET Utility (Win32) Version 2.05.11 (B19)']
win.wait('ready')
win.menu_select("Setup->Refresh Serial and Ethernet")
win.top_window().print_control_identifiers(filename="file.txt")
# win.top_window().OKButton.click_input() ---------This is what I hope to do
else
ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, __file__, None, 1)
Problem Statement
I had to run this application with elevation rights. The above is my code. The problem is I can't identify the window (view in output image) that pops up after selection from menu. I need to close the window. Please excuse the line
win.top_window().print_control_identifiers(filename="file.txt")
It was meant write the identifiers into a text file because the structure of this code does not display the outputs for me to view. However, since nothing is appended, I guess pywinauto couldn't identify the dialog.
For a clearer understanding, please view the image (input) of when it selects the menu.
Input
Now, it pops up with this dialog (output)
Output
I've also used spy to identify the caption and it gives:
(Handle: 004E07D4,
Caption: Information,
Class: #32770(Dialog),
Style: 94C801C5)
Other things I've tried:
Besides using win.topwindow() to identify the dialog, I've used
win[Information].OKButton.click_input()
win[Information].OK.click_input()
win[Information].OK.close()
win[Information].OK.kill(soft=false)
win.Information.OKButton.click_input()
win.Information.OK.click_input()
win.Information.OK.close()
win.Information.OK.kill(soft=false)
app[Information] ...... curious if I could discover the new window from original application
I've also send keys like enter, space, esc & alt-f4 to close the dialog with libraries like keyboard, pynput & ctypes. It still doesn't work.
Link to download the same application: http://downloadt.advantech.com/download/downloadsr.aspx?File_Id=1-1NHAMZX
Any help would be greatly appreciated !
I finally found a thread that demonstrated the way multi thread works to solve this issue. I tried it myself and it works. It's a little different as a few parts of the code have depreciated. Here is the link to the solution:
How to stop a warning dialog from halting execution of a Python program that's controlling it?
Here are the edits I made to solve the problem:
def is_admin():
try:
return ctypes.windll.shell32.IsUserAnAdmin()
except:
return False
if is_admin():
def __init__(self, window_name, quit_event):
threading.Thread.__init__(self)
self.quit_event = quit_event
self.window_name = window_name
def run(self):
while True:
try:
handles = windows.find_windows(title=self.window_name)
except windows.WindowNotFoundError:
pass
else:
for hwnd in handles:
app = Application()
app.connect(handle=hwnd)
popup = app[self.window_name]
popup.close()
if self.quit_event.is_set():
break
time.sleep(1)
quit_event = threading.Event()
mythread = ClearPopupThread('Information', quit_event)
mythread.start()
application = Application(backend="uia").start("C:\\Program Files (x86)\\Advantech\\AdamApax.NET Utility\\Program\\AdamNET.exe")
time.sleep(2)
win = application['Advantech Adam/Apax .NET Utility (Win32) Version 2.05.11 (B19)']
win.menu_select("Setup->Refresh Serial and Ethernet")
quit_event.set()
else:
ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, __file__, None, 1)
The best thing is this solution works for every other dialog that halts the main script from working & I could use them to do different actions like clicking buttons, inserting values, by adding more multi threads.

PyQt: mainWindow closed, but process still running

I'm writing a Python application with PyQt 5.10.It seems I have some sort of bug/memory leak, since when I call close() on my MainWindow the process keeps running. After a bit of research and debugging I was able to circumscribe the supposedly faulty code.This is my main:
if __name__ == '__main__':
app = QApplication(sys.argv)
matteo = God()
matteo.runApp()
sys.exit(app.exec())
Here you will find the runApp function from God class:
def runApp(self):
self.painter = Painter()
self.dbManager = DBManager()
self.userInput = UserInput()
self.excelFile = ExcelFile()
self.painter.connectToClasses(self, ["god","db","ui"])
self.excelFile.connectToClasses(self, ["god",])
self.painter.drawMainWindow()
self.loadConf()
self.openDB(True)
if self.dbManager.error==None:
self.painter.drawSearchWidget()
else:
print("Closed.")
The process keeps running when the app is not able to find the configuration file, and so it creates a new one from scratch and it asks the user to select the database which he wants to connect to. This prompts an error message - if the selected file is corrupted or it's not the right format - and I think there might lie my problem.That's the code:
def checkError(self, classType):
if classType=="db":
error = self.dbManager.error
elif classType=="excel":
error = self.excelFile.error
if error!=None:
self.painter.drawError(classType)
self.userInput.error = self.painter.error.clickedButton()
self.userInput.error = self.painter.error.buttonRole(self.userInput.error)
if (self.userInput.error==1):
self.painter.mainWindow.close()
return 0
return 1
def drawError(self, classType):
if (classType=="db"):
title = "Database"
error = self.dbManager.error
otherButton = "Browse"
elif (classType=="excel"):
title = "Excel file"
error = self.excelFile.error
try:
self.setErrorText(False, error)
if error[0]:
if self.error.icon()!=3:
self.error.setIcon(3)
buttons = self.error.buttons()
for button in buttons:
if button.text()!="Quit":
button.hide()
button.deleteLater()
except AttributeError:
self.error = QMessageBox()
self.setErrorText(True, error)
if (error[0]):
self.error.setIcon(3)
else:
self.error.setIcon(2)
self.error.addButton(otherButton, self.error.AcceptRole)
self.error.addButton("Quit", self.error.RejectRole)
self.error.setWindowTitle(title)
self.error.exec()
If the user clicks the quit button on the error window, the function closes main window and return 0. The other functions return and the application prints closed on the console. But the process keeps going.
I kind of resolved this issue calling sys.exit() instead of printing closed.
It's a brute force solution to say the least, but I needed to deliver the application fast and I had no time to debug it.I guess this sad story will end here.

Python GUI freezes or closes while trying to update the button label text

I am trying to read a string from the ubuntu terminal and set that string as a label of a button. It works perfectly for some iteration and then freezes or closes with error. I couldn't find any pattern about when it freezes or closes. I am using gtk libraries and python 2.7.
A screenshot of the UI after it has frozen can be seen below.
As seen in the above screenshot, it has successfully updated the value 234, 56 and then exited with error after receiving 213 string. You can also observe that the button in the UI also has 213 value.
Sometimes the UI just freezes without displaying any errors or exiting.
I have used the below codes
1. thread.py ( main program called from terminal )
import thread
import time
import gui2
import vkeys1
import os
try:
thread.start_new_thread( vkeys1.main, ( ) )
thread.start_new_thread( gui2.main, ( ) )
except:
print "Error: unable to start thread"
# To stop this script from closing
os.system("mkfifo d1 2> error.log")
fd = os.open('d1', os.O_RDONLY)
ch = os.read(fd,1) # No writer
2. vkeys1.py ( It reads the input from terminal and calls textinit() )
import gui2
def main() :
while True:
try :
gui2.ch = str(input('\nInput a string : '))
gui2.textinit()
except :
print(" \n\n Exception!! \n\n")
3. gui2.py ( Updates the button label )
from gi.repository import Gtk, GdkPixbuf, Gdk, GLib
import Image
import os, sys
import time
import vkeys1
import threading
global ch # ch is used at vkeys1.py to store the input
ch = 'dummy content'
button0 = Gtk.Button(label="Initially empty")
class TableWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="String retrieval widget")
self.set_size_request(500,200)
self.connect_after('destroy', self.destroy)
self.main_box=Gtk.VBox()
self.main_box.set_spacing(5)
self.label = Gtk.Label(" ")
table = Gtk.Table(7,4, True)
self.add(self.main_box)
self.main_box.pack_start(self.label, False, False, 0)
self.main_box.pack_start(table, False, False, 0)
table.attach(button0, 0, 4, 0, 1)
self.show_all()
def destroy(window, self):
Gtk.main_quit()
def textinit(): # called from vkeys1.py
class Thrd(threading.Thread) :
def __init__(self) :
threading.Thread.__init__(self)
print('\nReceived string')
print(str(ch))
print('\n')
button0.set_label(str(ch)) # Button label updated here
Thrd2 = Thrd()
Thrd2.start()
return
def main():
app=TableWindow()
app.set_keep_above(True)
app.set_gravity(Gdk.Gravity.SOUTH_WEST)
Gtk.main()
if __name__ == "__main__":# for any error exit
sys.exit(main())
The above codes can be run by typing python thread.py (after creating the above 3 files off-course). Please suggest any solution to overcome this freezing problem.
The most likely cause of the crash is that your code invokes GTK code from threads other than the thread that runs the main loop, which the documentation states is not allowed.
To resolve the issue, replace the call of gui2.textinit() with GLib.idle_add(gui2.textinit) (note the lack of parentheses after textinit).
Several remarks about the code:
The generic exception handler is masking exceptions that occur. Remove it, and you will see a useful traceback when something goes wrong.
If you are running under Python 2, you probably want to change input to raw_input, otherwise the code chokes on any input that is not a valid Python expression.
textinit creates a thread object that never runs an actual thread. When inheriting from threading.Thread, one must override the run function, which will be invoked in the new thread once start() is called. Doing the work in the constructor accomplishes nothing.
thread.start_new_thread is a low-level API that should not be used in normal circumstances and that is demoted to _thread in Python 3. Instead of thread.start_new_thread(fn, ()), use threading.Thread(target=fn), which has the same meaning, and also returns a Thread object.

In Python 3, how can I tell if Windows is locked?

How can I check whether a Windows OS workstation is locked? (e.g. Win+L or choosing the lock option after Ctrl+Alt+Del.)
I want something like ctypes.windll.user32.isWorkstationLocked().
This code worked today for me on four different Windows 7 and 10 machines, try something similar:
import ctypes
import time
user32 = ctypes.windll.User32
time.sleep(5)
#
#print(user32.GetForegroundWindow())
#
if (user32.GetForegroundWindow() % 10 == 0): print('Locked')
# 10553666 - return code for unlocked workstation1
# 0 - return code for locked workstation1
#
# 132782 - return code for unlocked workstation2
# 67370 - return code for locked workstation2
#
# 3216806 - return code for unlocked workstation3
# 1901390 - return code for locked workstation3
#
# 197944 - return code for unlocked workstation4
# 0 - return code for locked workstation4
#
else: print('Unlocked')
Edit: Also, this one works today:
import subprocess
import time
time.sleep(5)
process_name='LogonUI.exe'
callall='TASKLIST'
outputall=subprocess.check_output(callall)
outputstringall=str(outputall)
if process_name in outputstringall:
print("Locked.")
else:
print("Unlocked.")
A hack I discovered to get around to see if Windows 10 is locked is to look at the running processes using psutil. You then search to see whether or not LogonUI.exe is running. This process only runs when a user has a locked session.
Note: If you use "switch users" this process will be shown as running and this workaround will not work. Windows actually spawns multiple LogonUI.exe processes, one per logged on locked user. It is only useful when there is only one person logged on at a time.
import psutil
for proc in psutil.process_iter():
if(proc.name() == "LogonUI.exe"):
print ("Locked")
You can get the window on top, when the session is locked, the function return 0.
import ctypes
user32 = ctypes.windll.User32
def isLocked():
return user32.GetForegroundWindow() == 0
Something like this should do the trick:
import time
import ctypes
user32 = ctypes.windll.User32
OpenDesktop = user32.OpenDesktopA
SwitchDesktop = user32.SwitchDesktop
DESKTOP_SWITCHDESKTOP = 0x0100
while 1:
hDesktop = OpenDesktop ("default", 0, False, DESKTOP_SWITCHDESKTOP)
result = SwitchDesktop (hDesktop)
if result:
print "Unlocked"
time.sleep (1.0)
else:
print time.asctime (), "still locked"
time.sleep (2)
From the LockWorkStation() documentation:
There is no function you can call to determine whether the workstation is locked.
Not a Python limitation, but the system itself.
What works for me on Windows 10 Pro is getting the foreground window:
whnd = win32gui.GetForegroundWindow()
(_, pid) = win32process.GetWindowThreadProcessId(whnd)
handle = win32api.OpenProcess(win32con.PROCESS_ALL_ACCESS, False, pid)
filename = win32process.GetModuleFileNameEx(handle, 0)
window_text = win32gui.GetWindowText(whnd)
This returns Windows Default Lock Screen as window title and C:\Windows\SystemApp\Microsoft.LockApp_<randomcharacters>\LockApp.exe as filename when locked.
However, as James Koss mentioned, GetForeGroundWindow will return 0 if the user is typing their password. There are also other (non-locked) situations where the current ForegroundWindow is 0, so this cannot be relied upon.
Hi Check these 4 lines..
returns the application name which is on the screen.. if window is locked returns the string - Windows Default Lock Screen.
from win32gui import GetWindowText, GetForegroundWindow
import time
time.sleep(5)
# lock the system or open the application for a check
print(GetWindowText(GetForegroundWindow()))
Based on #Stardidi answer, this worked for me (Windows 10 Pro):
import time
import win32gui
import win32api
import win32con
import win32process
while True:
time.sleep(1)
_, pid = win32process.GetWindowThreadProcessId(win32gui.GetForegroundWindow())
try:
handle = win32api.OpenProcess(win32con.PROCESS_ALL_ACCESS, False, pid)
filename = win32process.GetModuleFileNameEx(handle, 0)
except Exception as _e:
filename = "LockApp.exe"
del _e
current_status = "locked" if "LockApp" in filename else "unlocked"
There is no easy answer here, but you can do this via Session Tracking.
From the LockWorkStation() documentation:
There is no function you can call to determine whether the workstation is locked. To receive notification when the user logs in, use the WTSRegisterSessionNotification function to receive WM_WTSSESSION_CHANGE messages. You can use session notifications to track the desktop state so you know whether it is possible to interact with the user.
Begin by registering session notifications to a window of your program.
def register(handle: HWND) -> bool:
"""
#param handle: handle for your message window.
When registered, Windows Messages related to session event changes will be
sent to the message window.
#returns: True is session tracking is successfully registered.
Blocks until Windows accepts session tracking registration.
Every call to this function must be paired with a call to unregister.
https://learn.microsoft.com/en-us/windows/win32/api/wtsapi32/nf-wtsapi32-wtsregistersessionnotification
"""
# OpenEvent handle must be closed with CloseHandle.
eventObjectHandle: HANDLE = ctypes.windll.kernel32.OpenEventW(
# Blocks until WTS session tracking can be registered.
# Windows needs time for the WTS session tracking service to initialize.
# must ensure that the WTS session tracking service is ready before trying to register
SYNCHRONIZE, # DWORD dwDesiredAccess
False, # BOOL bInheritHandle - sub-processes do not need to inherit this handle
# According to the docs, when the Global\TermSrvReadyEvent global event is set,
# all dependent services have started and WTSRegisterSessionNotification can be successfully called.
# https://learn.microsoft.com/en-us/windows/win32/api/wtsapi32/nf-wtsapi32-wtsregistersessionnotification#remarks
"Global\\TermSrvReadyEvent" # LPCWSTR lpName - The name of the event object.
)
if not eventObjectHandle:
error = ctypes.WinError()
log.error("Unexpected error waiting to register session tracking.")
return False
registrationSuccess = ctypes.windll.wtsapi32.WTSRegisterSessionNotification(handle, NOTIFY_FOR_THIS_SESSION)
ctypes.windll.kernel32.CloseHandle(eventObjectHandle)
if registrationSuccess:
log.debug("Registered session tracking")
else:
error = ctypes.WinError()
if error.errno == RPC_S_INVALID_BINDING:
log.error(
"WTS registration failed. "
"Waited successfully on TermSrvReadyEvent to ensure that WTS is ready to allow registration. "
"Cause of failure unknown. "
)
else:
log.error("Unexpected error registering session tracking.")
return registrationSuccess
def unregister(handle: HWND) -> None:
"""
This function must be called once for every call to register.
If unregistration fails, session tracking may not work properly until the session can be unregistered in a new instance.
https://learn.microsoft.com/en-us/windows/win32/api/wtsapi32/nf-wtsapi32-wtsunregistersessionnotification
"""
if ctypes.windll.wtsapi32.WTSUnRegisterSessionNotification(handle):
log.debug("Unregistered session tracking")
else:
error = ctypes.WinError()
log.error("Unexpected error unregistering session tracking.")
In your Window Message handler for the window, when you receive WM_WTSSESSION_CHANGE, handle WTS_SESSION_UNLOCK and WTS_SESSION_LOCK events to track the state of Windows being locked.
A similar answer here might give more context on handling windows messages.

How to get window application status in python

I'm currently writing a piece of code to test windows app based on pyautowin.
When of the test is to check if we can minimized the window.
Below is the code:
MyApp.Start_(bittorrentApp)
time.sleep(2)
w_handle = pywinauto.findwindows.find_windows(title=u'Bittorrent Automation Task', class_name='WindowsForms10.Window.8.app.0.2bf8098_r15_ad1')[0]
window = MyApp.window_(handle=w_handle)
window.Click()
window.ClickInput(coords = (300,10))
time.sleep(1)
lStyles = win32api.GetWindowLong(GWL_STYLE);
if( lStyles & WS_MINIMIZE ):
print "minimized"
else:
print "not minimized"
I have imported win32api and I can minimized the window.
By the way
lStyles = win32api.GetWindowLong(GWL_STYLE);
return an error, saying GWL_STYLE is not defined
Any idea ?
pywinauto already has all such functionality.
if window.HasStyle(pywinauto.win32defines.WS_MINIMIZE):
window.Minimize()
That's all in HwndWrapper class. You can see all its attributes when typing window.WrapperObject(). in popup hint. WrapperObject() call is usually hidden for readability, but it's called implicitly anyway.
BTW, GetWindowLong(handle, style) has 2 parameters.

Categories

Resources