DynWin, a dynamic GUI class library for Python and Win32


DynWin lives here: dynwin.zip

DynWin uses npstruct and calldll, which are here:
npstruct-2005-07-06.zip
calldll-2005-07-06.zip
(these modules have been linked against Python-2.4.1, and include instructions on how to recompile them with mingw gcc)

Maybe you want ctypes?

If you want to call functions in DLL's, and be able to pack and unpack structures with aplomb, reaching your fingers into their nasty innards with ease, nowadays you probably want to use ctypes. See the ctypes module documentation for more information.

Historical Note

This project was actually never quite finished - I stopped working with windows abruptly around 1998. If I ever find myself developing windows gui code again, I'll probably pick the project back up. That said, there is much to learn from looking at this code, and useful applications can be written with it.

There's an issue with XP related to gdi or ui callbacks being made from a different thread. At one point I had this fixed, but lost the code when my laptop died. I think the only thing necessary to get it working was to use the newer GIL api functions, but I don't remember the details. If anyone figures this out, send me a patch and I'll update the distribution

Goals

My intent is to leverage the powerful dynamic capability of Python to build a class library that is:

than the various C++ class libraries can hope to be. At first glance, one might expect this class library to perform poorly. However, most GUI's do not need the speed of a 1GHz CPU to control user interface elements. And in the case where raw CPU does not suffice, more sophisticated graphical techniques can often be employed to make up the difference.

I believe that in choosing only strongly-typed, compiled languages for the development of user interfaces, an entire area of the language spectrum, and the promise it holds for UI innovation, is ignored.

Since I started this project several years ago, the Java world has taken a very similar tack with the Swing project. If you haven't seen Swing yet, you should definitely take a look!

Other good design examples are ParcPlace Smalltalk and Fresco.

Win32 Renaming

Win32 entities are renamed, for my own sanity. I wrote this, so I have the right to rename them. If you don't like it, you can just GoBackToMfcWhereYouCameFrom. 8^)

The renaming will help distinguish those functions and objects intended to work with objects implemented in python from those intended to work at a lower level.

For example, dc.select_object() expects to be given a GDI object implemented in python, with a get_handle()/__int__ conversion method, where gdi32.SelectObject (which is actually a direct reference to the routine in the system dll) expects two parameters: an HDC and an HGDIOBJ.

In general, a multi-word name will be rendered in all lowercase, with underscore dividers between the words, e.g.:

PreTranslateMessage ==> pre_translate_message
DlgDirSelectComboBoxEx ==> dlg_dir_select_combo_box_ex

Win32con

Avoid using win32con. Rather than burying all system-wide constants in one huge file or database, include the constants in the source files providing interfaces relevant to them. For example, device context related constants such as DRIVERVERSION (for dc.get_device_caps()) belong in the module. One goal of this measure is that 'frozen' python programs will include references to only that subset of constants necessary for the program's operation.

Objects and Handles

Python's automatic integer-cast capability (the '__int__' method) greatly eases the burden of communicating with the Win32 API. On Win32, nearly every 'object' is referenced through an opaque 'handle'. When we build a wrapper for such an object, we provide an instance variable to hold the handle, and use an __int__ method to automatically convert a Python object to a handle. This allows us to write:

    dc.select_object (brush)

  Rather than
    dc.select_object (brush.get_handle())
  or
    dc.select_object (brush.handle)
and, in general, gives the code a much more object-oriented feel.

To give you an idea of how much work Python is doing for you, here's what that call might look like if constructed 'manually':

    calldll.call_foreign_function (
       gdi32.SelectObject.address,
       'll',
       'l',
       (dc.handle, brush.handle)
    )
You must not forget that you are programming 'against the iron' of the operating system - very much like programming in C, but without type safety. If you feed bogus values to system API's, you may crash your whole operating system. I have crashed NT twice this way in 3 years! [which is often enough for something that's not supposed to happen]

Controls

In general, I would like to avoid using 'controls', such as the builtin or common controls, because the interfaces to these windows are usually limited - consisting of a few structure definitions, api functions, and callbacks.

There is usually no simple way to extend or modify their behavior ("subclassing" doesn't count). To get a modified control, you must often dive into the 'owner-drawn' cesspool, write your own message loops/handlers, etc... At this point you might as well have written the control yourself!

Synergy and Critical Mass

Often a new facility invites a cleaner rewrite of an existing one. For example, once the layout managers were available, it became easy to redesign the existing scrollbar class: Instead of hard-coding the button-and-thumb behavior into a single class, we use the layout manager to position separate button and thumb objects. Now we have a more flexible scrollbar design; for example, we may not _want_ the buttons, or we may want to add extra buttons, or change the behavior of the thumb tracker, etc.

The unfortunate side-effect of this is that the entire library is constantly being rewritten. Caveat Emptor.

Dynwin as a Package

A short experiment with using dynwin as a package proved it feasible, however at the time the 'freeze' utility did not yet support packages; most of my uses for dynwin involve creating standalone executables.

I hope to some day explore the idea again...

Model-View-Controller

The entire library is in the process of being rewritten using the model-view-controller pattern/paradigm. You'll notice these modules because they all start with mvc_. This shift in thought has been tremendously successful in helping me to understand and organize the library.

To get started, read mvc.py carefully, and make sure you understand what the auto_model class is doing.

Nearly all the interactive objects; buttons, toolbars, menus use a model derived from the mvc_button.push_button_model class.

Trying it out

Make sure you have the two auxiliary dll modules, calldll.pyd and npstruct.pyd. Then add the library directory to your python path. Finally;

d:\python\dynwin> python demo/interp_demo.py

That should do it! Many of the modules have self-tests and can be run directly from the command line. You can also play with them from within the interpreter window.

Freezing Applications

Note:This section needs updating... haven't tried freezing since python1.5...

DynWin apps are easy to freeze!

d:\python\dynwin> freeze -p d:/src/python-1.5 demo/mvc_tree_view.py
...
d:\python\dynwin> nmake
...

I usually freeze the freeze utility itself, for convenience. To distribute your program, just send along python21.dll, calldll.pyd, and npstruct.pyd.

Freeze lets you choose between a console or windows application. If you choose windows, be sure to capture sys.stdout and sys.stderr, otherwise you may lose error output from the main message loop callback.