Introduction
Python is a cool scripting language available on many platforms and has an extensive standard library. Additionally, under Windows there are extensions available that allow Python to interface with the Win32 API or to call COM objects. You can even write COM servers in Python!
I had a copy of Charles Petzold’s “Programming Windows” lying around so I decided to try to translate the “Hello World” example of chapter 3 into Python. This example opens a gui window and paints some custom text inside its client area.
Problems encountered
Translating WinMain
The first difficulty while translating the code was to find out how to translate the signature of the Windows WinMain() function. In C, WinMain() takes a number of parameters which contains among other things the process handle of the running program. After some browsing through the documentation and on the net I found out that the Python main() can be left with no parameters and that the process handle can be retrieved using the win32api.GetModuleHandle() function call.
Specifying the window procedure
The second difficulty I had was when I wanted to specify the message-handling window procedure. Most examples I saw require you to define a dictionary which contains message constant – callback method pairs. This dictionary is then passed to the wndClass.lpfnWndProc attribute of the window class structure. Additionally, it requires you to write a Python class to contain the callback methods.
This was too distant from Charles Petzold’s example for my tastes, so I tried to simply define a callback function that had to handle all Windows messages in a traditional if-else statement. Alas, this didn’t to work, my message-handling function was never called. After some experimentation and additional searches on the Internet, I finally found the answer on a Korean site (sadly, I forgot to write down the URL…): the first parameter of the win32gui.CreateWindow() should take the class atom itself and not the class name. So the code was changed from
hWindow = win32gui.CreateWindow(
className,
'Python Win32 Window',
win32con.WS_OVERLAPPEDWINDOW,
win32con.CW_USEDEFAULT,
win32con.CW_USEDEFAULT,
win32con.CW_USEDEFAULT,
win32con.CW_USEDEFAULT,
0,
0,
hInstance,
None)
to
hWindow = win32gui.CreateWindow(
wndClassAtom,
'Python Win32 Window',
win32con.WS_OVERLAPPEDWINDOW,
win32con.CW_USEDEFAULT,
win32con.CW_USEDEFAULT,
win32con.CW_USEDEFAULT,
win32con.CW_USEDEFAULT,
0,
0,
hInstance,
None)
where wndClassAtom is the class atom returned by the Win32 RegisterClass function.
BeginPaint
The last problem I encountered was that the Python documentation of win32gui.BeginPaint() says that it doesn’t take an argument, whereas the example given by Charles Petzold shows that this method takes a handle to the window as parameter. My first try was without the argument, and the Python interpreter happily complained that an argument was missing while calling win32gui.BeginPaint(). So I added the missing parameter and the window finally appeared as expected.
The source code
Here is the source code which you can also download as a standalone file. It was developed with Python 2.4 and the Python Windows Extensions build 207:
import win32api
import win32con
import win32gui
def main():
#get instance handle
hInstance = win32api.GetModuleHandle()
# the class name
className = 'SimpleWin32'
# create and initialize window class
wndClass = win32gui.WNDCLASS()
wndClass.style = win32con.CS_HREDRAW | win32con.CS_VREDRAW
wndClass.lpfnWndProc = wndProc
wndClass.hInstance = hInstance
wndClass.hIcon = win32gui.LoadIcon(0, win32con.IDI_APPLICATION)
wndClass.hCursor = win32gui.LoadCursor(0, win32con.IDC_ARROW)
wndClass.hbrBackground = win32gui.GetStockObject(win32con.WHITE_BRUSH)
wndClass.lpszClassName = className
# register window class
wndClassAtom = None
try:
wndClassAtom = win32gui.RegisterClass(wndClass)
except Exception, e:
print e
raise e
hWindow = win32gui.CreateWindow(
wndClassAtom, #it seems message dispatching only works with the atom, not the class name
'Python Win32 Window',
win32con.WS_OVERLAPPEDWINDOW,
win32con.CW_USEDEFAULT,
win32con.CW_USEDEFAULT,
win32con.CW_USEDEFAULT,
win32con.CW_USEDEFAULT,
0,
0,
hInstance,
None)
# Show & update the window
win32gui.ShowWindow(hWindow, win32con.SW_SHOWNORMAL)
win32gui.UpdateWindow(hWindow)
# Dispatch messages
win32gui.PumpMessages()
def wndProc(hWnd, message, wParam, lParam):
if message == win32con.WM_PAINT:
hDC, paintStruct = win32gui.BeginPaint(hWnd)
rect = win32gui.GetClientRect(hWnd)
win32gui.DrawText(
hDC,
'Hello send by Python via Win32!',
-1,
rect,
win32con.DT_SINGLELINE | win32con.DT_CENTER | win32con.DT_VCENTER)
win32gui.EndPaint(hWnd, paintStruct)
return 0
elif message == win32con.WM_DESTROY:
print 'Being destroyed'
win32gui.PostQuitMessage(0)
return 0
else:
return win32gui.DefWindowProc(hWnd, message, wParam, lParam)
if __name__ == '__main__':
main()
Conclusion
It is somehow magic to be able to write a Windows application using a simple text editor only, without the need to run a compiler to produce a binary executable file. However, it should be noted that the Python Windows extensions don’t yet cover the whole Win32 API, and as such the real-world Python programmer will sooner or later have the need to translate some Win32 function himself. This is done using the ctypes module and is covered in this follow-up article: using ctypes.
Until then, happy Python programming!