< Zurück | Inhalt | Weiter >

16.7.2 Creating Pieces

The three pieces that we create—the status, the list, and the buttons—will each package up their objects into an intermediate container, a JPanel, and return that container to main(). This not only serves to chunk the problem into fewer pieces (just three parts, not eight or more), but also helps with the formatting. Each piece can format its objects relative to each other. Then main() only has to lay out the three big pieces. So watch for each of the create...() methods to return a JPanel—a good approach when you build your GUIs, too.

The JPanels returned to main() are just Swing objects. They, like the buttons or labels (that we will see here shortly), just get added into other con- tainers. For main(), that container is the JFrame, the main window. Any container will have a layout manager, the mechanism by which objects are placed in that container. For JFrame, the default is the BorderLayout manag- er. When you call the add() method on a container using a BorderLayout, you can specify (as a second parameter to the add() method) where the object being added will get placed. The constants defined for placing objects are NORTH, SOUTH, EAST, WEST, or CENTER—hence the “Border” of BorderLayout. There are also relative position values: PAGE_START, PAGE_END, LINE_START, and LINE_END which are just like north, south, west, and east, respectively, provided that the ComponentOrientation is set to LEFT_TO_RIGHT. (If you really want to know, check the Javadoc page for java.awt.BorderLayout.)


With a BorderLayout, if you put something in the NORTH section, it will appear across the top area of that container. If you resize the container (e.g., drag the window edges), it will take extra space and use it for horizontal, but not vertical, stretching. That is, the objects won’t get bigger than they need to vertically, though they will stretch wider. The same is true for SOUTH, but the objects are at the bottom rather than top of the container. Putting something in EAST or WEST will move them to the left or right of the container. For these two areas, though, space when resizing a window is added to the objects verti- cally, but not horizontally. Putting an object in EAST or WEST will let it get taller, but not wider.

The CENTER area, the default location if you use the add() method with no second parameter, will use extra space both vertically and horizontally.

Adding more than one object into a region (e.g., NORTH) will result in only the last item added being displayed. For this reason, too, one often builds inter- mediate containers to hold several objects. Then the single container object is added to one of BorderLayout’s regions.


16.7.2.1 Simple JLabels

Let’s look at the simplest of the three pieces that we create for our GUI—the top line of information indicating the status of the account. In lines 88–107 we create this portion of the GUI.


88 private Component

89 createStatus() 90 {

91 JPanel retval = new JPanel(); // default: flow layout 92

93 upton.addActionListener(upAction); 94

95


nam = new JLabel("Account: Name");

96


tot = new JLabel("Total: $");

97


val = new JLabel("Remaining: $");

98



99


retval.add(upton);

100


retval.add(nam);

101


retval.add(tot);

102


retval.add(val);

103



104


setStatus();

105



106


return retval;

107

}

// createStatus


It consists of three parts, one for the account name, one for the total value of the account, and one for the remaining value. Each part will be represented by its own label, using a JLabel object. (We could have done the entire line in one label, but this gives us a few more objects to manipulate.) Since we want to group the labels together, we create a JPanel, which is a Swing container, to hold all these objects. We’ll also add the JButton object (the variable named upton).

A JLabel is a simple Swing object. You can construct an empty one with new JLabel(); but you can also construct a label with a String as its initial value, which is more useful. You can later change a label’s value with a call to its setText() method, as you see here from line 117:


117 tot.setText("Total: $"+current.getTotal());


16.7.2.2 FlowLayout

The JLabels are added to their JPanel, but with no position argument, unlike the JFrame and BorderLayout used in main(). JPanel has a different default layout manager: It uses FlowLayout. With it, added objects are placed side by side according to the window size. If the window is narrowed, they will simply flow onto the next line. (You won’t see this behavior if you narrow the Budget- Pro window, but that’s because the JPanel has been added to the JFrame’s NORTH region, which means it’s no longer just a FlowLayout that determines sizes.) FlowLayout is a layout that’s easy to use, but doesn’t give you much control; it was just fine for our purposes here.


16.7.2.3 BoxLayout

Another simple layout mechanism is the BoxLayout. It allows you to place the objects like stacking boxes—though they can be stacked horizontally as well as vertically. Look at line 224:


224 retval.setLayout(new BoxLayout(retval, BoxLayout.X_AXIS));


Here we are creating a BoxLayout object and associating it with our JFrame to manage its objects. When we create a BoxLayout we can tell it that we want to stack our objects horizontally (using either X_AXIS or LINE_AXIS) or vertically (using either Y_AXIS or PAGE_AXIS). Note that the BoxLayout


object needs to be told about (i.e., given a reference to) the container (here, retval, a JPanel) whose objects it will manage, but that the container also needs to be told (via setLayout()) about the BoxLayout object. A bit confusing, perhaps.

