# How to build Pmw megawidgets

### Introduction

  This document briefly describes how to design and code Pmw megawidgets by inheriting from the Pmw base classes. It shows step by step how to build a simple example megawidget. This megawidget allows the user to select one of a range of numbers and it also indicates if the selected number is greater than a given threshold.

### Choosing the components

  The megawidget will be built using a tkinter.Scale widget to allow the user to select a number in a range, and a tkinter.Frame widget to act as an indicator, displaying red (say) if the selected number exceeds the threshold. It will look something like this:

  The programmer using this megawidget will need access to the scale widget, since they will need to set the scale's range. Therefore the scale will be made a component of the megawidget. The programmer will probably not need access to the indicator frame, but, just in case the need arises to change the borderwidth or relief of the indicator, we will make it a component too. This illustrates a convention about components - for maximum configurability, make all sub-widgets components.

### Choosing the options

  Apart from the component options now available through the scale and indicator components, the megawidget will need a few options of its own. It will need a threshold option to set the threshold. It may also need options to set the colors of the indicator when the selected value is both above and below the threshold. Other options could be orient or indicatorpos to specify the relative position of components and margin, padx or pady to specify spacing between and around the components. For this example, we will define three options - threshold, colors and value. The colors option will be a 2-element sequence specifying two colors (below threshold, above threshold). The value option will be the initial value of the scale.

### Coding the megawidget

  The first things to do are to decide on a name for the new megawidget, decide which base class to inherit from and to begin to write the constructor. Most Pmw megawidgets are derived from either Pmw.MegaWidget, Pmw.MegaToplevel or Pmw.Dialog. In this case, since the widget is not to be contained within its own toplevel window, we will inherit from Pmw.MegaWidget. The constructors of megawidgets take one argument (the widget to use as the parent of the megawidget's hull, defaulting to the root window) and any number of keyword arguments.

      class ThresholdScale(Pmw.MegaWidget):
          """ Megawidget containing a scale and an indicator.
          """

          def __init__(self, parent = None, **kw):

  Next, we need to define the options supplied by this megawidget. Each option is specified by a 3-element sequence. The first element is the option's name. The second element is the default value. The third element is either a callback function, Pmw.INITOPT or None. In the first case, the function is called at the end of construction (during the call to `self.inialiseoptions`) and also whenever the option is set by a call to `configure`. Pmw.INITOPT indicates that the option is an initialisation option - it cannot be set by calling `configure`. None indicates that the option can be set by calling `configure`, but that there is no callback function.

  The call to `self.defineoptions` also includes the keyword arguments passed in to the constructor. The value given to any option specified in the keywords will override the default value.

              # Define the megawidget options.
              optiondefs = (
                  ('colors',    ('green', 'red'), None),
                  ('threshold', 50,               None),
                  ('value',     None,             Pmw.INITOPT),
              )
              self.defineoptions(kw, optiondefs)

  After defining the options, the constructor of the base class should be called. The options need to be defined first so that a derived class can redefine the default value of an option defined in a base class. This is because the value specified by the derived class must be made available before the base class constructor is called. The keyword arguments should not be passed into the base class constructor since they have already been dealt with in the previous step.

              # Initialise base class (after defining options).
              Pmw.MegaWidget.__init__(self, parent)

  Now we should create the components. The components are created as children (or grandchildren ...) of the megawidget's interior.

              # Create the components.
              interior = self.interior()

  The first component to create is the indicator. The `createcomponent` method creates the sub-widget and registers the widget as a component of this megawidget. It takes five arguments plus any number of keyword arguments. The arguments are name, aliases, group, class and constructor arguments. See the Pmw.MegaArchetype reference manual) for full details.

              # Create the indicator component.
              self.indicator = self.createcomponent('indicator',
                      (), None,
                      tkinter.Frame, (interior,),
                              width = 16,
                              height = 16,
                              borderwidth = 2,
                              relief = 'raised')
              self.indicator.grid()

  The scale component is created in a similar way. In this case, the initial value of the scale is also set to the value of the value initialisation option.

              # Create the scale component.
              self.scale = self.createcomponent('scale',
                      (), None,
                      tkinter.Scale, (interior,),
                              command = self._doCommand,
                              tickinterval = 20,
                              length = 200,
                              from_ = 100,
                              to = 0,
                              showvalue = 0)
              self.scale.grid()

              value = self['value']
              if value is not None:
                  self.scale.set(value)

  At the end of the constructor, the `initialiseoptions` method is called to check that all keyword arguments have been used (that is, the caller did not specify any unknown or misspelled options) and to call the option callback functions.

              # Check keywords and initialise options.
              self.initialiseoptions()

  All other methods must now be defined. In this case, only one method is required - a method called whenever the scale changes and which sets the indicator color according to the threshold.

          def _doCommand(self, valueStr):
              if self.scale.get() > self['threshold']:
                  color = self['colors'][1]
              else:
                  color = self['colors'][0]
              self.indicator.configure(background = color)

  To complete the megawidget, methods from other classes can be copied into this class. In this case, all tkinter.Scale methods not already defined by the megawidget are made available as methods of this class and are forwarded to the scale component. Note that the third argument to `Pmw.forwardmethods` is the name of the instance variable referring to the tkinter.Scale widget and not the name of the component. This function is called outside of and after the class definition.

      Pmw.forwardmethods(ThresholdScale, tkinter.Scale, 'scale')

  Important note: If a megawidget defines options using `defineoptions()`, then this method must be called in the megawidget constructor before the call to the base class constructor and a matching call to `initialiseoptions()` must made at the end of the constructor. For example:

          def __init__(self, parent = None, **kw):
      	optionDefs = ...
      	self.defineoptions(kw, optionDefs)
      	BaseClass.__init__(self, parent)
      	...
      	self.initialiseoptions()

