netanel

  • Full Screen
  • Wide Screen
  • Narrow Screen
  • Increase font size
  • Default font size
  • Decrease font size

Tutorial
Print

The Matrix widget is designed with a separation of concerns in mind. It does not imply any data storage model for the visual attributes of the matrix elements. Instead it concentrates on efficient matrix layout calculation, painting, and event handling.

Layout

The diagram below presents the basic concepts of the Matrix layout.

Diagram 1

Axis

Axis is one of the two dimensions of the two-dimensional matrix. Axis names are appended with X and Y in order to make them as short as possible, even in length and staying next to each other when sorted.

Axis axisX = matrix.getAxisX();
Axis axisY = matrix.getAxisY();
is faster to type and more readable then
Axis columnAxis = matrix.getColumnAxis();
Axis rowAxis = matrix.getRowAxis();

Axis is composed of sections (at least one) and is indexed by a specific sub-type of the java.lang.Number class. By default it is Integer class. It can be switched to BigInteger for example by creating the Axis manually:

Axis axisX = new Axis(Integer.class, 2);    // Two sections indexed by Integer class
Axis axisY = new Axis(BigInteger.class, 3); // Three sections indexed by BigInteger class
final Matrix matrix = new Matrix(shell, SWT.V_SCROLL, axisX, axisY);

Section

Section is a continuous segment of a matrix axis containing a number of items. Section can serve as a header, body, footer, filter area, etc. By default each axis has two sections: header with one item and body with zero items. The number of sections can be specified by creating the axis manually:

Axis axisX = new Axis(Integer.class, 2);    // Two sections indexed by Integer class
Axis axisY = new Axis(BigInteger.class, 3); // Three sections indexed by BigInteger class
final Matrix matrix = new Matrix(shell, SWT.V_SCROLL, axisX, axisY);

On Diagram 1 row axis has a header section with 1 item and the body section with 10 items, while the column axis has header section with 1 item and body section with 5 items.

This approach may cause some confusion, because in order to show the labels for the columns one must set the header section on the row axis visible, for example:

matrix.getAxisY().getHeader().setVisible(true);

Zone

Zone constitutes a region of the matrix where a section from axis X and a section from the axis aY intersect with each other. There are four zones on the diagram below:

  • body
at intersection of axisX body section and axisY body section
  • column header
at intersection of axisX body section and axisY header section
  • row header
at intersection of axisX header section and axisY body section
  • top left
at intersection of axisX header section and axisY header section

zones

Zones are created automatically by the matrix to cover all intersections of axis sections.

Each zone has its own collection of painters and key/mouse bindings.

Cell

Cell is identified by indexes of two section items from perpendicular axises. Cell area does not include lines, so whenever the line width changes the cell content painting algorithm can stay untouched. Painting lines separately provides also speed optimization advantages.

The line at index n belongs logically to section n-th item as well as cell at index n, so whenever the n-th item is hidden or moved so is the n-th line. The exception is line with index equal to section.getCount(), which is not bound to any cell.

Painting

Painter is responsible to draw everything that appears on the matrix canvas: background, images, text, lines. The design of painting mechanism allows 100% customization of the matrix appearance.

Painting order

Both matrix and each zone has an individual list of painters that defines the order in which the painters paint method is executed. The painter list can be manipulated by the addPainter, removePainter, setPainter, replacePainter methods.

The default structure of painters is following:

  • matrix painters - collection of painters
    • frozen area painter - named by one of the Painter.NAME_FROZEN_... names, (description in the next paragraph)
      • zones - that are visible in the given frozen area
        • zone painters - collection of painters
          • cells painter - named Painter.NAME_CELLS
          • horizontal lines painter - named Painter.NAME_LINES_X
          • vertical lines painter - named Painter.NAME_LINES_Y
          • emulated controls painter - named Painter.NAME_EMULATED_CONTROLS
          • embedded controls painter - named Painter.NAME_EMBEDDED_CONTROLS
    • focus cell painter - a painter named Painter.NAME_FOCUS_CELL
    • drag horizontal item painter - a painter named Painter.NAME_DRAG_ITEM_X
    • drag vertical item painter - a painter named Painter.NAME_DRAG_ITEM_Y