Another handy part of BoxLayout is the uses of rigid areas, invisible ob-

jects that do nothing except putting some space between objects. These rigid areas are defined in pixels; for our GUI we create them with no height and a width of ten pixels. They are held together using “horizontal glue” (see line 226)


226 retval.add(Box.createHorizontalGlue());


so that if the window is stretched, the extra space doesn’t get added between the buttons, but only to the “glue” component, which absorbs all extra space. This keeps all the buttons to the right hand side of the window.


16.7.2.4 JButtons

The method named createButtons() actually packs up the buttons into a

JPanel to return to the caller. It begins like this:


218 private Component

219 createButtons(JRootPane root)

220 {

221 JPanel retval = new JPanel(); // default: flow layout 222

223 //Lay out the buttons from left to right.

224 retval.setLayout(new BoxLayout(retval, BoxLayout.X_AXIS));

225 retval.setBorder

(BorderFactory.createEmptyBorder(10, 10, 10, 10));

226 retval.add(Box.createHorizontalGlue());

227 retval.add(creat);

228 retval.add(Box.createRigidArea(new Dimension(10, 0)));

229 retval.add(view);

230 retval.add(Box.createRigidArea(new Dimension(10, 0)));

231 retval.add(clos);


The buttons themselves were created at the beginning of this class, in lines 27–29, thus:


27 private JButton creat = new JButton("New Subaccount");

28 private JButton view = new JButton("View Subaccount");

29 private JButton clos = new JButton("Quit");


Note that the constructor takes a String argument—that’s the text that will appear in the button. A button may also have an icon (image) in it (more on that in just a bit). These buttons, as created, don’t do anything. When clicked on by the user, they will behave as real buttons (depress, then release), but no action will occur. Yet.


16.7.2.5 Actions for Buttons

We need to attach an action to each button, which is little more than a special class to hold the code that you want to be run when the button is pressed. We can define the action as an anonymous inner class, so that the code is right there, inline with the rest of our code. Then we just attach that code to the button. Here is an example of that for our close button (the one labeled Quit):


234

ActionListener closAction = new ActionListener()

235

{

236

public void

237

actionPerformed(ActionEvent e)

238

{

239

System.exit(0);

240

}

241

} ;


ActionListener is an interface—a very simple interface that defines just one method, actionPerformed(). You can take any class, have it extend ActionListener, and then define an actionPerformed() method for it. That class can then serve as the action for a button. Here we just create an in- line class that does nothing but the actionPerformed() method, and a pretty simple one at that. It simply exits.

We could define the action elsewhere, and then just use the reference to the action. If we had put the declaration of closAction at a higher lexical scope (out at the beginning of the class definition, for example) then other UI elements could also use this action. Of course, if you’re going to share your action between GUI elements, be sure that you write the code to be reentrant. Lines 244–267 (still within the createButtons()method) define the action for the button labeled New Subaccount. Line 268 connects it to the button. Don’t pay attention to the specifics of this action just yet. We’ll discuss it in detail below, once we know more about the other objects. Here is how

that action is built:



244

ActionListener creatAction = new ActionListener()

245

{

246

public void

247

actionPerformed(ActionEvent e)

248

{

249

Account child;

250

// get the info via a Dialog (of sorts)

251

if (askem == null) {

252

askem = new AcctDialog(frame, "New Subaccount");

253

} else {

254

askem.clear();

255

askem.setVisible(true);

256

}

257

String subName = askem.getName();

258

String subAmnt = askem.getAmnt();

259


260

// if empty, assume the operation was cancelled, else:

261

if ((subName != null) && (subName.length() > 0)) {

262

child = current.createSub(subName, subAmnt);

263

setStatus();

264

model.fireTableDataChanged(); // notify the table

265

}

266

}

267

};

268

creat.addActionListener(creatAction);


We defined the action for the View Subaccount button (as we said you could) elsewhere in the program. Its action is defined in lines 54–75. Then on line 271 we connect the action to the button. (We’ll get back to this button’s action, too, once we’ve discussed the JTable.) But after we’ve attached the ac- tion, we also disable the button (line 273).


270 // function is to get selection from table and cd there

271 view.addActionListener(cdAction);

272 // but it starts off disabled, since there is no data yet

273 view.setEnabled(false);


In Swing, a button is either enabled or disabled. Enabled buttons are the active ones on which you can click. Disabled buttons are grayed out and not responsive to clicks. We can make a button either active or inactive with a method on the button called setEnabled() whose argument is a booleantrue to enable the button, false to disable it. For example:



203

if (lsm.isSelectionEmpty()) {

204

view.setEnabled(false);

205

} else {

206

view.setEnabled(true);

207

}


However, we start with the View Subaccount button disabled until the user has created and selected some subaccounts worth viewing.


16.7.2.6 The createStatus() Revisited

There is one other button on the BudgetPro application, one that is not located in this bottom panel of buttons. It’s the one on the status line. It, too, starts up disabled or grayed out—but it has an image in it. Any JButton can contain either text or an image, or both, but we’ve chosen to do just one or the other in our application. We declare it like any other button:


private JButton upton;


but for its initialization we use a variation of the JButton constructor, one that takes an ImageIcon object as its parameter:


upton = new JButton(new ImageIcon("net/multitool/gui/back.gif"));


Why do we do that all in one line? When you read it, you can certainly think of it as two steps:


ImageIcon backup = new ImageIcon("net/multitool/gui/back.gif"); upton = new JButton(backup);


but we have no other need for the image, so we don’t need to keep a reference for it in a variable. Some programmers prefer to write it out in two simple steps, as it is easier to read and perhaps to maintain. We’ve chosen to put it all in the JButton’s constructor to show that we’re making no other use of the image. Which style do you prefer?

And what about a button that needs to contain both text and an image? There is a constructor that takes both a String and an ImageIcon. Then you can set certain attributes of the JButton to position the text relative to the image. Look in the Javadoc of JButton for the methods setVerticalTextPosition() and setHorizontalTextPosition().


16.7.2.7 JTable: The Workhorse of Data Display

Look again at our GUI application. In its center you see the table object:



image


This is a JTable. A simple way to create a JTable is by passing in two arrays to the constructor—first, a two-dimensional array of data objects, and second, a one-dimensional array of column names. Notice that we said data objects; you need to use Integer objects, not simple int types, and Doubles instead of doubles. This allows the constructor to take any Object type and display it in the table via the object’s toString() method.

While this form of a table is simple to use, it usually isn’t enough for all the various things you’ll want to do with a table. Let’s look at the “industrial strength” table initialization. For that, we need to talk about a table model.


16.7.2.8 Table Model

If you’ve ever taken an object-oriented design class, they’ve probably talked about the Model/View/Controller design pattern. (If you haven’t taken such a class, at least read a good book or two on the subject; it will improve your Java programming skills.) A simpler version of this pattern is the View/Model pat- tern. What it describes is separating the core of the data from the frill of its presentation—what you want to display versus how you want to display it. The Model is the underlying data; the View is one particular way to show that data. This View versus Model distinction is used to great effect with JTable and TableModel objects in Swing. What you need to do is create a TableModel, then give that TableModel to the JTable via the JTable’s constructor. The TableModel will give you all sorts of control over your data—how, where, and when to get or update it. The JTable will display it

and let you rearrange or resize the columns.

Rather than implement a complete TableModel from scratch, Swing gives us a helping hand with its AbstractTableModel class. AbstractTableModel is a partially implemented class which handles most of the grundy details—it


has most of the Table interface implemented. You only need to implement three methods:


public int getRowCount(); public int getColumnCount();

public Object getValueAt(int row, int column);


Together, these three methods give a pretty good definition of a table: how many rows it has, how many columns it has, and how to access the value at any (row, column) location. Notice, too, that the getValueAt() returns an Object, so you can’t return an int or float or double. You can only return an Integer, Double, and so on. Another option is to return a String value of the number that you want to display.

Let’s take a look at how the AbstractTableModel was implemented in the BudgetPro application. We begin at line 135, inside the createList() method. The createList() method is going to build the central portion of our GUI, the table display. In order to do that, it creates an AbstractTableModel to give to the JTable it creates on line 193. The AbstractTableModel is defined inline as an anonymous inner class that implicitly extends AbstractTableModel. This section of code is listed in Example 16.2; follow along as we discuss it further.

(An aside: We could also have defined this inner class elsewhere in the class file, as a class which explicitly extends AbstractTableModel. However, as with the icon we used in the JButton example, we have no further need of the object other than this single use, so we didn’t bother to create it as a standalone entity. Both ways work, and are more a matter of preference or of how familiar you are with the inline syntax.

In our implementation of the AbstractTableModel, we are going to in- clude column headings, so we begin with a definition of Strings for our col- umn headings (line 137). Then the getColumnCount() method, one of the three methods that we need to implement in this class, is simply a matter of returning the size of this array (line 159). Lines 139–142 override the getColumnName() method, which isn’t one of the three that we must imple- ment. But if we don’t, the default behavior from AbstractTableModel will return nulls, so we’d get no column headings. Instead, we use the column number as an index to our array of column names.

The getRowCount() method is almost as simple (lines 144–155). The number of rows that this table should display for any account is the


image

Example 16.2 Defining our AbstractTableModel

130 private Component

131 createList()

132 {

133 JScrollPane retval; 134

135 model = new AbstractTableModel()

136 {

137 private String [] columnNames = {"Account", "Owner", "Value"}; 138

139 public String

140 getColumnName(int col) {

141 return columnNames[col];

142 } // getColumnName 143

144 public int

145 getRowCount()

146 {

147 int retval; 148

149 if (current != null) {

150 retval = current.size();

151 } else {

152 retval = 1; // testing only

153 }

154

155 return retval; 156

157 } // getRowCount 158

159 public int getColumnCount() { return columnNames.length; } 160

161 public Object

162 getValueAt(int row, int col) {

163 Object retval = null;

164 Account aa = null;

165 // return "---"; // rowData[row][col];

166 int count = 0;

167 for (Iterator itr=current.getAllSubs(); itr.hasNext(); )

168 {

169 count++;

170 aa = (Account) itr.next();

171 if (count > row) { break; }

172 } // next


173 switch (col) {

174 case 0:

175 retval = aa.getName();

176 break;

177 case 1:

178 retval = aa.getOwner();

179 break;

180 case 2:

181 retval = aa.getTotal();

182 break;

183 } // endswitch

184 return retval;

185 } // getValueAt 186

187 public boolean

188 isCellEditable(int row, int col)

189 {

190 return false;

191 } // isCellEditable 192 };

193 list = new JTable(model);

194 list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 195

196 list.getSelectionModel().addListSelectionListener(

197 new ListSelectionListener()

198 {

199 public void

200 valueChanged(ListSelectionEvent e)

201 {

202 ListSelectionModel lsm = (ListSelectionModel)e.getSource();

203 if (lsm.isSelectionEmpty()) {

204 view.setEnabled(false);

205 } else {

206 view.setEnabled(true);

207 }

208 } // valueChanged

209 }

210 );

211

212 retval = new JScrollPane(list); 213

214 return retval; 215

216 } // createList


image


number of subaccounts defined for the account. Since we’re defining our AbstractTableModel as an inner class, we have access to the data in the outer (BudgetPro) class. We use the instance variable current, which refers to whichever account we’re currently working with. A quick check of the Account class shows that an Account object can return the number of subaccounts (or “children”) via its size() method. So for our getRowCount() method we return current.size()—provided that current is not null. If it is null, we return 1 rather than 0, so that the table itself shows up and the headings appear. (But it also means that getValueAt() has to deal with requests for data from the first row when data may not exist.)

The core of what makes our data appear is the getValueAt() method, lines 161–185. Since each row represents a subaccount of the current account, we’ll just iterate through current’s list of subaccounts until we reach the row-th subaccount; for example, to get the third row we iterate over this list of subaccounts until we get to the third one returned by the iterator’s next() method. This is a bit “brute force,” to keep marching over the list of accounts, but for our small data size it’s not bad. (Another approach would be to change the Account class to provide a method to return the n-th subaccount. Then it can use its internal knowledge of the way it stores subaccounts to provide a more efficient access. Alternately, our extended AbstractTableModel could iterate over the list once and store the subaccounts in an array, for quicker access later; the trick here is that the array needs to be refreshed every time the account changes—so we took the simple approach.)

Once we have a row selected, we use the switch/case construct to choose the correct data for the requested column. (See the listing in Example 16.2, lines 173–183.)

The return value for getValueAt() is an Object. Here’s one situation where that is very useful. Refer to the definition of the Account object and you’ll see that getName() returns a String, but getOwner() returns a User and getTotal() returns an SAMoney object. Since retval is the most generic type, Object, it can handle all three results.

But how does JTable deal with these odd types? How can it display an SAMoney object when it doesn’t know what one is? There is both a simple and a complicated answer to that question; we’ll try to give you both.


16.7.2.9 Renderers

The simple answer is that JTable, to display the data returned by

getValueAt(), will call the toString() method on the object. As long as we


return an object which has a toString(), we’re fine. Both User and SAMoney

do have such a method, so they fit fine here.

The more complex answer has to do with why JTable calls the toString() method at all. The JTable uses, behind the scenes, a complex table cell display mechanism, called a table cell renderer. A renderer is an object that displays data in a certain way. Each table cell renderer returns a GUI component, and if you don’t want to use the default renderer, you can define your own table cell renderer for your table. This allows you to display almost anything you can imagine inside a table’s cell. The renderer acts as a template for those cells and will be called upon with the result of the getValueAt(), along with a few more parameters, so that it can build and display the resulting cell.

Let’s revisit our simple explanation above, in light of the concept of a renderer. The default cell renderer for a JTable uses just a JLabel. When called upon, the default cell renderer is given the object returned by getValueAt() and the renderer fills its JLabel by calling its setText() method, passing in the result of toString() on the given object. That’s how toString() got called on all our results. You can explicitly set a different renderer using the setDefaultRenderer() method on JTable.

In the Javadoc for Swing table objects we find this interface:


public Component getTableCellRendererComponent(JTable table,

Object value, boolean isSelected, boolean hasFocus, int row,

int column)


This tells us that if we want to write a class which can act as a renderer, it needs to implement this method. The method will be called with the value returned by getValueAt(), but the row and column (and table) will be repeated here in case your renderer cares. For example, having the row and column would allow you to create a table with the third column of the table in green—your method could check the column number, and if it is 2 (columns are numbered 0, 1, 2, . . . ) set the background color to green for the Component that you would return.


JLabel retval = new JLabel();

// ...

if (row == 2) { retval.setBackground(Color.GREEN);

} else {

retval.setBackground(Color.WHITE);

}

return retval;


The full implementation of a renderer can also take into account whether or not the cell is selected and/or has focus. This has to do with enabling mouse clicks to select either that particular cell or the row or column containing that cell. You will likely want to render the cell differently (with a darker color, perhaps) to show that it has been selected. Whatever the renderer, you set up and then return a GUI component whose attributes (font, color, size, and so on) are used to display that cell.

We hope you get the idea—there is a lot more to renderers than we will cover here. The Java Tutorial covers them more, and the Javadoc pages have some introduction, too.

Similar to renderers are editors. When a user clicks in a table cell, the table may allow him or her to edit its contents. A cell editor is needed to do that, and then your program needs to do something with the value that was entered. For our BudgetPro example we avoid this complexity by disallowing the user to enter anything into the table—our table is for display only. We do this on lines 187–191 by overriding the method isCellEditable() to always return false:


187

public boolean

188

isCellEditable(int row, int col)

189

{

190

return false;

191

} // is CellEditable


Notice that the method is passed the row and column means that you could make some cells editable and some not.


16.7.2.10 Selection Listeners

Let’s look at the last part of the table that we implement for BudgetPro:


194 list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);


This call tells our table (list) that we want to allow the user to select only a single row or column at a time. Valid options are:


ListSelectionModel.SINGLE_SELECTION ListSelectionModel.SINGLE_INTERVAL_SELECTION ListSelectionModel.MULTIPLE_INTERVAL_SELECTION


The latter two allow the user to select more than one row at a time; multi- ple intervals mean that the selected rows can be discontinuous. (Think “Shift+click” versus “Control+click” as the user action that selects these.)

So what will our program do, once the user has made a selection? The se- lected row is a subaccount of the current account and we will allow the user to display that account and its subaccount, if any. Think of it as “changing directory” into that account, to look at or change its status.

For a table to take an action when a selection is made you need another listener called a selection listener. We wrote:


196 list.getSelectionModel().addListSelectionListener(

197

new ListSelectionListener()

198

{

199

public void

200

valueChanged(ListSelectionEvent e)

201

{

202

ListSelectionModel lsm =

(ListSelectionModel)e.getSource();

203



if (lsm.isSelectionEmpty()) {

204



view.setEnabled(false);

205



} else {

206



view.setEnabled(true);

207



}

208


}

// valueChanged

209

}



210

);




Similar to how a table has a table model behind it, it also has a selection model behind it. We don’t need to reimplement an entire selection model; we just retrieve the default one from our table (list.getSelectionModel()) and add a listener to it so that it will notify us when something has changed.

The javax.swing.event.ListSelectionListener is an interface with only one method, so it’s easy to extend and override it in place, as we do, begin- ning at line 197. When called, it will be handed an event (e) and we take the source of that event and coerce it to a ListSelectionModel. That’s safe to


do here because it can’t be any other type of event—or we wouldn’t have been called. All we’re doing with it is checking to see if the user just selected or dese- lected something. The only action we take is to enable or disable the view button.

Deep inside the cdaction object is a line that does the real action that we’re after with our selection. It says:


61 int row = list.getSelectedRow();


This shows that a JTable (list) has a method, getSelectedRow(), which will return the row number of the row that the user has selected (that is, clicked on). This is all part of the action listener (defined on lines 54–75 of BudgetPro) for the View Subaccount button.


54 private ActionListener cdAction = new ActionListener() 55 {

56 public void

57 actionPerformed(ActionEvent e) 58 {

59 // this is the action for VIEW subdirectory;

60 // a "cd" into the subaccount.

61 int row = list.getSelectedRow();

62 // System.out.println("Row="+row); // DEBUG; TODO: REMOVE

63 if (row > -1) { // only if a row was selected

64 String subname = (String) model.getValueAt(row, 0);

// name column

65 Account next = current.getSub(subname);

66 if (next != null) {

67 current = next;

68 // System.out.println("cd to:"+current.getName());

69 setStatus();

70 // notify the table, too

71 model.fireTableDataChanged();

72 } // TODO: else infodialog or Beep. 73 }

74 }

75 } ;


With the row number in hand, the actionPerformed() method can then use the row number to look up the account name. Since the account name is in the first column (numbered 0) of our table, we call getValueAt(row, 0) to get that name. Then we give the name to the current account to look up the subaccount (line 65).


As long as this returned Account is not null (line 66), we can make it the current account (line 67). At that point the display needs to be updated, so we:

1) call our own setStatus() method, to update the upper portion of our GUI, and 2) tell the table that its data has changed (line 71).


16.7.2.11 Ready, aim, fire!

A word about the fire...() methods. They are not part of the TableModel interface definition. Rather, they are part of the AbstractTableModel class. When a Java class is declared abstract it means that some methods need to be implemented by those classes that use (extend) this class. An abstract class can still have lots of intact, completely implemented methods, and that is the case with AbstractTableModel.

The TableModel interface defines methods for adding and removing lis- teners. Any implementation of the TableModel interface needs to support these, and to notify any listeners when a change occurs. Such listeners will re- ceive a call to their tableChanged() method when such a change occurs. But it doesn’t tell us how such notification is triggered. Moreover, the change event, when received by the listener, needs to define the extent of the change—just a single cell? a whole row? a column? all columns? and so on.

The AbstractTableModel provides some methods for us to call when a change in the data has occurred, methods that will then notify all the registered listeners (Table 16.1). Moreover, it has different methods depending on the extent of the change, so that the TableModelEvent, sent to all TableModelListeners, can be constructed with the appropriate definition of what has changed.

We used (line 71) the fireTableDataChanged() since the content of the table will change with a change of accounts, but the structure remains the same. It is also a handy all-purpose method for you to use if you’d rather not add the complexity of determining which rows have changed to your code.

Finally, remember that anyone who uses (extends) AbstractTableModel, including the DefaultTableModel class, gets these methods for their use.

There are several other interactions that are supported by JTables, ones that don’t require you to do anything to provide them to your application’s end user. When running the BudgetPro GUI, did you try to drag the column headings? You can also rearrange and resize columns. This is the default behav- ior for JTables. You can turn it off, however, if you want your columns to be fixed:


Table 16.1 AbstractTableModel methods for data change notification


image

image

Method


fireTableCellUpdated(int row, int col)


fireTableRowsUpdated(int first, int last)


fireTableRowsDeleted(int first, int last)


fireTableRowsInserted(int first, int last)


fireTableDataChanged()


fireTableStructureChanged()


fireTableChanged(TableModelEvent e)

When to use

Use when only a single cell has changed.

Use when the given range of rows (inclusive) have changed.

Use when the given range of rows (inclusive) have been deleted.

Use when the given range of rows (inclusive) have been inserted.

Use when any/all of the row data have changed, including the number of rows; columns have not changed.

Use when the columns have changed—that is, when the names, number, or types of columns have changed.

An all purpose method, where you have to define the change in the TableModelEvent object.


image



table.getTableHeader().setResizingAllowed(false); table.getTableHeader().setReorderingAllowed(false);


The call is not made on the table directly, but rather on its header. We get the JTableHeader object with the call to getTableHeader(). There is much more that could be said about JTableHeader objects, but we will leave that “as an exercise for the reader”; we’ve got to draw the line somewhere.


16.7.2.12 Scrolling

One last thing to mention about the createList() method is how we deal with tables that are larger than the viewing area. This is typically done with a scroll pane, a GUI element familiar to anyone who has used a word processing program. Such scrolling is accomplished in Swing by putting the potentially big object, such as our table, into a JScrollPane container.


Don’t think of it as adding scrollbars to the table. Rather, we’re putting the table into a container that has scrollbars, and this container is smart enough to retrieve and display the table’s header separately from the table (thus, the table’s data scrolls but the header stays put).

Here, in one step, we create the JScrollPane object and initialize it with the JTable that we want to be scrolled over.


212 retval = new JScrollPane(list);


Think of the JScrollPane as a window with scrollbars through which we can view the JTable. It has the convenient side effect of taking care of the table’s heading for us. Without the scroll pane (e.g., if we just put the JTable in a JPanel) we’d get only the data and no heading, unless we also did a lot of extra work using other objects and method calls.

It is possible to set the JScrollPane to show horizontal as well as vertical scrollbars. Those scrollbars can be made to be always or never visible, or visible only as needed. Setting a scrollbar to “never visible” effectively turns off any scrolling in that direction. Use the setHorizontalScrollBarPolicy() and setVerticalScrollBarPolicy() methods to set the value to one of:


JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED JScrollPane.HORIZONTAL_SCROLLBAR_NEVER JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS


Scroll panes can scroll over any GUI element—that is, any Component object, not just tables. For more information on scroll panes, be sure to refer to the Javadoc pages.


16.7.2.13 Dialogs

We have covered most of the code in the main GUI functionality—the way it initially creates its parts and lays them out for display. We have examined the JTable in some considerable detail and looked at a few actions associated with buttons. Now we need to get to the user interaction that allows us to create a new account.

Lines 244–268 of BudgetPro are the action that gets attached to the button for creating a new subaccount.



244

ActionListener creatAction = new ActionListener()

245

{

246

public void

247

actionPerformed(ActionEvent e)

248

{

249

Account child;

250

// get the info via a Dialog (of sorts)

251

if (askem == null) {

252

askem = new AcctDialog(frame, "New Subaccount");

253

} else {

254

askem.clear();

255

askem.setVisible(true);

256

}

257

String subName = askem.getName();

258

String subAmnt = askem.getAmnt();

259


260

// if empty, assume the operation was cancelled, else:

261

if ((subName != null) && (subName.length() > 0)) {

262

child = current.createSub(subName, subAmnt);

263

setStatus();

264

model.fireTableDataChanged(); // notify the table

265

}

266

}

267

};

268

creat.addActionListener(creatAction);


Looking at the constructor for an Account, we see that we need three things: a User object (who will own the subaccount), a name for the new sub- account, and the dollars to be allocated to this subaccount. To keep our exam- ple simpler, we will always use the current user as the User for creating the new Account. That means we only need some way to get the name and dollar amount.

In the GUI world, this sort of information is typically provided in a dialog box, a window that has blanks to be filled in (Figure 16.5). Then, when the dialog is closed, we can ask that dialog for the values that the user provided.

Swing has some ready-to-use dialogs for warnings or for simple single value inputs. Since we want to get two pieces of data, we need to create our own dialog and display it.

What may seem strange about the createAction() is that we only create the dialog once (line 252), when the reference to it (askem) is null (line 251). Thereafter, we simply clear out the previous values (line 254) and make the dialog visible again (line 255). That is all that it takes to use the dialog more



image

Figure 16.5 Dialog for creating a new subaccount


than once. We could throw away the dialog (or let it get garbage-collected) by declaring it internal to the actionPerformed() method. Then on each button press the dialog would need to be recreated. Well, it’s slower to do it that way, and for a button click we want quick response—so we keep it around from one use to the next. When the user closes the dialog, all that really does is makes it invisible; to reuse it, we make it visible again.

Notice, too, that in either case—creating the dialog or making it visi- ble—control does not return to our method until the user has dismissed the dialog. That’s because it’s a modal dialog, one that allows no other interaction with the application until the user has responded to this dialog.

The dialog is dismissed (finished, ended, put away) simply by making it no longer visible. For example:


73 dialog.setVisible(false); // go away


New to our application, in AcctDialog, is the JTextField. On lines 22 and 23 we declare two of them, one for the account name and the other for the amount.


22 nameField = new JTextField(25);

23 amntField = new JTextField(9);


The size that we pass in to the constructor is the number of characters; it sets a maximum for that field, but also gives a clue to some layout managers as to how big the field needs to be.

Speaking of layout managers, we use a few here, including a BoxLayout, to format the buttons relative to each other; a BorderLayout, to hold the overall dialog; and a newer layout manager, the SpringLayout, which is new as of Java 1.4. The Swing Tutorial provides a handy utility class for dealing

16.8 Review 373

image


with SpringLayouts, and we make use of it to format the labels and text fields relative to each other.

Similar to a JTextField is a JPasswordField. It behaves just like a JTextField but instead of showing the characters that the user types it shows, by default, an asterisk for each character typed, thereby hiding the password from passers-by. The character that is displayed can be changed to other than the asterisk—see the Javadoc page.

We do something new with our JLabel in AcctDialog, too. We mess with its font:


44 Font font = label.getFont();

45 label.setFont(label.getFont().deriveFont(font.PLAIN, 14.0f));


This gets the font from the label, however it might have been set, then creates a new value for the font, keeping whatever font family it might have been, but making it 14 pt plain (not italic, not bold).

We also put HTML text in the JLabel:


40 JLabel label = new JLabel("<html><p align=left><i>"

41 + "Enter the info to create a subaccount.<br>"

42 + "</i>");


All but the oldest versions of Swing will display the HTML text as it would be formatted by a browser. Here, we make the text italic by means of the (now deprecated) <i> tag, thereby undoing the effort to make it plain in lines 44 and 45.

One of the arguments to the dialog’s constructor is the JFrame inside which the dialog will appear. Lines 102 and 103 round out this picture, setting the size of the dialog and anchoring its position relative to the parent frame. The last step for the constructor is to make the dialog visible, thereby passing control to it.


16.8 REVIEW


When programming in Swing, we create the GUI objects and then let Swing do the work of managing all the interactions. We created:


• Containers to hold GUI objects, such as JFrame for our outermost win- dow, JPanel for an assortment of objects, and JScrollPane for viewing larger objects through a scrollable window.

• Labels (JLabel class) to hold either a short bit of text, or an image, or both; it can even take snippets of HTML, for fancier formatting and coloring of text.

• Buttons (JButton class) to which we attached actions—the code frag- ments that get called when the buttons get pushed; a button could have text and/or an image displayed in it.

• Actions—whether for buttons or selections (or other triggers yet to be discussed), an action is the code that runs when the event (e.g., button press) occurs.

• Text fields (JTextField class) to take small amounts of user input; our application didn’t need the other types of text fields (JTextArea and JTextPane) useful for much more extensive user input.

• A JTable instance and its associated TableModel, SelectionModel, and TableCellRenderer which provide tremendous flexibility and control over table behavior and contents.

• A JDialog instance with custom content, to allow for multiple user in- puts; the dialog comes and goes with its visibility; since it’s a modal dialog, when it is visible, it “hogs” all the user interactions; it is possible to make nonmodal dialogs, but our application didn’t need to.

LayoutManagers for our JFrame and JPanels, used to place objects within a container with various algorithms for placement and expansion.


16.9 WHAT YOU STILL DONT KNOW


One could spend a career learning the vagaries of layout managers, especially the way they interact (e.g., a BoxLayout inside the various regions of a BorderLayout). There is still an art to getting all the interactions right; it’s often quickest to prototype the layout before you get too committed to a par- ticular layout. Also, putting objects into containers can help you subdivide the layout problem into more manageable pieces. You can even go so far as to write your own LayoutManager, a topic we do not cover in this book.

The information that we display in the JTable in our example is hierar- chical. Swing provides a JTree object for displaying such information. Like a

image

16.11 Exercises 375

filesystem tree familiar to many PC users, the JTree allows you to view multi- ple levels at once and to open and close nodes, exposing or hiding their subtrees. It would make more sense to use the JTree in our example, but then we wouldn’t have been able to describe all the ins and outs of the JTable, a class that is so useful in so many applications.

There are many more Swing classes that we haven’t discussed, though many will behave similarly to those you have seen here. There are topics that we have avoided—for example, we haven’t talked about sorting JTables by clicking on the column headings, or about TableColumnModels which add another layer to JTables. Some of what you would need to know in order to use these Swing classes you can glean from the Javadoc pages. The information there should make more sense now, based on your experience with the various Swing mechanisms that you’ve seen in these pages. For some other Swing topics you will have to search farther, and there are plenty of books on the topic—the classic one, the The JFC Swing Tutorial from Sun, being over 900 pages long. Is it any wonder that we didn’t cover it all in this chapter?

16.10 RESOURCES


The JFC Swing Tutorial: A Guide to Constructing GUIs by Kathy Walrath and Mary Campione, Addison-Wesley, also available online at http://java.sun.com/docs/books/tutorial/uiswing/index.php.

• Our favorite bookmark within the Swing tutorial, the visual index of the various Swing components, is at http://java.sun.com/docs/books/ tutorial/uiswing/components/components.php.

• If you want a better understanding of layout managers, we recommend this tutorial by Jan Newmarch at Monash University in Australia: http://pandonia.canberra.edu.au/java/xadvisor/geometry/ geometry.php. Don’t let the mention of AWT scare you away. Almost all of the layout managers (except BoxLayout and SpringLayout) are actually from AWT, and they all apply to Swing.


16.11 EXERCISES


1. Use different layout managers to create of the status area of the BudgetPro main window, laying out the status information differently. Make the


button and account name information left-justified, and stack the Total and Remaining labels vertically on top of each other. Do you always need to create new intermediate containers? Can you do it just with GridBagLayout?

2. Modify the BudgetPro program so that it displays a pop-up dialog when you try to create a subaccount with more money than is available to that account.

3. Modify the dialog used for creating subaccounts, so that it also prompts for the owner’s name. This can get more complicated if you want to allow only valid user names. Instead, let any name be entered and create a User object for it.

4. Modify the BudgetPro program and associated classes to allow for editing of the values in the accounts, so that the user can change dollar allocations. Start with the ability to edit the value in the table (custom editor).

5. Replace the JTable (and the View Subaccount button) with a JTree

object.