* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
Download Separate Classes for Event Handling
Java syntax wikipedia , lookup
Falcon (programming language) wikipedia , lookup
Design Patterns wikipedia , lookup
Java (programming language) wikipedia , lookup
Application Interface Specification wikipedia , lookup
Java performance wikipedia , lookup
Java ConcurrentMap wikipedia , lookup
Name mangling wikipedia , lookup
Class (computer programming) wikipedia , lookup
Object-oriented programming wikipedia , lookup
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(); } }