Frozen area painter is responsible for painting separately one of 9 matrix areas formed by the head and tail freeze dividers on both axises:

  • frozen none nonearea not frozen neither on X axis not on Y axis
  • frozen none tailarea not frozen on X axis and tail frozen on Y axis
  • frozen tail nonearea tail frozen on X axis and not frozen on Y axis
  • frozen none headarea not frozen on X axis and head frozen on Y axis
  • frozen head nonearea head frozen on X axis and not frozen on Y axis
  • frozen head tailarea head frozen on X axis and tail frozen on Y axis
  • frozen tail headarea tail frozen on X axis and head frozen on Y axis
  • frozen tail tailarea tail frozen on X axis and tail frozen on Y axis
  • frozen head headarea head frozen on X axis and head frozen on Y axis

Painter Scope

The painter type specifies the scope in which it paints. The zone painting mechanism feeds the paint method with with values appropriate for the given scope.

Only zone painters can handle various painter scopes. Matrix works with only one type SCOPE_SINGLE, so the actual painter type is ignored.

Painter Properties

Painter has a set of public properties that are investigated by the paint method to determine what should be drawn on the canvas. The most significant ones are image, text and style. The same style object can be used in many painters. It has attributes like:

  • textAlignX, textAlignY, textMarginX, textMarginY,
  • imageAlignX, imageAlignX, imageMarginX, imageMarginY
  • foreground and background colors
  • selectionForeground and selectionBackground

In order to alter properties depending on the cell indexes one should replace the default painter, for instance (Snippet_0017):

final Color color = display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
matrix.getBody().replacePainter(new Painter(Painter.NAME_CELLS, Painter.SCOPE_CELLS) {
    @Override
    public void setup(Integer indexX, Integer indexY) {
        style.background = indexY % 2 + indexX % 2 == 1 ? color : null;
    }
});

Zone

Cell Merging

Cell merging can be done by (Snippet S0210):

zone.setMerged(startX, countX, startY, countY);

If the range defined by setMerging contains a cell that is merged, then all cells merged with that cell will be unmerged. This behavior is the same as in spreadsheets.

The merged area is attached to the cell with (startX, startY) coordinates, and is not attached the cell at the end of the merged area, so the cells can be moved in and out the merged region - this opertion does not change the merged area size. The area size changes when the cells inside of it get hidden or shown.

The maximum size of the merged area can be set by:

zone.setMergeLimit(X limitX, Y limitY);

It is introduced to prevent performance problems with layout calculation which in case of merged cells must go beyond viewport area. The default value of the merge limit is 1000.

Cell can be checked for being merged by:

zone.isMerged(indexX, indexY);

Event handling

Command Binding

Typical commands are bound to keyboard and mouse gestures. The default bindings are enlisted here. The more complex action triggering conditions includes moving by select axis items and start dragging from one of them.

The bindings can be added or removed by the bind and unbind methods. For example the default matrix binding to move the focus cell to the beginning of line can be altered by (Snippet_0901):

matrix.unbind(Matrix.CMD_FOCUS_MOST_LEFT, SWT.KeyDown, SWT.HOME);    // Windows standard
matrix.bind(Matrix.CMD_FOCUS_MOST_LEFT, SWT.KeyDown, SWT.MOD1 | SWT.ARROW_LEFT); // Mac OS standard

Zones also have the bind and unbind methods.

Secondary Events

A SelectionEvent event of selecting cells by user can be handled with the Zone.addSelectionListener method and selecting section items by the user can be handled with the Section.addSelectionListener method. Also a ControlEvent can be handled by Section.addControlListener method. See Snippet_0902.

Editing

Editing of a matrix cells is facilitated by the ZoneEditor class, for each <code>Zone</code> separately. The simplest way to make the matrix editable is to instiate the ZoneEditor class with a concrete implementation of the setModelValue method to apply the modified value to the model (see Snippet_0401):

data = new ArrayList<Object[]>
new ZoneEditor(matrix.getBody()) {
    @Override
    public void setModelValue(Number indexX, Number indexY, Object value) {
        data.get(indexY.intValue())[indexX.intValue()] = value;
    }
};

The default cell editor control for the above is Text and the value to edit is taken from the Painter.getText(Number, Number) method of the "cells" painter of the zone being edited.

