1

I've written two Swing GUI: one with single column and one with two columns. Let me show you screen of the first app.

enter image description here

I also wrote an app with two columns mode. Here's screenshot:

enter image description here

Problem

I'd like to combine these two GUI into one applications. The second application is based on GroupLayout; I find the code clean as there is virtually no hand-made positioning of components. However, many tutorials suggest that GroupLayout is not the right layout without pointing which layout would be better, thought.

Here's the code of my first app:

public static void main(final String[] args) {
       SwingUtilities.invokeLater(Mvp::createGui);
    }

    private static void createGui() {
        final JFrame frame = new JFrame("Page labels app");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        final JPanel panel = new JPanel(new GridLayout(2, 1));
        final JPanel tablePanel = createTablePanel();
        panel.add(tablePanel);

        frame.add(panel, BorderLayout.CENTER);

        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    private static JPanel createTablePanel() {
        final PageLabelsTable table = new PageLabelsTable();
        final JScrollPane scrollPane = new JScrollPane(table);
        scrollPane.setPreferredSize(new Dimension(400, 120));
        final JPanel tablePanel = new JPanel();
        tablePanel.add(scrollPane);
        tablePanel.setBorder(BorderFactory.createTitledBorder("Page styles"));
        return tablePanel;
    }

public class PageLabelsTable extends JTable {

    private final DefaultTableModel tableModel;

    public PageLabelsTable() {

        tableModel = new DefaultTableModel();

        tableModel.addColumn("Style");
        tableModel.addColumn("Prefix");
        tableModel.addColumn("From");
        tableModel.addColumn("To");

        tableModel.addRow(
                new String[]{"Table A 00", "Table A 01", "Table A 02", "Table A 03"});

        tableModel.addRow(
                new String[]{"Table A 10", "Table A 11", "Table A 12", "Table A 13"});

        setModel(tableModel);

        Toolkit.getDefaultToolkit().addAWTEventListener(event -> {
            if (event.getID() == MouseEvent.MOUSE_CLICKED) {
                final MouseEvent mouseEvent = (MouseEvent) event;
                final int row = rowAtPoint(mouseEvent.getPoint());
                if (row == -1) {
                    clearSelection();
                }
            }
        }, AWTEvent.MOUSE_EVENT_MASK);

        getTableHeader().setReorderingAllowed(false);
        setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
        setDragEnabled(true);
    }
}

And here's the code for the second app (heavily based on the code found here at SO):

public void createUI() {

      initUI();

      final JFrame frame = new JFrame(APP_NAME);
      frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
      frame.setResizable(false);

      closeOnEscape(frame);
      frame.getRootPane().setDefaultButton(fixButton);

      frame.setContentPane(ui);
      frame.pack();
      frame.setLocationRelativeTo(null);
      frame.setMinimumSize(frame.getSize());

      frame.setVisible(true);
   }

   private void closeOnEscape(final JFrame frame) {
      // close on ESCAPE
      frame.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
          .put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "Cancel");

      frame.getRootPane().getActionMap().put("Cancel", disposeFrame(frame));
   }

   private AbstractAction disposeFrame(final JFrame frame) {
      return new AbstractAction() {
         @Override
         public void actionPerformed(final ActionEvent e) {
            frame.dispose();
         }
      };
   }

   private JComponent getTwoColumnLayout(final JLabel[] labels, final JComponent[] fields) {

      if ( labels.length != fields.length ) {
         throw new IllegalArgumentException(
             "%d labels supplied for %d fields!".formatted(labels.length, fields.length));
      }
      final JComponent panel = new JPanel();
      final GroupLayout layout = new GroupLayout(panel);
      panel.setLayout(layout);
      // Turn on automatically adding gaps between components
      layout.setAutoCreateGaps(true);
      // Create a sequential group for the horizontal axis.
      final GroupLayout.SequentialGroup hGroup = layout.createSequentialGroup();
      final GroupLayout.Group yLabelGroup = layout.createParallelGroup(
          GroupLayout.Alignment.TRAILING);
      hGroup.addGroup(yLabelGroup);
      final GroupLayout.Group yFieldGroup = layout.createParallelGroup();
      hGroup.addGroup(yFieldGroup);
      layout.setHorizontalGroup(hGroup);
      // Create a sequential group for the vertical axis.
      final GroupLayout.SequentialGroup vGroup = layout.createSequentialGroup();
      layout.setVerticalGroup(vGroup);

      // add the components to the groups
      for (JLabel label : labels) {
         yLabelGroup.addComponent(label);
      }
      for (Component field : fields) {
         yFieldGroup.addComponent(field, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE);
      }
      for (int i = 0; i < labels.length; i++) {
         vGroup.addGroup(layout.createParallelGroup().addComponent(labels[i])
             .addComponent(fields[i], GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE));
      }

      addMnemonics(labels, fields);

      return panel;
   }

   private void addMnemonics(final JLabel[] labels, final JComponent[] fields) {
      final Map<Character, Character> map = new HashMap<>();
      for (int i = 0; i < labels.length; i++) {
         labels[i].setLabelFor(fields[i]);
         final String lowerCase = labels[i].getText().toLowerCase();
         for (int j = 0; j < lowerCase.length(); j++) {
            final char ch = lowerCase.charAt(j);
            if ( Character.isLetterOrDigit(ch) && map.get(ch) == null ) {
               map.put(ch, ch);
               labels[i].setDisplayedMnemonic(ch);
               break;
            }
         }
      }
   }

   /**
    * Provides a JPanel with two columns (labels & fields) laid out using GroupLayout. The arrays
    * must be of equal size.
    *
    * @param labelStrings Strings that will be used for labels.
    * @param fields       The corresponding fields.
    * @return JComponent A JPanel with two columns of the components provided.
    */
   private JComponent getTwoColumnLayout(final String[] labelStrings, final JComponent[] fields) {
      final JLabel[] labels = new JLabel[labelStrings.length];
      for (int i = 0; i < labels.length; i++) {
         labels[i] = new JLabel(labelStrings[i]);
      }
      return getTwoColumnLayout(labels, fields);
   }


   private void initUI() {
      if ( ui != null ) {
         return;
      }
      ui = new JPanel(new BorderLayout(4, 4));
      ui.setBorder(new EmptyBorder(4, 4, 4, 4));

      // Here is our control.  This puts a titled border around it,
      // instead of using a label in the PAGE_START
      final JPanel jPanel = new JPanel(new BorderLayout());
      ui.add(jPanel);

      final JPanel actionPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 15, 15));
      jPanel.add(actionPanel, BorderLayout.PAGE_END);

      this.fixButton = new JButton("Fix");
      this.resetButton = new JButton("Reset");
      this.cancelButton = new JButton("Cancel");

      Stream.of(fixButton, resetButton, cancelButton).forEach(actionPanel::add);

      // Use GroupLayout for the label/cotrnl combos.
      // make the arrays for the factory method
      final String[] labels = { "Page label style", "Start page", "Remove bookmarks" };
      final String[] ccString = LabelStyle.getAllStyles();

      final JPanel checkBoxPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0));
      checkBoxPanel.add(new JCheckBox("", false));

      final JComponent[] controls = { new JComboBox<>(ccString), new JTextFieldNumeric(5),
          checkBoxPanel };

      jPanel.add(getTwoColumnLayout(labels, controls));

      // throw in a few borders for white space
      final Border insideBorder = new CompoundBorder(new EmptyBorder(10, 10, 5, 10),
          new TitledBorder(APP_NAME));
      final Border border = new CompoundBorder(insideBorder, new EmptyBorder(10, 10, 5, 10));
      jPanel.setBorder(border);
   }
