0

I'm trying to separate the logic from Jframe in a different class under the same package. But when I add functions for Jframe conponents such as a button it's added in the Jframe file. And I can't access my objects in my logic file from there. What's the correct way to separate logic and graphic?

7
  • Start by taking a look at Model-View-Controller, but basically you're on the right track. Instead of providing direct access to the components consider provide getters (and setters) which provide access to their state. You could use a interface as a contract between the controller and the view to describe what the controller can get/do to the view as well as provide some event notification so the view can notify the controller when something important happens Commented Mar 1, 2016 at 23:04
  • Swing itself is a type of MVC, trying to force a pure MVC onto is just asking for headaches, instead, you should focus on wrapping an MVC around it's existing framework Commented Mar 1, 2016 at 23:04
  • For example, example, example Commented Mar 1, 2016 at 23:07
  • @MadProgrammer Could you be more specific? Let's give it a context. Say I have only one object Data containing an int, a Jframe contains a button which I want it to add 1 to Data. How should I set up the system? Commented Mar 1, 2016 at 23:15
  • Well, that describes a model, when the button is clicked it needs to call the models add method, which performs the physical actions. This way, when you want to change the logic you can just change the model, the rest will continue to work Commented Mar 1, 2016 at 23:17

1 Answer 1

0

Could you be more specific? Let's give it a context. Say I have only one object Data containing an int, a Jframe contains a button which I want it to add 1 to Data. How should I set up the system?

This basically describes a model, the model is responsible for controlling the logic and providing the required functionality for other interested parties to work with it

So you might start with a simple contract...

public interface DataModel {
    public void add();
    public int getData();
}

You I would then create an abstract version of the model which does most of the boiler plate work...

public abstract class AbstractDataModel implements DataModel {

    private int data;

    public AbstractDataModel(int value) {
        this.data = value;
    }

    public void add(int delta) {
        data += delta;
    }

    @Override
    public int getData() {
        return data;
    }
}

Which then allows me to create simple concrete implementations...

public class AddByOneDataModel extends AbstractDataModel {

    public AddByOneDataModel(int value) {
        super(value);
    }

    @Override
    public void add() {
        add(1);
    }
}

or if you wanted to be really lazy, you could just do...

public class DeltaDataModel extends AbstractDataModel {

    private int delta;

    public DeltaDataModel(int delta, int value) {
        super(value);
        this.delta = delta;
    }

    @Override
    public void add() {
        add(delta);
    }

}

but, so far, the UI's not be involved in any of it, it doesn't care, it will only want an instance of DataModel

Then your UI might look something like...

public class TestPane extends JPanel {

    private DataModel model;
    private JButton add;

    public TestPane() {
        //...
        add = new JButton("Add");
        add.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                getModel().add();
                int data = getModel().getData();
                // Update the UI in some meaningful way...
            }
        });
        //...
    }

    public void setModel(DataModel model) {
        this.model = model;
    }

    public DataModel getModel() {
        return model;
    }

}

This would then allow you to do things like...

TestPane pane = new TestPane();
pane.setModel(new DeltaDataModel(0, 100));