Besides Text the ZoneEditor also supports other cell editor controls: native Combo, DateTime controls and emulated, native looking check boxes.

In order to use other controls then Text the createControl(Number, Number) method of ZoneEditor must be overriden to return the desired control for the specified cells. For example:

    @Override
    public Control createControl(Number indexX, Number indexY, Composite parent) {
        if (indexX.intValue() == 2) {
            Combo combo = new Combo(parent, SWT.BORDER);
            combo.setItems(comboItems);
            return combo;
        } 
        else if (indexX.intValue() == 3) {
            return new DateTime(matrix, SWT.BORDER);
        }
        else {
             return super.createControl(index0, index1);
         }
     }

Since DateTime control is suited to Date values and check boxes to Boolean values thus the getModelValue(Number, Number) method provides a mechanism to get a proper type of value suited to the cell editor control. For example:

    @Override
    public Object getModelValue(Number indexX, Number indexY) {
        return data.get(indexY.intValue())[indexX.intValue()];
    }

There is also method to set value in the cell editor control after it is activated setEditorValue(Control, Object) and getEditorValue(Control) method to get the value from the cell editor control after the apply action has been triggered. They need to be overridden in order to handle custom control types other then the built-in ones. For example:

    @Override
    public void setEditorValue(Control control, Object value) {
        if (control instanceof List) {
            List list = ((List) control);
            list.deselectAll();
            int position = list.indexOf((String) value);
            if (position != -1) {
                list.select(position);
            }
        } else {
            super.setEditorValue(control, value);
        }
    }
    
    @Override
    public Object getEditorValue(Control control) {
        if (control instanceof List) {
            List list = (List) control;
            int position = list.getSelectionIndex();
            return position == -1 ? null : list.getItem(position);
        } else {
            return super.getEditorValue(control);
        }
    }

Embedded Controls

Edit controls can be be embedded in the cells by returning true from the hasEmbeddedControl(Number, Number) method, for example (Snippet_0402):

    @Override
    public boolean hasEmbeddedControl(Number indexX, Number indexY) {
        Object value = data.get(indexY.intValue())[indexX.intValue()];
        return value instanceof Boolean;
    }
    
    @Override
    protected Control createControl(Number indexX, Number indexY, Composite parent) {
        Object value = data.get(indexY.intValue())[indexX.intValue()];
        if (value instanceof Boolean) {
            return new Button(parent, SWT.CHECK);
        }
        return super.createControl(indexX, indexY);
    }

Checkbox Emulation

Embedding controls in cells hurts performance. Check boxes can be emulated by returning a value from the getCheckboxEmulation(Number, Number) method, for example:

    @Override
    public Object[] getCheckboxEmulation(Number indexX, Number indexY) {
        Object value = data.get(indexX.intValue())[indexY.intValue()];
        return value instanceof Boolean ? getDefaultCheckBoxImages() : null;
    } 

The images are taken from the path specified by the setEmulationPath(String) method.

The images of the check boxes in the current system theme can be created by then snapControlImages(String) the method.

Clipboard Operations

MatrixEditor is also responcible for clipboard operations.

The copy() method uses the format(Number, Number) function to get the String values for the individual cell. By default it is the value returned from the Painter.getText(Number, Number) method of the "cells" painter of the zone being edited.

The cut() method sets null value to the selected cells after invoking copy() method.

The paste() method uses the parse(Number, Number, String) function to parse the values for the individual cells.

The clipboard operations can be customized by overriding any of the above mentioned methods.

Commands

The default command bindings are listed below:

Command Matrix Edit Control
CMD_EDIT SWT.KeyDown SWT.F2 or SWT.MouseDoubleClick or
(for check buttons only) SWT.KeyDown ' '

CMD_APPLY_EDIT
SWT.CR or SWT.FocusOut
CMD_CANCEL_EDIT
SWT.ESC
CMD_COPY SWT.KeyDown SWT.MOD1 | 'c'
CMD_PASTE SWT.KeyDown SWT.MOD1 | 'p'
CMD_CUT SWT.KeyDown SWT.MOD1 | 'x'
CMD_DEL SWT.KeyDown SWT.DEL


You are here: SWT Matrix Tutorial