4

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.

Kristián Filo
  • 827
  • 2
  • 8
  • 25
  • Do not implement you listener as an anonymous inner class. Do it exactly as you say you want. Like in http://stackoverflow.com/a/4985539/2055998 – PM 77-1 Apr 10 '15 at 19:03
  • Looks like you're creating a Swing application, so [this thread](http://stackoverflow.com/questions/3066590/gui-not-working-after-rewriting-to-mvc) should give some ideas on proper separation of concerns. – Mick Mnemonic Apr 10 '15 at 19:10
  • @TinoArts just wondering to know whether you have worked with angularJS ? – Kick Buttowski Apr 10 '15 at 19:16
  • Also, an important component of a layered architecture is [dependency injection](http://en.m.wikipedia.org/wiki/Dependency_injection). Basically this means that you shouldn't instantiate models or services from within your controller / GUI class. – Mick Mnemonic Apr 10 '15 at 19:17

6 Answers6

3

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.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • bascially Java does not support MVC design pattern very well? – Kick Buttowski Apr 10 '15 at 19:14
  • don't mix Swing with Java – notanormie Apr 10 '15 at 19:24
  • @KickButtowski, I don't see how my answer supports such a conclusion. If you want to find fault, however, then it seems you must be picking on Swing in particular, not Java in general. – John Bollinger Apr 10 '15 at 19:24
  • @JohnBollinger no I did not mean to find fault in your awesome answer which I have already voted for at all. I am studying angularJS which support MVC and I found some connections between your answer and what I have learned so far. Just try to understand better that is all :) – Kick Buttowski Apr 10 '15 at 19:30
  • @notanormie I always thought Swing is part of Java? – Kick Buttowski Apr 10 '15 at 19:30
  • 1
    @KickButtowski It is but there is also JavaFX which is supposedly it's successor and supports MVC better. – notanormie Apr 10 '15 at 19:43
  • 1
    @KickButtowski, rather than Swing, [JavaFX](http://docs.oracle.com/javase/8/javase-clienttechnologies.htm) is probably more comparable with AngularJS. JavaFX provides capabilities for declarative markup (via FXML) and binding capabilities. If paired with [afterburner.fx](http://afterburner.adam-bien.com) then you have a MVP style model with dependency injection. So, a bit similar to Angular, but still different. Both Swing and JavaFX are part of the Oracle Java Runtime distribution. – jewelsea Apr 10 '15 at 19:48
  • thank you guys. I have worked a little with JavaFX but I did not know it support MVC or MVP. I defiantly take a look at it deeply. – Kick Buttowski Apr 10 '15 at 19:51
1

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());
JFPicard
  • 5,029
  • 3
  • 19
  • 43
1

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).

notanormie
  • 435
  • 5
  • 20
  • Better yet do: `SwingUtilites.invokeLater(new Runnable() { logicClass.addEmployeeToFirm(); })` – TameHog Apr 10 '15 at 19:22
  • This will still be done on EDT thread. If it was heavy lifting it shouldn't be done on EDT at all but I think this isn't focus of this question. – notanormie Apr 10 '15 at 19:25
1

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);

    }
}
Soley
  • 1,716
  • 1
  • 19
  • 33
1

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.

yngwietiger
  • 1,044
  • 1
  • 11
  • 15
0

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());
ControlAltDel
  • 33,923
  • 10
  • 53
  • 80