This section describes the design and implementation of the matrix
demonstration tool used in an earlier lab and assignment. You can run
the tool by executing this
Matrix.jar
file.
Recall that
Matrix is an interface type that is implemented by
the
AbstractMatrix class which is itself extended by
the
ArrayImplementation and
ArrayListImplementation
concrete classes. (See
Encapsulation and Abstraction on the menu
at left.)
Client classes that use matrices need only type their variables with
the
Matrix
type. Thus, at the center
of our matrix GUI design we have the
Matrix interface
type:
We start by considering the display of a matrix with the ability of the
user to input element values, as shown below.
Note that all elements of the display, whether a row/column number or a
matrix element, are arranged in a grid.
We will call the class that lays out the matrix
display
MatrixDisplay. It has two primary relationships:
- It must have a matrix to display as part of its state
- It must create a grid layout
We model the first relationship using aggregation
between
MatrixDisplay and
Matrix.
We model the second relationship by having
MatrixDisplay extend
the
GridPane class from the Java library:
To control a matrix is to fill it with elements,
create an identity, clear it, or transpose it.
We make these options, as well as creating a new matrix, available as
shown below. The user has clicked on an
Actions choice box to
reveal matrix control options.
We will call the class that provides matrix
controls
MatrixControl. It lays out two parts, arranged
vertically:
- A MatrixDisplay object, which is a GridPane
- A choice box of actions
Thus a
MatrixControl object depends on the
Matrix type
and contains a
MatrixDisplay object.
We get vertical orientation of the components by extending
the
VBox class from the Java library:
When the user elects to create a new matrix, a choice of dimensions is
given using integer spinners:
Since the spinners are identical in function, we define an inner class
called
LabeledSpinner, two instances of which are contained in
the
MatrixControl object.
A labeled spinner is laid out as a vertical arrangement of a label and
a spinner, so
LabeledSpinner also extends
VBox:
To demonstrate matrix operations, we need two matrix operands, a choice
of operations, and a matrix displayed for the result.
Here, a matrix multiplication has been performed:
We will call the class that provides matrix
operations
MatrixOperation. It lays out four parts, arranged
horizontally:
- Two MatrixControl objects
- A choice box of operations
- A MatrixDisplay object for the result
We get horizontal orientation of the components by extending
the
HBox class from the Java library:
Since the four objects are labeled scene nodes, we define an inner class
called
LabeledNode, four instances of which are contained in
the
MatrixOperation object.
A labeled node is laid out as a vertical arrangement of a label and
a node, so
LabeledNode also extends
VBox:
To this point, we have been concerned primarily with layout issues.
In this section we address how our GUI classes use listeners and
properties to dynamically change aspects of the display as the user
interacts with it.
The primary interactions are:
- When the user chooses an action for either matrix operand, the
action is carried out and the matrix is redisplayed
- When the user chooses an operation to be performed on the matrix
operands, the result matrix is displayed
Handling these are straightforward using the
addListener method
for choice box value properties.
A
MatrixControl object has a choice box
called
actionChoices that allows the user to perform matrix
actions. We add a listener for actions like this:
Similarly, the
MatrixOperation object has a choice box
called
opChoices that allows the user to perform matrix
operations. We add a listener for operations like this:
There are other interactions that also affect the display.
Specifically:
- When the user manually edits or changes either operand matrix
using a matrix control action, the result matrix is
cleared and the operation choices are reset to Choose
- When the user changes the dimensions of an operand matrix so that it is
not square, the Make Identity option is removed from the
action choices for that matrix
- When the user changes the dimensions of either matrix operand so that
the operand matrices are not compatible for adding, the Add
option is removed from the operation choices
- When the user changes the dimensions of either matrix operand so that
the operand matrices are not compatible for multiplying, the Multiply
option is removed from the operation choices
Listening for matrix actions is straightforward, as we simply need to
add a listener to the
MatrixControl object's
actionChoices
choice box.
Since the
MatrixOperation object does the
listening,
MatrixControl provides a getter for the choice box's
value property:
Note the return type
ObservableValue. This is an interface type
that contains the
addListener method.
The
MatrixOperation object must listen so that it can clear the
result matrix when either of the operand matrices has an action taken
on it. Since the
MatrixOperation object
creates and contains the two
MatrixControl objects
mc1
and
mc2, it uses
getActionProperty to add listeners that
clear the result when any actions occur:
To indicate when the user has manually edited a matrix, we add
a
SimpleBooleanProperty instance field to
MatrixDisplay:
In the constructor, we initialize it:
We add a listener to each text field representing a matrix element that
toggles the boolean property each time it is edited:
Now we make the property available to outside classes through a getter:
Since the
MatrixControl object creates and contains
a
MatrixDisplay object
display,
MatrixControl
makes
display's
edited property available through a
getter of its own:
The
SimpleBooleanProperty class implements
the
ObservableValue interface.
The
MatrixOperation object must listen so that it can clear the
result matrix when either of the operand matrices is edited:
Beyond simply listening for whether a matrix element has been edited or
modified through an action,
we also need to know when a matrix's dimensions have changed, for example,
through transposing or creating an entirely new matrix.
In that case we need to:
- Update the actions, i.e. remove the identity action if a matrix
is no longer square, and
- Update the operations, i.e. remove the add or
multiply operations if the matrices are not compatible
For the actions, a listener is not needed; we can simply update the
actions when a new matrix is created.
For the operations, we introduce into
MatrixControl an object
property. Note the type parameter
Matrix:
In the constructor, after the matrix has been created, it is wrapped in
a
SimpleObjectProperty:
SimpleObjectProperty implements the
Property interface,
which is a subinterface of
ObservableValue.
By adding a listener to this property, we can detect when the entire
matrix object has changed, not just when an element has been modified.
So that
MatrixOperation can listen for changes to the property,
we provide an accessor in
MatrixControl:
In
MatrixOperation we add code that updates the available
operations