### Creating instances of the megawidget

  The code below creates two of our example megawidgets. The first is created with default values for all options. The second is created with new values for the options. It also redefines some of the options of the components.

        # Create and pack two ThresholdScale megawidgets.
        mega1 = ThresholdScale()
        mega1.pack(side = 'left', padx = 10, pady = 10)

        mega2 = ThresholdScale(
                colors = ('green', 'yellow'),
                threshold = 75,
                value = 80,
                indicator_width = 32,
                scale_width = 25)
        mega2.pack(side = 'left', padx = 10, pady = 10)

### The complete code

  The complete code for this example can be seen here.

### Exercises

  These exercises build on the example presented so far.

  1. Change the call to create `mega1` so that the scale widget displays the current value next to the slider. (You may need to look at the Tk scale manual page to find which option to the scale component to set.) You will be able to do this without modifying the ThresholdScale class code.
  2. Add a tkinter.Label component between the indicator and scale components. Modify the `_doCommand` method so that it displays the current value of the scale in this label.
  3. Modify the colors and threshold options so that they both accept a tuple. Now implement multiple thresholds, so that the indicator displays one of several colors, depending on the value of the scale.
  4. Add an orient initialisation option and lay out the components horizontally or vertically depending on its value.
  5. Read the description of the `createlabel()` method in the Pmw.MegaArchetype reference manual and add labelpos and labelmargin initialisation options which allow the creation of a label for the megawidget.

  An example of how these changes can be made can be seen here.

### Contributing your megawidgets to Pmw

  If you have completed a megawidget that may be useful to others, you may like to consider contributing it to Pmw. See Contributions welcome for how to contribute.

### Pmw coding conventions

  As a final note, the Pmw code makes an attempt to follow these coding conventions.

  - Class names: initial of each word is upper case (including first word).
  - Public method and function names: all in lower case.
  - Megawidget options: all in lower case.
  - Megawidget component names: all in lower case.
  - Function arguments: initial of each word is upper case (except first word).
  - Private names: initial of each word is upper case (except first word if not a class)
  - Underscores as word separators are only used when overriding tkinter methods of same name.
  - Indent is four spaces.
  - Continuation lines are indented by eight spaces, so that they won't be confused with a following nested code block. Continuation lines should be used when a statement, which would normally be written on one line, is longer than 80 characters. Examples are "if" statements which contain many conditions and function calls with many arguments.
  - Surround `=` with spaces when used with keyword parameters in function calls.
  - Multi-line function calls should have one keyword parameter per line.

Pmw 2.1 - 31 Dec 2020 - Home