add skeleton for variant type support
[pywinlite.git] / wndclass.txt
blob2ff86ddb446a99518d820f04c9af6a67e7e928df
2 Windows has a concept of a "window class". In order to create a normal window, one must first register a window class and define a "window procedure" to handle events sent to windows of that class.
4 This whole process is a bit awkward, but I have to use it because I am writing a wrapper around the Windows API, not making my own toolkit. Therefore, I have provided a class called Window that can be subclassed to make the process simpler.
6 Here's a simple example:
8 import wndclass, message
9 class SimpleWindow(wndclass.Window):
10     def DO_WM_DESTROY(self, hwnd, msg, wparam, lparam):
11         message.PostQuitMessage(0)
12 window = SimpleWindow()
13 window.show() #FIXME: window.show() doesn't actually work yet
14 message.loop()
16 When the SimpleWindow class is created, a corresponding window class named PyWinLite0 is registered using RegisterClassEx. By default, the window procedure will call specially named DO_* methods if they exist or DefWindowProc otherwise.
18 The wndclass module does not define any Window classes that can be instantiated. You have to subclass Window if you want a normal top-level window. This is to remind me that I have to register a window class before I can create a window.
20 The special class attribute 'classname' allows you to choose a name for your class. If no name is specified, one will be chosen for you at class creation time. You shouldn't need to care about this unless you are dealing with libraries that for some reason want the name of a window class.
23 Using predefined classes:
25 To define a python class for the predefined "Button" class, you would do this:
27 import wndclass
28 class Button(wndclass.Window):
29     classname = 'Button'
30     internal = False
32 Like always, classname specifies the name of the class. If 'internal' is False, this class is not registered as it normally would. Instead, the necessary attributes are read from the existing window class. If the Button window class doesn't already exist, this code raises an exception.
34 Note that if you set 'internal' to False, DO_* methods will have no effect. If you need to use a predefined class but process some methods before they get to that class, you will need to create a subclass.
37 Instance creation quirks:
39 A sensible thing for me to do for instance creation would have been a process something like this:
41 1. A new Window object is created using __new__.
42 2. The object is initialized by calling __init__.
43 3. Window.__init__ calls CreateWindow and assigns the window object an hwnd based on its return value.
44 4. The window is added to an internal mapping of hwnd's to python objects so that message handlers can be called on the correct instance.
46 Unfortunately, CreateWindow actually sends message to the window it is creating BEFORE IT RETURNS. If I tried to use that process, I would have to process these messages without knowing what instance would eventually correspond to the hwnd.
48 Instead, the process is like this:
50 1. When a window class is called, a new window is created using the __create__ method, which calls CreateWindow and returns an hwnd, with no regard for python objects.
51 2. When a window receives its first message, an instance is created for it in a somewhat more normal process, calling __new__ and __init__, but these methods are not given the original arguments that were passed to the class. They are only given the hwnd argument.
52 3. CreateWindow returns, and the python object is found by looking up the hwnd in a table (or, if it is a subclass of an external class, creating the new object at this time). FIXME: Subclasses of internal classes probably don't work.
53 4. The instance's __setup__ method is called with the arguments that were originally passed to the class, so that any additional initialization can be done. If information from these arguments is needed earlier, it must be passed using lparam.
55 Note: If the class is initialized using CreateWindow directly using its classname (presumably by an external library) instead of by calling the python class, __create__ and __setup__ will not be called at all. So if the class is going to be used outside of python, it's very important that you don't rely on __setup__ to do initialization.
58 Subclassing:
60 FIXME: This section is full of lies.
62 Confusingly enough, a "window subclass" isn't really a new class. It's simply a method of intercepting window messages before they get to the normal window procedure. If you want to know how this works at a low level, read http://msdn.microsoft.com/en-us/library/ms997565.aspx.
64 If you have a window class, you can subclass it by creating a python subclass, like so:
66 class SubclassedButton(Button):
67     pass
69 You can define DO_* methods in this class, and you'll be able to process the messages before the predefined 'Button' class can. That wouldn't work in the Button class defined above.
71 The 'subclass' attribute is True if a Python class descended from Window is a subclass.
73 You can also subclass classes that you create, but this is less useful.
76 Superclassing (this is the thing you thought subclassing was):
78 Superclassing is the term for creating a new window class (the "superclass") based on an existing class (the "base class"). It's just like subclassing, but you get a new class name.
80 You can create a superclass by making a Python subclass of an existing window class and setting classname to something other than the classname of the base class. If you set classname to None, a name will be chosen for you.
82 Useless example:
84 class SuperclassedButton(Button):
85     classname = None
88 DO_* methods:
90 For internal classes and subclasses, methods starting with DO_ are special. These methods, if they exist, will be called to handle messages sent to windows of that class.
92 DO methods can be named using a literal integer message id (DO_15 or DO_0xf), the name of a message constant (DO_WM_PAINT), or R_ and a name to be passed to RegisterWindowMessage (DO_R_PING).
94 If a DO method does not exist for a message, the class will call DefWindowProc or the base class's window procedure.
96 To send messages to DefWindowProc or the base class, use the super_on_msg method, like so:
97 def DO_WM_PAINT(self, hwnd, msg, wparam, lparam):
98     print("got WM_PAINT message")
99     return self.super_on_msg(hwnd, msg, wparam, lparam)
102 Handling all message types:
104 The on_msg class method is called for every message a window receives. By default, it checks for a DO method, calls that if it exists, and raises a NotImplementedError otherwise.
106 The class attribute 'message_handlers' contains a dictionary of integer message ids to the DO methods that handle them.
108 A class can override on_msg to do something for all message types.
110 Example:
112 class ChattyButton(Button):
113     @classmethod
114     def on_msg(klass, wnd, hwnd, msg, wparam, lparam):
115         print("window %x got message %x, wparam=%x, lparam=%x" % (hwnd, msg, wparam, lparam))
116         try:
117             handler = klass.message_handlers[msg]
118         except KeyError:
119             raise NotImplementedError
120         return handler(wnd, hwnd, msg, wparam, lparam)
122 Note that calling the base class's on_msg will call the DO methods defined in the base class, not this one. Therefore, if you override on_msg and want to call message handlers you define, you must look them up in message_handlers yourself.