Refactoring multiple actionListeners - java

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
}
}

Related

Adding a action listener in java for a counter

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?

How to change a JLabel with a button from another class?

I have two classes and a text file database, The JLabel in the first class, let's call it class1 automatically set it self to the number in the database. Now, in class2 I have this little JFrame and a text field and of course a button, the value I put in the text field overwrites the one in the database, but here's the problem. The label in the first class wont update while running, but if I restart it it will show me the value that I want.
How do I update it while the program is running?
I've tried to change the label in the buttonActionperformed in the other class but it gives me a NullPointException every time.
How do I fix this?
THE MAIN CLASS ( JUST THE JFRAME )
package BankrollG;
import java.awt.Graphics;
import javax.swing.JFrame;
public class BGbrain {
BGbody body = new BGbody();
JFrame Frame = new JFrame();
public BGbrain() {
Frame.setSize(body.width, body.height);
Frame.setTitle(body.title);
Frame.setResizable(false);
Frame.setDefaultCloseOperation(Frame.EXIT_ON_CLOSE);
Frame.add(body.panel);
Frame.setLocationRelativeTo(null);
Frame.setFocusable(true);
Frame.setVisible(true);
}
public static void main(String[] args ) {
new BGbrain();
}
}
Then you got the class with the components:
private JLabel bankroll_label
public BGbody(){
panel.setLayout(null);
windowComponents();
}
public void windowComponents() {
// Bankroll database access
try {
FileReader fr = new FileReader("Bankroll.txt");
BufferedReader br = new BufferedReader(fr);
set_bankroll = br.readLine();
} catch(IOException e) {
System.out.println("FEL MED LĂ„SNING AV DATABAS /// BGBODY");
}
}
}
THEN you got the JFrame class that I created with the netbeans function
private void AddcurrencyActionPerformed(java.awt.event.ActionEvent evt) {
String CBR = txt_bankroll.getText();
try {
FileWriter fw = new FileWriter("Bankroll.txt");
PrintWriter pw = new PrintWriter(fw);
pw.println(CBR);
pw.close();
} catch(IOException e) {
System.out.println("FEL MED INSKRIVNINGEN I DATABASEN");
}
}
Now, everything goes as plan, but I can't update my JLabel "bankroll_label" from the button class because it just returns nullpointsexceptions. The data is there, because the JLabel reads from the database but it wont update when changes has been made from the button class. So a getter setter method wont work because the value IS there but it wont update the JLabel.
I hope this made it easier to understand my problem.
It's ALOT more code, that dont have to do with this, I hope I simplified it at least some.
Your question is a specific example of a basic problem in programming in Java -- how to transfer information between classes. There are several ways to do this, one of the most elegant being giving to use a "model" class that holds your program's logic code and key data, having one class change the model's state by changing a text String that it holds. Then using a listener or observer pattern, have the model notify the other class that it has been changed so the other class can extract the new information, its new String from the model. While this is likely the best solution, it may be a bit of overkill and likely is above your current level of coding, so for you, I'm not going to recommend this.
Instead, I'm going to recommend a simpler less elegant solution, that you instead have one class call a setter method of the other to push the new String into it.
One problem we have as volunteer answerers here is that your question is hard to answer because it lacks critical code, making it hard for us to guess why your code is misbehaving, why specifically you're running into a NullPointerException (or NPE) when you try to run it. So all I can do is guess, but guess I will try nevertheless.
For simplicity's sake, let's call one class the, the one that holds the JLabel, the LabelClass and the other class the ButtonTextFieldClass.
One possible reason is that you've got a NullPointerException is because your ButtonTextFieldClass may have a LabelClass variable, but never initialized the variable, something like so:
// this guy is null because it is never initialized
private LabelClass labelClass;
A simple solution could be to try to initialize it like so:
private LabelClass labelClass = new LabelClass();
But this won't work because while it does create and assign a LabelClass instance, it's likely not the LabelClass instance that is visualized in the running GUI.
A better solution is to give the ButtonTextFieldClass a setter method that allows other classes to set the ButtonTextFieldClass with the proper LabelClass instance.
e.g.,
public void setLabelClass(LabelClass labelClass) {
this.labelClass = labelClass;
}
This way the code that sets up both classes can pass the visualized LabelClass to the first class, and it can call methods on it.
A simple example of LabelClass could look like so:
class LabelClass extends JPanel {
private JLabel label = new JLabel("");
public LabelClass() {
setBorder(BorderFactory.createTitledBorder("Label Panel"));
add(label);
}
public void setLabelText(String text) {
label.setText(text);
}
}
I have it extend JPanel because this way it gives me the freedom of placing it into a JFrame or JDialog or other JPanel as I see fit. Note that I've made the JLabel private and have given the class a public setter method, setLabelText(String text), that allows outside classes the ability to set the JLabel's text.
The ButtonTextFieldClass could look something like:
class ButtonTextFieldClass extends JPanel {
private JTextField textField = new JTextField(10);
private JButton button = new JButton(new ButtonAction("Send Text"));
private LabelClass labelClass;
public ButtonTextFieldClass() {
setBorder(BorderFactory.createTitledBorder("Button TextField Panel"));
add(textField);
add(button);
}
// here we allow other classes to set instances of our LabelClass
public void setLabelClass(LabelClass labelClass) {
this.labelClass = labelClass;
}
// ....
I've also given the button an AbstractAction in place of an ActionListener since it is like a super ActionListener on steroids. Inside of it, I'd get the text from the JTextField and then call the LabelClass's setter method (if the variable is not null) to set the label's text:
public void actionPerformed(ActionEvent e) {
String text = textField.getText();
if (labelClass != null) {
labelClass.setLabelText(text);
}
}
Then to set everything up, in another class I'd create instances of both LabelClass and ButtonTextFieldClass, and then "hook them up" by calling the setter method:
LabelClass labelClass = new LabelClass();
ButtonTextFieldClass buttonTextFieldClass = new ButtonTextFieldClass();
buttonTextFieldClass.setLabelClass(labelClass); // set our reference
The whole thing could look like so:
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import javax.swing.*;
public class TransferData {
private static void createAndShowGui() {
LabelClass labelClass = new LabelClass();
ButtonTextFieldClass buttonTextFieldClass = new ButtonTextFieldClass();
buttonTextFieldClass.setLabelClass(labelClass); // set our reference
JPanel mainPanel = new JPanel(new GridLayout(0, 1));
mainPanel.add(buttonTextFieldClass);
mainPanel.add(labelClass);
JFrame frame = new JFrame("TransferData");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
class LabelClass extends JPanel {
private JLabel label = new JLabel("");
public LabelClass() {
setBorder(BorderFactory.createTitledBorder("Label Panel"));
add(label);
}
public void setLabelText(String text) {
label.setText(text);
}
}
class ButtonTextFieldClass extends JPanel {
private JTextField textField = new JTextField(10);
private JButton button = new JButton(new ButtonAction("Send Text"));
// one possible solution -- give this class a variable
// of the LabelClass -- but don't initialize the variable
// here, but rather do it in a setter
private LabelClass labelClass;
public ButtonTextFieldClass() {
setBorder(BorderFactory.createTitledBorder("Button TextField Panel"));
add(textField);
add(button);
}
// here we allow other classes to set instances of our LabelClass
public void setLabelClass(LabelClass labelClass) {
this.labelClass = labelClass;
}
// an AbstractAction is like a "super" ActionListener
private class ButtonAction extends AbstractAction {
public ButtonAction(String name) {
super(name); // set the button's text and actionCommand
int mnemonic = (int) name.charAt(0); // get first char
putValue(MNEMONIC_KEY, mnemonic); // set mnemonic
}
#Override
public void actionPerformed(ActionEvent e) {
String text = textField.getText();
if (labelClass != null) {
labelClass.setLabelText(text);
}
}
}
}
For simplicity's sake, I've displayed both JPanels within the same GUI, but it could work just as well if one JPanel were in one JFrame and the other within a JDialog.

Listen to the paste events JTextArea

I want to call a function when the user pastes text in my JTextArea. Is there any event generated when the text is pasted to the JTextArea and which listener can I use to trigger my function on this event?
One possible solution (and I hope some one has a better one) would be to replace the key binding Action responsible for actually performing the paste operation.
Now, before you do this, the default paste operation is not trivial, instead, I would replace the default paste Action with a proxy, which could call the original, but would allow you to intercept the operation, but not have to re-implement the functionality yourself, for example...
public class ProxyAction extends AbstractAction {
private Action action;
public ProxyAction(Action action) {
this.action = action;
}
#Override
public void actionPerformed(ActionEvent e) {
action.actionPerformed(e);
System.out.println("Paste Occured...");
}
}
Then you would simply need to look up the default Action and replace it...
JTextArea ta = new JTextArea(10, 10);
Action action = ta.getActionMap().get("paste-from-clipboard");
ta.getActionMap().put("paste-from-clipboard", new ProxyAction(action));
The problem here is, this won't tell you if the operation failed or succeeded or what was actually pasted. For that, you could use a DocumentListener, registered before you call the default Action which could record the changes to the document. Obviously, you'd want to deregister this after the default action ;)...
Now, equally, you could just override the paste method of the JTextArea, which equates to about the same thing, but, the first option would be more portable...
As an idea...
Take a look at How to Use Actions and How to Use Key Bindings for more details
you can have something like below, whenever you paste something in the textarea, then 'Pasted!' is printed out on your console. It prints only on paste !
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.*;
public class TextAreaDemo extends JFrame {
JTextArea _resultArea = new JTextArea(6, 20);
public TextAreaDemo() {
_resultArea.setText("");
JScrollPane scrollingArea = new JScrollPane(_resultArea);
JPanel content = new JPanel();
content.setLayout(new BorderLayout());
content.add(scrollingArea, BorderLayout.CENTER);
this.setContentPane(content);
this.setTitle("TextAreaDemo B");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.pack();
_resultArea.addKeyListener(new KeyListener() {
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyPressed(KeyEvent e) {
if ((e.getKeyCode() == KeyEvent.VK_V) && ((e.getModifiers() & KeyEvent.CTRL_MASK) != 0)) {
System.out.println("Pasted!");
}
}
#Override
public void keyReleased(KeyEvent e) {
}
});
}
public static void main(String[] args) {
JFrame win = new TextAreaDemo();
win.setVisible(true);
}
}
You can also check out Wrapping Actions which is basically the same suggestion as MadProgrammer except that the WrapperAction will delegate all the methods of the Action to the original Action. This will allow you to pick up the text and Icons associated with the original Action in case you ever want to add your custom Action to a JMenuItem or JButton.

GUI - How do I switch between panels or frames while saving user input

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.

Notifying objects about an event

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

Categories