In this blog I give a Python COM component that returns a windows handle hierarchy as found in Spy++. It returns the details in one large table.
So I had cause to poke around the windows hierarchy for Excel and I had previously written code to query the Windows API and get all the windows handles in a tree just like Spy++ but I chose to revisit the code with Python. Also, I chose to return the results in tabular form.
Here is the Python listing
import pythoncom
import os
import logging
import win32gui
import win32con
class LocalsEnhancedErrorMessager(object):
@staticmethod
def Enhance(ex, localsString):
locals2 = "n Locals:{ " + (",n".join(localsString[1:-1].split(","))) + " }"
if hasattr(ex,"message"):
return "Error:" + ex.message + locals2
else:
return "Error:" + str(ex) + locals2
class PythonFindWindow(object):
_reg_clsid_ = "{490784B6-5174-4794-8888-769DE4688B2C}"
_reg_progid_ = 'PythonInVBA.PythonFindWindow'
_public_methods_ = ['FindAllXlMainWindows','FindXlMainWindowWithCaptionFragment','FindChildWindows']
_reg_clsctx_ = pythoncom.CLSCTX_LOCAL_SERVER ## uncomment this for a separate COM Exe server instead of in-process DLL
def FindAllXlMainWindows(self):
try:
logging.basicConfig(filename = (os.path.dirname(os.path.realpath(__file__))) + '\app2.log',
format="%(asctime)s: %(message)s",
level=logging.INFO, datefmt="%H:%M:%S")
windows = []
hwnd = win32gui.FindWindowEx(0,0,"XLMAIN",None)
while hwnd != 0:
windows.append(hwnd)
hwnd = win32gui.FindWindowEx(0,hwnd,"XLMAIN",None)
logging.info('PythonFindWindow.FindAllXlMainWindows completed')
return windows
except Exception as ex:
msg = "PythonFindWindow.FindAllXlMainWindows error:" + LocalsEnhancedErrorMessager.Enhance(ex,str(locals()))
logging.info(msg)
return msg
def FindXlMainWindowWithCaptionFragment(self, captionStringFragment):
try:
logging.basicConfig(filename = (os.path.dirname(os.path.realpath(__file__))) + '\app2.log',
format="%(asctime)s: %(message)s",
level=logging.INFO, datefmt="%H:%M:%S")
windows = []
hwnd = win32gui.FindWindowEx(0,0,"XLMAIN",None)
while hwnd != 0:
caption = win32gui.GetWindowText(hwnd)
if captionStringFragment in caption:
windows.append(hwnd)
hwnd = win32gui.FindWindowEx(0,hwnd,"XLMAIN",None)
logging.info('PythonFindWindow.FindXlMainWindowWithCaptionFragment completed')
return windows
except Exception as ex:
msg = "PythonFindWindow.FindXlMainWindowWithCaptionFragment error:" + LocalsEnhancedErrorMessager.Enhance(ex,str(locals()))
logging.info(msg)
return msg
def FindChildWindows(self, parentHandle, selectStyles):
try:
logging.basicConfig(filename = (os.path.dirname(os.path.realpath(__file__))) + '\app2.log',
format="%(asctime)s: %(message)s",
level=logging.INFO, datefmt="%H:%M:%S")
windows = []
hwnd = parentHandle
row = [hwnd,0,"{0:#0{1}x}".format(hwnd,8),
win32gui.GetWindowText(hwnd),
win32gui.GetClassName(hwnd),
win32gui.GetWindowLong(hwnd, win32con.GWL_STYLE)]
windows.append(row)
self.FindChildWindowsInner(parentHandle,windows, selectStyles,0)
logging.info('PythonFindWindow.FindChildWindows completed')
return windows
except Exception as ex:
msg = "PythonFindWindow.FindChildWindows error:" + LocalsEnhancedErrorMessager.Enhance(ex,str(locals()))
logging.info(msg)
return msg
def FindChildWindowsInner(self, parentHandle, windows, selectStyles, depth):
try:
hwnd = win32gui.FindWindowEx(parentHandle,0,None,None)
while hwnd != 0:
style = win32gui.GetWindowLong(hwnd, win32con.GWL_STYLE)
stylesSelected = True if selectStyles is None else (style & selectStyles)!=0
if stylesSelected:
row = [hwnd,parentHandle,"{0:#0{1}x}".format(hwnd,8),
win32gui.GetWindowText(hwnd),
win32gui.GetClassName(hwnd),
style]
windows.append(row)
self.FindChildWindowsInner(hwnd, windows, selectStyles, depth+1)
hwnd = win32gui.FindWindowEx(parentHandle,hwnd,None,None)
return windows
except Exception as ex:
msg = "PythonFindWindow.FindChildWindowsInner error:" + LocalsEnhancedErrorMessager.Enhance(ex,str(locals()))
logging.info(msg)
return msg
def run():
# this code is to be run in Microsoft Visual Studio by pressing F5
# it is a developer's entry. for production instantiate the COM component
try:
print("Executing run")
print((os.path.dirname(os.path.realpath(__file__))))
logging.basicConfig(filename = (os.path.dirname(os.path.realpath(__file__))) + '\app2.log',
format="%(asctime)s: %(message)s",
level=logging.INFO, datefmt="%H:%M:%S")
fw = PythonFindWindow()
xlMains = fw.FindAllXlMainWindows()
windowList = fw.FindChildWindows(xlMains[0], win32con.WS_VISIBLE)
logging.info('called PythonFindWindow.FindChildWindows ...n')
logging.info('finishing run()n')
except Exception as ex:
print(ex)
def RegisterCOMServers():
print("Registering COM servers...")
import win32com.server.register
win32com.server.register.UseCommandLine(PythonFindWindow)
if __name__ == '__main__':
RegisterCOMServers()
run()
and here is some sample VBA client code...
Option Explicit
Const WS_VISIBLE As Long = &H10000000 'WS_VISIBLE = 0x10000000
Sub Test()
Dim obj As Object
Set obj = VBA.CreateObject("PythonInVBA.PythonFindWindow")
Dim vXlMains
vXlMains = obj.FindXlMainWindowWithCaptionFragment("MyWorkbook")
Dim vWindows
vWindows = obj.FindChildWindows(vXlMains(0), Empty) '* No styles to select with, so selects all
vWindows = obj.FindChildWindows(vXlMains(0), WS_VISIBLE) '* Only shows those that are visible (and whose ancestors are visible)
Dim lRow As Long
For lRow = LBound(vWindows, 1) To UBound(vWindows, 1)
If vWindows(lRow, 4) = "EXCEL7" Then
Stop
End If
Next
Stop
End Sub
Once the table is returned one can dig in and find what you want, much better to take a whole snapshot recursing down through the hierarchy then to piece together separate calls to FindWindow in my humble opinion.
No comments:
Post a Comment