Download Separate Classes for Event Handling

Survey
yes no Was this document useful for you?
   Thank you for your participation!

* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project

Document related concepts

Java syntax wikipedia , lookup

Falcon (programming language) wikipedia , lookup

Smalltalk wikipedia , lookup

Design Patterns wikipedia , lookup

Java (programming language) wikipedia , lookup

Application Interface Specification wikipedia , lookup

Java performance wikipedia , lookup

Java ConcurrentMap wikipedia , lookup

C++ wikipedia , lookup

Name mangling wikipedia , lookup

Class (computer programming) wikipedia , lookup

Object-oriented programming wikipedia , lookup

C Sharp syntax wikipedia , lookup

C Sharp (programming language) wikipedia , lookup

Transcript
CSIS 3701: Advanced Object-Oriented Programming
Event Handling in Java
Introduction
In this section we give the beginnings of an explanation of how the event handling
system in Java works – that is, the steps that Java takes to execute code when things like
buttons are pressed. This will include:
o What the addActionListener call does.
o What the keyword this represents in that call, and how callbacks are involved.
o How the actionPerformed method is used.
o An alternative (and more modular) idea for event handling using event handling
classes.
o Other related event handling syntax using anonymous inner classes.
Callbacks and the Java Event Model
The this keyword – and its use in callbacks – are an essential part of the Java event
model, which defines what happens when events (such as pressing buttons) occur.
If you have been looking over the applets previously given as examples, you may have
noticed the this keyword used in several cases. For example, consider the Hello
application again:
public class Hello extends JFrame
implements ActionListener {
private JButton go;
private JTextField greet;
// This constructor is run when the application is created
public Hello() {
go = new JButton("Press");
greet = new JTextField(20);
go.addActionListener(this);
JPanel P = new JPanel();
getContentPane().add(P);
P.add(go);
P.add(greet);
setSize(400, 80);
setVisible(true);
}
// This code is run when the button is pressed
public void actionPerformed(ActionEvent e) {
greet.setText("Welcome to Java Programming!");
}
// This main program creates the Hello application
public static void main(String[] args) {
Hello h = new Hello();
}
}
The addActionListener method is a special method in the JButton class. It does
the following things:
1. It takes a reference to a Java component as its parameter, and stores that
reference in the JButton as a state variable (much like the setCruise and
setAccel methods did earlier).
2. When the JButton is pressed by the user, the JButton invokes the
actionPerformed method of the object it has stored the reference to.
In the above case, this means the following occurs:
1. The Hello object stores a reference to a JButton objects as one of its state
variables:
private JButton go;
2. In its constructor, the Hello objects constructs that JButton object:
go = new JButton(“Press”);
3. The Hello object invokes the addActionListener method of the
JButton object, passing its own address to the JButton using this:
go.addActionListener(this);
4. The JButton now stores a reference to the Hello object as one of its state
variables.
5. When the JButton is pressed, it then invokes the actionPerformed method of the
Hello object (this is the callback), which causes a message to be written into the
JTextField:
public void actionPerformed(ActionEvent e) {
greet.setText(“Welcome to Java programming”);
}
Or, to explain it more visually:
Hello object
go
public void
actionPerformed
JButton object
addActionListener(this)
reference to Hello
when pressed
actionPerformed
Separate Classes for Event Handling
The above method for event handling works well if there are only a few possible events
that can occur. However, consider an application that might have dozens of buttons, or
other components which might cause events (such as checkboxes, drop-down lists, etc.).
In that case, putting all of the event handling in a single actionPerformed method
could result in a method hundreds of lines long, with dozens of if statements – not
something we want to encourage in modular programming.
An alternative approach often used by Java programmers (and also used by our textbook)
is to create separate objects for event handling. More specifically:
o A separate class is created to handle events. That class must have an
actionPerformed method to respond to the event (and must also implement
the ActionListener interface, for reasons we will explain later).
o Instead of passing this to the addActionListener method, we construct a
new object of that event handling class and pass it instead. When the button is
pressed, an actionPerformed message will be sent to that object instead of
the application.
To illustrate that, we rewrite the above example using a class called Responder for
event handling. Each Responder takes a String (which tells it the string to write)
and the greet textfield (which tells it where to write the string) as parameters its
constructor, and stores them as state variables. When its actionPerformed
method is invoked, that string is written into the greet textfield of the application.
Each of the buttons is passed a new Responder object using its
addActionListener method. That Responder object is constructed using a
string appropriate for the button.
import javax.swing.*;
import java.awt.event.*;
// This class illustrates how event class methods can be used to find
// information about the nature of an event -- in this case, which of
// two buttons has been pressed.
public class Hello2 extends JFrame {
// Two buttons
private JButton first, second;
private JTextField greet;
public Hello2() {
first = new JButton("Introduction");
second = new JButton("Current message");
greet = new JTextField(30);
// Construct an appropriate Responder object for each button
first.addActionListener(
new Responder("Welcome to Java Programming!",
greet));
second.addActionListener(
new Responder("Hope you're enjoying Java Programming!,
greet"));
JPanel P = new JPanel();
getContentPane().add(P);
P.add(first);
P.add(second);
P.add(greet);
setSize(400, 160);
setVisible(true);
}
// This main program creates the Hello application
public static void main(String[] args) {
Hello2 h = new Hello2();
}
}
// Separate event class for handling events.
public class Responder implements ActionListener {
private String myMessage;
// message printed by this Responder
private JTextField myField; // textfield to write message to
// The constructor takes a string and stores it.
private Responder(String m, JTextField t) {
myMessage = m;
myField = t;
}
// When an actionPerformed message is received, print that
// message into the greet textfield of the application.
public void actionPerfromed(ActionEvent e) {
myField.setText(myMessage);
}
}
// End of inner class
Graphically, the structure of the objects in this program look like this:
Hello object
JButton object
Responder object
myMessage: “Welcome to Java Programming”
myField
first
g
greet
second
JButton object
Responder object
myMessage:
myField
“Hope you’re enjoying Java Programming”
Inner Classes for Event Handling
Many programmers do not like above way of handling events. It does have a couple of
major things that are inconvenient:
 A separate class is needed for the events. This usually requires a separate class for
each event-related component on the application (it is actually pretty rare that
multiple events can be handled with a single class, as in the above Hello2
example). This requires a lot of effort keeping track of multiple files of event
handling classes.
 Components on the application affected by the event (such as the greet
textfield) must be passed as references to the event handling object. This can get
messy if many components ae involved – for example, if an event requires that
many textfields be updated.
As a result, event handling classes are often implemeted as inner classes of the
application they are associated with. This has the advantages:
 This keeps all of the event handling code in the same file as the application.
 Non-static inner classes have direct access to state varibales in the outer class –
that is, the components that they need to affect.
Below is a modified Hello2 class that uses inner classes:
import javax.swing.*;
import java.awt.event.*;
// This class illustrates how event class methods can be used to find
// information about the nature of an event -- in this case, which of
// two buttons has been pressed.
public class Hello2 extends JFrame {
// Inner event class for handling events.
private class Responder implements ActionListener {
private String myMessage;
// message printed by this Responder
// The constructor takes a string and stores it.
private Responder(String m) {
myMessage = m;
}
// When an actionPerformed message is received, print that
// message into the greet textfield of the application.
public void actionPerfromed(ActionEvent e) {
greet.setText(myMessage);
}
}
// End of inner class
// Two buttons
private JButton first, second;
private JTextField greet;
public Hello2() {
first = new JButton("Introduction");
second = new JButton("Current message");
greet = new JTextField(30);
// Construct an appropriate Responder object for each button
first.addActionListener(
new Responder("Welcome to Java Programming!"));
second.addActionListener(
new Responder("Hope you're enjoying Java Programming!"));
JPanel P = new JPanel();
getContentPane().add(P);
P.add(first);
P.add(second);
P.add(greet);
setSize(400, 160);
setVisible(true);
}
// This main program creates the Hello application
public static void main(String[] args) {
Hello2 h = new Hello2();
}
}
Anonymous Inner Classes for Event Handling
Many programmers do not like creating a separate class for each event, even an inner
class, particularly if the event code is very simple (that is, just a few lines long – like the
code in the Hello2 application above). In such cases, the event handling code can be
embedded directly into the addActionListener method.
The syntax looks a little strange for this; however, it is somewhat similar to the idea of
anonymous objects such as some of the JLabel and layout objects illustrated in previous
sections – they are just created directly as parameters to be passed to some other object:
P.add(new JLabel(“Name: “));
In this case, we are doing the following:
 Creating a very simple class, with no constructors or state variables – all it has is a
single actionPerformed method.
 Declaring that it implements the ActionListener interface.
 Creating an anonymous object of that type, which is then passed to the button.
When the button is pressed, that actionPerformed method is called.
The basic syntax is:
button.addActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent e) {
code to execute when button pressed
}
}
);
For example, the Hello2 class can be rewritten as:
import javax.swing.*;
import java.awt.event.*;
// This class illustrates how event class methods can be used to find
// information about the nature of an event -- in this case, which of
// two buttons has been pressed.
public class Hello2 extends JFrame {
// Two buttons
private JButton first, second;
private JTextField greet;
public Hello2() {
first = new JButton("Introduction");
second = new JButton("Current message");
greet = new JTextField(30);
// Create anonymous inner ActionListener classes to handle
// events for each button
first.addActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent e) {
greet.setText("Welcome to Java Programming!");
}
}
);
second.addActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent e) {
greet.setText(
"Hope you’re enjoying Java Programming!");
}
}
);
JPanel P = new JPanel();
getContentPane().add(P);
P.add(first);
P.add(second);
P.add(greet);
setSize(400, 160);
setVisible(true);
}
// This main program creates the Hello application
public static void main(String[] args) {
Hello2 h = new Hello2();
}
}
Which method is best?
You will note that in these different methods, we have traded off modularity for
convenience. Which method is preferred may depend on how reusable your event
handling code is.
Both the original idea of putting the actionPerformed method in the application,
and the idea of anonymous inner classes are very convenient to write. However, neither
one leads to reusable code. In addition, neither results in particularly readable code for
complex applications – the first idea can lead to large, complex actionPerformed
method, while the latter can lead to large, complex constructors with lots of embedded
code.
In my opinion, neither is a good option for true modular programming. However, you
should be aware of both, since they are commonly used in the real world, where
convenience often outweighs good programming practice. Both can be made much more
modular, however, if you at least define separate private methods for each event, and
restrict the actionPerformed method or the constructor to calling those methods.
Separate event-handling classes are clearly the best for reuse, but require lots of
references back to components in the application. They are not particuarly common in the
real world, and may be best only if reuse is a major concern – that is, if your system has a
large number of application windows (possobly for multiple options in the system), all of
which have similar events.
For example, suppose that you are developing a system that requires a number of steps
for the user, each of which involves a separate window. Also suppose that at any window
the user has the option to save their data – that is, each application has a SAVE button. It
would make sense to implement the save event as a separate event class, so all of the
related code of opening a file and writing could be implemented just once.
Non-anonymous inner classes are a good, modular solution for cases where code is not
likely to be repeated between applications.
As a final example, here is a “calculator” example with add, subtract, multiply, and
divide buttons. The first version puts the actionPerformed method in the
application, the second version uses inner classes, and the third version uses anonymous
inner classes.
//
//
//
//
This is an example of event handling using a built-in
actionPerformed method that contains a simple switch,
which calls a number of modular support methods for
handling the events.
// The class acts as a simple calculator, which allows
// the user to add, subtract, multiply, and divide 2 numbers.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Calculator1 extends JFrame implements ActionListener {
private JButton addButton, subButton, multButton, divButton;
private JTextField op1Field, op2Field, resultField;
public Calculator1() {
addButton = new JButton("+");
subButton = new JButton("-");
multButton = new JButton("*");
divButton = new JButton("/");
op1Field = new JTextField(10);
op2Field = new JTextField(10);
resultField = new JTextField(20);
// In this case, the address of the application itself is passed to
// each button. This means that its actionPerfromed method is called
// when ANY button is pressed
addButton.addActionListener(this);
subButton.addActionListener(this);
multButton.addActionListener(this);
divButton.addActionListener(this);
JPanel inputPanel = new JPanel(new GridLayout(1, 2));
inputPanel.add(op1Field);
inputPanel.add(op2Field);
JPanel operPanel = new JPanel(new GridLayout(1, 4));
operPanel.add(addButton);
operPanel.add(subButton);
operPanel.add(multButton);
operPanel.add(divButton);
JPanel mainPanel = new JPanel(new GridLayout(3, 1));
mainPanel.add(inputPanel);
mainPanel.add(operPanel);
mainPanel.add(resultField);
getContentPane().add(mainPanel);
setSize(200, 200);
setVisible(true);
}
// The application handles events on the buttons
public void actionPerformed(ActionEvent e) {
// Simple switch to call event-handling support methods
if (e.getSource() == addButton) sum();
if (e.getSource() == subButton) subtract();
if (e.getSource() == multButton) multiply();
if (e.getSource() == divButton) divide();
}
// Support methods
private void sum() {
double x = Double.parseDouble(op1Field.getText());
double y = Double.parseDouble(op2Field.getText());
resultField.setText(""+(x+y));
}
private void subtract() {
double x = Double.parseDouble(op1Field.getText());
double y = Double.parseDouble(op2Field.getText());
resultField.setText(""+(x-y));
}
private void multiply() {
double x = Double.parseDouble(op1Field.getText());
double y = Double.parseDouble(op2Field.getText());
resultField.setText(""+(x*y));
}
private void divide() {
double x = Double.parseDouble(op1Field.getText());
double y = Double.parseDouble(op2Field.getText());
if (y == 0) resultField.setText("infinity");
else resultField.setText(""+(x/y));
}
public static void main(String[] args) {
Calculator1 c = new Calculator1();
}
}
// This is an example of event handling using a inner classes
// for event handling, one for each button.
// The class acts as a simple calculator, which allows
// the user to add, subtract, multiply, and divide 2 numbers.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Calculator2 extends JFrame {
private JButton addButton, subButton, multButton, divButton;
private JTextField op1Field, op2Field, resultField;
public Calculator1() {
addButton = new JButton("+");
subButton = new JButton("-");
multButton = new JButton("*");
divButton = new JButton("/");
op1Field = new JTextField(10);
op2Field = new JTextField(10);
resultField = new JTextField(20);
// In this case, each button constructs a specific event
// handling object related to its function
addButton.addActionListener(new Adder());
subButton.addActionListener(new Subtracter());
multButton.addActionListener(new Multiplier());
divButton.addActionListener(new Divider());
JPanel inputPanel = new JPanel(new GridLayout(1, 2));
inputPanel.add(op1Field);
inputPanel.add(op2Field);
JPanel operPanel = new JPanel(new GridLayout(1, 4));
operPanel.add(addButton);
operPanel.add(subButton);
operPanel.add(multButton);
operPanel.add(divButton);
JPanel mainPanel = new JPanel(new GridLayout(3, 1));
mainPanel.add(inputPanel);
mainPanel.add(operPanel);
mainPanel.add(resultField);
getContentPane().add(mainPanel);
setSize(200, 200);
setVisible(true);
}
// Inner classes for event handling
private class Adder implements ActionListener {
public void actionPerformed(ActionEvent e) {
double x = Double.parseDouble(op1Field.getText());
double y = Double.parseDouble(op2Field.getText());
resultField.setText(""+(x+y));
}
}
private class Subtracter implements ActionListener {
public void actionPerformed(ActionEvent e) {
double x = Double.parseDouble(op1Field.getText());
double y = Double.parseDouble(op2Field.getText());
resultField.setText(""+(x-y));
}
}
private class Multiplier implements ActionListener {
public void actionPerformed(ActionEvent e) {
double x = Double.parseDouble(op1Field.getText());
double y = Double.parseDouble(op2Field.getText());
resultField.setText(""+(x*y));
}
}
private class Divider implements ActionListener {
public void actionPerformed(ActionEvent e) {
double x = Double.parseDouble(op1Field.getText());
double y = Double.parseDouble(op2Field.getText());
if (y == 0) resultField.setText("infinity");
else resultField.setText(""+(x/y));
}
}
public static void main(String[] args) {
Calculator2 c = new Calculator2();
}
}
//
//
//
//
This is an example of event handling using a built-in
actionPerformed method that contains a simple switch,
which calls a number of modular support methods for
handling the events.
// The class acts as a simple calculator, which allows
// the user to add, subtract, multiply, and divide 2 numbers.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Calculator3 extends JFrame {
private JButton addButton, subButton, multButton, divButton;
private JTextField op1Field, op2Field, resultField;
public Calculator1() {
addButton = new JButton("+");
subButton = new JButton("-");
multButton = new JButton("*");
divButton = new JButton("/");
op1Field = new JTextField(10);
op2Field = new JTextField(10);
resultField = new JTextField(20);
// In this case, we create an anonymous event handling class for each
// button. For modularity, each just calls a method.
addButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {add();}
});
subButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {subtract();}
});
multButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {multiply();}
});
divButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {divide();}
});
JPanel inputPanel = new JPanel(new GridLayout(1, 2));
inputPanel.add(op1Field);
inputPanel.add(op2Field);
JPanel operPanel = new JPanel(new GridLayout(1, 4));
operPanel.add(addButton);
operPanel.add(subButton);
operPanel.add(multButton);
operPanel.add(divButton);
JPanel mainPanel = new JPanel(new GridLayout(3, 1));
mainPanel.add(inputPanel);
mainPanel.add(operPanel);
mainPanel.add(resultField);
getContentPane().add(mainPanel);
setSize(200, 200);
setVisible(true);
}
// Support methods
private void sum() {
double x = Double.parseDouble(op1Field.getText());
double y = Double.parseDouble(op2Field.getText());
resultField.setText(""+(x+y));
}
private void subtract() {
double x = Double.parseDouble(op1Field.getText());
double y = Double.parseDouble(op2Field.getText());
resultField.setText(""+(x-y));
}
private void multiply() {
double x = Double.parseDouble(op1Field.getText());
double y = Double.parseDouble(op2Field.getText());
resultField.setText(""+(x*y));
}
private void divide() {
double x = Double.parseDouble(op1Field.getText());
double y = Double.parseDouble(op2Field.getText());
if (y == 0) resultField.setText("infinity");
else resultField.setText(""+(x/y));
}
public static void main(String[] args) {
Calculator3 c = new Calculator3();
}
}