0

I am student working on a personal java project (read: NOT homework) with a large GUI component and attempting to follow a MVC architecture. I am building up a complicated view using many nested JPanels with various layouts. The actionListeners for buttons in these panels are/will be implemented in a controller class that contains a reference to the model and the view. Each panel only contains a reference to its immediate child panels,so as it stands right now when the controller needs to update something in one of the deeply nested panels, it requires code like this:

view.getCardLayoutPanel().getReceptionPanel().getOrderContentsPanel().updateTable(tableModel);

which seems less than ideal. Some more pseudoish code of the whole system in question is below. I have omitted simple get methods from the code here, but all of my GUI component classes include get methods that return the reference to the child panels that they directly own.

How can this situation be improved while still obeying MVC?

The controller:

public class Controller implements ActionListener {

    private View view;

    public Controller(View view, Model model){
        this.view = view;
        this.model = model;
    }       

    private void updateOrderTable() {

        TableModel tableModel; //= **call model method to query db and generate a TableModel**  
        // This looks bad
        view.getCardLayoutPanel().getReceptionPanel().getOrderContentsPanel().updateTable(tableModel);
    } 
}

The View:

public class View {
    // Setup stuff

    JFrame mainFrame = new JFrame();
    myCardLayoutPanel = new MyCardLayoutPanel ();\
    // add cardLayoutPanel to mainframe
}

Next layer:

public class MyCardLayoutPanel extends JPanel {

    receptionPanel = new ReceptionPanel();
    dataEntryPanel = new DataEntryPanel();
    // etc

    // Add all the panels to the layout
}

One more intermediate panel, which I will omit, follows the same pattern...and finally, the JPanel with the table in it.

public class OrderContentsPanel extends JPanel {

    private JTable table;
    private JScrollPane scrollpane;

    public OrderContentsPanel() {
        setLayout(new BorderLayout());
        scrollPane = newJScrollPane(table);
        add("Center", scrollPane) //
    }

    public void updateTable(TableModel tablemodel) {
        table.setModel(tableModel);
    }
}
Doug
  • 140
  • 10
  • After more digging, I found ahawtho's answer to this question http://stackoverflow.com/questions/9338020/how-should-child-gui-components-access-their-parents-with-mvc which seems promising. Still interested in any other ideas / thoughts on this one. – Doug Jun 30 '16 at 19:18
  • [`Action`](https://docs.oracle.com/javase/tutorial/uiswing/misc/action.html), examined [here](http://stackoverflow.com/a/37886783/230513), is a common way to encapsulate functionality in the manner cited. More on Swing controllers [here](http://stackoverflow.com/q/25502552/230513). – trashgod Jun 30 '16 at 21:54

1 Answers1

2

Possible it's helpful to enrich your panels and the model with application-level events.

For example, consider your application has an OrderEntryPanel that produces an event every time a user enters an order, and an ArrivedOrdersPanel that consumes the event of an arriving order. The inner structure of these panels and how they are visually arranged relative to each other is not of interest to the controllers. Instead, the controllers will focus on transferring the events from the panels to the model and vice versa.

OrderEntryPanel would look like this:

public class OrderEntryPanel extends JPanel {
    private final JTextField orderTextField = new JTextField(20);
    private final JButton sendButton = new JButton("Send order");

    private Consumer<String> consumer;

    public OrderEntryPanel() {
        add(new JLabel("Place your order here: "));
        add(orderTextField);
        add(sendButton);

        sendButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                consumer.accept(orderTextField.getText());
                orderTextField.setText("");
            }
        });
    }

    public void setOrderEntryConsumer(Consumer<String> consumer) {
        this.consumer = consumer;
    }
}

Similarly for ArrivedOrdersPanel:

public class ArrivedOrdersPanel extends JPanel {

    private JTable table = new JTable();
    private DefaultTableModel tableModel = new DefaultTableModel(0, 2);

    public ArrivedOrdersPanel() {
        setLayout(new BorderLayout());
        JScrollPane scrollPane = new JScrollPane(table);
        add("Center", scrollPane);

        table.setModel(tableModel);
    }

    public void orderArrived(String orderText) {
        Integer orderNumber = Integer.valueOf(tableModel.getRowCount()+1);
        tableModel.addRow(new Object[] {orderNumber, orderText});
    }
}

A View class creates and initializes them and provides getter methods to them (I'll leave out the details of this one; it shouldn't contribute much to the idea.)

The model (here, an OrderStore) consumes the event of a user having entered an order and in turn produces the event of a newly arrived order:

public class OrderStore {
    private final List<Order> orders = new LinkedList<>();

    private Consumer<Order> orderArrivedConsumer;

    public void incomingOrder(String orderText) {
        Order order = new Order(orderText);

        orders.add(order);

        orderArrivedConsumer.accept(order);
    }

    public void setOrderArrivedConsumer(Consumer<Order> consumer) {
        this.orderArrivedConsumer = consumer;
    }
}

Finally, we can connect model and views using simple lambdas:

public static void main(String[] args) throws IOException {
    View view = new View();
    view.init();

    OrderStore model = new OrderStore();

    // In order not to dilute the essential point, the consumers do not switch execution control
    // from Swing's event dispatcher thread to a background thread and vice versa

    view.getOrderEntryPanel().setOrderEntryConsumer(
            orderText -> model.incomingOrder(orderText));

    model.setOrderArrivedConsumer(
            order -> view.getArrivedOrdersPanel().orderArrived(order.getOrderText()));
}

So the complexity of the UI is separated from coupling the UI to the model.