menteith
  • 596
  • 14
  • 51
  • 2
    The better layout for the two-column `JPanel` is a `GridBagLayout`. – Gilbert Le Blanc Jul 08 '23 at 21:21
  • Some `GroupLayout` advantages are illustrated [here](https://stackoverflow.com/a/8504753/230513). – trashgod Jul 08 '23 at 23:34
  • you need to create a new class and copy both codes into that class. then remove one main method. create a new panel with flowLayout. add two panels to it. to use both guis. but as I see you are very new to swing. So you can try an IDE like Netbeans or IntelJ to create GUI easily. – samgi Jul 09 '23 at 12:49

1 Answers1

0

You can combine (as commented) both applications into one and add them to a JPanel with a FlowLayout.
For instance, you can define a new class named CombinedApplication that includes the logic from both of your previous applications, and include a createUI method to set up the main UI. Each section of the UI will get their separate methods.

Something like this code (I had to remove references to LabelStyle.getAllStyles or JTextFieldNumeric, which might be part of the codenameone/CodenameOne project).

package com.tutorialspoint.gui;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.table.DefaultTableModel;
import javax.swing.border.*;
import java.util.stream.Stream;
// import com.codename1.ui.*;
// import com.codename1.ui.plaf.*;


public class CombinedApplication {
    private DefaultTableModel tableModel;
    private JButton fixButton;
    private JButton resetButton;
    private JButton cancelButton;
    private JPanel ui;
    private static final String APP_NAME = "Your App Name";

    public CombinedApplication() {
        // Initialization logic from your first application.
        tableModel = new DefaultTableModel();
        tableModel.addColumn("Style");
        tableModel.addColumn("Prefix");
        tableModel.addColumn("From");
        tableModel.addColumn("To");
        tableModel.addRow(
                new String[]{"Table A 00", "Table A 01", "Table A 02", "Table A 03"});
        tableModel.addRow(
                new String[]{"Table A 10", "Table A 11", "Table A 12", "Table A 13"});
    }

    public void createUI() {
        // Create the main JFrame.
        JFrame frame = new JFrame("Combined Application");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // Create the first and second application sections.
        JPanel firstAppSection = createFirstAppSection();
        JPanel secondAppSection = createSecondAppSection();

        // Create a panel with FlowLayout and add both sections.
        JPanel mainPanel = new JPanel(new FlowLayout());
        mainPanel.add(firstAppSection);
        mainPanel.add(secondAppSection);

        // Add the main panel to the frame and set it visible.
        frame.getContentPane().add(mainPanel);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    private JPanel createFirstAppSection() {
        final JPanel panel = new JPanel(new GridLayout(2, 1));
        final JPanel tablePanel = createTablePanel();
        panel.add(tablePanel);

        return panel;
    }

    private JPanel createTablePanel() {
        final JTable table = new JTable();
        table.setModel(tableModel);

        final JScrollPane scrollPane = new JScrollPane(table);
        scrollPane.setPreferredSize(new Dimension(400, 120));
        final JPanel tablePanel = new JPanel();
        tablePanel.add(scrollPane);
        tablePanel.setBorder(BorderFactory.createTitledBorder("Page styles"));
        return tablePanel;
    }

    private JPanel createSecondAppSection() {
        // Copy the code from your second application that initializes the UI,
        // but use getTwoColumnLayout for the two-column layout.

        // Initialization logic for your second application.

        ui = new JPanel(new BorderLayout(4, 4));
        ui.setBorder(new EmptyBorder(4, 4, 4, 4));

        final JPanel jPanel = new JPanel(new BorderLayout());
        ui.add(jPanel);

        final JPanel actionPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 15, 15));
        jPanel.add(actionPanel, BorderLayout.PAGE_END);

        this.fixButton = new JButton("Fix");
        this.resetButton = new JButton("Reset");
        this.cancelButton = new JButton("Cancel");

        Stream.of(fixButton, resetButton, cancelButton).forEach(actionPanel::add);

        final String[] labels = { "Page label style", "Start page", "Remove bookmarks" };
        final String[] ccString = labels;

        final JPanel checkBoxPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0));
        checkBoxPanel.add(new JCheckBox("", false));

        final JComponent[] controls = { new JComboBox<>(ccString), new JTextField(5),
            checkBoxPanel };

        jPanel.add(getTwoColumnLayout(labels, controls));

        final Border insideBorder = new CompoundBorder(new EmptyBorder(10, 10, 5, 10),
            new TitledBorder(APP_NAME));
        final Border border = new CompoundBorder(insideBorder, new EmptyBorder(10, 10, 5, 10));
        jPanel.setBorder(border);

        // Add mnemonics to buttons
        addMnemonics();

        return ui;
    }

    private void addMnemonics() {
        // Copy the code from your second application that sets the mnemonics.
    }

    private JComponent getTwoColumnLayout(final String[] labelStrings, final JComponent[] fields) {
        if (labelStrings.length != fields.length) {
            throw new IllegalArgumentException(
                    "Different number of labels and fields!");
        }

        final JPanel panel = new JPanel(new GridBagLayout());
        final GridBagConstraints gbc = new GridBagConstraints();
        gbc.fill = GridBagConstraints.HORIZONTAL;

        for (int i = 0; i < labelStrings.length; i++) {
            gbc.gridx = 0;
            gbc.gridy = i;
            gbc.weightx = 0.5;
            panel.add(new JLabel(labelStrings[i]), gbc);

            gbc.gridx = 1;
            panel.add(fields[i], gbc);
        }

        return panel;
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> new CombinedApplication().createUI());
    }
}

