How To Create Buttons

P.J. Hinton
Mathematica Programming Group
Wolfram Research, Inc.

Buttons provide a means of creating highly interactive notebooks; however, writing your own buttons to perform nonstandard tasks can be frustrating. This talk will discuss buttons from a fundamental viewpoint and demonstrate how to create and test them.  The audience is assumed to have a basic understanding of the box expressions used in Mathematica's math editing environment, notebook manipulation commands, and pure functions.

Typesetting Structure

A ButtonBox delineates a block of styled text that can be clicked with a mouse pointer to perform an action.  It forms the basis of buttons and other classes of active elements.

The syntax for a ButtonBox is:

ButtonBox[contents]

A ButtonBox may appear within cells that contain plain text (TextData) or two-dimensional typeset text (BoxData).

The following examples illustrate how a ButtonBox may appear within a cell expression and what it looks like on screen.

Here is a cell expression with plain text content.

Cell[
  TextData[{
    "some text with a ",
    ButtonBox["button"],
    " in it"
  }], 
  "Text",
  TextAlignment->Center
]

This is what it looks like the cell is formatted by the front end.  It is indistinguishable from the surrounding text.  If you move your mouse pointer over the word "button," the cursor will turn into an arrow as if it were clickable.

some text with a button in it

Here is a cell expression with two-dimensional typeset content.

Cell[
  BoxData[
    FractionBox[
      "a", 
      ButtonBox["b"]
    ]
  ], 
  "Text",
  TextAlignment->Center
]

When the box data expression is rendered by the front end, the button appears as a gray box with a raised effect.  When the mouse pointer is moved over the gray region, it does not change appearance.

(a/b)

We will use the term “button” as a synonym for ButtonBox.

Creating a Button

There are three ways to create a button: through a kernel function, through direct box input, or through a specialized menu command.

Direct Box Input

The Mathematica notebook front end provides a dialog box for entering low-level box structures.  It can be summoned with  the front end menu command Edit -> Expression Input -> Insert Raw Expression… or with the keystroke shortcut Ctrl[⋆].

When the user dismisses the dialog box with the Insert button, the front end will place the box data input wherever the text cursor resides.  If a horizontal cell insertion point is present in the notebook, a new cell with the notebook's default cell style will be created.

Specialized Front End Menu Command

The front end also provides the menu command Input -> Create Button, which is useful for creating active elements with predefined attributes.  We will discuss this feature in greater depth once we are more familiar with the details of tuning buttons.

Kernel-based Expression Formatting

The kernel function DisplayForm can be used to generate a button from its raw typeset form.

ButtonBox["a test button"] //DisplayForm

A ButtonBox expression created this way will always appear within a two-dimensional typeset environment.

Of the approaches discussed here, this is probably the most useful for the purposes of prototyping more complicated buttons.  The other methods are less forgiving, meaning that you can lose your work by supplying incorrect input.

Controlling Button Behavior

A button is designed to perform an action when you click on it.  Options to the button can be added to specify what piece of Mathematica will perform the action and what will be done.

ButtonEvaluator

The option ButtonEvaluator specifies what part of Mathematica is performing the action.  The default value of None indicates that the action will be performed by the front end entirely.  If the action is to be performed in the notebook's kernel, the option can be set to the value Automatic.  Any kernel defined as an evaluator in the front end may be used as well.

Here is a way to obtain the value of EvaluatorNames using the kernel.

EvaluatorNames /.Options[$FrontEnd, EvaluatorNames] /.(name_ -> _) -> name //TableForm

ButtonFunction

The option ButtonFunction specifies what will be done when you click on the button.  The function may accept anywhere from zero to two arguments.  Here is a button with a trivial button function.

