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.

Processes

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 Demo Program - MswDllsInWx_Demo.py

The wxPython demo program, MswProcsInWx_Demo.py demonstrates the following:

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.

MswDllsInWx_Wiki_Page_Files.zip

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[0], sizeBeforeMoving[1], posnBeforeMoving[0], posnBeforeMoving[1])
  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[0], sizeAfterMoving[0], posnAfterMoving[0], posnAfterMoving[0])
  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[0], sizeAfterGrowing[0], posnAfterGrowing[0], posnAfterGrowing[0])
  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[0], sizeAfterShrinking[1], posnAfterShrinking[0], posnAfterShrinking[1])
  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[0], sizeAfterReturning[1], posnAfterReturning[0], posnAfterReturning[1])

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:

1_BEFORE_THE DEMO.PNG


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.

2_DEMO_STARTED.PNG


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.

3_BUTTON_CLICKED.PNG


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.

4_APP_ENDED.PNG


Downloads

PyWin32 @ http://python.net/crew/skippy/win32/Downloads.html

Win32api @ http://timgolden.me.uk/python/winshell.html

MswGuiDllCallsForWxPython (last edited 2010-10-26 04:03:50 by pool-71-175-95-133)

NOTE: To edit pages in this wiki you must be a member of the TrustedEditorsGroup.