O'Reilly Book Excerpts: Python Programming on Win32
Python Programming on Win32 using PythonWin
As mentioned in Chapter 4, Integrated Development Environments for Python, PythonWin is a framework that exposes much of the Microsoft Foundation Classes (MFC) to Python. MFC is a C++ framework that provides an object-based model of the Windows GUI API, as well as a number of services useful to applications.
The term PythonWin is a bit of a misnomer. PythonWin is really an application written to make use of the extensions that expose MFC to Python. This means PythonWin actually consists of two components:
- Python modules that provide the raw MFC functionality
- Python code that uses these modules to provide the PythonWin application
We focus primarily on the MFC functionality exposed to Python so we can build a fully functional GUI application.
As PythonWin mirrors MFC, it's important to understand key MFC concepts to understand how PythonWin hangs together. Although we don't have room for a complete analysis of MFC, an introduction to its concepts is in order.
Introduction to MFC
The Microsoft Foundation Classes are a framework for developing complete applications in C++. MFC provides two primary functions:
- An object-oriented wrapper for the native Windows user-interface API
- Framework facilities that remove much of the grunge work involved in making a complete, standalone Windows application
The object-oriented wrapping is straightforward. Many Windows API functions take a "handle" as their first parameter; for example, the function
SendMessage() takes a handle to a window (an
DrawText() takes a handle to a device context (an
HDC) and so forth. MFC wraps most of these handles in objects and thus provides
CDC classes, both of which have the relevant methods.
So, instead of writing your C++ code as:
HWND hwnd = CreateWindow(...); // Create a handle to the window...
EnableWindow(hwnd); // and enable it.
You may write code similar to:
CWnd wnd; // Create a window object.
wnd.CreateWindow(...); // Create the Window.
wnd.EnableWindow();// And enable it.
There are a large number of objects, including generic window objects, frame windows, MDI child windows, property pages, fonts, dialogs, etc. It's a large object model, so a good MFC text or the MFC documentation is recommended for anything more than casual use from Python.
The framework aspects of MFC provides some useful utility classes, both for structuring your application and performing many of the mundane tasks a good Windows application should do. The mundane but useful tasks it performs include automatic creation of tool-tip text and status-bar text for menus and dockable toolbars, reading and writing preferences in the registry, maintaining the "recently used files" list, and so forth.
MFC also provides a useful application/template/document/view architecture. You create an application object, then add one or more document templates to the application. A document template knows how to create a specific document, meaning your application can work with many documents. A "document" is a general concept; it holds the data for the object your application manages, but doesn't provide any user interface for viewing that data. The last link in the chain is the view object that's responsible for the user interaction. Each view defines a way of looking at your data. For example, you may have a graphical view and also a tabular view. Included in all of this are many utility functions for managing these objects. For example, when a view notifies its document that data has been changed, the document automatically notifies all other views, so they can be kept up-to-date.
If your application doesn't fit this model, don't be alarmed: you can customize almost all this behavior. But there is no doubt that utilizing this framework is the simplest way to use MFC.
The PythonWin Object Model
Think of PythonWin as composed of two distinct portions. The
win32ui module is a Python extension that provides access to the raw MFC classes. For many MFC objects, there is an equivalent
win32ui object. For example, the functionality of the MFC
CWnd object is provided by a
PyCWnd Python object; an MFC
CDocument object by a
PyCDocument object, etc. For a full list, see the PythonWin reference (on the PythonWin help menu).
For the MFC framework to be useful, you need to be able to override default methods in the MFC object hierarchy; for example, the method
CView::OnDraw() is generally overridden to draw the screen for a view. But the objects exposed by the
win32ui module are technically Python types (they aren't classes) and a quirk in the Python language prevents these Python types from having their methods overridden.
To this end, the
win32ui module provides a mechanism to "attach" a Python class instance object to a
win32ui type. When MFC needs to call an overridden method, it then calls the method on the attached Python object.
What this means for the programmer is that you can use natural Python classes to extend the types defined in
More from this Chapter:
pywin.mfc package provides Python base classes that interface with many of the
win32ui objects. These base classes handle the interaction with
win32ui and allow you to use Python subclassing to get your desired behavior.
This means that when you use a PythonWin object, there are two Python objects involved (the object of a
win32ui type and the Python class instance), plus an underlying MFC C++ object.
Let's see what this means in practice. We will examine a few of these objects from the PythonWin interactive window and create a dialog object using one of the standard PythonWin dialogs:
>>> import win32ui
>>> from pywin.mfc.dialog import Dialog
Looking at the object, you can see it's indeed an instance of a Python class:
<pywin.mfc.dialog.Dialog instance at 1083c80>
And you can see the underlying
object 'PyCDialog' - assoc is 010820C0, vf=True, notify=0,ch/u=0/0, mh=1, kh=0
It says that the C++ object is at address 0x010820c0 and also some other internal, cryptic properties of the object. You can use any of the underlying
win32ui methods automatically on this object:
>>> button.SetWindowText("Hello from Python")
The prompt in the dialog should now read "Hello from Python."