Commit | Line | Data |
---|---|---|
920dae64 AT |
1 | |
2 | <html> | |
3 | <head> | |
4 | <meta name="description" content="Pmw - a toolkit for building high-level compound widgets in Python"> | |
5 | <meta name="content" content="python, megawidget, mega widget, compound widget, gui, tkinter"> | |
6 | <title>How to build Pmw megawidgets</title> | |
7 | </head> | |
8 | ||
9 | <body bgcolor="#ffffff" text="#000000" link="#0000ee" | |
10 | vlink="551a8b" alink="ff0000"> | |
11 | ||
12 | <h1 ALIGN="CENTER">How to build Pmw megawidgets</h1> | |
13 | ||
14 | <center><P ALIGN="CENTER"> | |
15 | <IMG SRC = blue_line.gif ALT = "" WIDTH=320 HEIGHT=5> | |
16 | </p></center> | |
17 | ||
18 | <dl> | |
19 | <dt> <h3>Introduction</h3></dt><dd> | |
20 | <p> | |
21 | This document briefly describes how to design and code Pmw | |
22 | megawidgets by inheriting from the Pmw base classes. It shows step | |
23 | by step how to build a simple example megawidget. This megawidget | |
24 | allows the user to select one of a range of numbers and it also | |
25 | indicates if the selected number is greater than a given threshold. | |
26 | ||
27 | </p> | |
28 | ||
29 | </dd> | |
30 | <dt> <h3>Choosing the components</h3></dt><dd> | |
31 | ||
32 | <p> | |
33 | The megawidget will be built using a Tkinter.Scale widget to allow | |
34 | the user to select a number in a range, and a Tkinter.Frame widget | |
35 | to act as an indicator, displaying red (say) if the selected number | |
36 | exceeds the threshold. It will look something like this: | |
37 | ||
38 | </p> | |
39 | ||
40 | <center><P ALIGN="CENTER"> | |
41 | <IMG SRC = scale1.gif ALT = "Scale 2" WIDTH=70 HEIGHT=244> | |
42 | </p></center> | |
43 | ||
44 | <p> | |
45 | The programmer using this megawidget will need access to the scale | |
46 | widget, since they will need to set the scale's range. Therefore | |
47 | the scale will be made a component of the megawidget. The | |
48 | programmer will probably not need access to the indicator frame, | |
49 | but, just in case the need arises to change the borderwidth or | |
50 | relief of the indicator, we will make it a component too. This | |
51 | illustrates a convention about components - for maximum | |
52 | configurability, make all sub-widgets components. | |
53 | ||
54 | </p> | |
55 | ||
56 | </dd> | |
57 | <dt> <h3>Choosing the options</h3></dt><dd> | |
58 | ||
59 | <p> | |
60 | Apart from the component options now available through the scale and indicator | |
61 | components, the megawidget will need a few options of its own. It | |
62 | will need a <strong>threshold</strong> option to set the threshold. | |
63 | It may also need options to set the colors of the indicator when the | |
64 | selected value is both above and below the threshold. Other options | |
65 | could be <strong>orient</strong> or <strong>indicatorpos</strong> to | |
66 | specify the relative position of components and | |
67 | <strong>margin</strong>, <strong>padx</strong> or | |
68 | <strong>pady</strong> to specify spacing between and around the | |
69 | components. For this example, we will define three options - | |
70 | <strong>threshold</strong>, <strong>colors</strong> and | |
71 | <strong>value</strong>. The <strong>colors</strong> option will be | |
72 | a 2-element sequence specifying two colors (below threshold, above | |
73 | threshold). The <strong>value</strong> option will be the initial | |
74 | value of the scale. | |
75 | ||
76 | </p> | |
77 | ||
78 | </dd> | |
79 | <dt> <h3>Coding the megawidget</h3></dt><dd> | |
80 | ||
81 | <p> | |
82 | The first things to do are to decide on a name for the new | |
83 | megawidget, decide which base class to inherit from and to begin to | |
84 | write the constructor. Most Pmw megawidgets are derived from either | |
85 | Pmw.MegaWidget, Pmw.MegaToplevel or Pmw.Dialog. In this case, since | |
86 | the widget is not to be contained within its own toplevel window, we | |
87 | will inherit from Pmw.MegaWidget. The constructors of megawidgets | |
88 | take one argument (the widget to use as the parent of the | |
89 | megawidget's hull, defaulting to the root window) and any number of | |
90 | keyword arguments. | |
91 | ||
92 | </p> | |
93 | ||
94 | <pre> | |
95 | class ThresholdScale(Pmw.MegaWidget): | |
96 | """ Megawidget containing a scale and an indicator. | |
97 | """ | |
98 | ||
99 | def __init__(self, parent = None, **kw): | |
100 | </pre> | |
101 | ||
102 | <p> | |
103 | Next, we need to define the options supplied by this megawidget. | |
104 | Each option is specified by a 3-element sequence. The first element | |
105 | is the option's name. The second element is the default value. The | |
106 | third element is either a callback function, | |
107 | <strong>Pmw.INITOPT</strong> or <strong>None</strong>. In the first | |
108 | case, the function is called at the end of construction (during the | |
109 | call to <code>self.inialiseoptions</code>) and also | |
110 | whenever the option is set by a call to | |
111 | <code>configure</code>. <strong>Pmw.INITOPT</strong> indicates that | |
112 | the option is an initialisation option - it cannot be set by calling | |
113 | <code>configure</code>. <strong>None</strong> indicates that the | |
114 | option can be set by calling <code>configure</code>, but that there | |
115 | is no callback function. | |
116 | ||
117 | </p> | |
118 | ||
119 | <p> | |
120 | The call to <code>self.defineoptions</code> also includes the | |
121 | keyword arguments passed in to the constructor. The value given to | |
122 | any option specified in the keywords will override the default | |
123 | value. | |
124 | ||
125 | </p> | |
126 | ||
127 | <pre> | |
128 | # Define the megawidget options. | |
129 | optiondefs = ( | |
130 | ('colors', ('green', 'red'), None), | |
131 | ('threshold', 50, None), | |
132 | ('value', None, Pmw.INITOPT), | |
133 | ) | |
134 | self.defineoptions(kw, optiondefs) | |
135 | </pre> | |
136 | ||
137 | <p> | |
138 | After defining the options, the constructor of the base class should | |
139 | be called. The options need to be defined first so that a derived | |
140 | class can redefine the default value of an option defined in a base | |
141 | class. This is because the value specified by the derived class | |
142 | must be made available before the base class constructor is called. | |
143 | The keyword | |
144 | arguments should not be passed into the base class constructor since | |
145 | they have already been dealt with in the previous step. | |
146 | ||
147 | </p> | |
148 | ||
149 | <pre> | |
150 | # Initialise base class (after defining options). | |
151 | Pmw.MegaWidget.__init__(self, parent) | |
152 | </pre> | |
153 | ||
154 | <p> | |
155 | Now we should create the components. The components are created as | |
156 | children (or grandchildren ...) of the megawidget's interior. | |
157 | ||
158 | </p> | |
159 | ||
160 | <pre> | |
161 | # Create the components. | |
162 | interior = self.interior() | |
163 | </pre> | |
164 | ||
165 | <p> | |
166 | The first component to create is the indicator. The | |
167 | <code>createcomponent</code> method creates the sub-widget and | |
168 | registers the widget as a component of this megawidget. It takes | |
169 | five arguments plus any number of keyword arguments. The arguments | |
170 | are name, aliases, group, class and constructor arguments. See the | |
171 | <a href="MegaArchetype.html">Pmw.MegaArchetype reference manual</a>) | |
172 | for full details. | |
173 | ||
174 | </p> | |
175 | ||
176 | <pre> | |
177 | # Create the indicator component. | |
178 | self.indicator = self.createcomponent('indicator', | |
179 | (), None, | |
180 | Tkinter.Frame, (interior,), | |
181 | width = 16, | |
182 | height = 16, | |
183 | borderwidth = 2, | |
184 | relief = 'raised') | |
185 | self.indicator.grid() | |
186 | </pre> | |
187 | ||
188 | <p> | |
189 | The scale component is created in a similar way. In this case, the | |
190 | initial value of the scale is also set to the value of the | |
191 | <strong>value</strong> initialisation option. | |
192 | ||
193 | </p> | |
194 | ||
195 | <pre> | |
196 | # Create the scale component. | |
197 | self.scale = self.createcomponent('scale', | |
198 | (), None, | |
199 | Tkinter.Scale, (interior,), | |
200 | command = self._doCommand, | |
201 | tickinterval = 20, | |
202 | length = 200, | |
203 | from_ = 100, | |
204 | to = 0, | |
205 | showvalue = 0) | |
206 | self.scale.grid() | |
207 | ||
208 | value = self['value'] | |
209 | if value is not None: | |
210 | self.scale.set(value) | |
211 | </pre> | |
212 | ||
213 | <p> | |
214 | At the end of the constructor, the <code>initialiseoptions</code> | |
215 | method is called to check that all keyword arguments have been used | |
216 | (that is, the caller did not specify any unknown or misspelled | |
217 | options) and to call the option callback functions. | |
218 | ||
219 | </p> | |
220 | ||
221 | <pre> | |
222 | # Check keywords and initialise options. | |
223 | self.initialiseoptions() | |
224 | </pre> | |
225 | ||
226 | <p> | |
227 | All other methods must now be defined. In this case, only one | |
228 | method is required - a method called whenever the scale changes and | |
229 | which sets the indicator color according to the threshold. | |
230 | ||
231 | </p> | |
232 | ||
233 | <pre> | |
234 | def _doCommand(self, valueStr): | |
235 | if self.scale.get() > self['threshold']: | |
236 | color = self['colors'][1] | |
237 | else: | |
238 | color = self['colors'][0] | |
239 | self.indicator.configure(background = color) | |
240 | </pre> | |
241 | ||
242 | <p> | |
243 | To complete the megawidget, methods from other classes can be | |
244 | copied into this class. In this case, all Tkinter.Scale methods | |
245 | not already defined by the megawidget are made available as methods | |
246 | of this class and are forwarded to the scale component. Note that | |
247 | the third argument to <code>Pmw.forwardmethods</code> is the name of | |
248 | the instance variable referring to the Tkinter.Scale widget and not | |
249 | the name of the component. This function is called outside of and | |
250 | after the class definition. | |
251 | ||
252 | </p> | |
253 | ||
254 | <pre> | |
255 | Pmw.forwardmethods(ThresholdScale, Tkinter.Scale, 'scale') | |
256 | </pre> | |
257 | ||
258 | <p> | |
259 | <strong>Important note:</strong> If a megawidget defines options | |
260 | using <code>defineoptions()</code>, then this method must be | |
261 | called in the megawidget constructor before the call to the base | |
262 | class constructor and a matching call to | |
263 | <code>initialiseoptions()</code> must made at the end of the | |
264 | constructor. For example: | |
265 | ||
266 | </p> | |
267 | <pre> | |
268 | def __init__(self, parent = None, **kw): | |
269 | optionDefs = ... | |
270 | self.defineoptions(kw, optionDefs) | |
271 | BaseClass.__init__(self, parent) | |
272 | ... | |
273 | self.initialiseoptions() | |
274 | </pre> | |
275 | ||
276 | </dd> | |
277 | <dt> <h3>Creating instances of the megawidget</h3></dt><dd> | |
278 | ||
279 | <p> | |
280 | The code below creates two of our example megawidgets. The first is | |
281 | created with default values for all options. The second is created | |
282 | with new values for the options. It also redefines some of the | |
283 | options of the components. | |
284 | ||
285 | </p> | |
286 | ||
287 | <dl> | |
288 | <dd> | |
289 | <pre> | |
290 | # Create and pack two ThresholdScale megawidgets. | |
291 | mega1 = ThresholdScale() | |
292 | mega1.pack(side = 'left', padx = 10, pady = 10) | |
293 | ||
294 | mega2 = ThresholdScale( | |
295 | colors = ('green', 'yellow'), | |
296 | threshold = 75, | |
297 | value = 80, | |
298 | indicator_width = 32, | |
299 | scale_width = 25) | |
300 | mega2.pack(side = 'left', padx = 10, pady = 10) | |
301 | </pre> | |
302 | </dd> | |
303 | </dl> | |
304 | ||
305 | <center><P ALIGN="CENTER"> | |
306 | <IMG SRC = scale2.gif ALT = "Scale 1" WIDTH=150 HEIGHT=244> | |
307 | </p></center> | |
308 | ||
309 | </dd> | |
310 | <dt> <h3>The complete code</h3></dt><dd> | |
311 | ||
312 | <p> | |
313 | The complete code for this example can be seen | |
314 | <a href="example.py">here</a>. | |
315 | ||
316 | </p> | |
317 | ||
318 | </dd> | |
319 | <dt> <h3>Exercises</h3></dt><dd> | |
320 | ||
321 | <p> | |
322 | These exercises build on the example presented so far. | |
323 | ||
324 | </p> | |
325 | ||
326 | <ol> | |
327 | <li> | |
328 | Change the call to create <code>mega1</code> so that the scale | |
329 | widget displays the current value next to the slider. (You may | |
330 | need to look at the Tk scale manual page to find which option to | |
331 | the <strong>scale</strong> component to set.) You will be able to | |
332 | do this without modifying the ThresholdScale class code. | |
333 | ||
334 | </li> | |
335 | <li> | |
336 | Add a Tkinter.Label component between the indicator and scale | |
337 | components. Modify the <code>_doCommand</code> method so that it | |
338 | displays the current value of the scale in this label. | |
339 | ||
340 | </li> | |
341 | <li> | |
342 | Modify the <strong>colors</strong> and <strong>threshold</strong> | |
343 | options so that they both accept a tuple. Now implement multiple | |
344 | thresholds, so that the indicator displays one of several colors, | |
345 | depending on the value of the scale. | |
346 | ||
347 | </li> | |
348 | <li> | |
349 | Add an <strong>orient</strong> initialisation option and lay out | |
350 | the components horizontally or vertically depending on its value. | |
351 | ||
352 | </li> | |
353 | <li> | |
354 | Read the description of the <code>createlabel()</code> method in | |
355 | the <a href="MegaArchetype.html">Pmw.MegaArchetype reference | |
356 | manual</a> and add <strong>labelpos</strong> and | |
357 | <strong>labelmargin</strong> initialisation options which allow | |
358 | the creation of a label for the megawidget. | |
359 | ||
360 | </li> | |
361 | </ol> | |
362 | ||
363 | <p> | |
364 | An example of how these changes can be made can be seen | |
365 | <a href="exercises.py">here</a>. | |
366 | ||
367 | </p> | |
368 | ||
369 | </dd> | |
370 | <dt> <h3>Contributing your megawidgets to Pmw</h3></dt><dd> | |
371 | ||
372 | <p> | |
373 | If you have completed a megawidget that may be useful to others, you | |
374 | may like to consider contributing it to Pmw. See | |
375 | <a href="starting.html#contributions">Contributions welcome</a> for | |
376 | how to contribute. | |
377 | ||
378 | </p> | |
379 | ||
380 | </dd> | |
381 | <dt> <h3>Pmw coding conventions</h3></dt><dd> | |
382 | ||
383 | <p> | |
384 | As a final note, the Pmw code makes an attempt to follow these coding | |
385 | conventions. | |
386 | </p> | |
387 | ||
388 | <ul> | |
389 | <li> | |
390 | Class names: initial of each word is upper case (including first word). | |
391 | ||
392 | </li> | |
393 | <li> | |
394 | Public method and function names: all in lower case. | |
395 | ||
396 | </li> | |
397 | <li> | |
398 | Megawidget options: all in lower case. | |
399 | ||
400 | </li> | |
401 | <li> | |
402 | Megawidget component names: all in lower case. | |
403 | ||
404 | </li> | |
405 | <li> | |
406 | Function arguments: initial of each word is upper case (except first word). | |
407 | ||
408 | </li> | |
409 | <li> | |
410 | Private names: initial of each word is upper case (except first | |
411 | word if not a class) | |
412 | ||
413 | </li> | |
414 | <li> | |
415 | Underscores as word separators are only used when overriding | |
416 | Tkinter methods of same name. | |
417 | ||
418 | </li> | |
419 | <li> | |
420 | Indent is four spaces. | |
421 | ||
422 | </li> | |
423 | <li> | |
424 | Continuation lines are indented by eight spaces, so that they | |
425 | won't be confused with a following nested code block. | |
426 | Continuation lines should be used when a statement, which would | |
427 | normally be written on one line, is longer than 80 characters. | |
428 | Examples are "if" statements which contain many conditions and | |
429 | function calls with many arguments. | |
430 | ||
431 | </li> | |
432 | <li> | |
433 | ||
434 | Surround <code>=</code> with spaces when used with keyword | |
435 | parameters in function calls. | |
436 | ||
437 | </li> | |
438 | <li> | |
439 | ||
440 | Multi-line function calls should have one keyword parameter per | |
441 | line. | |
442 | ||
443 | </li> | |
444 | </ul> | |
445 | </dd> | |
446 | </dl> | |
447 | ||
448 | ||
449 | <center><P ALIGN="CENTER"> | |
450 | <IMG SRC = blue_line.gif ALT = "" WIDTH=320 HEIGHT=5> | |
451 | </p></center> | |
452 | ||
453 | ||
454 | <font size=-1> | |
455 | <center><P ALIGN="CENTER"> | |
456 | Pmw 1.2 - | |
457 | 5 Aug 2003 | |
458 | - <a href="index.html">Home</a> | |
459 | ||
460 | </p></center> | |
461 | </font> | |
462 | ||
463 | </body> | |
464 | </html> | |
465 |