## page was renamed from MswGuiDllUsageForWxPython ##master-page:HelpTemplate ##master-date:Unknown-Date #format wiki #language en = 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''' ('''P'''rocess '''ID'''entification) 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 '''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 Python'''__w__'''.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 '''s'''ystem '''m'''etrics values of the '''c'''urrent '''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. [[attachment: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 Python'''__w__'''.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. {{{#!python import sys import time import MswDllFuncs as mdf # A collection of MSW DLL call functions import HexCap as hc # A "pretty print" function def used much in the demo #----- # Find the MSW window handle id (hwnd) of this Python app shell. pythonAppShellTitle = mdf.GetConsoleTitle() print '\n---- This Python App Shell Title : \n[ % s ]' % (pythonAppShellTitle) pyAppShellHwnd = mdf.FindTopWindow( wantedText=pythonAppShellTitle, warnMultiple=True ) pythonAppShellClassName = mdf.GetClassNameA( pyAppShellHwnd ) print '---- This Python Shell Class Name : [ % s ]' % (pythonAppShellClassName) # Find the pid of this Python app shell. pythonAppShellPid = mdf.GetPidFromHwnd( pyAppShellHwnd ) print ' hwnd, pid = ', hc.HexCap( pyAppShellHwnd, digits=8 ), \ hc.HexCap( pythonAppShellPid, digits=4 ) # Find this shell's position on the screen. pythonAppShellSizeOrig = mdf.GetWinSize( pyAppShellHwnd ) pythonAppShellPosnOrig = mdf.GetWinPosition( pyAppShellHwnd ) print '\n---- Python Command Shell :' sizeBeforeMoving = mdf.GetWinSize( pyAppShellHwnd ) sizeOrigX, sizeOrigY = pythonAppShellSizeOrig posnBeforeMoving = mdf.GetWinPosition( pyAppShellHwnd ) print '\n Size and Position Before Moving = (%3d %3d) (%3d %3d)' % \ (sizeBeforeMoving[0], sizeBeforeMoving[1], posnBeforeMoving[0], posnBeforeMoving[1]) #----- # Reposition this shell window mdf.SlideWindowFx( pyAppShellHwnd, 0, 0 ) # Arbitrary destination position. #time.sleep( 2 ) # pause for "the effect". mdf.HideWin( pyAppShellHwnd ) time.sleep( 0.5 ) mdf.ShowWin( pyAppShellHwnd ) sizeAfterMoving = mdf.GetWinSize( pyAppShellHwnd ) posnAfterMoving = mdf.GetWinPosition( pyAppShellHwnd ) print '\n Size and Position After Moving = (%3d %3d) (%3d %3d)' % \ (sizeAfterMoving[0], sizeAfterMoving[0], posnAfterMoving[0], posnAfterMoving[0]) #----- # Command shells can't be widened, only narrowed. mdf.GrowWinSize( pyAppShellHwnd, sizeOrigX-200, sizeOrigY+200 ) sizeAfterGrowing = mdf.GetWinSize( pyAppShellHwnd ) posnAfterGrowing = mdf.GetWinPosition( pyAppShellHwnd ) print '\n Size and Position After Growing = (%3d %3d) (%3d %3d)' % \ (sizeAfterGrowing[0], sizeAfterGrowing[0], posnAfterGrowing[0], posnAfterGrowing[0]) time.sleep( 1.0 ) #----- # Restore the shell window size mdf.GrowWinSize( pyAppShellHwnd, sizeOrigX, sizeOrigY ) sizeAfterShrinking = mdf.GetWinSize( pyAppShellHwnd ) posnAfterShrinking = mdf.GetWinPosition( pyAppShellHwnd ) print '\n Size and Position After Shrinking = (%3d %3d) (%3d %3d)' % \ (sizeAfterShrinking[0], sizeAfterShrinking[1], posnAfterShrinking[0], posnAfterShrinking[1]) time.sleep( 1.0 ) #-------- # Restore the shell window position. origX, origY = pythonAppShellPosnOrig mdf.SlideWindowFx( pyAppShellHwnd, origX, origY ) time.sleep( 0.5 ) mdf.HideWin( pyAppShellHwnd ); time.sleep( 0.5 ) mdf.ShowWin( pyAppShellHwnd ) sizeAfterReturning = mdf.GetWinSize( pyAppShellHwnd ) posnAfterReturning = mdf.GetWinPosition( pyAppShellHwnd ) print '\n Size and Position After Returning = (%3d %3d) (%3d %3d)' % \ (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: {{attachment:1_BEFORE_THE DEMO.PNG|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. {{attachment: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. {{attachment: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. {{attachment:4_APP_ENDED.PNG}} ------ == Downloads == PyWin32 @ http://python.net/crew/skippy/win32/Downloads.html Win32api @ http://timgolden.me.uk/python/winshell.html