grid-widget

Class

The class of all grid-widget controls.

Control

An instance of the grid-widget class. This is a very
versatile (and therefore relatively complex) widget that is useful for
implementing things such as spreadsheets and tables. Grid cells can
either use built-in cell widgets or implement custom drawing and
mouse/keyboard behavior.

A grid-widget may be divided into multiple sections, each of which may
be independently scrollable and resizable. Within each section are
rows and columns that are collectively known as subsections.
(Sections are sometimes referred to as main sections to
distinguish them from subsections.) Rows and columns may be
interactively resized, moved (to change their order), selected, and
deleted, whenever each of those options is enabled. Rows and columns
may be added and removed programmatically at any time. A row or
column can have its own individual stylistic properties, and can also
be replicated to produce a set of identical visual rows or columns
that are represented by a single row or column lisp object.

Each section, row, and column is implemented as a standard-object. A grid cell does not
have a Lisp object to represent it because the number of cells
increases quadratically as the rows and columns increase. Instead, a
cell is accessed by methods that specialize on both the row and column
whose intersection defines the cell.

Some illistrations

The Examples tab of the Navigator Dialog has sevel Grid
widgets examples. We illustrate some here.

Here is Grid-widget: the simplest possible
grid-widget-example. A simple grid. Left click on a cell and an X
is marked (as we have done with several). Right click and the X goes
away. Note the scroll bars. The widget is bigger than the window
displaying it.

Here is a more complex example Grid-widget: using built-in cell
widgets in ARBITRARY cells.

All these examples (and there are several more) are available to run
and the code the generates them can be examined and adapted or
reused. See the Examples tab of the Navigator Dialog.

Writing Grid-Widget Code

Using a grid-widget consists of constructing the grid-widget from
sections and subsections, specifying various stylistic properties, and
(the trickier part) writing methods that control how various grid
cells read application data to display, draw themselves based on that
data, respond to mouse clicks and keypresses, and finally write
user-modified data back to the application.

Optionally, you can also specify the section-count property of a row or
column to make that single row or column lisp object produce multiple
identical visual rows or columns, which are distinguished by an index
only. Subsections can also be added and removed later by calling
add-row, add-column, delete-row, and delete-column. Call
make-instance as
usual to create each object.

Manipulating Application Data in a Grid-Widget

In addition to writing code to constuct a grid-widget and specify its
mechanical behavior, you need to write methods that specify how it
displays application data and optionally modifies it. (Further below
we discuss some higher-level alternatives that can remove the need to
write some or all of these methods.) Here are the basic generic
functions to specialize in order to display data in a grid-widget:

draw-cell draws
something in a grid cell, typically to represent a value that it
retrieved by calling read-cell-value. draw-cell is called automatically
whenever a grid-cell is uncovered and therefore needs to be redrawn.

If you know that an application value has changed and needs to be
redrawn in its grid cell, you still should not call draw-cell yourself; instead, you
should force the cell to redraw itself by calling invalidate-cell, or force a whole
section to update by calling invalidate-section, or the whole
grid-widget by calling invalidate.

And here are the basic generic functions to specialize if you also
want to allow the user to modify the data interactively in the grid,
rather than simply viewing it:

cell-click is
called when the user presses a mouse button over a cell, and cell-key-down is called
when the user presses a keyboard key while a cell has the keyboard
focus. Methods can be written on these generic functions that decide
how to respond to the gestures and change the value that is
represented in a cell, and then to write the modified value back into
the application, typically by calling write-cell-value. You may also
specialize cell-mouse-in and cell-mouse-out to respond in some
way as the mouse cursor enters and leaves various cells.

write-cell-value
modifies an application's data based on changes that a user has made
with interactive gestures in a grid cell.

Higher-Level Alternatives

The above generic functions are all that you need to specialize in
order to write your data-manipulating grid code from scratch. But
there is some additional higher-level grid functionality that can
reduce the number of grid methods that must be written for typical
applications.

These alternatives are applicable to any application that uses grid
rows to represent data objects and grid columns to represent their
properties. For example, one of the Navigator grid examples uses grid
rows to represent employees, and grid columns to represent properties
such as employee name and department. We will discuss that example
here.

This higher-level functionality falls into two camps: reader and
writer properties, and cell widgets.

Reader and Writer Properties

The first higher-level alternative lets you avoid writing read-cell-value and
write-cell-value
methods. In an application such as the employee example, you would
probably have an employee class that already has accessor functions
such as employee-name and employee-department. When
representing employees and their properties in a grid-widget, you
could specify the data-object property of each
grid-row as
the employee that the row represents, and specify the data-reader property of
each grid-column as the existing
accessor function (such as employee-name or
employee-department) for the property that the column
represents. If you are allowing the user to modify data values in the
grid, then you could also specify a function such as (setf
employee-name) as the data-writer property of the
column.

Built-In Grid Cell Widgets

The other higher-level alternative lets you avoid writing custom
draw-cell,
cell-click, and
cell-key-down
methods. It consists of several types of built-in "cell widgets" that
know how to draw themselves and how to respond to user gestures to
modify the represented value. When using these built-in cell widgets,
you can still write your own read-cell-value and write-cell-value methods
that will be called automatically by the built-in cell widget code.
Or use both of these higher-level alternatives together to
avoid writing any custom methods.

A quicker but less object-oriented way to use the built-in cell
widgets is to write cell-widget methods. This bypasses the widget-column-mixin and widget-row-mixin classes
altogether, and allows the widgets to be used in arbitrary cells
rather than only in particular columns for each type of widget.

Caching Read-Cell-Value Values and Delaying Write-Cell-Value Calls

By default, an application's read-cell-value method (or a data-reader function that the default method
calls) is called every time a cell is drawn to return the value to
display. If this code takes significant time, then these calls can be
minimized by setting the grid-widget's cache-cell-values property to true.

Similarly by default, an application's write-cell-value method (or a data-writer function that the default method
calls) is called immediately whenever the user interactively edits the
value in a grid cell. To delay these calls that modify application
data until the user either confirms or rejects recent edits sometime
later, set the grid-widget's delay-write-cell-value property to true.

Bypassing Read-Cell-Value and Write-Cell-Value Altogether

Perhaps it will be clear at this point that read-cell-value and write-cell-value exist
primarily because the higher-level grid features uses them to separate
the functionality that they provide from the methods that you still
have to write. So if you are not using either higher-level
alternative, then a draw-cell method actually would not
need to call a separate read-cell-value method, and could
instead fetch its application data to draw directly. Similarly, a
cell-click or
cell-key-down
method would not need to call a separate write-cell-value method, and could
instead modify the application's data directly. The "simple color
editor" example in Navigator Dialog is an example of
this.

Adding a Grid-Widget to a Form

A grid-widget can be added to a form interactively as with other
widgets, but it is probably simpler to write all of its code
programmatically, due to the need to define subclasses and properties
for sub-components. To usefully add a grid-widget to a form
interactively, you probably should first define the subclasses for
your grid-widget, sections and subsections, and give them
default-initargs that specify the properties that you would like for
their instances, and then call add-to-component-toolbar to add
your customized grid-widget class to the widget palette.

A higher-level alternative that is already in the widget palette is
the class-grid. A class-grid works well when created
interactively on a form because all of its customizable properties are
at the top level rather than on grid sections.