Defining a selection tree of animals
The left-hand section of the screen shows a scrollable tree of all of the animals in the zoo, categorised by the animal's type. The natural component to use for this is a JTree nested inside a JScrollPane.
The AnimalTree class
Create this class in virtualzoo.ui as follows:
package virtualzoo.ui;
import virtualzoo.core.*;
import java.util.*;
import javax.swing.*;
import javax.swing.tree.*;
public class AnimalTree extends JTree {
private ZooAdministrator admin;
private DefaultTreeModel model;
private DefaultMutableTreeNode root;
public AnimalTree() {
admin = ZooAdministrator.getInstance();
root = new DefaultMutableTreeNode("Animals");
model = new DefaultTreeModel(root);
setModel(model);
buildTree();
getSelectionModel().setSelectionMode
(TreeSelectionModel.SINGLE_TREE_SELECTION);
}
private void buildTree() {
root.removeAllChildren();
Collection<Pen> pens = new TreeSet<Pen>(admin.getPens());
for (Pen pen : pens) {
DefaultMutableTreeNode node =
new DefaultMutableTreeNode(pen);
buildAnimalTreeNodes(pen, node);
root.add(node);
}
}
private void buildAnimalTreeNodes(Pen pen,
DefaultMutableTreeNode node) {
Collection<Animal> animals =
new TreeSet<Animal>(admin.getAnimals(pen));
for (Animal animal : animals) {
node.add(new DefaultMutableTreeNode(animal));
}
}
}
- The class extends
JTreeand uses the private helper methodsbuildTree()andbuildAnimalTreeNodes()to create nodes for each animal, categorised by the pen they are in - The tree is set to only allow selection of a single node at a time
- Note how after the collections of
Penobjects andAnimalobjects are obtained fromZooAdministratorthey are sorted into their natural ordering by passing the returned collections into a newTreeSetobject
An editor panel for an animal
In NetBeans, right-click on the virtualzoo.ui package node and select New | JPanel Form..., entering AnimalEditor as the Class Name. You will see the initially empty form.
The process of designing the form will be similar to that used for the zookeeper's editor form in the previous section. The final screen should look like this:
There follows some guidance to help you create the above form:
First switch into Source mode and import the following packages:
- virtualzoo.core.*;
- com.example.util.*;
- java.util.*;
- javax.swing.*;
Switch back to Design mode;
The top Label should be named modeLabel and set to have a bold font style with the text Mode will go here;
Because there are three allowed animal types the user needs some way of telling the system which type each animal is. You may recall that you defined an enum called Type inside the Animal class, and you can use this to provide the options inside a JComboBox component. One of this component's constructors accepts an array of objects as its argument, and you can obtain an array from any enum through its values() method:
- Use Combo Box under Swing Controls in the Palette window
- Rename the field as
animalTypeCombo - Under the Properties section of the Properties window empty the sample data (Item 1, etc.) from the model property
- Click the Code button in the Properties window and enter the following in the Custom Creation Code box:
new JComboBox(Animal.Type.values())
For the Combo Box of pens do the following:
- Rename it to
penCombo - Under the Properties section of the Properties window empty the sample data (Item 1, etc.) from the model property
- Under the Code section of the Properties window click the 3-dotted button to the right of the Pre-Creation Code box to bring up a text entry dialogue, into which enter the following statements:
ZooAdministrator admin = ZooAdministrator.getInstance(); Collection<Pen> pens = admin.getPens(); Pen[] pensArray = pens.toArray(new Pen[pens.size()]);
The above will obtain the Collection of Pen objects and convert them to an array. This is needed because there is no JComboBox constructor that accepts a collection, but there is one that accepts an array
- Under the Code section of the Properties window enter the following in the Custom Creation Code box:
new JComboBox(pensArray)
Rename the text field for the name as nameField, and empty its contents
For the gender you should firstly place a Button Group from the Palette window anywhere on the form. This is a non-visual component, and you will see it listed under the Other Components node in the Navigator window;
- Rename it as
genderButtonGroupby right-clicking on its entry in the Navigator window - Drag a Radio Button next to the Gender label, rename it
maleOptionwith text of Male. You also need to attach it to the above button group, so locate thebuttonGroupattribute in the Properties section of the Properties window. In the drop-down selector you can selectgenderButtonGroup. Check the selected property checkbox so that this option will be selected by default - Repeat the above to create another radio button with text Female, named
femaleOptionand attached togenderButtonGroup, but don't check the selected property checkbox
For the age you will use a JSpinner component that restricts its values from zero to fifty. Use Spinner from the Palette window:
- Rename it as
ageSpinner - Resize the component to make it slightly wider than its default width
- Under the Code section of the Properties window enter the following in the Pre-Creation Code box:
SpinnerNumberModel ageSpinnerModel = new SpinnerNumberModel(0, 0, 50, 1) - Under the Code section of the Properties window enter the following in the Custom Creation Code box:
new JSpinner(ageSpinnerModel)
The button should be named saveButton, having the text Save
The bottom Label should be named messageLabel, and with a bold font of size 18, coloured blue, with text Message will go here
For AnimalEditor to be any use it needs to know if it should be adding a new animal or editing an existing one. To this end, define a new instance variable to reference an Animal object:
private Animal animal;
If the animal instance variable is null, then you will take this to mean that this panel should add a new animal using the values entered into the form fields. Conversely, if the variable is not null then it will reference the Animal object that should be used to pre-fill the entry fields to be potentially updated.
Define a new method in AnimalEditor called clearAnimal() to handle the first of the above two scenarios:
void clearAnimal() {
animal = null;
animalTypeCombo.setSelectedIndex(0);
penCombo.setSelectedIndex(0);
nameField.setText("");
maleOption.setSelected(false);
femaleOption.setSelected(false);
ageSpinner.setValue(0);
modeLabel.setText("Add New Animal");
messageLabel.setText("");
}
- The
setSelectedIndex()method ofJComboBoxmakes the specified index of the box the selected one, where zero is the first entry
Define another method called setAnimal() which accepts an Animal object as an argument representing the one that needs editing:
void setAnimal(Animal animal) {
this.animal = animal;
animalTypeCombo.setSelectedItem(animal.getType());
penCombo.setSelectedItem(animal.getPen());
nameField.setText(animal.getName());
if (animal.isMale()) {
maleOption.setSelected(true);
} else {
femaleOption.setSelected(true);
}
ageSpinner.setValue(animal.getAge());
modeLabel.setText("Change Animal Details");
messageLabel.setText("");
}
By default, you want the editor to be in "add" mode when first instantiated, so add a call to clearAnimal() inside the constructor:
public AnimalEditor() {
initComponents();
clearAnimal();
}
You can now enter some code to handle the Save button being clicked. In Design mode, double-click the Save button to bring up the saveButtonActionPerformed() method and edit it to be as follows:
private void saveButonActionPerformed(java.awt.event.ActionEvent evt) {
ZooAdministrator admin = ZooAdministrator.getInstance();
try {
// Prepare selected data
Animal.Type selectedAnimalType =
(Animal.Type) animalTypeCombo.getSelectedItem();
Pen selectedPen = (Pen) penCombo.getSelectedItem();
Gender selectedAnimalGender = null;
if (maleOption.isSelected()) {
selectedAnimalGender = Gender.MALE;
} else {
selectedAnimalGender = Gender.FEMALE;
}
int selectedAge = (Integer) ageSpinner.getValue();
if (animal == null) {
// Adding a new animal
admin.createAnimal(selectedAnimalType,
selectedPen,
nameField.getText(),
selectedAnimalGender,
selectedAge);
messageLabel.setText("New animal added");
clearAnimal();
} else {
// Changing an existing animal
admin.changeAnimal(animal,
selectedPen,
nameField.getText(),
selectedAnimalGender,
selectedAge);
messageLabel.setText("Animal details changed");
}
} catch (ValidationException ex) {
JOptionPane.showMessageDialog(null, ex.getMessage(),
"Error", JOptionPane.ERROR_MESSAGE);
}
}
The AnimalPanel class
If you look at again at the figure at the beginning of this section you will see that the two classes you have developed so far, AnimalTree and AnimalEditor, are set as the left and right components inside a JSplitPane, underneath which is a panel containing two centred buttons. You will now use NetBeans to create a new class called AnimalPanel to model this.
In the Projects window use Tools | Add to Palette... against both AnimalTree.java and AnimalEditor.java to add them to the Zoo Components section in the Palette window.
You are now ready to create the new panel, so right-click on the virtual.ui package node and select New JPanel Form..., enter AnimalPanel as the Class Name, and click Finish.
Enlarge the size of the blank design area to make it easier to see the components which will be added. Right-click the form and select Set Layout | Border Layout. Now drag from the palette a Panel (from the Swing Containers category) and place it at the bottom of the form. As you are dragging, just above the form you will see some text which tells you where it will be placed, so you want it to say, "Place the component into the Last area".
Now right-click the panel you just placed at the bottom and select Set Layout | Flow Layout. Drag two Button objects from the palette onto the bottom panel, setting the first one's text to Add and variable name to addButton, and the second one's text to Remove and variable name to removeButton. To make the button panel stand out a bit more, click on it and inspect the Properties window. Locate the border property, which is currently set to (No Border) and click the three-dotted icon to its right to open the customiser dialog. Select Soft Bevel Border and click OK.
From the Swing Containers section drag a Split Pane onto the centre area of the form. Under Swing Containers drag a Scroll Pane over the top of left button (which will replace it) and then under Zoo Components drag a AnimalTree component on top of the scroll pane (which will nest inside it). Change its variable name to animalTree.
From Zoo Components drag AnimalEditor over the top of right button to replace that too. Change its variable name to animalEditor.
Comments