I went through plenty of articles and (imo too complex) examples of how to separate GUI from the rest of the program logic in Java, and to be completely honest, I still have no clue.
Could someone give me a hint of how to do it on this simple example, with just one button?
Let's say there is a button in the Window class:
JButton firstButton = new JButton("My first button");
btnCreateProject.setBounds(100, 100, 80, 30);
frame.getContentPane().add(firstButton);
and let's say this button would call a constructor of the Employee class. So, I could do it like this:
JButton firstButton = new JButton("My first button");
firstButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
.....constructor calling, and rest of the code....
}
});
btnCreateProject.setBounds(100, 100, 80, 30);
frame.getContentPane().add(firstButton);
But this is exactly what I DO NOT want. I want my Window class to be just pure GUI, with buttons, radio boxes, and other stuff, with it's properties. Nothing more. I would like to have all the listeners in another class, let's say Controller, and from this class I would call all the needed methods.
Could someone give me an example of how to do it with this simple button? Thank you very much.
You are drawing a distinction of degree, not kind. Your GUI code cannot be completely separated from program logic, else it would be a static work of modern art.
The various kinds of AWT/Swing listener classes do separate GUI from logic, into altogether separate classes. You do not, however, need to implement listeners as anonymous inner classes; perhaps it would feel better to you to implement them as ordinary, top-level classes instead. Alternatively, you might find that it suits you to model program behaviors via classes implementing the Action interface. Then you can assign those behaviors to controls (buttons, menu items) via the controls' setAction() method.
Any way around, however, the code that sets up your GUI has to somehow know about both the GUI components and the logic that needs to be hooked up to them, else there's no way it could do its job.
This is a constructor for a new class:
new ActionListener() {
public void actionPerformed(ActionEvent e) {
.....constructor calling, and rest of the code....
}
}
You could make a class like:
public class ActionListenerTest implements ActionListener {
...
}
Then make something like this:
firstButton.addActionListener(new ActionListenerTest());
I had the same issue with my projects, but finally I decided to use a concept I learned in android programming. Here is how it works:
I add an identified to my objects (for buttons, menus, etc I use setActionCommand method) so I can use the same identifier for the same operations for different components.
All my objects call one controller with their getActionCommand as cmd and the object itself. Within the controller, I control the cmd and call the proper method from it.
It is much more easier to control the GUI and the rest of the program.
It worked for me this way like charm.
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JDialog;
/**
*
* #author Pasban
*/
public class ActionListenerTest {
public static void main(String[] args) {
JButton b1 = new JButton("My first button");
JButton b2 = new JButton("My first button");
b1.setActionCommand("BTN_1");
b2.setActionCommand("BTN_2");
//put this in another class
ActionListener controller = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
String cmd = e.getActionCommand().toUpperCase().trim();
System.out.println(cmd);
//System.out.println(e.getSource()); // cast to anything you have in your mind about the caller
if (cmd.equals("BTN_1")) {
System.out.println("BUTTON 1 is clicked");
} else if (cmd.equals("BTN_2")) {
System.out.println("BUTTON 2 is clicked");
}
}
};
b1.addActionListener(controller);
b2.addActionListener(controller);
JDialog frame = new JDialog();
frame.setSize(new Dimension(300, 300));
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
frame.getContentPane().setLayout(new FlowLayout());
frame.getContentPane().add(b1);
frame.getContentPane().add(b2);
frame.setVisible(true);
}
}
You could always just implement something like:
Controller.handleActionEvent(ActionEvent e);
and delegate your button click to that. And if you don't want your controller to know about Swing, you could always create some interface and wrap the Swing ActionEvent in some kind of event of your creation.
Or just implement an Observer (Listener) pattern in the UI class (or you could have a "registry" class that sits in the middle). When the button is clicked, the UI would delegate the event to all registered listeners (implementing some interface that you define). So the Controller (client) would just tell the UI "notify me when Button X is clicked" and the UI would just delegate to all interested parties.
So, the UI wouldn't need to know who to call explicitly. But the controller would have to know who he wants to listen to.
What you should do is write a method where appropriate to access datamodel (supposing you have one) and do the work there, and just call the method from button click.
firstButton.addActionListener(e -> logicClass.addEmployeeToFirm());
or you could event write a custom Listener that would call/do that logic (a class that implements ActionListener).
Inside your controller, create a method
Action get[Operation]Handler(); //Fill in [Operation] will what your button functionality should do
which returns an inner class that implements the proper functionality.
Then, in your GUI code, do
JButton b = ...;
b.setAction(controller.get[Operation]Handler());
Related
I'm currently trying to build a small program for school. If you click on a checkbox it should show other elements. I learned in python that you need a while loop because the program needs to go over the same lines again where you check if the box is checked but if i put a loop the whole program won't start. I don't understand why.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class test extends JFrame {
private JCheckBox moredetailscheck;
private JTextField inputfielduser;
public static void main(String[] args) {
test venster = new test();
venster.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
venster.setSize(800, 600);
venster.setVisible(true);
venster.setTitle("true");
venster.setResizable(false);
}
public test() {
setLayout(new FlowLayout());
moredetailscheck = new JCheckBox("checkbox", false);
add(moredetailscheck);
inputfielduser = new JTextField(15);
while(true) { // you want to let the program keep going over these lines
if(moredetailscheck.isSelected()) {
add(inputfielduser);
}
}
}
If you click on a checkbox it should show other elements.
So, you would attach a listener to the JCheckBox, here an ItemListener, that responds when the state of the JCheckBox changes.
I learned in python that you need a while loop because the program needs to go over the same lines again where you check if the box is checked
This is called "polling" and is needed for linear console programs where you need to continually obtain input from the user, again in a "linear" fashion. In these types of programs, you the programmer are in complete control over program code flow, but that's not what you want here.
but if i put a loop the whole program won't start. I don't understand why.
That's because you're now using an event-driven GUI library, there the Swing library, and by calling a while (true) loop on the event thread, you completely block it, rendering your GUI useless. Your program is starting, but it can't construct the GUI, draw itself or listen for events.
Solution:
Get rid of the while (true) loop. Again, it is useful for simple console programs but not in this situation.
Add an ItemListener to your JCheckBox. You can find out how to do that in the check box tutorial
Don't keep adding items to your GUI. Use a CardLayout to swap views. The tutorial can be found here: CardLayout tutorial.
Or even better, have all the GUI items on the GUI at startup, but use the JCheckBox state to enable/disable an item.
As an aside, you will want to learn and use Java naming conventions. Variable names should all begin with a lower letter while class names with an upper case letter. Learning this and following this will allow us to better understand your code, and would allow you to better understand the code of others.
For example:
import java.awt.event.ItemEvent;
import javax.swing.*;
public class TestCheckBox extends JPanel {
private static final long serialVersionUID = 1L;
private JCheckBox moreDetailsCheck = new JCheckBox("More Details", false);
private JTextField inputFieldUser = new JTextField(15);
public TestCheckBox() {
inputFieldUser.setEnabled(false);
add(moreDetailsCheck);
add(inputFieldUser);
// add a listener to the JCheckBox
moreDetailsCheck.addItemListener(e -> {
// if checkbox selected, enable the text field. else disable it
inputFieldUser.setEnabled(e.getStateChange() == ItemEvent.SELECTED);
});
}
private static void createAndShowGui() {
TestCheckBox mainPanel = new TestCheckBox();
JFrame frame = new JFrame("Test CheckBox");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
In Java, the AWT starts a thread to handle events automatically; you just let main finish and the program keeps running anyway until you call System.exit. You do need event handlers, though, for which any number of tutorials exist.
(Incidentally, your infinite loop comes before even showing your JFrame.)
I have a single ActionListener for multiple buttons:
private class MyActionListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent event) {
if (event.getSource().equals(button1)) {
// do something
} else if (event.getSource().equals(button2)) {
// do something else
}
}
}
Which one of the following is the better way to add the ActionListener to these buttons and why?
1.:
button1.addActionListener(new MyActionListener());
button2.addActionListener(new MyActionListener());
2.:
MyActionListener mal = new MyActionListener();
button1.addActionListener(mal);
button2.addActionListener(mal);
I went with the 2nd option, but not sure it's the proper way.
This question asks for opinion and may be closed, and because of this, I'm answering as a community wiki, but I think the best answer is "it depends".
What I do is if all listeners are exactly the same, and you're not passing anything different into each constructor, then just have each button share the same listener. If I need to pass unique information into the listeners, then I use separate listeners, but really either will work, and neither will likely cause an appreciable difference in program operation or responsiveness.
Please help me understanding the difference between adding the action listener to JComponent in following two approaches.
First Method: Implementing actionListener to my class and adding the common actionPerformed method which choose selection based on the events
class Test implements ActionListener
{
JButton jbutton = null;
public Test(){
jbutton = new JButton();
jbutton.addActionListener(this);
}
public void actionPerformed(ActionEvent e){
//Perform operation here;
}
}
Second Method: Defining the action listener for individual JComponent.
JButton jbutton = new JButton();
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
//Perform operation here
}
});
What is the difference between these two approach and which one is more cleaner and maintainable approach and if there is any efficiency benefit involved ?
I'd go with the first approach if:
The action is fired via different events. For example, you have an action that changes the language of the GUI from English to Arabic (where you need to re-arrange the components to lay from right to left) and that action can be fired via some key bindings like (Alt + R) and via a JMenuItem, and maybe via some buttons.
Several actions have the same base code. For example, a calculator application where each math operation button would fire the same action and based on the action command you can determine the operation from inside actionPerformd(). They share the GUI updates.
and I'd go with the second approach if:
The action is tied to only one event and you want to write it on the fly.
What I wouldn't do is something similar to this:
public class MainFrame extends JFrame implements ActionListener
but I would write:
public class CustomListener implements ActionListener
Also see:
Best practice for implementing ActionListener
best practice for gui and actionlistener
ActionListener best practices
Nested class vs implements ActionListener
Organize Code for GUI and ActionListener in Java
define button actions in inner class Vs define button actions in public class in swing
What is better to use: Action vs ActionListener?
I want that main should print hello (in a pop up dialogue box) everytime the button is clicked. So I designed the following program but it doesn't seem to work. The program compiles and executes just fine but when I click the button, I don't see the any dialogue box popping up. I have put in place the getter and setter of the boolean variable (button_clicked) whose value is being tested in the main() block.
Please help if you know..
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
class abc extends JFrame implements ActionListener{
boolean button_clicked = false;
JButton b1;
abc(){
this.setSize (400, 400);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.createUI();
}
void createUI(){
this.setLayout(null);
b1 = new JButton("x");
b1.setSize(110,30);
b1.setLocation(10,210);
this.add(b1);
b1.addActionListener(this);
}
public boolean isButton_clicked() {
return button_clicked;
}
public void setButton_clicked(boolean button_clicked) {
this.button_clicked = button_clicked;
}
public void actionPerformed(ActionEvent arg0) {
button_clicked = true;
//I don't want to print the dialogue box from here..
//I want to keep everything under main()'s control.
}
}
public class tempMain extends JFrame {
public static void main(String[] args) {
abc temp = new abc();
temp.setVisible(true);
while(true){
if(temp.isButton_clicked())
JOptionPane.showMessageDialog(null, "Hello");
}
}
}
Move the JOptionPane.showMessageDialog() call under the actionPerformed() method and delete the while() thing under the main method.
As has already been pointed out by a number of people, you approach and design are flawed. I won't go into further as it has already been dealt with, but as a suggestion you could try...
abc temp = new abc();
temp.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
JOptionPane.showMessageDialog(null, "Look Ma, I'm in Main...");
}
});
temp.setVisible(true);
And in your abc class...
class abc JFrame implements {
// build your class as normal
public void addActionListener(ActionListener listener) {
b1.addActionListener(listener);
}
}
You might want to read about "How to Write an Action Listener" to help you implement an ActionListner in your code.
The basic idea for your code would be to:
Declare an event handler class
Register an instance of the event handler class as a listener with your JButton
Include code that implements the methods in listener interface. In your case, you would over-ride actionPerformed() and write your logic over there to show a dialog box. "How to Make Dialogs" would be another useful tutorial for you.
As #Quinman pointed out, your code design is really flawed. I understand that you do not want to put the JOptionPane under the actionperformed method for some personal reason which I don't understand. Based on that premise, I think that the best solution would be to create a callback, that is, make your JFrame know the main class and call it when the button is clicked.
It is possible to make this mechanism independent of the main class. Please check the Observer design pattern in order to understand how to do that.
Your code also has another flaw - when the button is clicked I get infinite Hello messages. In order to avoid that, you should set the button_clicked variable to false. I only mention that as a general tip, for in truth you really should get rid of the busy wait that your while is causing.
You may be looking for a modeless dialog. In this example, the main panel, named Observer, listens to an ObservedPanel in a dialog. By using a PropertyChangeListener, any changes made to the dialog are immediately reflected in the main panel.
Judging from the comments you provided, you want to reuse your abc class (which has a very poor name and does not comply to the Java naming standards) for several different purposes, so you do not want to include the code which is executed when you press the button in the abc class.
There are multiple solutions for this (where the first is my preferred one)
Pass an Action in the constructor of your abc class and couple that Action to the JButton. This way the class which constructs the abc instance is responsible for the behavior when the button is pressed
make abc abstract and let your ActionListener call that abstract method. You can then make concrete implementations of this class each time you want different behavior.
Further notes on your code:
get rid of that while( true ) loop
get rid of the null layout and use a LayoutManager instead
Swing components should be created and accessed on the Event Dispatch Thread. Consult the Concurrency in Swing tutorial for more information
When developing Swing applications, I've typically defined a delegate interface for each UI component for action callbacks. For example, if there is a class, MyDialog, which contains a button, MyButton, then the ActionListener for MyButton will call MyDialog.Delegate.OnMyButtonClick(Event e). The UI component then becomes "dumb" and requires a controller to handle events as well as update the component itself.
I thought that by using the Swing Application Framework's #Actions, I could get around creating delegate interfaces and implementations by defining #Action methods in implementation classes, letting the ApplicationContext figure out what to call. Apparently, that is not the case, as I don't see any clear way of adding those classes into the ApplicationContext, nor do I see any examples out there of doing such a thing.
Has anyone managed to use SAF in this manner so that there is a clean separation between UI and UI action code?
I've discovered a good way to keep the UI separate from the behavior using #Actions.
First, create a UI Component, say a JPanel with a button and then give it a public method that can be used to set the action of the Button:
class CustomJPanel extends JPanel {
private JButton myButton;
public CustomJPanel() {
initializeComponents();
}
public void initializeComponents() {
myButton = new JButton();
}
public void setButtonAction(javax.swing.Action action)
{
myButton.setAction(action);
}
}
Next, create an Action class that will provide the logic for that button:
class CustomJPanelActions {
#Action
public void doSomething()
{
JOptionPane.showMessageDialog(null,"You pressed me!");
}
}
Finally, setup the application controller and during setup, assign the appropriate action to the appropriate UI:
class MyApp extends SingleFrameApplication {
private JFrame mainFrame;
private JLabel label;
#Override
protected void startup() {
getMainFrame().setTitle("BasicSingleFrameApp");
CustomJPanel panel = new CustomJPanel();
panel.setButtonAction(getContext().getActionMap(new CustomJPanelActions()).get("doSomething");
show(panel);
}
public static void main(String[] args) {
Application.launch(BasicFrameworkApp.class, args);
}
}
In this way, the UI is logically separated from the control (i.e. Action) and can be tested on its own. The controller can make any decisions it needs to in order to determine what Action set to use and which specific action to assign to the UI controls. That is, one can create a Test Action Set and a Live Action Set, etc.
This method of using SAF has worked rather well for me.
The SAF javadoc has some information on how to do this sort of thing in the doc for ActionManager#getActionMap