MSW DLL GUI Function Calls for Use in WxPython
This is an unabashed Microsoft Windows (AKA: MSW) platform-specific guide to using some of the OS-supplied DLL libraries. This is made easy with the help of Python "wrappers" that allow pure Python calls to many DLL library functions. This tutorial is focused mainly on just those routines that are GUI or visually related. Just as wxPython can create and manipulate frames (wx.Frame) so can MSW create and manipulate both "third-party" application frames as well as wxPython Frames. This is how wxPython achieves its native "look and feel" on MSW platforms.The MSW GUI functions even allow creation of controls and menus within their frames they create, but this is pointless when wxPython is available.
MSW DLLs have been available since the days of the nt operating system. This tutorial is concerned with only those that are directly useful in wxPython programs. All these functions can be called in Python without importing the wxPython GUI module. Many calls can directly build MSW GUI controls using just Python, but this is pointless when wxPython is available.
MSW Desktop Frames
In the MSW world the Desktop frame can display many other application frames and their children. Each application, whether it has a GUI or not and whether it runs within a MSW shell window (just another application's top level frame), has a unique PID (Process IDentification) number assigned to it by the OS when the app is started. Each process with a GUI may have one or more top level frames, though there is usually only one. Each top level frame is also assigned a unique ID number by MSW called a window handle, most often referred as the the hwnd. Knowing the hwnd value allows you to call a variety of DLL functions to manipulate that frame, such as moving it, hiding and showing it, sizing it and relocating the frame on the Desktop. It can even be hidden with or without showing its MSW Task Bar icon.
From within a python application (process) other child processes can be started. These processes can be other non-Python applications using the os.fork() implemented on *nix (non-MSW platforms). All platforms support various popen() function calls. External applications can be started using the os.system() function on all MSW platforms. Any kind of MSW application can be started using Python's access to the MSW DLL functions. The most important benefit of starting an application with the MSW DLL function call Win32_Process.Create() is that the call directly returns the new process's PID, unlike os.system(). The PID can be used to kill that process at any time. Killing a MSW process can be done programmatically in Python using a DLL call or at a command line using the MSW utility TaskKill.exe.
External Application Frames
When a process is created the process's frame window can't be manipulated until the hwnd ID's of the window is known. Another DLL call using the PID argument returns a list of process frame handle hwnd ID's. The frame titles can be changed with a DLL call. In reverse, if a process frame's title is known, even if partially, the window handle can be retrieved , and thus the process's PID can be identified.
The DLL Function Calls The various MSW DLL calls used in the demo are spread among quite a few MSW DLL files. However, convenient access to them are made possible by installing two Python add-on packages that provide easy-to-use Python "wrappers" :
The PyWin32 Python package by Mark Hammond
The WMI Python package by Tim Golden Each of these packages are composed of Python wrapper modules for each of the multiple DLL libraries. Both packages must be installed for this demo. Also, not all the possible functions within each DLL may have had a wrapper written for it. However, all MSW DLL functions can be accessed using Python with the help of the ctypes Python module. Using wrapper modules greatly simplifies DLL calls and eliminates the need to use the ctypes functions and most of the ctypes constants. For more information concerning DLL function calling see the python.org web page 15.16. ctypes — A foreign function library for Python @ http://docs.python.org/library/ctypes.html .
The Demo Program - MswDllsInWx_Demo.py
The wxPython demo program, MswProcsInWx_Demo.py demonstrates the following:
Identifying the PID, window handle and frame title of demo app's own Python MSW shell window (AKA the "DOS box" and the "command shell/window"). Be sure to start the app using Python.exe rather than Pythonw.exe ! The demo will work properly whether it was started "manually" by a command line or by starting it in Windows Explorer(tm).
- Retrieving the "rect" (rectangle) 4-tuple that describes the size and position of MSW OS process (top level GUI app) frames.
- Moving, hiding, resizing and redisplaying of process frames. This can be applied to any app frame window, not just the ones involved with the demo app. GUI app child windows are able to be identified and accessed, but this is not done in this demo.
- Creating two application windows that can remain after the wxPython app has ended. Since their PID's are known at their creation their frame titles and window handles can easily be queried.
- Killing the created application processes using their PIDs when the demo app is ended.
- Use of a custom wxPython popup that can be closed at any time either by the user, programmatically, or, of course, destroyed automatically with the ending of the demo program. This is done using only pure wxPython.
The ability to close the popup message frame at any time by right-clicking anywhere within the frame's client area, even on its button that is normally used to close the popup with a usual left-click. This same feature is implemented for the demo frame, too. This is a much easier way to end an app than using one of the three other more typical means of ending a wx application (The Frame File:Close menu, a wxPython Close button or clicking the app Frame "X" close app frame toolbar decorator button. WxPython frames can easily be designed to have none of these three typical app close features.
All the various wrapped DLL call functions are gathered in the file [ MswDllFuncs.py ]. There are extra functions definitions that are not used in the demo which were included from some third-party demo programs. Their credits and origin are listed. All of the DLL call functions from Hammond and Golden retain the original MSW DLL function name as they should. I have renamed a few of the inane function names to more reasonable ones. For example, function win32gui.SetWindowText() doesn't change what I first assumed was a shell window's displayed text. In reality, it changes the app Frame's title text. So, my Python function is named SetFrameTitle(), which includes the WX method variable name title.
Quite a few MSW DLL function names are uninformative and even somewhat misleading. Now that their names have long since been "set in stone" they can't be changed by Microsoft. Many of the constant names are even worse. Consider the constant win32con.SM_CYSCREEN. I'm sure it was clear to the coding monkey that dreamed up this name at the time. I can deduce from its name and its usage that is has to do with the system metrics values of the current screen size along the Y axis... Maybe, or maybe not. Does it seem to you that I am ranting ? I do think that MS needs to be seriously "slapped upside the head" about many obvious aspects of their various OS versions. This is not to belittle the huge leap that Win7 has made in collecting and organizing the tools for system organization and the GUI apps to do so.
Explore the module MswDllFuncs.py. There are some interesting calls that can be used to insert text into a new Notepad window. It can even access buttons and menus in Notepad. The third party demo app by Simon Brunning originally called [ winGuiAuto.py ] isn't totally functional in Win7 probably due to the changes that have been made to the Notepad GUI since the MS XP era.
Example - Resizing an Application Frame
First, keep in mind that in a wxPython GUI app the wx frame (wx.Frame) is NOT the top level frame for program. The Python command shell is actually the top window ! Even when kicking off a wxPython scripts with Pythonw.exe the command shell window is simply hidden from view. Python is a command line level program with no inherent GUI, and so, it must run in a MSW command shell window. wxPython is just an add-on package to Python. The point is: Use Python and/or wxPython to create and manipulate wxPython top level and child frames/windows created by wxPython. Use MSW DLL calls to manipulate MSW-created frames and windows. Since a Python command shell window is created by MSW, we can use Python calls to the MSW DLLs to affect its appearance.
1 import sys 2 import time 3 4 import MswDllFuncs as mdf # A collection of MSW DLL call functions 5 import HexCap as hc # A "pretty print" function def used much in the demo 6 7 #----- 8 9 # Find the MSW window handle id (hwnd) of this Python app shell. 10 pythonAppShellTitle = mdf.GetConsoleTitle() 11 print '\n---- This Python App Shell Title : \n[ % s ]' % (pythonAppShellTitle) 12 13 pyAppShellHwnd = mdf.FindTopWindow( wantedText=pythonAppShellTitle, warnMultiple=True ) 14 15 pythonAppShellClassName = mdf.GetClassNameA( pyAppShellHwnd ) 16 print '---- This Python Shell Class Name : [ % s ]' % (pythonAppShellClassName) 17 18 # Find the pid of this Python app shell. 19 pythonAppShellPid = mdf.GetPidFromHwnd( pyAppShellHwnd ) 20 21 print ' hwnd, pid = ', hc.HexCap( pyAppShellHwnd, digits=8 ), \ 22 hc.HexCap( pythonAppShellPid, digits=4 ) 23 24 # Find this shell's position on the screen. 25 pythonAppShellSizeOrig = mdf.GetWinSize( pyAppShellHwnd ) 26 pythonAppShellPosnOrig = mdf.GetWinPosition( pyAppShellHwnd ) 27 print '\n---- Python Command Shell :' 28 29 sizeBeforeMoving = mdf.GetWinSize( pyAppShellHwnd ) 30 sizeOrigX, sizeOrigY = pythonAppShellSizeOrig 31 posnBeforeMoving = mdf.GetWinPosition( pyAppShellHwnd ) 32 print '\n Size and Position Before Moving = (%3d %3d) (%3d %3d)' % \ 33 (sizeBeforeMoving, sizeBeforeMoving, posnBeforeMoving, posnBeforeMoving) 34 35 #----- 36 37 # Reposition this shell window 38 mdf.SlideWindowFx( pyAppShellHwnd, 0, 0 ) # Arbitrary destination position. 39 #time.sleep( 2 ) # pause for "the effect". 40 mdf.HideWin( pyAppShellHwnd ) 41 time.sleep( 0.5 ) 42 mdf.ShowWin( pyAppShellHwnd ) 43 44 sizeAfterMoving = mdf.GetWinSize( pyAppShellHwnd ) 45 posnAfterMoving = mdf.GetWinPosition( pyAppShellHwnd ) 46 print '\n Size and Position After Moving = (%3d %3d) (%3d %3d)' % \ 47 (sizeAfterMoving, sizeAfterMoving, posnAfterMoving, posnAfterMoving) 48 49 #----- 50 51 # Command shells can't be widened, only narrowed. 52 mdf.GrowWinSize( pyAppShellHwnd, sizeOrigX-200, sizeOrigY+200 ) 53 54 sizeAfterGrowing = mdf.GetWinSize( pyAppShellHwnd ) 55 posnAfterGrowing = mdf.GetWinPosition( pyAppShellHwnd ) 56 print '\n Size and Position After Growing = (%3d %3d) (%3d %3d)' % \ 57 (sizeAfterGrowing, sizeAfterGrowing, posnAfterGrowing, posnAfterGrowing) 58 59 time.sleep( 1.0 ) 60 61 #----- 62 63 # Restore the shell window size 64 mdf.GrowWinSize( pyAppShellHwnd, sizeOrigX, sizeOrigY ) 65 66 sizeAfterShrinking = mdf.GetWinSize( pyAppShellHwnd ) 67 posnAfterShrinking = mdf.GetWinPosition( pyAppShellHwnd ) 68 print '\n Size and Position After Shrinking = (%3d %3d) (%3d %3d)' % \ 69 (sizeAfterShrinking, sizeAfterShrinking, posnAfterShrinking, posnAfterShrinking) 70 71 time.sleep( 1.0 ) 72 73 #-------- 74 75 # Restore the shell window position. 76 origX, origY = pythonAppShellPosnOrig 77 mdf.SlideWindowFx( pyAppShellHwnd, origX, origY ) 78 time.sleep( 0.5 ) 79 80 mdf.HideWin( pyAppShellHwnd ); 81 time.sleep( 0.5 ) 82 mdf.ShowWin( pyAppShellHwnd ) 83 84 sizeAfterReturning = mdf.GetWinSize( pyAppShellHwnd ) 85 posnAfterReturning = mdf.GetWinPosition( pyAppShellHwnd ) 86 print '\n Size and Position After Returning = (%3d %3d) (%3d %3d)' % \ 87 (sizeAfterReturning, sizeAfterReturning, posnAfterReturning, posnAfterReturning)
Running The Demo Program
Open an MSW command shell and run the script with python.exe. The program will output logging statements as it runs. It's best to keep your Desktop clear of other app windows, though this won't affect the operation of the demo program. Here's a sample sequence of screen shots. They can't show the animated motion of the windows. Run the demo and compare your results. The demo program and the DLL call library are heavily commented.
Just before starting the demo app:
The Demo has started: 1) The command shell has been moved, 2) The yellow main wx.Frame is displayed, 3) The blue message popup window is displayed. Note that the message popup is not a dialog at all, though it has been derived from wx.Dialog. It does not ask for nor returns any user input data or action (as standard wx.Dialogs do) other than being closed. The user may close it at any time or leave it showing for the duration of the program, much like a wx.Frame, yet it is a child window of the wx.Frame, unlike an actual wx.Frame. Actually, the popup can be rewritten to be as complex as desired since it is essentially is a wx.Frame in appearance and function. This could have implemented the identical functionality by subclassing a wx.Frame instead.
The user has clicked the [Create Two New...] button. Oops ! I forgot to change the button label - It should read "Create Two New Processes". As you can see, another MSW command shell was started as well as an instance of Notepad. Both frames were moved from their default positions where the user last placed them.
The button may be clicked as many times as desired and two new pairs of apps instances will be created. The titles of the command shells are time stamped to differentiate them. Any or all of the new processes may be manually closed before ending the demo app. Calling a DLL for an app frame using a nonexistant hwnd or pid will crash Python unless suitable, but simple exception trapping is used.
The Demo has ended: The new processes were killed. Just before they were killed they were returned to their original positions. So has the demo command shell. The animation techniques are a bit clunky, but effective in demonstrating some of the effects that can be used. The algorithms are simple and "linear". That's a nice way to say that they certainly can be improved with some more work on them.For example, I (briefly) considered making the frames enlarge and shrink cyclically as they were being moved, but I came to the conclusion that this would be "over the top" for this demo. I'll leave that for you to implement as a "student exercise for extra credit", so to speak.
Win32api @ http://timgolden.me.uk/python/winshell.html