1406 Notes 3
1406 Notes 3
Here are the individual topics found in this set of notes (click on one to go there):
What is an event ?
• An event is something that happens in the program based on some kind of triggering
input.
• typically caused (i.e., generated) by user interaction (e.g., mouse press, button press,
selecting from a list etc...)
o the component that caused the event is called the source.
• can also be generated internally by the program
Nothing happens in your program UNLESS an event occurs. JAVA applications are thus
considered to be event-driven.
Here is a picture that describes the process of user interaction with a GUI through events:
1. The user causes an event (e.g., click button, enter text, select list item etc...)
2. The JAVA VM invokes (i.e., triggers) the appropriate event handler (if it has been
implemented and registered).
o This invocation really means that a method is called to handle the event.
3. The code in the event handling method changes the model in some way.
4. Since the model has changed, the interface will probably also change and so components
should be updated.
Notice that JAVA itself waits for the user to initiate an action that will generate an event.
• This is similar to the situation of a cashier waiting for customers ... the cashier does
nothing unless an event occurs. Here are some events which may occur, along with how
they may be handled:
o a customer arrives - employee wakes up and looks sharp
o a customer asks a question - employee gives an answer
o a customer goes to the cash to buy - employee initiates sales procedure
o time becomes 6:00pm - employee goes home
JAVA acts like this employee who waits for a customer action. JAVA does this by means of
something called an EventLoop. An Event Loop is an endless loop that waits for events to
occur:
Notice that incoming events (i.e., customers/clients) are stored in the event queue in the order
that they arrive. As we will see later, events MUST be handled by your program. The JVM
spends all of its time taking an event out of the queue, processing it and then going back to the
queue for another.
While each event is being handled, JAVA is unable to process any other events. You MUST be
VERY careful to make sure that your event handling code does not take too long. Otherwise the
JVM will not take any more events from the queue. This makes your application seem to
"hang" so that the screen no longer updates, and all buttons, window components seem to freeze
up !!!
In a way, the JVM event loop acts as a server. It serves (or handles) the incoming events one at
a time on a first-come-first-served basis. So when an event is generated, JAVA needs to go to
the appropriate method in your code to handle the event. How does JAVA know which method
to call ? We will register each event-handler so that JAVA can call them when the events are
generated. These event-handlers are called listeners (or callbacks).
A listener:
For each event type in JAVA, there are defined interfaces called Listeners which we must
implement. Each listener interface defines one or more methods that MUST be implemented in
order for the event to be handled properly.
There are many types of events that are generated and commonly handled. Here is a table of
some of the common events. The table gives a short description of when the events may be
generated, gives the interface that must be implemented by you in order for you to handle the
events and finally lists the necessary methods that need to be implemented. Note, for a more
complete description of these events, listeners and their methods, see the JAVA API
specifications.
So, if you want to handle a button press in your program, you need to write an
actionPerformed()method:
If you want to have something happen when the user presses a particular key on the keyboard,
you need to write a keyPressed() method:
Once we decide which events we want to handle and then write our event handlers, we then need
to register the event handler. This is like "plugging-in" the event handler to our window. In
general, many applications can listen for events on the same component. So when the
component event is generated, JAVA must inform everyone who is listening. We must therefore
tell the component that we are listening for (or waiting for) an event. If we do not tell the
component, it will not notify us when the event occurs (i.e., it will not call our event handler).
So, when a component wants to signal/fire an event, it sends a specific message to all listener
objects that have been registered (i.e., anybody who is "listening"). For every event, therefore,
that we want to handle, we must write the listener (i.e., event handler) and also register that
listener.
To help you understand why we
need to do this, think of the
Olympic games. There are
various events in the Olympics
and we may want to participate
(i.e., handle) a particular event.
Our training and preparation for
the event is like writing the event
handler code which defines what
we do when the event happens.
But, we don't get to participate in
the Olympic games unless we
"sign-up" (or register) for the
events ... right ? So registering
our event handlers is like joining
JAVA's sign-up list so that JAVA
informs us when the event
happens and then allows our event
handler to participate when the
event occurs.
To register for an event (i.e., enable it), we need to merely add the listener (i.e., your event
handler) to the component by using an addXXXListener()
method (where XXX depends on the type of event to be handled). Here are some examples:
aButton.addActionListener(ActionListener anActionListener);
aJPanel.addMouseListener(MouseListener aMouseListener);
aJFrame.addWindowListener(WindowListener aWindowListener);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(200, 200);
}
// Must write this method now since SimpleEventTest implements
the ActionListener interface
public void actionPerformed(ActionEvent e) {
System.out.println("I have been pressed");
}
Why would you want to disable a listener ? If we don't want to use it, why even make one ? We
will see later that we sometimes need to temporarily disable events while other events are being
handled so as to avoid overlapping events, which can cause problems.
Adapter Classes:
Assume that we would like to handle a single event ... a mouseClicked event whenever someone
clicks the mouse inside of our application's window. Recall from COMP1405/1005, that if a
class implements an interface it MUST implement ALL of the methods listed in the interface.
For example, the MouseListener interface is defined as follows:
So, if we simple make our main application implement the MouseListener interface, then we
will be forced to implement all 5 methods: mouseClicked, mouseEntered, mouseExited,
mousePressed and mouseReleased !!!! We can however, merely write empty methods for these
other 4 event types but this is a lot of extra code writing that just wastes time and makes the code
more confusing:
public class MyApplication extends JFrame implements
MouseListener {
...
public void mouseClicked(MouseEvent e) { /* Put your code
here */ };
public void mouseEntered(MouseEvent e) {};
public void mouseExited(MouseEvent e) {};
public void mousePressed(MouseEvent e) {};
public void mouseReleased(MouseEvent e {};
...
}
It does seem a little silly to have to write 4 blank methods when we do not even want to handle
these other kinds of events. The JAVA guys recognized this inconvenience and solved it using
the notion of Adapter classes. For each listener interface that has more than one method
specified, there exists an adapter class with a corresponding name:
These adapter classes are abstract JAVA classes that implement the interfaces they correspond
to. However, even though they implement these interfaces ... their methods remain empty. For
example, the MouseAdapter class looks like this:
public abstract class MouseAdapter implements MouseListener {
public void mouseClicked(MouseEvent e) {};
public void mouseEntered(MouseEvent e) {};
public void mouseExited(MouseEvent e) {};
public void mousePressed(MouseEvent e) {};
public void mouseReleased(MouseEvent e {};
}
They are merely classes that are provided for convenience sake to help us avoid writing empty
methods. So, we can simply write subclasses of these adapter classes, then we can take
advantage of these blank methods through inheritance.
Well, we don't want to have to make our user interfaces subclass one of these adapter classes ...
this would be bad since we would lose the freedom of creating our own arbitrary class
hierarchies. Consider handling an event for dealing with a simple mouse click. We could make
our own internal class to handle this event.
new MouseAdapter() {
public void mouseClicked(WindowEvent event) {
System.out.println("Do Something fun");
}
}
This syntax actually creates an internal class as a subclass of MouseAdapter. The class has no
name, it is considered to be an anonymous class. This code actually creates an instance of the
anonymous class and returns it to us. It is weird syntax. We will see below how we can
"embed" this code inside other code just like we use any other objects.
setLayout(new FlowLayout());
add(aButton1);
add(aButton2);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(250,100);
}
Notice that the getActionCommand() method is sent to the ActionEvent. It returns a String
containing the text that is on the button that generated the event. We then compare this string
with the labels that we put on the buttons to determine which button was pressed. One
disadvantage of this approach is that our event handler depends on the label associated with the
button. Although this is safe in this particular example, there are many occasions when the label
associated with a button could change (e.g., international applications). Therefore, we could use
the getSource() method which returns the component (i.e., an Object) that raised the event
instead of getActionCommand() to compare the actual button objects instead of the labels. To
do this, we need to make two modifications. First, we need to store the buttons we create into
instance variables and second, we need to compare the object that generated the event with these
buttons using the identity (==) comparison.
// We need to make the buttons instance variables and assign
// them in the constructor so that we can access these objects
// from within our event handler code.
JButton aButton1, aButton2;
Another disadvantage of the previous example is that if more buttons (or other components that
generate action events) are added, the number of "if-statements" in our handler will increase and
become more complex, which may not be desirable. One way to handle this disadvantage (and
the previous one as well) is by using anonymous classes. The following code would replace the
code in the constructor that registers our frame subclass as a listener. The actionPerformed
method of our class would no longer be required. Here, each button has its own event handler:
aButton1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("That felt good!");
}
});
aButton2.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("Ouch! Stop that!");
}
});
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.plaf.basic.BasicArrowButton;
public class SlideShow extends JFrame {
JPanel slides;
CardLayout layoutManager;
slides.setBorder(BorderFactory.createLineBorder(Color.BLACK));
slides.setLayout(layoutManager = new CardLayout(0,0));
slides.add("trilobot.jpg", new JLabel(new
ImageIcon("trilobot.jpg")));
slides.add("laptop.jpg", new JLabel(new
ImageIcon("laptop.jpg")));
slides.add("satelite.jpg", new JLabel(new
ImageIcon("satelite.jpg")));
slides.add("torch7.gif", new JLabel(new
ImageIcon("torch7.gif")));
slides.add("SIGNIN.jpg", new JLabel(new
ImageIcon("SIGNIN.jpg")));
add(slides);
fwd.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
layoutManager.next(slides);
}
});
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(220,300);
}
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class HandleTextFieldAndButton extends JFrame {
JTextField valueField, squareField, rootField;
setLayout(new BoxLayout(this.getContentPane(),BoxLayout.Y_AXIS));
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(250,180);
}
Notes:
Although not done in our example here, we can actually handle an ActionEvent for a text field.
An action event is generated when the user presses the ENTER key while typing in a text field.
As with button clicks, you can handle this ENTER key press in the text field by writing an
actionPerformed() method for the text field. In such a method, the getActionCommand()
method will return the text inside the text field. We can also send the getSource() method to the
action event to get the text field itself and then get its text as follows:
((JTextField)e.getSource()).getText()
As we will discuss later, the style of coding that we are using in our example here is not "clean"
since the button accesses the text field directly.
Handling RadioButtons
Let us modify the previous example by using radio
buttons that allow us to decide which kind of operation
we will do on the value entered in the text field. We
will replace the Compute button with 4 radio buttons
where each radio button, when clicked, will perform a
different operation on the value from the text field and
then display the result in the answer text field. Here are
some interesting points about the code:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class HandleTextFieldWithRadioButtons extends JFrame
implements ActionListener {
setLayout(new
BoxLayout(this.getContentPane(),BoxLayout.Y_AXIS));
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(250,220);
}
Note that for this example it seems appropriate to have our JFrame class act as a listener since
the event handling code is the same for all components. Nothing is gained by using another class
to handle the events.
Note as well that the JCheckBox works similar to the JRadioButton, except that normally
JRadioButtons should have only one on at a time, while JCheckBoxes may normally have
many on at a time. Here is how the window would look if JCheckBoxes were used instead
(although keep in mind that in this application, it doesn't make sense to have more than one
button on at a time.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class SelectedButtonsExample extends JFrame implements
ActionListener {
private JButton[] buttons;
setLayout(new FlowLayout());
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(350,75);
}
Notice that we did not use a ButtonGroup to ensure that only one button is selected by itself.
That is because JButtons do not work with ButtonGroups. Instead, we had to do everything
manually. If we wanted to allow multiple buttons on at a time, we would merely change the
action performed method to be:
Notice that we did a setRolloverEnabled(false) for our buttons. This is because JAVA, by
default, has a default rollover enabled value of true for the buttons, which redraws our buttons
whenever the mouse passes over them. In this case, a completely separate icon may be used.
So, we can modify our code to have our image show when the mouse rolls over the button
instead of when we click it by setting this icon and enabling the rollover effect.
• mouseClicked - one of the mouse buttons has been both pressed and released within the
same component.
• mouseEntered - the mouse cursor has entered the component's area.
• mouseExited - the mouse cursor has left the component's area.
• mousePressed - a mouse button has been pressed.
• mouseReleased - a mouse button has been released.
• getClickCount() -
returns the number of
successive mouse clicks
• getButton() - returns the
button that was pressed (1
= left, 2 = middle, 3 =
right)
• getX() - returns the x
coordinate of the mouse
location w.r.t. top left
corner of component.
• getY() - returns the x
coordinate of the mouse
location w.r.t. top left
corner of component.
• getPoint() - returns the
(x,y) point of the mouse
location w.r.t. top left
corner of component.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class WorkingWithMouseEvents extends JFrame implements
MouseListener {
JPanel blankArea;
JTextArea textArea;
JLabel movableImage;
Class latestComponent;
public WorkingWithMouseEvents(String title) {
super(title);
blankArea.setBorder(BorderFactory.createLineBorder(Color.black));
blankArea.setPreferredSize(new Dimension(350, 150));
add(blankArea);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(370,390);
}
How can we modify the code to only handle clicked events if it was a double-click ?
We can have a component listen for KeyEvents by adding a KeyListener with the
addKeyListener() method. Inside these listeners, we can determine which key was pressed by
examining the KeyEvent object itself. The KeyEvent class has a bunch of static constants that
represent all the keys on the keyboard. These constants all begin with VK_ and you can look in
the JAVA API to get the exact names. Here are a few:
We send the getKeyCode() message to the KeyEvent to get back the code representing the key
that was pressed. We then compare the code to one of these constants. Since every key press
generates an event, if we want to detect multiple keys pressed at the same time, me must make
use of both keyPressed() and keyReleased() listeners and keep track by ourselves as to which
key has been pressed. There is also a keyTyped() event which can detect the entering of a
Unicode character. Often a keyTyped() event is synonymous with a key press/release sequence
(kinda like a mouseClicked event).
Also, we can combine other listeners with key presses ... for example, if we want to detect a
SHIFT-CLICK operation.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ShiftButtonTest extends JFrame implements ActionListener,
KeyListener {
private boolean shiftPressed;
shiftPressed = false;
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(300,100);
}
If you do not want this "focus-oriented" behavior (e.g., perhaps you want to listen for a particular
key press regardless of which component has been selected) you can have the JFrame listen for
the key press. In this case, you must "disable" the focus ability for all the components on the
window (but not the JFrame itself). In our example, we would replace the line:
aButton.addKeyListener(this);
with the following lines that disallow the buttons to have the focus:
this.addKeyListener(this);
aButton.setFocusable(false);
bButton.setFocusable(false);
Be aware however, that this disables the "normal" behavior for the window and will prevent
standard use of the TAB key to traverse between components in the window.
• the portion of your code that deals with the appearance of the application and the
interaction between the interface components.
It is IMPORTANT to keep the model separate from the interface. Also, with respect to the
GUI, we need to have a "good" understanding of how the components of the interface will work
and interact with each other. Let us see if we can explain how the components interact. We
need to determine ALL of the following:
Recall that a user interface works as follows (based on what is called the Observer Pattern).
There are two questions regarding the updating stage of the components:
Well, as a rule of thumb, we should do an update whenever we make any change to the model.
When we go to do the update, if we know our code very well, we can determine which
components will need updating based on the particular change to the model. However, in case
we have a complex application, we may not know which ones need updating, so we can take the
simple brainless approach and just update everything. We often simply write an update()
method, where we update all the components (i.e., a kind of global update is performed). We
call this method whenever the model changes.
So, REMEMBER the two VERY important things that
you should normally do in every event handler:
Example:
Let us now build the following application which represents a list of "things to do":
• The model consists of a collection (stored in a list) of items. In our example, the items
will be Strings.
• The user can add new items to the list by typing the new item in the text field and
clicking the Add Button.
• Items are removed from the list by selecting an item from the list and clicking the
Remove Button.
In this "to do list" application, the model is simply the collection (e.g., a Vector) of things to do
(i.e. Strings). So we don't need to make a model class in this simple example. What about the
GUI ? First, we determine how the interface should react to user input. We must determine
which events are necessary to be handled. The events and their consequences are as follows:
AddButton
actionPerformed() - Should take text from text field and add it to the list.
RemoveButton
actionPerformed() - Should determine selected item from the list and then
remove it.
TextField
Nothing for now. We will add some behavior here later.
List
Nothing for now. We will add some behavior here later.
Now what about the model updating ? How do these events change the model ? How does the
model then change the window again ?
• Adding an item should cause a new entry to be added to the list (i.e., model). Then we
must show these changes in the list.
• Removing an item also changes the model and we should show the changes right away in
the list as well.
Refreshing the user interface to show the changes in the model is called updating.
Let us now look at a basic "working" application. We will handle the Adding and Removing of
items from the list. The highlighted code below indicates the code required for handling the
events from the buttons and updating the interface:
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class TodoListFrame extends JFrame {
private JTextField newItemField;
private JList itemsList;
private JButton addButton;
private JButton removeButton;
public TodoListFrame() {
this(new Vector<String>());
}
public TodoListFrame(Vector<String> todoEntries) {
super("To Do List");
items = todoEntries;
// ...
// The code for building the window has been omitted
// ...
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(300,200);
update();
Notice:
• we made a single update() method that is called from the Add and Remove button
event handlers as well as when the window is first opened.
• the update really just updated the data in the JList to reflect the changes in the model.
So, REMEMBER the two VERY important things that
you should normally do in your update() method:
1. When no text is in the text field and Add is pressed, a blank item is added.
2. When no item is selected from the list and a Remove is done, our code tries to remove a
null item from the model. Since the model is a Vector, and the remove method for
vectors handles this attempt with grace (i.e., no exception), then it is not really a
problem. However, what if someone changes the underlying model to be something
other than a vector ? We should fix this.
How can we fix these ? First check if there is any text before doing the Add. If there is none,
don't add. The change occurs in the event handler for the Add button. Here is the changed code:
For the remove problem, we would like to have a way of determining whether or not anything
was selected from the list. To do this, we merely ask if the selected list value is null:
private void removeButtonEventHandler() {
if (itemsList.getSelectedValue() !=
null) {
items.remove((String)itemsList.getSelectedValue());
update();
}
}
We have fixed the problems ... but now we have a messy situation. It seems that the JButtons
MUST know about the JList component. The JButtons are somehow "tied" with the JList so
that if the JList is removed and perhaps replaced by something else, we must go into the
JButton event handler and make changes. This is "messy".
• code is not easily maintained when many components rely on other components
• components need to know exactly how they affect other components
A better way to solve these problems is to make the list selection a part of the model. We would
like to use the model as a kind of "middle man" between all component interaction so that this
"dependence" between components is severed.
So ... we will keep track of the item that was selected and this will be part of our model.
Of course this means that we will have to handle the selection event for the JList component.
2. Modify the constructor to initially select a list item if there is one available, and also add
a list selection listener for whenever someone makes a selection from the list:
if (items.size() > 0)
selectedItem = (String)items.firstElement();
else
selectedItem = null;
itemsList.addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
listSelectionEventHandler();
}
});
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(300,200);
update();
}
4. Modify the Remove button event handler to use the new selectedItem variable now. We
must make sure to set the selectedItem to null after an item is removed, since the list will
not have anything selected in it anymore:
5. Modify the update() method to make sure that the selectedItem variable always matches
the item selected from the list:
itemsList.setSelectedValue(selectedItem, true);
At this point, we have a strange bug in our application. It seems that we are unable to actually
select anything from our JList now ! The problem is that our update() method calls
setSelectedValue() which changes the contents of the list. This generates an internal
valueChanged() event ... which is the event that we are handling. Hence, when we do a list
selection, our event handler is called, which itself calls update(). Then update generates another
valueChanged() event which again calls our handler and update() again. Really, this is an
endless loop. JAVA is able to deal with this problem without generating exceptions, but it does
not give us desirable results in that we cannot really select anything from the list ;).
The simplest and most logical solution is to disable the list selection listener while updating and
then re-enable it afterwards. To do this, we will need the actual listener object and de-register it
at the beginning of the update() method, then re-register it afterwards. Here are the steps:
itemsList.addListSelectionListener(itemsListListener =
new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
listSelectionEventHandler();
}
}
);
We have prevented the Add button from doing anything when there is no text in the text field
and the Remove button from doing anything when there is nothing selected. It is best to let the
user know that these buttons will not work under these circumstances. The proper way of doing
this is to disable the buttons at these times. Let us make these changes now. We will make use
of the setEnabled() method for buttons which enables or disables the button according to a
given boolean.
Where do we write the code for disabling these buttons ? Well, does it have to do with
functionality or with appearance ?
After some thought, you realize that this is a "cosmetic" issue and that it has to do with the
"look" of the buttons. Hence, we should make these changes in the update() method.
Disabling the Remove button is easy. Just add the following line to the update() method:
removeButton.setEnabled(selectedItem != null);
There is a small problem. When the interface starts up, the text field is empty and so the Add
button is disabled. That's good. But when the user starts typing in the text field, there is then
text in the text field but the Add button remains enabled. The problem is that update() is not
being called unless an event occurs. So we need to have some kind of event for when the user
types text in the text field. So we will need to make some changes. In addition, this approach
to enabling the Add button results in a dependency on there being a text field. We should create
another instance variable to indicate whether or not there is any text in the text field. We can just
use a boolean, but we may as well keep the whole item that is in there instead of just a flag.
First we need to make the following additions:
Now, we could use handle ActionEvents for the TextField, but these events only occur when
the user presses Enter within that field. Instead, we will make use of something called a
DocumentListener. Every JTextField has a document object associated with it that can be
obtained with getDocument(). We can then add the listener to this object. This way, we can
handle events that occur whenever the text changes (character by character) even if no Enter key
is pressed. We need to make the following changes to our code:
// Declare this instance variable at the top
private DocumentListener newItemFieldListener;
Of course, we will want to now modify the event handler for the Add button to make use of the
newItem field. We will also select the item that was just added. This is not necessary, but it is
a nice form of "feedback" for the user:
private void addButtonEventHandler() {
if (newItem.length() > 0) {
items.add(newItem);
selectedItem = newItem; // select the newly added item
update();
}
}
We should also modify our update() method so that the Add button uses the newItem variable
now:
One last feature that we will add is to clear out the text field AFTER we add a new item. Well,
to do this, we will have to set the newItem to "" in the Add button handler as follows:
private void addButtonEventHandler() {
if (newItem.length() > 0) {
items.add(newItem);
selectedItem = newItem;
newItem = "";
update();
}
}
Notice however, that at this point, the newItem variable will be "", but there will still be some
contents in the text field ... so they are not in agreement. To fix this, we will have to actually
clear out the text field contents. This, of course, has to do with the appearance of the text field,
so we could try placing the following code within the update() method:
newItemField.setText(newItem);
But, we have a small problem. If we were to run our code right now, we would notice a bug
when we tried to type into the text field. Our code would generate the following exception:
JAVA version 1.4 and onward, however, will not allow us to set or modify the contents of the
JTextField while we are handling one of its document events. So, we will need to make the
following changes:
1. avoid setting the text field's text within the update() whenever we call update() from a
DocumentListener
2. remove/add the document listener at the start/end of the update() method.
To do this, we will make a special update() method. In fact, we will split up our update()
method as follows:
// Update called by Document Listeners directly
private void update(boolean calledFromTextField) {
itemsList.removeListSelectionListener(itemsListListener);
newItemField.getDocument().removeDocumentListener(newItemFieldListener);
itemsList.setListData(items);
itemsList.setSelectedValue(selectedItem, true);
removeButton.setEnabled(selectedItem != null);
addButton.setEnabled(newItem.length() > 0);
if (!calledFromTextField) newItemField.setText(newItem);
itemsList.addListSelectionListener(itemsListListener);
newItemField.getDocument().addDocumentListener(newItemFieldListener);
}
// Update used by all the event handlers, except Document event handlers
private void update() {
update(false);
}
Note that all event handlers will call the usual update() method (which will set the text in the
text field), but the DocumentListeners will call update(true) so as to avoid setting the text
illegally. Thus, when we press the Add button and set the newItem variable to "", the call to
update() at the end of the event handler will ensure that the text field's contents are set to "".
Meanwhile, if we make changes to the text field directly, we will not be updating the text field
appearance, as it does not need updating since it will always have the same contents as the
newItem variable anyway.
Now the last improvement is within the update() method. When we make changes to our code
by adding or removing components, we must go to the update method and make changes. It is
difficult to determine what code pertains to which components. We can extract this update code
so that we make separate methods such as updateTextField(), updateList(), and
updateButtons(). These will do the corresponding updates for the individual components. This
way, when we modify or remove a component, it is clear as to what code should be
modified/removed. This alternative method also allows us to update only those components that
have changed (not all). This is good if the interface becomes slow in drawing the components.
We can speed everything up by only updating necessary components. We can also extract the
code for disabling/enabling the listeners into separate methods as well.
The final completed code is shown below (the class name has been changed to
ToDoListFrame2):
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
public ToDoListFrame2() {
this(new Vector<String>());
}
if (items.size() > 0)
selectedItem = (String)items.firstElement();
else
selectedItem = null;
initializeComponents();
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(300,200);
update();
}
updateList();
updateButtons();
if (!calledFromTextField)
updateTextField();
enableListeners();
}
newItemField.getDocument().removeDocumentListener(newItemFieldListener);
}
private void enableListeners() {
newItemField.getDocument().addDocumentListener(newItemFieldListener);
itemsList.addListSelectionListener(itemsListListener);
}
private void updateList() {
itemsList.setListData(items);
itemsList.setSelectedValue(selectedItem, true);
}
private void updateButtons() {
removeButton.setEnabled(selectedItem != null);
addButton.setEnabled(newItem.length() > 0);
}
private void updateTextField() {
newItemField.setText(newItem);
}
Exercise:
But wait a minute! There is still a problem with our code. If we try adding two or more items
with the same name, JAVA will not allow us to select any of these items except the topmost
one!
Looking at the updateList() method and the listSelectionEventHandler() method, can you
determine what the problem is ? As a practice exercise, try to solve this problem by making
some appropriate changes to the code. You may want to look at the JList class in the JAVA
documentation.