which allows you to specify the actual functionality of the model you want to use (because who knows what you'll want to do in the future)

But I'm still a bit confused how the View and Controller part seperate from each other. Can you explain a bit still based on the example above? Also, I saw MVC are 3 objects so the main method is in none of the 3, am I correct?

As explained in How MVC work with java swing GUI, Java and GUI - Where do ActionListeners belong according to MVC pattern?, Listener Placement Adhering to the Traditional (non-mediator) MVC Pattern and verious other answers on the subject, Swing is an implementation of MVC, albit more like M-VC, where the components are self contained views and controllers and the model is dynamic

This makes it difficult to try and wrap a more traditional MVC around. Instead, we work with the concept of a view been a series of components contained with a container which then conforms to some specified contract.

In a more traditional MVC, the model and the view don't interact with each, they don't know anything about each other and instead the controller maintains the relationship.

Model...

Let's go back and update our model. In order to facilitate the MVC, we need to provide an Observer Pattern to it, so it can trigger notifications when the model is updated (because the model could be updated indepdently of the controller or view)

public abstract class AbstractDataModel implements DataModel {

    private List<ChangeListener> changeListeners;
    private int data;

    public AbstractDataModel(int value) {
        this.data = value;
        changeListeners = new ArrayList<>(25);
    }

    @Override
    public void addChangeListener(ChangeListener listener) {
        changeListeners.add(listener);
    }

    @Override
    public void removeChangeListener(ChangeListener listener) {
        changeListeners.remove(listener);
    }

    protected void fireStateChanged() {
        if (!changeListeners.isEmpty()) {
            ChangeEvent evt = new ChangeEvent(this);
            for (ChangeListener listener : changeListeners) {
                listener.stateChanged(evt);
            }
        }
    }

    public void add(int delta) {
        data += delta;
        fireStateChanged();
    }

    @Override
    public int getData() {
        return data;
    }
}

public class DeltaDataModel extends AbstractDataModel {

    private int delta;

    public DeltaDataModel(int value, int delta) {
        super(value);
        this.delta = delta;
    }

    @Override
    public void add() {
        add(delta);
    }

}

public class AddByOneDataModel extends DeltaDataModel {

    public AddByOneDataModel(int value) {
        super(value, 1);
    }
}

View...

Next, let's have a look at the view. First we define the contract for the view, this ensures that the controller can only do what what the contract says it can.

public interface AddView {
    public static final String ADD_ACTION_COMMAND = "Action.add";
    public void setData(int data);
    public void addActionListener(ActionListener listener);
    public void removeActionListener(ActionListener listener);
}

nb: I might also be tempted to add a getComponent method which returns the actual JComponent which the implementation actually uses, but that will come down to what you want to do and is demonstrated in some of the other links

And a physical implementation...

public class AddViewPane extends JPanel implements AddView {

    private JButton btn;
    private JLabel label;

    public AddViewPane() {
        setLayout(new GridBagLayout());
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.gridwidth = GridBagConstraints.REMAINDER;

        btn = new JButton("Add");
        label = new JLabel("...");

        add(btn, gbc);
        add(label, gbc);

        btn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                fireAddAction();
            }
        });
    }

    @Override
    public void setData(int data) {
        label.setText(NumberFormat.getNumberInstance().format(data));
    }

    @Override
    public void addActionListener(ActionListener listener) {
        listenerList.add(ActionListener.class, listener);
    }

    @Override
    public void removeActionListener(ActionListener listener) {
        listenerList.remove(ActionListener.class, listener);
    }

    protected void fireAddAction() {
        ActionListener[] listeners = listenerList.getListeners(ActionListener.class);
        if (listeners.length > 0) {
            ActionEvent evt = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, ADD_ACTION_COMMAND);
            for (ActionListener listener : listeners) {
                listener.actionPerformed(evt);
            }
        }
    }

}

Controller...

Again, starting at the lowest level and building the funcitonality up

public interface AddController {
    public DataModel getModel();
    public AddView getView();
}

public class AbstractAddController implements AddController {

    private AddView view;
    private DataModel model;

    public AbstractAddController(AddView view, DataModel model) {
        this.view = view;
        this.model = model;

        view.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                getModel().add();
            }
        });

        model.addChangeListener(new ChangeListener() {
            @Override
            public void stateChanged(ChangeEvent e) {
                getView().setData(getModel().getData());
            }
        });
    }

    @Override
    public DataModel getModel() {
        return model;
    }

    @Override
    public AddView getView() {
        return view;
    }

}

public class DefaultAddController extends AbstractAddController {

    public DefaultAddController(AddView view, DataModel model) {
        super(view, model);
    }

}

Putting it together...

And finally, you might be able to use something like...

EventQueue.invokeLater(new Runnable() {
    @Override
    public void run() {
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
            ex.printStackTrace();
        }

        DataModel model = new AddByOneDataModel(0);
        AddViewPane view = new AddViewPane();
        DefaultAddController controller = new DefaultAddController(view, model);

        JFrame frame = new JFrame("Testing");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        // This is where having a getComponent method in
        // view interface would be helpful      
        frame.add(view);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
});

to put it all together

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.