The CombinedApplication class has a createUI method that sets up the main UI of the combined application. It creates a JPanel with a FlowLayout, adds both application sections to it, and sets this panel as the content pane of the main JFrame.

The createFirstAppSection and createSecondAppSection methods return JPanel instances that represent the UI sections of your first and second applications, respectively.

  • The createFirstAppSection method should include the logic from your first application that creates the single-column table and adds it to a JPanel, and
  • the createSecondAppSection method should include the logic from your second application that creates the two-column layout and adds it to a JPanel.

That way, both of your applications will be displayed side by side in the same window, thanks to the FlowLayout of the main panel.

The GridBagLayout and a GridBagConstraints object set the constraints for each component. The fill field is set to GridBagConstraints.HORIZONTAL to allow components to fill their cells horizontally. The weightx field is set to 0.5 to distribute extra horizontal space evenly between the two columns.

For each label and field, the gridx and gridy fields are set to specify the cell in the grid where the component should be placed. For labels, gridx is 0 (the first column) and for fields, gridx is 1 (the second column). The gridy value is increased for each new row.

The addMnemonics method is used the same as in your original code.

The result should be a two-column layout, similar to the one created with GroupLayout. The specifics of the layout can be adjusted by changing the GridBagConstraints. For example, you can set insets to add padding around components, ipadx and ipady to set the internal padding of the components, and anchor to specify where in the cell the component should be placed if it does not fill the cell entirely.