ButtonBox[
  "a button that does nothing whatsoever",
  ButtonEvaluator -> Automatic,
  ButtonFunction :> (
    CompoundExpression[
      {#1, #2}, 
      Return[Null]
    ]&
  )
]

Note that for functions which accept arguments, the option value must be written as a pure function.  It is customary to write this option with the RuleDelayed (:>) operator to ensure that the option value is not evaluated during formatting operations like DisplayForm.  Since the postfix Function operator has a lower precedence than RuleDelayed, you must group your pure function in parentheses.

If the function is a compound expression, the function may need to be rewritten using  FullForm so that the front end parses the expression correctly.  When you generate a button with DisplayForm, FullForm is used in the ButtonFunction.

In the next example, we enter a compound expression using the traditional semicolon infix operatior.

ButtonBox["frog",ButtonEvaluator->Automatic,ButtonFunction:>(foo;bar;woof)]// DisplayForm

Here is the underlying cell expression for the button after it has been formatted with DisplayForm.  Notice that the right-hand side of the button function has been written in FullForm.

Cell[
  BoxData[
    TagBox[
      ButtonBox[
        "frog",
        ButtonFunction :>
          CompoundExpression[foo,bar,woof],
        ButtonEvaluator -> Automatic
      ],
    DisplayForm
  ],
  "Output"
]

The default value of this option is a set of front end operations that paste the contents of the button at the current selection point in the input notebook.

Creating button functions can be a tricky task all by itself.  Before coding the actual button structure, think about what types of operations you will need to perform.  If they can be performed without processing the structure of a Mathematica expression, have them evaluate entirely in the front end.  On the other hand, actions which may require sophisticated manipulations like pattern matching and string processing can be evaluated only in the kernel.

Some button operations may require executing operations which are accessible via a menu command.  These commands can be scripted in button functions through front end tokens.  The Mathematica book touches on this subject only slightly.  A more thorough discussion can be found in Chapter 63 of the book The Beginner's Guide to Mathematica Version 4 by Jerry Glynn and Theodore W. Gray.  There is an extensive listing of tokens at the end, including notes about which ones are platform specific and  which ones may not be supported in future releases of Mathematica.

ButtonSource

In the previous section, we mentioned that a button's function could take up to two arguments.  The front end will insert the value of the ButtonSource option into the first slot of the button function.  The recognized option values are:

The option ButtonSource is useful when your button function needs to operate on a portion of the button's notebook as part of its operation.

You may note that the default value of ButtonSource is Automatic and that this will resolve to ButtonData if that option is set in the ButtonBox.  At first glance, you may get the impression that you would never have to set ButtonSource to have the value ButtonData.  In most cases, this may be true; however, it is possible to construct a pair of buttons that demonstrates a non-trivial difference.

Here is a ButtonBox expression which moves the current selection in the notebook to be after the button's cell and then pastes the value of ButtonSource at that location.  Here, there is no explicit setting for ButtonData.

Here is a second button where there is an explicit setting for ButtonData.

We now create a notebook expression where ButtonData is set to a value at the Notebook level.

If you evaluate the three cells above in order and then click on the buttons in the resulting notebook, you'll see that they give different results.  Specifically, the first button prints bar and the second prints foo.  Why is that so?

In the first button, ButtonSource is not set explicitly, so it falls back on the default value of Automatic.  Since there is no explicit value of ButtonData, the front end uses the contents of the button, which is bar.  In the second button we have set ButtonSource explicitly to ButtonData.   The front end then inherits the value of ButtonData that is set at the notebook level (note the ButtonBoxOptions setting).  The bottom line is that if you have the possibility that you will need to inherit the ButtonData option from a higher level, you need to set ButtonSource to be ButtonData.

The next example demonstrates how one can use ButtonSource to extract notebook options.  In this case, we print the value of FrontEndVersion, which is an option that is used to tag the platform on which the notebook is being edited.

ButtonBox[
"what platform created this notebook?",
ButtonFunction:>(
Print[ReplaceAll[FrontEndVersion, Apply[List,Rest[#]]]]&
), 
ButtonSource -> Notebook, 
ButtonEvaluator -> Automatic, 
Active -> True
] //DisplayForm

ButtonData

The option ButtonData specifies what will be supplied as the second argument to a button function.  The value for this option can be any valid Mathematica expression.   If it is not specified, the front end assumes that the contents of the button will be ButtonData.

It is possible even to define a button function that works only with ButtonData.  The correct way to code such a function is to express all explicit references to slot 2.  Here is an example that underscores this point.   This first code block will paste the value of ButtonData at the current selection in the notebook.

ButtonBox[
  "foo", 
  ButtonFunction :> (
    FrontEndExecute[
      FrontEnd`NotebookApply[
        FrontEnd`InputNotebook[],
        #
      ]
    ]&
  ),
  ButtonData -> "bar"
]

However, this formulation is fragile because it uses slot 1 as the insertion point for the ButtonData.  The only reason that this works is that the default value of the option ButtonSource is set to ButtonData.  If ButtonSource is accidentally set with an explicit value other than ButtonData, the button will no longer operate as the programmer intended.  A more robust way to write this button is shown in the next example

ButtonBox["foo", 
   ButtonFunction :> (
      FrontEndExecute[
        FrontEnd`NotebookApply[
          FrontEnd`InputNotebook[],
          #2
        ]
      ]&
   ),
   ButtonData -> "bar"
]

The function always uses ButtonData regardless of the value of ButtonSource.

Active

The option Active is a boolean switch which determines whether the ButtonFunction will be performed when it is clicked.  Setting the option to True means that the function will be evaluated.  Setting the option to False will mean that the function will not be evaluated.  The default value of this option is False.

The Active option can be set at the cell level, using the front end menu command sequence Cell ->  Cell Properties -> Cell Active.  A button appearing within the cell will inherit this value if the Active option does not appear explicitly in the ButtonBox expression.   A button which is active cannot be edited as in the example cell that follows.

Here is an active cell.  You cannot edit the button's contents.

cannot edit this button

Controlling Button Appearance

Since buttons can play different interface roles, options are available for controlling what the appearance of the button should be.

ButtonFrame

Buttons typically play one of three roles in Mathematica –  a toolbar element, a dialog box widget, or a hyperlink.  The option ButtonFrame allows the programmer to specify the appearance of the button that best befits the button's functional role.  This option applies only to BoxData expressions, and the default value is "Palette".  As noted earlier, buttons appearing in TextData expressions do not have any distinguishing appearance.  In essence, the button looks as if ButtonFrame has been set to None regardless of its actual setting.

The following table provides a side-by-side comparison of the differences between the frame styles.

The actual appearance of these buttons will vary according to windowing environment.  On most platforms, the appearances generated by the values "Palette" and "DialogBox" will be the same.  The most notable exception is MacOS.

Background

The option Background can be used to specify the color which will be used in the body of the button.  The value of the option is any valid color directive that you could use in a graphics object.  As with ButtonFrame, this option only applies when the button resides within a BoxData expression.

This example creates a button with a violet background.

ButtonBox[
"violet button",
Background -> RGBColor[1, 0, 1]
] // DisplayForm

You can experiment with other colors by using the color selector dialog.

The behavoir of this option finds an unorthodox application as a way to color the backgrounds of table (GridBox) elements.  Here is an example that creates a one-column table which has different colors for each element.

DisplayForm[
GridBox[
{ButtonBox[
StyleBox[#,
FontFamily -> "Times",
FontColor -> PrussianBlue
],
Background -> ToExpression[#],
ButtonExpandable -> True
]
}& /@ {
"Salmon",
 "OrangeRed", 
"Khaki", 
"LimeGreen",
"TurquoiseBlue", 
"Orchid"
},
GridFrameMargins -> False,
GridFrame -> True,
RowLines -> True,
ColumnLines -> True,
RowSpacings -> 0]
]

ButtonNote

This option specifies what text will be shown in the notebook status area when the mouse pointer appears over the button.  The default value is ButtonData.  Here is an example that creates a button with a note.

ButtonBox[
"⌚", 
ButtonNote-> "do nothing",
 ButtonFunction :>FrontEndExecute[{}],
Active -> True
] // DisplayForm

If you don't want a note to appear, you can set the option to None.  This option is useful for providing functional hints for smaller buttons.  Care should be taken to keep the length of the message to a minimum as the size of the status area is not uniform across platforms.  Notebook windows on MacOS and Windows tend to have narrower areas.

ButtonMinHeight

This option specifies the minimum total height of a button frame – in units of font size – that should be allowed for the button.  Setting the option to 1 forces the button to have a height that will accomodate all glyphs from the font, regardless of whether they are present.  A value of 0 makes the frame shrink to accomodate the size of the tallest glyph.  The default value is 1.

Here is an example that demonstrates the difference.  Note that the height of left button is taller than the right button.  The left button remains tall enough to accomodate the glyphs that are larger than lowercase “x.”

RowBox[{
ButtonBox["x", ButtonMinHeight -> 1],
ButtonBox["x", ButtonMinHeight -> 0]
}
] // DisplayForm

This option is useful when you are dealing with clusters of buttons that appear adjacent to one another.  Setting this option to 1 ensures uniformity in height.

RowBox[
{
"bad",
ButtonBox["a", ButtonMinHeight -> 0],
ButtonBox["Z", ButtonMinHeight -> 0],
"\n",
"good",
ButtonBox["a", ButtonMinHeight -> 1],
ButtonBox["Z", ButtonMinHeight -> 1]
}
] // DisplayForm

For the case where a button appears within a GridBox, and ButtonMinHeight is set to 0, each button will be automatically adjusted to accomodate the height of the tallest glyph in the row.  This helps to guarantee uniformity within a given row.

GridBox[
{
{
"now okay",
ButtonBox["a", ButtonMinHeight -> 0],
ButtonBox["Z", ButtonMinHeight -> 0]
},
{
"good",
ButtonBox["a", ButtonMinHeight -> 1],
ButtonBox["Z", ButtonMinHeight -> 1]
}
}
] // DisplayForm

ButtonExpandable

The option ButtonExpandable specifies whether button frames within a GridBox column should expand to match the width of the widest set of button contents.  The values for this option are True or False.

These two examples demonstrate the impact of this option.  The first example is for the case where ButtonExpandable is set to True.

({{Hello,world.}, {The quick brown fox jumped over the lazy sleeping dog.}, {Launch the Mathematica kernel.}})

The second example shows what happens when it is set to False.

({{Hello, world.}, {\(The\ quick\ brown\ fox\ jumped\ over\ the\ lazy\ sleeping\ \(\(dog\)\(.\)\)\)}, {Launch the Mathematica Kernel}})

ButtonMargins

This option specifies the amount of space to leave between the between the borders of the button's frame and the button's contents.  The value is measured in printer's points, and the default is 3.

Reusing Code

Inheriting Options through ButtonStyle

So far, we have created rather simple examples of buttons on a case-by-case basis.  The actions defined for each button have been included explicitly as options.  For larger projects, this development model is very awkward because one must repeatedly enter unique button functions.  It also leads to bloated notebooks.

To avoid this repetitious state of affairs, the option ButtonStyle exists.  It specifies that the front end should look to the notebook's style sheet for options to inherit.  This allows you to define a behavior for a class of buttons and centralize the definition of the class in one location.  Hyperlinks to other notebooks and to the online documentation are designed to work this way.

To define a style for a button, you will need to create a new cell that has the following format

Cell[
  StyleData["style name"],
  normal options
  StyleMenuListing -> None,
  ButtonStyleMenuListing -> Automatic,
  ButtonBoxOptions -> {
    options specific to buttons
  }
]

The three mandatory options are StyleMenuListing, ButtonStyleMenuListing, and ButtonBoxOptions.  The first option suppresses listing of the style in the front end Format -> Style menu.  The  second option ensures that the style appears on the Input -> Create Button menu.  The final option specifies what options should be inherited by a button that uses this style.  Once you have created the cell, wrap it in CellPrint and evaluate it.  The result is a formatted cell which may be pasted into a style sheet notebook.

Creating Button Instances Using Front End Menus

Evaluation in Place

You may create buttons empoying styles with commands under the front end Input menu.

In the following example,  make a button that evaluates selected text in place much as they are used in the AlgebraicManipulation palette.  This is accomplished through the Evaluate button style.  If we look at the style sheet prototype for this style, we see that it has the following function.

ButtonFunction :> (
  FrontEndExecute[ 
    {
      FrontEnd`NotebookApply[ 
        FrontEnd`InputNotebook[ ], #, All
      ], 
      SelectionEvaluate[ 
        FrontEnd`InputNotebook[ ], All
      ]
    }
  ]&
)

Note that the function handles only one argument – the ButtonSource.  In this case, there is no explicit setting for ButtonSource, so it falls back to ButtonData.  There is no explicit setting for ButtonData, so the button's contents are used.  All we need to do is to supply the wrapper function with a selection placeholder character residing where the selection's contents will be placed for evaluation.

The first step is to create the text that will serve as the anchor for the button.

Now we select the text and peform the menu command sequence Input -> Create Button ->  Evaluate

The result is the creation of the following low level box structure.

ButtonBox[
  RowBox[
    {"Inverse", "[", "■", "]"}
  ],
  ButtonStyle->"Evaluate"
]

If we now activate the button with the front end menu command Cell -> Cell Properties -> Cell Active, we can now see how it works.  Make a copy of this cell and select the matrix therein.  Now try clicking on the button we created above.

Setting up a Link to a Online Documentation

The process of creating links to online documentation is a little more complicated than straight hyperlinks, which is demonstrated in the Getting Started guide for Mathematica.

Before you can create the button, you must know the following information: what branch of the online help is being accessed, what text must be entered in the Go To field (the IndexTag) and to what tagged cell must be scrolled in the documentation. (the CopyTag).

Suppose we wanted to create a hyperlink to point to the list of pre-defined button styles.  We first open up our Help Browser.  Next, we set the help branch to The Mathematica Book.  Portions of the Mathematica book are retrieved through their section numbers.  For our case, we want Section 2.10.6.  Our IndexTag is then "2.10.6".

We can use the front end menu command Find -> Show Cell Tags to make the cell tags visible as tiny blue labels preceding each cell.  The unique tag for the definition box is 6.9, so our CopyTag has a value of "6.9".

With the relevant information available, we can now create the link.  The first step is to enter the text that will serve as an anchor for the hyperlink.  We then select the anchoring text and perform the front end menu command Input -> Create Button -> MainBookLink.  The button box is now embedded.

The underlying button function expects as a ButtonData value either the IndexTag string or a list containing the IndexTag and CopyTag strings respectively.  We can add these option values using the dialog box produced by the front end menu command Input -> Edit Button.  Before doing this operation, we must reselect the link.  This is done by carefully drag selecting the text from right to left.  Once the dialog is open, enter the list in the ButtonData field and then click on the Apply button to embed the option.

Here is a link to the list of pre-fab button styles.

Examples

Input Style Cell Group Closer

This button opens and closes cell groups that are headed by an Input style cell. In uses NotebookFind to track down all cells of a given style.  After the cells are located, a command token is issued to perform the equivalent of Cell -> Cell Grouping -> Open All Subgroups.

ButtonBox[
"close all input cell groups",
ButtonEvaluator->None,
ButtonFunction:>
FrontEndExecute[
{
FrontEnd`NotebookFind[
FrontEnd`InputNotebook[],
"Input",All,CellStyle
],
FrontEndToken[
FrontEnd`InputNotebook[],"SelectionCloseAllGroups"
]
}
],
Active -> True
] //DisplayForm

To make this button available to all notebooks, drag select it and then use the front end menu command File -> Generate Palette from Selection.

Version Stamp

This button is designed for providing a date/version stamp for notebooks with bug reports.  It uses MathLink packet trips to request information from the front end and then writes the results in new cell.  Because the ouput is generated by assembling a cell expression, the kernel must play the role of evaluator.

We make use of undocumented front end packets here.

Here is an example of how one can use FrontEnd`ToFileName to resolve paths used by the front end.

LinkWrite[
$ParentLink,
FrontEnd`ToFileName[
FrontEnd`FileName[{$PreferencesDirectory}]
]
];LinkRead[$ParentLink]

The philosophy of FrontEnd`FileName objects are discussed in Glynn and Gray, Chapter

Here is a means by which one can get the date from the front end.

LinkWrite[
$ParentLink,
FrontEnd`Value["DateLong"]
];LinkRead[$ParentLink]

You can find the names of values that may be plugged into a FrontEnd`Value packet in the menu that is summoned via the menu command Input -> Create Value Display Object…  Packet trips can resolve only those setttings which are meaningful at the global level.

ButtonBox[
"Information Stamp",
ButtonEvaluator -> Automatic,
ButtonFunction :> 
Module[
{topdir, vers, creatdate, machname},
topdir = (
LinkWrite[
$ParentLink, 
FrontEnd`ToFileName[
FrontEnd`FileName[{$TopDirectory}]
]
];
LinkRead[$ParentLink]
);
{vers, creatdate ,machname}  = (
LinkWrite[
$ParentLink,
FrontEnd`Value[#]
];
LinkRead[$ParentLink]
)& /@ {"$FullVersion","$CreationDate", "$MachineName"};
 NotebookWrite[
InputNotebook[],
 Cell[
BoxData[
FormBox[
GridBox[
{
{"Installation Directory", topdir},
{"Front End Version", vers},
{"Creation Date",creatdate},
{"Machine Name", machname}
}
], 
TextForm
]
],
"Text"
]
]
],
Active -> True
] //DisplayForm

Toggling Button

In this example, we create a button that toggles between two states.  The state of the button is recorded in the button cell's CellTags option.  The button function as a separate kernel function.  The cell acts as a container for the button-specific options through the aggregate option ButtonBoxOptions.

The button function ToggleColor first locates the target cell and obtains the value of ButtonBoxOptions.  It then swaps "red" for "green" (and vice versa) in the cell tag list, updates the cell tags, and reverts the button's color according to the state.

Evaluate both cells below and then click on the resulting button to see the toggling effect.

Form-Based Assignment

In the next example, we demonstrate how to use ButtonSource and TagBox to make a form for assigning values to symbols.  Interfaces such as this are useful in the development of courseware.

To create the button, evaluate this cell.  Enter a value in the selection placeholder and click on the “do assignment.”

Now let's be clever about it and create a button style and generator function.  Here is the style definition prototype cell for the button.

The resulting prototype can be pasted into a notebook's style sheet.  Now we write a function to make the creation of these buttons a single-step process.

Paste this expression in a notebook whose style sheet has been modified and evaluate it to create a bank of buttons.

Style Selector Palette

The following example shows how to create a button that changes the style setting for a cell or a block of text.  In this case, we are using the "style" token to wrap a StyleBox around some selected text.

ButtonBox[
"InlineInput",
ButtonEvaluator -> None,
ButtonFunction :> 
FrontEndExecute[
{
FrontEndToken[
FrontEnd`InputNotebook[],
 "Style", 
"InlineInput"
]
}
],
Active -> True
] //DisplayForm

Here is some text for testing the button.  Select a portion of the text and then click on the button.  The selected text should now be shown in the InlineInput cell style.

We can be really clever and generalize this into a button style as shown below.

Using the approach described eariler, we can copy the formatted cell and paste the cell into a private style sheet.

We can now create a vertical grid of buttons that use this style.  To do this, open a new notebook, paste the following expression in an Input cell and evaluate it.

Copy the prototype style generated above and paste it into the new notebook's style sheet.

Note that if you try to turn this grid of buttons into a palette using the front end menu command File -> Generate Palette from Selection, the buttons on the palette will not work the same way.  The palette knows only about button styles defined in the Default style sheet.  It does not share any information about styles defined in the target notebook.  Getting around this problem is discussed in greater detail below.

Addendum – Adding Button Style to Palette Notebook

You can correct the missing style problem above by adding your custom button styles to the palette's style sheet.  Here is one way to do this:

1) Click on the front end menu command sequence Edit -> Preferences… to summon the Option Inspector dialog.

2) Change the drop down list indicator for Window to reflect the name of the palette and the indicator for Scope to be notebook.

3) Make sure that you are viewing options by category.

4) Open up the expandable menu for Editing Options so that the setting for Editable is visible.  Change this value from False to True.

5) Open up the expandable menu for Window Properties so that the setting for WindowClickSelect is visible.  Change this value from False to True.

6) Open up the expandable menu for Window Properties so that the setting for WindowClickSelect is visible.  Change this value from False to True.

7) Close the Option Inspector dialog window.

8) With the palette window as the selected notebook, click on the front end menu command sequence Format -> Edit Style Sheet…  This will present you with a dialog box.  Click on the button that reads Import Private Copy or Import and Edit Styles.

9) Paste the cell prototype for the custom button style into the notebook in an appropriate location, such as the Palette Styles section.

10) Close the style sheet notebook window.

11) Click on the front end menu command sequence Edit -> Preferences… to summon the Option Inspector dialog once again.

12) Change the drop down list indicator for Window to reflect the name of the palette and the indicator for Scope to be notebook.

13) Make sure that you are viewing options by category.

14) Open up the expandable menu for Editing Options so that the setting for Editable is visible.  Change this value from True to False.

15) Open up the expandable menu for Window Properties so that the setting for WindowClickSelect is visible.  Change this value from True to False.

16) Close the Option Inspector dialog window.