|
~ Java for Web Page Production ~ Session 5 - Scoring, AWT and Swing, Interfaces, and Event Handling |
We will also explore a little more about object-oriented techniques by looking at the concept of Interfaces, and how they apply to looking at Button Clicks.
In Session 4, we covered:
Looping & Branching - using the for and if statements
Random Numbers - using the System.random() method
Generating Objects - in particular, looking at arrays
Container Objects - for example, JPanel, JFrame, JApplet
Layouts - Containing and Positioning controls with BorderLayout, GridLayout and FlowLayout
Combining Layouts with JPanels - Using the JPanel container to combine different Layouts
JFrames and Dialog boxes - A look at the JOptionPane container and a confirmation dialog box
In session 5, we will be looking at:-
Introduction
Session 4 Summary
Session 5 Overview
How to implement a scoring and high-scoring facility to an Applet, and feed it back to the user
The Abstract Windows Toolkit and Swing to see how user interfaces are put together
Commonly used Swing controls that can be used in your applet (we've seen JButtons and JLabels already)
Object Oriented Programming: Interfaces, and how it gets around the multiple inheritance problem
Event handling with Interfaces - ActionListener example that has been extensively used up to now
Inner classes, and anyonymous inner classes - a shortcut to prevent having to write lots of little classes
The Java Event Handling mechanism
How Interfaces, Inner Classes, and Anonymous inner classes relate to Event Handling
How Adapters can be used in place of interfaces.
How to create borders around components
Summary & Further Resources
If we look back at our Pairs game example from last session, one thing that was missing was a scoring system - for example, to say how many pairs have been matched, how many are remaining, and how many pairing attempts have been made.
The most logical way to store this information is as integers (whole numbers), to be available throughout the Class. Thus, it makes sense to store the variables (attributes) outside of any methods, so that they can be accessed by all methods (scope again!)
Thus we add the following variables:-
int attempts=0, pairsFound=0, pairsRemaining;
at the top of the Class, soon after the opening curly brace ({) for the class.
Note that we know at the start that the number of attempts made by the user to reveal a pair will be zero, and the number of pairs found will be zero, so we can set the values at this stage. However, we will not know the number of pairs remaining until we know how many buttons there will be on the screen.
At this stage (after pairsRemaining has had a value assigned to it), we can set the number of pairs remaining as follows:-
pairsRemaining = buttonCount / 2;
The total number of pairs will be the total number of buttons divided by two. We know that this will be an integer value, as we put in a check in the previous session to ensure that there are an even number of buttons.
Now that values have been set, they need to be placed on the screen as a reminder. They can be displayed as a label in the BorderLayout.SOUTH region of the applet's container.
scoreLabel = new JLabel("",JLabel.CENTER); updateScore();
getContentPane().add(scoreLabel,BorderLayout.SOUTH);
A new method has been created, as it will be re-used periodically through the program. This is called updateScore() and it updates the scoreLabel text with the latest score information:-
| private void updateScore(){ scoreLabel.setText(Integer.toString(pairsRemaining)+" pairs left, "+ Integer.toString(pairsFound)+" found, "+Integer.toString(attempts)+" attempts"); } |
As you can see, all this does is to put the text "8 pairs left, 0 found, 0 attempts" into the label at the bottom of the applet if (for example) there were 16 buttons and we'd just started the game.
So how do we go about changing the score themselves. We have to think about the conditions under which we would need to change the scores.
So, we would increase the attempts (attempts++) every time the second button press was made, irrespective of whether a pair was matched or not.
We would increase the number found (pairsFound++) and decrease the number remaining (pairsRemaining--) when a match had been found.
In both circumstances, we would need to call the updateScore method to ensure the label gets updated with the latest score changes.
Look at the Pairs2 project to see the resulting code.
If you look carefully, there is an if statement that checks to see if the pairsRemaining value reaches 0. If it does, a dialog box is popped up, and you will be given a congratulatory message, together with an option on whether you wish to re-start the game.
Understanding the Abstract Windows Toolkit & Swing
The original version of Java tried to reuse the facilities built into each computer to write buttons, text, and other controls onto a web page. Contrary to what you might expect, this led to it being very slow and cumbersome, and also looking different on different computers. This was known as the Abstract Windows Toolkit. It had a number of facilities that allowed you to create your own custom-made controls so that you did not have to use those that came with your computer, and it was this ability that was built upon to create a new version of the whole interface (i.e. look-and-feel) design mechanism that appeared in version 1.1 of Java, called Swing.
We have been working with Swing components without necessarily realising it. Most swing component begin with the Capital letter J (e.g. JApplet, JButton, JLabel etc.) whereas the older AWT counterparts tended to be called the same, but without the J.
In general, Swing versions of components are faster, more robust, work across different computers better, and have greater functionality - for example, buttons can have images on them, and the overlapping text shown in our original TextBurst example is handled a lot more efficiently.
Try not to mix original AWT controls with Swing controls. Although it can be done, you will lose most, if not all, of the speed benefits gained from using Swing.
Finally, another term you may encounter when looking through articles on Java is the JFC or Java Foundation Classes - these are the classes that make up Swing, designed to build better, more consistent, and more flexible user interfaces (what you see on the screen).
We have looked at some Swing controls already in our previous examples. The TextBurst example used a JLabel control, continually changing its size of font to make it look bigger and smaller. We used several JButton controls in an array in the Pairs example, as well as adding a few JLabels later to show scoring and a heading.
We'll go into a bit more depth with each control now, and introduce a few new controls, building up an example of a form to obtain an access code and password, and give the option of clicking on a checkbox to change your password. You might use this to allow access to parts of your application, or to different levels in a game.
Frame control
We talked about frames in the last
session, and gave a sample in the form of a simple dialog box.
You can also create your own pop-up windows very easily, using the JFrame class. Like a JApplet, this class also has a container in which to place controls, and which can be formatted and partitioned using the appropriate Layout (e.g. BorderLayout, GridLayout, FlowLayout).
Open the QuickCup frame project from the week05 directory. This gives examples of many of the controls described here.
Have a look at the LoginFrame.java tab. This shows an example of a custom Frame class, which inherits from the parent class, JFrame. The basic structure of the Frame is as follows:-
| import java.awt.event.*; import javax.swing.*; import java.awt.*; public class LoginFrame extends JFrame implements ActionListener { public LoginFrame(String useridString, AbstractButton btn) { super ("Login Dialog"); setSize(450,150); getContentPane().setLayout(new GridLayout(5,2)); getContentPane().add(new JLabel("example Label")); JButton okBtn = new JButton("Button with ActionListener"); okBtn.addActionListener(this); getContentPane().add(okBtn); } public void actionPerformed(ActionEvent event) { } } |
The first part imports the appropriate libraries as usual.
The line:-
public class LoginFrame extends JFrame implements ActionListener { }
defines a new class called LoginFrame which takes the functionality of a JFrame, and adds some extra to it. It also implements the ActionListener interface, in order to know what to do when a user clicks on the button.
With the LoginFrame class is a method, also called LoginFrame. This is the Class Constructor - it is the method that is called when a new object is created from the class, to set the class up so that it is ready to go:-
public LoginFrame(String useridString, AbstractButton btn) {
In this example, the constructor expects two values to be passed to it: a String (which it calls useridString) to represent the default User Id to put in the appropriate text box, and an AbstractButton, which is another interface, but basically means any old button can be passed to the class so that the class can access the button (this is so that the button can be ungreyed from the class).
In the constructor, the first thing that is done is that the constructor from the parent (JFrame) class is called, in order to set the title . If you inherit from another class, it's usually a good idea to call the parent's constructor - you never know what useful work it may be doing that you will later rely on.
The super keyword means 'execute the constructor from the Parent class'):-
super ("Login Dialog");
The next line will set the size of the dialog box to be 450 pixels wide and 150 deep:-
setSize(450,150);
The next line:-
getContentPane().setLayout(new GridLayout(5,2));
will set the layout for the Frame's container, to define how controls will be layed out on the frame - in this case, as a grid five rows down by 2 columns across.
The next line:-
getContentPane().add(new JLabel("example label"));
is not actually in the frame example, but is demonstrative - it shows that Swing components such as JLabel can be added to the Frame's container very easily in the same way as with a JApplet using the getContentPane().add() method.
Similarly, a JButton is created:-
JButton okBtn = new JButton("Button with ActionListener");
However, this time an ActionListener is added, which means that when the button is clicked, the actionPerformed method in the LoginFrame class will be executed - see the next section for more on this.
If you try running the frame, you can see that the actual example wait for you to click on a button, and then shows a frame with a userid and password prompt. If you type in a user ID of Hello and a password of HELLO5, you will be - sort of - logged in, and the second button on the applet will be ungreyed.
Now close the applet, and look at the FrameExample.java tab.
You can see on line 85 where the LoginFrame is being used. A new object called lframe is created, and the constructor is called with the initial user id being USER and the secondButton button being the button to enable if the password is correct:-
LoginFrame lframe = new LoginFrame("USER",secondButton);
The next line displays the frame on the screen:-
lframe.setVisible(true);
In this way, you can have frames predefined and sitting around in memory until you need them, and turn them on and off as they are required. The frames can retain the values of their variables or attributes in-between uses too, if required.
Notice that once we have created our LoginFrame class, how easy it is to use it from another class. Simply import it (see line 30) and use it by creating an instance of it, and making it visible.
Notice also that the password checking is kept within the class - thus, someone using the class by importing it would not necessarily have access to the method by which the password is gained (except in our case, we have access to the source code!)
JLabel
Control (
)
Controls that do not have containers of their own such as JLabels, are known as lightweight controls. This is because they are painted onto the surface of heavyweight controls such as JPanels, JApplets and JFrames.
In general, you can tell a heavyweight control, because it has a container, and so can have a layout defined for it.
We have seen ample examples of JLabels. In the frames project, look at the LoginFrame.java tab.
The JLabel control is simply used to display text on the screen, usually to identify another nearby control.
The GridLayout layout for the Frame is 5 rows by 2 columns. If you follow the order in which controls are added to the container:-
|
JLabel ("User ID") |
JTextField (useridString) |
|
JLabel ("Password") |
JPasswordField |
|
JLabel (" ") |
JCheckBox("Click here to change password") |
|
JLabel ("New Password") |
JPasswordField |
| JLabel(" ") | JButton("Ok") |
When the JLabel is created in these examples, the constructor specifies both the text of the label, and the alignment within the grid - e.g. on line 55:-
getContentPane().add(new JLabel("User ID:",SwingConstants.RIGHT));
So, a new JLabel is being created with the text User ID: and the label, when added to the container given by getContentPane() is aligned to the right of the grid position (using SwingConstants.RIGHT).
Notice as well that in order to skip a few of the grid cells, a technique has been used, where a dummy JLabel has been created with the text set to a single space - so it is effectively invisible:-
getContentPane().add(new JLabel(" "));
JButton Control (
)
The JButton control acts like a button when pressed. You will almost inevitably find that once a button has been created, it will have an ActionListener added to it, so that once the button is clicked, something will happen. Look at the next section to see how this works.
In the frames example, there are various examples of buttons. For example, in the FrameExample.java tab, on line 67, you can see a button being created with the text Click here to log in as follows:-
firstButton = new JButton("Click here to log in");
The ActionListener is added as follows:-
firstButton.addActionListener(this);
Notice that it is added to the NORTH part of a BorderLayout:-
getContentPane().add(firstButton,BorderLayout.NORTH);
The ActionListener is actually in the FrameExample class (i.e. this) and is defined by the actionPerformed method, which is what gets triggered when the button is clicked:-
public void actionPerformed(ActionEvent event) { ... }
Note that a parameter called event of class type ActionEvent is passed. You can use this parameter to get more information about the event and/or control that has been processed. The pairs project gives a good example of this parameter being used.
You can enable (i.e. make clickable) a button as in line 111 of LoginFrame.java:-
outsideBtn.setEnabled(true);
In this case, the button has been passed in the constructor, and set to a class-level variable called outsideBtn to make it available from all methods (not just the constructor). This makes the button enabled. Similarly, you can disable the button to ensure it cannot be clicked, as in line 72 of FrameExample.java:-
secondButton.setEnabled(false);
Note that this is the same button as gets enabled above - it is passed to the constructor as a parameter.
JTextField
Control (
)
The JTextField is a text box that you can type text into. You would typically use it to get information from the user, typed in at the keyboard.
In the frames example, look on the LoginFrame.java tab.
On line 56, a text field is created. Notice though, that the text placed into the text box as default is the String value passed to the constructor as a parameter (under the name useridString). The approximate visible length is 10 characters wide:-
userBox = new JTextField(useridString,10);
As with all of the other Swing controls, you add the text field to the frame's container as follows:-
getContentPane().add(userBox);
To refer to text in the box, use the getText() method as in line 107, where not only is the Text obtained, but also the length of the text appended to the end:-
String actualPassword = userBox.getText()+Integer.toString(userBox.getText().length());
You may wish to place the cursor into a specific text box after certain events have occurred, or after validation has failed. To do this, use the requestFocus method as in line 131:-
passBox.requestFocus();
JPasswordField
Control (
)
If you wish to obtain a password or other secret code from a user, it is useful to be able to hide the letters as they are typed. You can do this by using a descendant of JTextField called JPasswordField.
As a consequence, the JPasswordField works similarly to the JTextField component (see line 59 on LoginFrame.java):-
passBox = new JPasswordField(10);
This creates a new text field of approximately ten characters in width.
One method that JPasswordField has extra to JTextField is the setEchoChar method, which sets which character is displayed in place of a character in order to hide what the user is typing:-
passBox.setEchoChar('#');
In this case, when the user types a letter into the text-box, a # will appear in its place.
To read the text from the box, you should use the getPassword() method. Oddly, this returns an array of characters rather than a String. Happily, it easy easy to convert one to the other using the class method String.valueOf(chararray) - see line 107 for an example:-
String usersPassword = String.valueOf(passBox.getPassword());
This gets the text from the JPasswordField called passBox, and converts it from an array of characters to a String, placing the result in the usersPassword string variable.
JCheckBox Control (
)
The JCheckBox control is a label with a box beside it. The box can be empty or have a tick in it. Each time you click on the control, the tick will be alternately placed in and taken out of the box.
This is often used to turn a setting on and off - in the frame example, it is used to specify and make visible an option to change the password.
For example, on line 64 of LoginFrame.java:-
passChangeChk = new JCheckBox("Click here to change password");
This creates a new JCheckBox object with the text Click here to change password. An ActionListener is added as an anonymous inner class - described later - which makes the 'change password' label and text box - visible or invisible depending on whether there is a tick in the passChangeChk JCheckBox.
See line 71 and 72 for how this is done:-
passChangeLabel.setVisible(passChangeChk.isSelected());
passChangeBox.setVisible(passChangeChk.isSelected());
The isSelected() method will return the value of true if the JCheckBox is ticked, or false if it is not. Thus, if passChangeChk is ticked, the passChangeLabel control is made visible, otherwise it is made invisible. Similarly for the passChangeBox control.
Interfaces and the ActionListener
Interfaces are an extremely valuable concept in Java as another way of defining templates.
Up to now, we have created 'templates' from which we can inherit the functionality to form subclasses or child classes - for example the Bicycle and the 'Souped Up Bicycle'.
It is also possible to specify commonality between completely different classes that do not inherit details from each other, but share similar methods nonetheless. In other languages, we would use a principle known as multiple inheritance to accomplish this, but this would introduce a number of potential difficulties that can be difficult to untangle.
For example, a number of different (but not all) components perform a definite action such as a 'click' on a button or checkbox. The question is, how do you tell the components what to do when the click happens?
What you do is define what is known as a listener - this is a class which contains a predefined method. This method contains instructions to say what happens when a particular action occurs.
For example, the interface ActionListener is defined as follows:-
interface
ActionListener {
void actionPerformed (ActionEvent e);
}
Thus, any class that is to implement the ActionListener interface, has to have a method called actionPerformed with the parameter ActionEvent e.
The way a class specifies that it is to implement the ActionListener interface is to use the implements keyword. For example, in the frame project, on the FrameExample.java tab, you can see that the FrameExample class is defined as follows:-
public class FrameExample extends JApplet implements ActionListener { ... }
thus, FrameExample is a subclass of JApplet, and it implements the ActionListener interface.
This means that it has to have an actionPerformed method as specified in the ActionListener interface - you can see on line 80 that it is indeed defined this way.
Look at line 69:-
firstButton.addActionListener(this);
Thus, to specify what happens when the button is clicked, an object instance of a class that implements an ActionListener has to be specified - in this case, this is used - i.e. the current class, FrameExample. As we've seen, this does indeed implement the ActionListener.
Note that whilst a class cannot inherit from multiple classes, it can implement multiple interfaces.
The problem with this method of adding listeners is that if you have more than one control using an ActionListener, how do you distinguish between them?
If you look at the Pairs example from the week04 directory, the same ActionListener class is used for all buttons in the JApplet:-
public void actionPerformed(ActionEvent event) {
if (event.getSource() instanceof
JButton) {
JButton clickedButton =
(JButton)event.getSource(); ...
The ActionEvent object called event is passed to the actionPerformed method when the button is clicked. This class has a method called getSource() which returns the object that triggered the event in the first place.
The instanceof operator checks to see what type of object it is. If it is a JButton, then it gets assigned to clickedButton.
Inner Classes and Anonymous Inner Classes
This can get somewhat cumbersome, as you have to place all of your code in one place for controls that may be functionally very different, which could lead to some confusion. In this case, you may wish to create a separate class for the purpose of implementing a separate ActionListener. That separate class could then hold just the actionPerformed method.
Using an inner class
This class would typically be inside the class in which the control is used - i.e. a nested or inner class.
We looked at inner classes back in session three (see the session3_apps project) where we created a nested class for a Dynamo that was accessible only within the Bicycle class. Take a look back to remind yourself how this worked.
We can define an inner or nested class in order to use to interface to a button in the following way - look at week 05's frames project again, and click on the FrameExample.java tab.
Go to line 44. This defines an inner class for what should happen when the secondButton JButton is pressed. The outline of this is as follows:-
| private class myActionListener implements ActionListener { public void actionPerformed(ActionEvent e) { JOptionPane.showMessageDialog( ... ); secondButton.setEnabled(false); } } |
So you can see that we have created a fake class just so that we can implement to ActionListener interface with an actionPerformed method, which will display a message and disable (make grey) the second button.
In order to link this class to the secondButton JButton, we need to:-
Create a new object instance for the myActionListener Class
Add that object instance to the button using the button's addActionListener method.
We would do this using the following Java instruction (see line 75 of FrameExample.java):-
secondButton.addActionListener(new myActionListener());
Thus, a new instance of the myActionListener class is created, and passed to the secondButton JButton via the addActionListener event. Thus, when a click of the secondButton next occurs, it will know to call the actionPerformed method on the object of the myActionListenerclass in response to the click - i.e. a dialog box is displayed, and the second button is greyed again.
This method is particularly useful if different buttons benefit from sharing the same listener class - i.e. share similar actions.
However, if the class is being used for a single control for a single purpose, it's rather untidy to have separate classes defined early in the class for different controls.
It would be much better if the instructions could be defined as a one-off when the new object instance is created and passed to the control...
Using an anonymous inner class
The solution is to use an anonymous inner class.
If you take the above example, it would be useful to bring the source code into the main part of the source code, so you down have to look back at the top to see what is happening when the button is clicked. Here is how you would define and implement the myActionListener using an anonymous inner class:-
| secondButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { JOptionPane.showMessageDialog( JOptionPane.getFrameForComponent(FrameExample2.this), "Well done! You logged in AND pressed a button...\n"+ "...so i'll log you out again. Hah!", "Logged in and out", JOptionPane.INFORMATION_MESSAGE); secondButton.setEnabled(false); } }); |
You can see that the class is defined as before, except instead of using the extends keyword, we define the class that we are extending from using the constructor, which is ActionListener(). We don't even give the class name - hence the anonymous - what's the point when it's a one-off?
The class definition then follows the constructor call, starting with an opening curly brace. The method is defined exactly as before, and then there's a closing curly brace to end the class.
What's happening here is:-
A class is being defined as extending from ActionListener, but given no name.
It is being set-up using the ActionListener() constructor, which also defines which class we are extending from.
The new keyword is creating an object from this nameless class
This new object is passed to the addActionListener method of the secondButton object.
Thus when a button is clicked, the actionPerformed method of the anonymous class passed to the JButton will be executed.
The construction of this looks odd to start with, and takes a bit of getting used to. However, you will find in time that it's a lot more intuitive, as it allows you to specify what a control should do at the place in your code that is relevant - when you create it.
If you want to identify anonymous inner classes within Java programs that you are looking through, a good indicator is the last line - }) - you are unlikely to see this combination of symbols (the end of a block followed by the end of a method parameter list) in any other circumstance.
Exercise
Try making this change for yourself on the FrameExample.java tab of the frame project:-
Delete the original named inner class
Change the secondButton.addActionListener() method to incorporate the inner class
Compile and run the applet to ensure it still works the same - remember that this dictates what happens when you click the bottom-most button, after having logged in.
N.b. you could save yourself a bit of time by copying the original named inner class, pasting it between the brackets of the secondButton.addActionListener method, and deleting the parts that are no longer needed.
If you want another example of an anonymous inner class, take a look at LoginFrame.java starting on line 66 - this defines what happens when the user clicks on the password change checkbox. See if you can follow what happens when the JCheckBox is clicked based on what you've already learned.
If you want to see how the frame class changes after the addition of the anonymous inner class, take a look at the frame2 project.
We will be returning later to the subject of event handing and anonymous inner classes, when we look at other types of events - some controls can react to more than simple mouse clicks.
Okay, so now we know how to react to clicks. Different components cause different events to be triggered. The table below gives a list of Interfaces and their associated methods that must be implemented.
If you are looking at the web version of this document, each of these have links to the Sun Java site to explain how to implement each of the listeners.
| Listener Interface | Adapter Class | Listener Methods |
|---|---|---|
ActionListener |
none | actionPerformed(ActionEvent) |
AncestorListener |
none | ancestorAdded(AncestorEvent)ancestorMoved(AncestorEvent)ancestorRemoved(AncestorEvent) |
CaretListener |
none | caretUpdate(CaretEvent) |
CellEditorListener |
none | editingStopped(ChangeEvent)editingCanceled(ChangeEvent) |
ChangeListener |
none | stateChanged(ChangeEvent) |
ComponentListener |
ComponentAdapter |
componentHidden(ComponentEvent)componentMoved(ComponentEvent)componentResized(ComponentEvent)componentShown(ComponentEvent) |
ContainerListener |
ContainerAdapter |
componentAdded(ContainerEvent)componentRemoved(ContainerEvent) |
DocumentListener |
none | changedUpdate(DocumentEvent)insertUpdate(DocumentEvent)removeUpdate(DocumentEvent) |
FocusListener |
FocusAdapter |
focusGained(FocusEvent)focusLost(FocusEvent) |
HyperlinkListener |
none | hyperlinkUpdate(HyperlinkEvent) |
InternalFrameListener |
InternalFrameAdapter |
internalFrameActivated(InternalFrameEvent)internalFrameClosed(InternalFrameEvent)internalFrameClosing(InternalFrameEvent)internalFrameDeactivated(InternalFrameEvent)internalFrameDeiconified(InternalFrameEvent)internalFrameIconified(InternalFrameEvent)internalFrameOpened(InternalFrameEvent) |
ItemListener |
none | itemStateChanged(ItemEvent) |
KeyListener |
KeyAdapter |
keyPressed(KeyEvent)keyReleased(KeyEvent)keyTyped(KeyEvent) |
ListDataListener |
none | contentsChanged(ListDataEvent)intervalAdded(ListDataEvent)intervalRemoved(ListDataEvent) |
ListSelectionListener |
none | valueChanged(ListSelectionEvent) |
MenuDragMouseListener |
none | menuDragMouseDragged(MenuDragMouseEvent)menuDragMouseEntered(MenuDragMouseEvent)menuDragMouseExited(MenuDragMouseEvent)menuDragMouseReleased(MenuDragMouseEvent) |
MenuKeyListener |
none | menuKeyPressed(MenuKeyEvent)menuKeyReleased(MenuKeyEvent)menuKeyTyped(MenuKeyEvent) |
MenuListener |
none | menuCanceled(MenuEvent)menuDeselected(MenuEvent)menuSelected(MenuEvent) |
MouseInputListener |
MouseInputAdapter |
mouseClicked(MouseEvent)mouseEntered(MouseEvent)mouseExited(MouseEvent)mousePressed(MouseEvent)mouseReleased(MouseEvent)mouseDragged(MouseEvent)mouseMoved(MouseEvent) |
MouseListener |
MouseAdapter |
mouseClicked(MouseEvent)mouseEntered(MouseEvent)mouseExited(MouseEvent)mousePressed(MouseEvent)mouseReleased(MouseEvent) |
MouseMotionListener |
MouseMotionAdapter |
mouseDragged(MouseEvent)mouseMoved(MouseEvent) |
PopupMenuListener |
none | popupMenuCanceled(PopupMenuEvent)popupMenuWillBecomeInvisible(PopupMenuEvent)popupMenuWillBecomeVisible(PopupMenuEvent) |
TableColumnModelListener |
none | columnAdded(TableColumnModelEvent)columnMoved(TableColumnModelEvent)columnRemoved(TableColumnModelEvent)columnMarginChanged(ChangeEvent)columnSelectionChanged(ListSelectionEvent) |
TableModelListener |
none | tableChanged(TableModelEvent) |
TreeExpansionListener |
none | treeCollapsed(TreeExpansionEvent)treeExpanded(TreeExpansionEvent) |
TreeModelListener |
none | treeNodesChanged(TreeModelEvent)treeNodesInserted(TreeModelEvent)treeNodesRemoved(TreeModelEvent)treeStructureChanged(TreeModelEvent) |
TreeSelectionListener |
none | valueChanged(TreeSelectionEvent) |
TreeWillExpandListener |
none | treeWillCollapse(TreeExpansionEvent)treeWillExpand(TreeExpansionEvent) |
UndoableEditListener |
none | undoableEditHappened(UndoableEditEvent) |
WindowListener |
WindowAdapter |
windowActivated(WindowEvent)windowClosed(WindowEvent)windowClosing(WindowEvent)windowDeactivated(WindowEvent)windowDeiconified(WindowEvent)windowIconified(WindowEvent)windowOpened(WindowEvent) |
What, you may ask, is an Adapter class?
Let's say you wish to set up your applet so that when the mouse cursor hovers over particular controls, focus automatically shifts to those controls. It could save the user lots of clicks when filling in forms (when you take into consideration how few people seem to know about the use of the TAB button to move to the next control).
If you look down the list of listeners, you can see that the most appropriate looking would be the MouseListener.
However, there are five methods defined for the MouseListener:-
mouseClicked(MouseEvent)
mouseEntered(MouseEvent)
mouseExited(MouseEvent)
mousePressed(MouseEvent)
mouseReleased(MouseEvent)
To implement an interface, you need to implement every method in the interface, irrespective of whether you wish to use them all or not. If you don't, you won't be fulfilling the requirements of the interface. Thus, a sample class might look like this:-
| exampleControl.addMouseListener(new
MouseListener() { public void mouseClicked(MouseEvent e) { } public void mouseEntered(MouseEvent e) { ((Component)(e.getSource())).requestFocus() } public void mouseExited(MouseEvent e) { } public void mousePressed(MouseEvent e) { } public void mouseReleased(MouseEvent e) { } }); |
To get around this problem, a set of classes known as Adaptor Classes have been created, which are basically implemented version of the interfaces, but with empty methods defined.
Thus, if you inherit (not implement) from an Adaptor class, you only need to define the methods that you're interested in. The rest of the methods will be inherited directly from the parent Adaptor class. Thus, your code might now look like this:-
| exampleControl.addMouseListener(new
MouseAdaptor() { public void mouseEntered(MouseEvent e) { ((Component)(e.getSource())).requestFocus(); } }); |
Take a look at project frame3 which implements this idea in the LoginFrame3.java frame class.
In this example, rather than repeating the same code for each control, we have created an anonymous inner class, but this time assigned it to a variable and re-used that object for each of the controls in the frame:-
| MouseAdapter focusEvent = new MouseAdapter() { public void mouseEntered(MouseEvent e) { ((Component)e.getSource()).requestFocus(); } }; |
The new anonymous class (inherited from MouseAdapter) is defined as before, and a new instance of it is created.
Polymorphism
However,
we have a dilemma. If the new class does not have a name, how can we assign
it to a variable - to do that, we have to know the name of the class? A
catch 22 situation.
The answer is to define the variable as being the parent class (MouseAdapter). Variables can hold not only their own class, but any descendant classes too. This is a principle known as polymorphism.
We will only be able to access the methods that are available from the MouseAdaptor class, but those that are defined in the new (anonymous) class will override those in the MouseAdaptor class automatically.
This principle is rather difficult to conceptualize at first, but is an important part of object-orientation. We'll go through it in more detail later in the course.
Okay, so variable focusEvent now holds an object which has defined for it an overriden mouseEntered event, which can be used to decide what should happen when a mouse enters a control - it will find out which control caused the method to be triggered using e.getSource() and turn it into a Component using the (Component) cast, and finally, use a method on the Component class to request that the current component gets the focus.
Now we need to add this variable to each of the controls that need to get focus.
Here are the lines from the program that perform this task:-
| userBox.addMouseListener(focusEvent); passBox.addMouseListener(focusEvent); passChangeChk.addMouseListener(focusEvent); passChangeBox.addMouseListener(focusEvent); okBtn.addMouseListener(focusEvent); |
So it's just a variation of the addActionListener method - this time, it's a MouseListener that we are passing.
Hold on, you might say, it's a descendant of MouseAdaptor that we're passing, not a MouseListener.
Remember though, that our anonymous class is a descendant of MouseAdaptor, and MouseAdaptor implements MouseListener, so defines the interface for MouseListener. Thus, it is okay to pass it as an interface, because it defines the interface.
Try the example out, and watch what happens when you hover your cursor over the different controls. Handy?
Creating borders with the BorderFactory class
As a final example, take a look at the Pairs project in the week05 directory. In this example, we have used the MouseListener interface again, but this time applied to each of the created buttons to change the border when the mouse first goes over the button, and when it leaves the area of the button.
To change the border, we can use the component's setBorder method, and create a new border using the BorderFactory class as follows (see line 95 of the Pairs.java tab):-
| buttons[i].addMouseListener(new MouseAdapter() { public void mouseEntered(MouseEvent e) { ((JButton)e.getSource()).setBorder(BorderFactory.createRaisedBevelBorder()); } public void mouseExited(MouseEvent e) { ((JButton)e.getSource()).setBorder(BorderFactory.createEtchedBorder()); } }); |
So each time around the loop, a new mouse listener is being added using an anonymous inner class. In this example, two of the listeners have been implemented - mouseEntered to change be border to a raised bevelled border when the mouse starts going over the button, and mouseExited to change the border back to an etched border when the mouse leaves the button.
This introduces a new method called setBorder which changes the border around a component.
To create a new standard border, use one of the BorderFactory class methods to create a border instance. Here are some of the borders available from BorderFactory:-
BorderFactory.createLineBorder(Color,thickness)
BorderFactory.createRaisedBevelBorder()
BorderFactory.createLoweredBevelBorder()
BorderFactory.createBevelBorder(BevelBorder.LOWERED)
BorderFactory.createBevelBorder(BevelBorder.RAISED)
BorderFactory.createEtchedBorder()
BorderFactory.createEtchedBorder(EtchedBorder.LOWERED)
BorderFactory.createEtchedBorder(EtchedBorder.RAISED)
BorderFactory.createTitledBorder(String)
BorderFactory.createTitledBorder(Border,String)
BorderFactory.createEmptyBorder()
null
As an exercise, you could try substituting some of these borders into the mouseEntered method to see how they look.
| TechRepublic - sign up for their Java Tips email newsletter - it gives excellent Java tips straight to your mailbox twice weekly. | http://www.techrepublic.com |