I'm looking for an effective method to trigger a change in a set of objects after a change was done to the key object. What I want to do is this:
There is one object that if changed executes a method in other objects.
I was trying to use PropertyChangeListener to accomplish this:
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class BoundSample {
public static void main(String args[]) {
JFrame frame = new JFrame("PropertyChangeListener Sample");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JButton button1 = new JButton("Open");
final JButton button2 = new JButton("Cancel");
final String[] languages = { "English", "Spanish" };
final JComboBox combo = new JComboBox(languages);
final JPanel panel = new JPanel();
ActionListener actionListener = new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
JComboBox comboTmp = (JComboBox) actionEvent.getSource();
if (combo.getSelectedItem().equals("English") || combo.getSelectedItem().equals("Ingles")) {
String[] langs = { "English", "Spanish" };
comboTmp.setModel(new JComboBox(langs).getModel());
}
else if (combo.getSelectedItem().equals("Spanish") || combo.getSelectedItem().equals("Espanol")) {
String[] langs = { "Ingles", "Espanol" };
comboTmp.setModel(new JComboBox(langs).getModel());
}
}
};
PropertyChangeListener propertyChangeListener = new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent propertyChangeEvent) {
String property = propertyChangeEvent.getPropertyName();
JComboBox comboTmp = (JComboBox) propertyChangeEvent.getSource();
String language = (String) comboTmp.getItemAt(0);
System.out.println(language);
if ("model".equals(property)) {
if (language.equals("English")) {
button1.setLabel("Open");
button2.setLabel("Cancel");
} else if (language.equals("Ingles")) {
button1.setLabel("Abierto");
button2.setLabel("Cancelar");
}
}
}
};
combo.addPropertyChangeListener(propertyChangeListener);
combo.addActionListener(actionListener);
Container contentPane = frame.getContentPane();
panel.add(button1, BorderLayout.CENTER);
panel.add(button2, BorderLayout.SOUTH);
contentPane.add(combo, BorderLayout.NORTH);
contentPane.add(panel, BorderLayout.SOUTH);
frame.setSize(300, 100);
frame.setVisible(true);
}
}
The problem with this approach is that with the growing number of objects the propertyChange() method will expend and it will become difficult to manage. Moreover to add a new JComponent I will also have to modify propertyChange().
Is there a way to do it the other way around by making the objects "look" for a change in the key object and cause them to act accordingly instead of acting upon them by performing the action in the PropertyChangeListener of the key object? Or maybe other neat way to do this?
First off, a recommendation - always get a native speaker to do your translations. It helps to avoid certain types of errors. For instance, the word you've chosen as the replacement for 'open' is usually used as an adjective (as in, 'an open window') not as a verb, while you're using the inifinitive tense for 'cancel'. You also don't have accent marks, which may be confusing to some readers (some words are only different because of their accent marks...).
Next, you shouldn't hard-code language choices or uses; they should be loaded from .properties files or similar. This makes maintanence trivial, especially when adding additional languages. #Mort's answer about using the observer pattern is correct - that is how updates should be handled. Here's (really roughly) how to deal with the language aspects:
First, you need some .properties files.
spanishButtonNames.properties
==================================
openButton=Abrir
cancelButton=Cancelar
englishButtonNames.properties
==================================
opernButton=Open
cancelButton=Cancel
You'll want to wrap these up in some sort of PropertyManager that'll deal with loading the resource files behind the scenes, when the language is asked for (and possibly deallocating resources).
Next, when creating your buttons, name them:
JButton openButton = new JButton();
openButton.setName("openButton");
// And add them to a list
buttonList.add(openButton);
// More buttons....
Updating the buttons off of the button list is then trivial. You can have just the buttonList listen for (and propogate) updates:
for(JButton button : buttonList) {
button.setText(languagePropertyManager.getProperty(button.getName()));
}
(Note that with some clever coding, it's possible to load even the GUI layout from a text file, but I've never tried that. I believe there are frameworks to handle that, however.)
Do you know the observer pattern? It does what you want: objects will register at an object to get notified when something happens.
observer pattern
Related
This might be a very basic question. But I am stuck at this. The error that I get for the String variable display states:
Cannot refer to the non-final local variable display defined in an enclosing scope.
If I use a final keyword, I get the message:
The final local variable display cannot be assigned, since it is defined in an enclosing slope.*
The code is:
public class Frame {
public static void main(String[] args) {
String display=" ";
Frame ob=new Frame();
JFrame frame=new JFrame("Test");
frame.setBounds(300,100,800,500);
//Container c=frame.getContentPane();
frame.setLayout(null);
final JTextField name=new JTextField();
name.setBounds(500,212,150,20);
JLabel nameLabel=new JLabel("Name: ");
nameLabel.setForeground(Color.WHITE);
nameLabel.setBounds(450,171,100,100);
JTextField ohr=new JTextField();
ohr.setBounds(500,282,150,20);
JLabel ohrID=new JLabel("OHR ID: ");
ohrID.setForeground(Color.WHITE);
ohrID.setBounds(450,241,100,100);
final JButton button=new JButton("Submit");
button.setBounds(530,350,90,20);
frame.add(name);
frame.add(ohr);
frame.add(ohrID);
frame.add(nameLabel);
frame.add(button);
frame.getContentPane().setBackground(Color.DARK_GRAY);
frame.setVisible(true);
button.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
if(e.getSource()==button){
display=name.getText();
JOptionPane.showMessageDialog(null, "Hi "+ display);
System.exit(0);
}
}
});
}
Thanks in advance!
There are multiple issues with your code, and we'll address them right here, right now and solve your problem at the same time.
public class Frame { this particular line has an error, Frame is the name of an AWT class, so it might confuse you or anyone who reads this code later on, give it a more meaningful name and avoid those names that could be confused with other Java packages.
Frame ob=new Frame(); you create an instance of your class and never use it again, why?
frame.setLayout(null); NEVER, please don't use null-layout, Swing has to deal with multiple PLAFs, screen sizes and resolutions, different OS, pixel perfect apps might seem like the easiest way to create complex UIs but later on you'll find that errors like this happen very often.
.setBounds(...) on every component, again, this is due to null-layout but it's better to use Layout managers
final JTextField name=new JTextField(); There's no need to declare any of your components as final, this is due to a poor design of your class, your components should be declared as class members (outside any method including main).
Speaking about main, separate your program into smaller pieces, don't throw everything at main or at the very least create a method that is not static so you can call it after creating an instance of your class (or else later on you'll end up with tons of static variables and that's a poor design of your class once again).
System.exit(0); it will stop the JVM, it's never a good idea to do that, it's better to .dispose() the JFrame and have your JFrame's defaultCloseOperation set to EXIT_ON_CLOSE which will safely dispose your app and then stop the JVM.
display=name.getText();, for this particular case, display could be an inner variable rather than a class member. This will solve your particular question
JOptionPane.showMessageDialog(null, "Hi "+ display); that null should be a reference to your JFrame, this will place your dialog in the middle of that JFrame rather than in the middle of the screen.
You never place your program inside the EDT, see point #2 in this answer.
So, having all the above points in mind, here's an improved version of your code.
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
public class UsingVariablesInsideActionListenerExample {
//We declare our components here
private JFrame frame;
private JButton button;
private JTextField name;
private JTextField ohr;
private JLabel nameLabel;
private JLabel ohrID;
private JPanel pane;
private JPanel namePane;
private JPanel ohrPane;
public static void main(String[] args) {
SwingUtilities.invokeLater(new UsingVariablesInsideActionListenerExample()::createAndShowGUI); //This is using Java 8 lambdas to place your program in the EDT
}
private void createAndShowGUI() {
frame = new JFrame("Test"); //Create your JFrame
pane = new JPanel();
pane.setLayout(new BoxLayout(pane, BoxLayout.PAGE_AXIS)); //This will make this JPanel to arrange components vertically
namePane = new JPanel(); //By default, JPanels have FlowLayout which will arrange components horizontally
ohrPane = new JPanel();
name = new JTextField(10); //We create a JTextField with 10 columns
nameLabel = new JLabel("Name: ");
nameLabel.setForeground(Color.WHITE);
ohr = new JTextField(10);
ohrID = new JLabel("OHR ID: ");
ohrID.setForeground(Color.WHITE);
button = new JButton("Submit");
//Add the action listener
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == button) {
String display = name.getText(); //The display variable is now an inner variable rather than a class member
JOptionPane.showMessageDialog(frame, "Hi " + display);
frame.dispose(); //We dispose the JFrame and it will be closed after due to EXIT_ON_CLOSE below.
}
}
});
//We add the components to the namePane (horizontally), the order matters
namePane.add(nameLabel);
namePane.add(name);
//Now we add these components to the ohrPane (horizontally again)
ohrPane.add(ohrID);
ohrPane.add(ohr);
//We then add the name and ohr panes to a bigger JPanel (pane, which if you remember will add them vertically) and we add the button at the end
pane.add(namePane);
pane.add(ohrPane);
pane.add(button);
//We make them non opaque (transparent) so that we can see the background color of the JFrame
namePane.setOpaque(false);
ohrPane.setOpaque(false);
pane.setOpaque(false);
frame.add(pane);
frame.getContentPane().setBackground(Color.DARK_GRAY);
frame.pack(); //This will get every component's preferred size and make the JFrame as small as possible where it looks good on every OS, PLAF, screen size and resolution.
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true); //We make the frame visible (always at the very end, when we've added everything to it).
}
}
And this is how it looks like now.
The UI may not be perfectly equal to the one you have, but I'm sure you can play with the different layout managers, and nest various JPanels to get a much better looking UI than mine, or at least a more similar one to the one you had.
Variable used in side an inner class should be effectively final . You can use a string[] of length 1 instead of string to resolve this . Please read bellow post for more details
Difference between final and effectively final
Also check this post for more details
Variable used in lambda expression should be final or effectively final
I am a fairly new user with programming in Java with about a week and a bit experience, as of before I have been using python for about 3 years but thought to give java a try.
I have been trying to develop my skills by creating small projects and applications and am now creating a small GUI counter.
I have achieved creating the GUI with 2 buttons and a label and have tested the maths behind the application but I am struggling to work out how the ActionListener works as it feels a lot different to python when making a button have a action.
This is My Code;
package gui;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.*;
public class GUI{
//This creates a frame or panel to contain things
public static void main(String[] args) {
//Maths To The Counter
int Counter = 0;
System.out.println(Counter);
Counter =+ 1;
System.out.println(Counter);
//Creating The Frame
JFrame frame = new JFrame();
JPanel panel = new JPanel();
panel.setBackground(Color.WHITE);
frame.getContentPane().add(panel);
//Creating The Label
JLabel label3 = new JLabel("Counter: ");
panel.add(label3);
//Button Which should have a funtion to add and display the number
JButton button = new JButton("Click Here.");
panel.add(button);
//Button to reset the counter
JButton buttonReset = new JButton("Reset Counter.");
panel.add(buttonReset);
//Set Size Of Window
frame.setSize(new Dimension(500, 400));
//Set Starting Position to centre
frame.setLocationRelativeTo(null);
//Setting a default close action
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Set Title
frame.setTitle("Counter");
//Disable Resize
frame.setResizable(false);
//Setting if its visible
frame.setVisible(true);
//Fits frame to fit everything
frame.pack();
}
}
enter code here
I know that in python a action is in a function so that has been my logic to this problem however I have seen that I need to use the actionlistener instead and I am struggling to get my head around it.
If Someone could show me how this type of action should be implemented it would be great help, I have watch some youtube videos and done a bit of research but im still struggling to understand in my situation how to do it.
For any confussion im sorry, overall my question is how do I add a action to a button in my program that can implement my maths at the start.
As well any feedback on the structure of my code would be welcomed as I am just starting in java and I do know poor structure can lead to mistakes.
This code should work:
Basically, in the main method I am creating an instance of the class and calling a method to create the gui.
I also created an instance variable as the counter, otherwise you won't be able to update the variable in your action listener.
public class Gui {
private int counter;
// This creates a frame or panel to contain things
public static void main(String[] args) {
Gui gui = new Gui();
gui.create();
}
private void create() {
// Creating The Frame
JFrame frame = new JFrame();
JPanel panel = new JPanel();
panel.setBackground(Color.WHITE);
frame.getContentPane().add(panel);
// Creating The Label
JLabel label3 = new JLabel("Counter: ");
panel.add(label3);
// Button Which should have a funtion to add and display the number
JButton button = new JButton("Click Here.");
panel.add(button);
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println(counter++);
}
});
// Button to reset the counter
JButton buttonReset = new JButton("Reset Counter.");
panel.add(buttonReset);
buttonReset.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
counter = 0;
}
});
// Set Size Of Window
frame.setSize(new Dimension(500, 400));
// Set Starting Position to centre
frame.setLocationRelativeTo(null);
// Setting a default close action
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Set Title
frame.setTitle("Counter");
// Disable Resize
frame.setResizable(false);
// Setting if its visible
frame.setVisible(true);
// Fits frame to fit everything
}
}
With Lambda expressions, you can simplify your action listeners as follows:
button.addActionListener(a -> System.out.println(counter++));
buttonReset.addActionListener(a -> counter = 0);
If you want to write more than 1 statement, then you can just put your code in curly brackets:
button.addActionListener(a -> {
System.out.println(counter++);
System.out.println("doing more stuff...");
});
JButton has a function called addActionListener. You can pass on an action listener by doing this:
button.addActionListener(() -> {
// Do some logic here
});
Here, I use a lambda expression as an action listener. Within the lambda expression you can place whatever logic you want to have.
Also note that you can add multiple different action listeners to the same button. In a nutshell, the way the JButton interacts with the ActionListeners is based on the observer-pattern.
Imagine this: When the JButton is pressed, it will notify all of it's observers saying "Hey, I have been pressed". Each observer can then independently decide what to do. In case of the JButton, all observers are ActionListeners. If you add multiple ActionListeners then the JButton will notify all of them, and as a result all of their actionPerformed(ActionEvent e) functions are executed. In the example above, I used a lambda expression which then by java is interpreted as an ActionListener.
Other ways to achieve the exact same functionality are:
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// Do some logic here
}
});
In the example above, you use an anonymous class as an actionlistener.
public class MyClass {
public MyClass() {
JButton button = new JButton("press me");
button.addActionListener(new MyActionListener());
}
private class MyActionListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
// Do some logic here
}
}
}
In the example above, an inner class is used.
In a nutshell, there is a ton of ways you can make your button have functionality. Above are just a few examples of how to do so.
Does this clarify it a bit more, or do you have some remaining questions?
So I have a layout made with buttons,textfields, and labels. A user is supposed to put input into the textfields. When he hits a button, I want it so that the input is cleared and a new "page" is shown with the layout i have made. The user can input as much information into new "pages" as he wants until he hits an "finished" button. In short, I want to switch between panels or frames (i dont know which, probably panels??). Now, I was thinking of using card layout to do this but since i'm reading user input it wouldn't really make sense since cardlayout is made based on a predetermined amount of panels and what will be in the panels. Since I won't know when the user is "finished", I won't know how many panels to use.
Anyways, I'm just a beginner with GUI so any help would be great!
Now, I was thinking of using card layout to do this but since i'm
reading user input it wouldn't really make sense since cardlayout is
made based on a predetermined amount of panels and what will be in the
panels. Since I won't know when the user is "finished", I won't know
how many panels to use.
You can dinamically add components to CardLayout on next button's click. If all the pages have the same structure you can have a class for those pages and add a new one every time next button is pressed. When finish button is pressed do something with all those pages iterating over the panel (with CardLayout) components. Take a look to Container.getComponents() method. You don't even need to keep any kind of array nor list because the container already do so.
Example
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
public class Demo {
private void createAndShowGUI() {
final JPanel cardPanel = new JPanel(new CardLayout());
cardPanel.add(new Page(), "1");
final JButton nextButton = new JButton("Next");
nextButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
cardPanel.add(new Page(), String.valueOf(cardPanel.getComponentCount() + 1));
CardLayout layout = (CardLayout)cardPanel.getLayout();
layout.next(cardPanel);
}
});
final JButton finishButton = new JButton("Finish");
finishButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
nextButton.setEnabled(false);
for(Component comp : cardPanel.getComponents()) {
if(comp instanceof Page) {
Page page = (Page)comp;
page.printData();
}
}
}
});
JPanel buttonsPanel = new JPanel();
buttonsPanel.add(nextButton);
buttonsPanel.add(finishButton);
JFrame frame = new JFrame("Demo");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(cardPanel, BorderLayout.CENTER);
frame.getContentPane().add(buttonsPanel, BorderLayout.SOUTH);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
class Page extends JPanel {
final private JTextField data;
public Page() {
super();
add(new JLabel("Please add some info:"));
data = new JTextField(20);
add(data);
}
public void printData() {
System.out.println(data.getText());
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Demo().createAndShowGUI();
}
});
}
}
As far as I understand from your description you do not need multiple panels. I am assuming that you have some sort of object hierarchy for your model layer. So, let's say you use those input values to create AnObject objects.
You can create an ArrayList<AnObject> in your top class. And as user inputs and clicks done you just create one more AnObject with given input and add it to the ArrayList you defined in top class.
BTW, you can also define ArrayList whereever it is reachable. But you must think carefully, to keep your data persistent. If the object of the class that you defined ArrayList is "gone", your data is also "gone". I think this should be clear enough.
The next step is just trivially clearing out those input fields.
This is the most straightforward way, it may not be the smartest way to do that depending on your use case. But it would give you an idea for what to look and learn for.
In our code we have a 10 by 10 button array.
We made the 10 by 10 array using a nested for loop, and we have no issue creating the buttons.
Also, we have it so that when a button a is clicked it displays "Button Clicked". But how can we identify which button was clicked?
We're using actionListeners and actionPerformed methods.
You can call the getSource() method on the event.
Or you can use Action classes in your buttons and create a new instance of each when you build the buttons.
Put all the buttons in a list (easily accomplished in the inner loop), make the list available to the ActionListener (eg. as a property of the outer class; I do not know how your numerous team arranged the listeners, so I cannot provide any details). Then call:
int buttonIndex = listWithButtons.indexOf(event.getSource())
If one of you wants to know the exact coordinates of the button, they can be calculated by the formulas:
int row = buttonIndex / 10;
int col = buttonIndex % 10;
I'm assuming this is a JButton. You can use setActionCommand("command" + row + "-" + column). Then in the listener just say getActionCommand() to see which button was clicked.
Two simples solutions, but probably not the best ones :
The button class implementing its own listener.
You can also just test each button to see if it equals the action.getSource() object.
Or simply cast (ButtonClass) to the getSource() to be able to use the retrieved button.
You can use getActionCommand just like this:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class TestButtons extends JPanel {
private static final long serialVersionUID = 1L;
public TestButtons() {
JButton btn1 = new JButton("Btn1");
btn1.addActionListener(new ButtonListener());
add(btn1);
JButton btn2 = new JButton("Btn2");
btn2.addActionListener(new ButtonListener());
add(btn2);
}
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.getContentPane().add(new TestButtons());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(200, 200);
frame.setVisible(true);
}
}
class ButtonListener implements ActionListener {
ButtonListener() {
}
public void actionPerformed(ActionEvent e) {
System.out.println(e.getActionCommand()+ " has been clicked");
}
}
We ended up using a nested for loop inside of actionPerformed that ran through the 2d array and called the action methods from there. It probably isn't the best solution, and it's probably best to use a different technique, but it seems to work just fine.
Though there are some really nice ideas in here, thank you guys!
I am currently working on some Java code that has a lot of ActionListeners defined in it (one for each JButton) and there are around 60 buttons. These are all defined as anonymous inner classes in the JButton.addActionListener method. I have been thinking of ways to refactor this to make the code look neater as this is making it looked very cluttered. I thought about possibly taking the listeners into a separate class that essentially has a load of static methods each returning a listener. This will mean that the code will look something like addActionListener(GetActionListener.addActionListener()). Whilst this will make it neater I feel that it is not really an elegant solution. I thought also a static final map holding KV pairs with listener name to the listener itself. Again however this still does not seem like a very elegant solution. I was wondering if anyone had any ideas? I should also say that all the actionListeners are pretty different.
I would suggest not to directly add actions using ActionListener. If you do this way it becomes non-reusable. Instead wrap your actions in javax.swing.Action class. So that you can reuse the action wherever you want. For e.g now you can use the same action for say a menu shortcut of a Copy action and the copy button in toolbar.
Basically the idea is not to directly couple runnable actions with GUI elements.
Now coming to your question. I would make a repository of actions in a class called, say, ActionRepsoitory with a public method public Action getAction(String). Each of your action would be identified by a String constant which you use to retrieve the action from the repository. Typically that string would be the actionCommand for the element. How you manage the actions in the ActionRepository, via a HasMap or whatever, is completely dependent on you.
This is how its doen in most proffesional code, AFAIK.
Not a duplication of this question (which was not a duplicate of the question it answered... wow) but the answer should apply.
If your inner classes are doing more than just calling a method inside of the outer class then you are doing it "wrong" (to my definition of "right"). In the posted code the calls to increment() and decrement() are the "right" way to do it. Refactoring the code have the listeners forward the method call to the outer class is a good place to start to make the code more manageable.
That being said... 60 buttons on a UI?! Really! Ouch! Are they all on one screen or is it done with tabs or something? (if it is tabs or something I have more to offer in the answer).
You could make a special Subclass of ActionListener that uses reflection to call a given method name, then you can implement all your 60 actions as normal methods.
package com.example;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class MethodAsActionListener implements ActionListener {
private Object receiver;
private Method method;
public MethodAsActionListener(Object receiver, String name) {
this.receiver = receiver;
try {
this.method = receiver.getClass().getDeclaredMethod(name);
} catch (SecurityException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
#Override
public void actionPerformed(ActionEvent event) {
try {
method.invoke(receiver);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}
and then if you a method call to your class
private call(String name) {
return new MethodAsActionListener(this, name);
}
then you can add your actions as follows
button1.addActionListener(call("methodName1"));
button2.addActionListener(call("methodName2"));
button3.addActionListener(call("methodName3"));
button4.addActionListener(call("methodName4"));
button5.addActionListener(call("methodName5"));
if one of these methods is missing, the program will fail when the UI is built (since we lookup the method when the action listener is created). Which is not as nice as at compile time, but still better than totally late-bound when the action is triggered.
I would recommend something like what you suggested--create a single listener class that you subscribe to all the events. You PROBABLY want to use a different instance of the class for each event though, telling the instance (in the constructor) in general what to do with this specific event.
The advantage of this is that you can then start factoring the code inside the listeners together into fewer methods because It's usually pretty similar. Sometimes you can get it into a single method.
One trick I've used for a "Pure dispatch" situation for menu creation was to specify the menu, the structure of the menu and the method each menu item links to in data. Needs a little reflection but it works.
In fact--let me look.
Yeah, I kept the classes in a google doc :) The data was specified like this:
final static String[] menus = { "File:*", "Save:save", "Load:load", "(", "Print:-", "Preview:preview", ")", "Quit:quit" };
It just parsed this. File becomes a top level item because of the start, save will call your "Save" method, load will call your "Load" method, Print is a sub-menu (hence the parens), with preview underneath it and print is not bound to anything.
This string can create and bind an entire menu with one call.
Here's my source code if you want to play with it.
The "TestMenu" class at the top is a testing class demonstrating how to use the buildMenus method.
This was done quite a few years ago, I might do it differently now, but it works. I'm not sure I like it actually generating the menu, and I think I'd make the string parser use a single string instead of breaking it down into strings for each item--should be easy to ensure each item is whitespace separated...
A better API might be a bind method like this:
bind(this, new JButton("Save"), "save", this);
where pressing the save button would cause the save method to be called on this (or whatever other object you passed in). You could even make the "save" parameter optional and just use the JButton.getText().toLower() as the method to call if no parameter exists (I guess that's convention before configuration)
I didn't do it this way with the menu because I also wanted to abstract out the menu creation and menu relationships into my data.
Note that coding this way is an awesome way to get your MVC separation in Java--all your controller code can be removed from your view.
package hEvil;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.border.EmptyBorder;
public class JDial extends JDialog {
private static final long serialVersionUID = -26565050431585019L;
private final JPanel contentPanel = new JPanel();
public static void main(String[] args) {
try {
JDial dialog = new JDial();
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
dialog.setVisible(true);
dialog.setTitle("Heavy Evil");
dialog.setBackground(Color.WHITE);
} catch (final Exception e) {
e.printStackTrace();
}
}
public JDial() {
setBounds(0, 0, 1300, 800);
getContentPane().setLayout(new BorderLayout());
contentPanel.setLayout(new FlowLayout());
contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
getContentPane().add(contentPanel, BorderLayout.CENTER);
JPanel windowPane = new JPanel();
windowPane.setLayout(new FlowLayout(FlowLayout.RIGHT));
getContentPane().add(windowPane, BorderLayout.SOUTH);
{
JButton cancelButton = new JButton("Exit");
cancelButton.setActionCommand("Exit");
windowPane.add(cancelButton);
cancelButton.setBounds(0, 0, 1200, 700);
}
{
JPanel textPane = new JPanel();
textPane.setLayout(new FlowLayout(FlowLayout.LEFT));
getContentPane().add(textPane, BorderLayout.NORTH);
textPane.setVisible(true);
{
JTextArea textArea = new JTextArea("Username", 2, 15);
textPane.add(textArea);
textArea.setWrapStyleWord(true);
textArea.setEditable(true);
textArea.setFont(Font.getFont(Font.SANS_SERIF));
textArea.setVisible(true);
textArea.enableInputMethods(isEnabled());
textArea.computeVisibleRect(getBounds());
textArea.setBackground(Color.GRAY);
JTextArea textArea2 = new JTextArea("Password", 2, 15);
textPane.add(textArea2);
textArea2.setWrapStyleWord(true);
textArea2.setEditable(true);
textArea2.setFont(Font.getFont(Font.SANS_SERIF));
textArea2.setVisible(true);
textArea2.enableInputMethods(isEnabled());
textArea2.computeVisibleRect(getBounds());
textArea2.setBackground(Color.GRAY);
}
{
JButton registerButton = new JButton("Register");
textPane.add(registerButton);
}
{
JButton newButton = new JButton("Submit");
textPane.add(newButton);
newButton.setEnabled(true);
getRootPane().setDefaultButton(newButton);
newButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
JFrame newFrame = new JFrame("Welcome");
newFrame.setVisible(true);
newFrame.setBackground(Color.BLACK);
newFrame.setBounds(0, 0, 580, 200);
JPanel newPanel = new JPanel();
newFrame.add(newPanel);
dispose();
JButton nuButton = new JButton("Mario");
newPanel.add(nuButton);
JButton nuButton2 = new JButton("Kirby");
newPanel.add(nuButton2);
JButton nuButton3 = new JButton("Mew Two");
newPanel.add(nuButton3);
JButton nuButton4 = new JButton("Vegeta");
newPanel.add(nuButton4);
JButton nuButton5 = new JButton("Tidus");
newPanel.add(nuButton5);
JButton nuButton6 = new JButton("Link");
newPanel.add(nuButton6);
JButton nuButton7 = new JButton("Master Chief");
newPanel.add(nuButton7);
JButton nuButton8 = new JButton("Snake");
newPanel.add(nuButton8);
JButton nuButton9 = new JButton("Cash");
newPanel.add(nuButton9);
JButton nuButton10 = new JButton("Lara");
newPanel.add(nuButton10);
JButton nuButton11 = new JButton("Max");
newPanel.add(nuButton11);
JButton nuButton12 = new JButton("Spyro");
newPanel.add(nuButton12);
JButton nuButton13 = new JButton("Sephiroth");
newPanel.add(nuButton13);
JButton nuButton14 = new JButton("Scorpion");
newPanel.add(nuButton14);
}
});
}
}
}
}
//AND I WANT TO BE ABLE TO IMPLEMENT EACH BUTTON FROM ANOTHER CLASS
//FROM ACTIONEVENT WITH SOMETHINGS SIMILAR TO nuButtonX.actionImplemented...
//CALLING THE SAME ACTIONLISTENER IF I CAN AND THEN CREATING A CLASS FOR
//MODIFICATIONS AT EACH INSTANCE.
enter code here
package hEvil;
import java.awt.event.ActionEvent;
import javax.swing.JFrame;
public class ActoEve extends ActionEvent {
/**
*
*/
private static final long serialVersionUID = -2354901917888497068L;
public ActoEve(Object arg0, int arg1, String arg2) {
super(ActionEvent.ACTION_PERFORMED, arg1, "flame_001.jpg");
// TODO Auto-generated constructor stub
}
public void actionImplemented(ActionEvent evt1) {
JFrame nuFrame = new JFrame();
nuFrame.setVisible(true);
nuFrame.setBounds(0, 0, 300, 200);
// TODO Auto-generated constructor stub
}
}