You wrote both of your applications will be displayed side by side in the same window, thanks to the FlowLayout of the main panel.
How do I display my application one below the other? Which layout should I choose?

If you want to display your applications one below the other, you should use the BoxLayout layout manager with a BoxLayout.PAGE_AXIS or BoxLayout.Y_AXIS orientation. That layout manager will arrange your components in a single column, top to bottom.

Here is how you can modify the main panel to use a BoxLayout:

// main panel with BoxLayout
JPanel mainPanel = new JPanel();
BoxLayout boxlayout = new BoxLayout(mainPanel, BoxLayout.Y_AXIS);
mainPanel.setLayout(boxlayout);
mainPanel.add(app1Panel);
mainPanel.add(app2Panel);

Just replace the creation of the mainPanel in your combined application with this code, and the two applications should appear one below the other. Note that the BoxLayout constructor takes two parameters: the container that it is applied to, and the axis along which the components will be arranged.


How do I shorten the vertical space between two applications (one above, one below)?

You can control the vertical space between components in a BoxLayout using a Border to the panels or directly inserting a rigid area between them.

Option 1: Using Borders

You can add an empty border to the bottom of the first application panel and the top of the second one to decrease the space between them.
As an example:

app1Panel.setBorder(BorderFactory.createEmptyBorder(0, 0, 5, 0)); // add a 5-pixel padding to the bottom
app2Panel.setBorder(BorderFactory.createEmptyBorder(5, 0, 0, 0)); // add a 5-pixel padding to the top

Option 2: Inserting a Rigid Area

You can also use Box.createRigidArea(Dimension d) to add a fixed space between the two panels.
For instance, to create a space of 5 pixels, you can do:

mainPanel.add(Box.createRigidArea(new Dimension(0, 5)));

Make sure to add this line after adding app1Panel and before adding app2Panel to mainPanel. You can adjust the height in the Dimension argument to control the spacing.

These approaches give you fine control over the spacing between components, allowing you to achieve the precise layout you want.


I'm missing something. I've changed the code to what follows but I still have a huge gap.

final JPanel app1Panel = createFirstAppSection();
final JPanel app2Panel = createSecondAppSection();
// main panel with BoxLayout
final JPanel mainPanel = new JPanel();
final BoxLayout boxlayout = new BoxLayout(mainPanel, BoxLayout.Y_AXIS);
mainPanel.setLayout(boxlayout);
mainPanel.add(app1Panel);
mainPanel.add(Box.createRigidArea(new Dimension(0, 5)));
mainPanel.add(app2Panel);

First, make sure that the panels app1Panel and app2Panel do not have any extra space or padding that could cause the large gap. If these panels use a layout manager, ensure that the layout is not adding any extra space.

And make sure the BoxLayout is not stretching the components. By default, BoxLayout will stretch components to fill the available space in the box's axis. If your app1Panel or app2Panel contains fewer components or uses a layout that does not use all the available space, it could be causing the gap. In this case, consider setting the maximum size of your components:

app1Panel.setMaximumSize(app1Panel.getPreferredSize());
app2Panel.setMaximumSize(app2Panel.getPreferredSize());

Before adding them to the mainPanel.

If the above two points do not seem to be the cause, it might be the way the mainPanel is being added to its parent component or how the parent component's layout is set up. If the parent component of mainPanel is set up with a layout manager that stretches its child components, it could cause the mainPanel (and all its child components) to be stretched, creating the gap.

Consider also using debugging methods to isolate the problem. One common method is setting the background color of different components to see exactly where the extra space is. That can help you identify whether the problem is in app1Panel, app2Panel, or mainPanel.

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • That's very helpful, thanks! You wrote `both of your applications will be displayed side by side in the same window, thanks to the FlowLayout of the main panel`. How do I display my application one below the other? Which layout should I choose? – menteith Jul 11 '23 at 18:20
  • @menteith I have edited the answer to address your comment / question. – VonC Jul 11 '23 at 18:42
  • Thanks, this answers my question! – menteith Jul 11 '23 at 19:28
  • I will award you with a bounty as soon as SO allows me to to so. Last question: how do I shorten the space between two applications? https://imgur.com/a/01EBVLe – menteith Jul 11 '23 at 19:49
  • @menteith No problem. I have edited the answer to address your comment/question. – VonC Jul 11 '23 at 20:27
  • I'm missing something. I've changed the code to what follows but I still have a huge gap. `final JPanel app1Panel = createFirstAppSection(); final JPanel app2Panel = createSecondAppSection(); // main panel with BoxLayout final JPanel mainPanel = new JPanel(); final BoxLayout boxlayout = new BoxLayout(mainPanel, BoxLayout.Y_AXIS); mainPanel.setLayout(boxlayout); mainPanel.add(app1Panel); mainPanel.add(Box.createRigidArea(new Dimension(0, 5))); mainPanel.add(app2Panel);`. – menteith Jul 12 '23 at 07:55
  • @menteith OK. I have edited the answer to address your comment. – VonC Jul 12 '23 at 08:40
  • Many thanks! With your input and comments I'm going to do the debugging on my own. – menteith Jul 12 '23 at 09:07