2

I'm a second year CS student (on winter break) and I'm trying to teach myself Swing. Ultimately, this is just a project to add to my portfolio of code and to reinforce what I learned in Data Structures & Algorithms over the semester.

However, diving into Swing, I have hit a blockade (perhaps biting off a little too much at once). I am stuck on trying to get my two JLabels: heading & dets, to appear underneath each other in the JPanel tbdItem.

I've searched around and tried a solution here (the solution using GridLayout()) but that didn't work either.

If you check my code bellow, I believe I have correctly used the FlowLayout() layout manager, but in actuality the output looks like it's ignoring my JPanel and is instead using the layout manager of my JFrame? An image of (wrong) output here:

http://i.imgur.com/k6RrYHU.png

Here is my code for reference. I really don't understand why it isn't working as the JPanel I'm inserting into is a FlowLayout():

    super("To Be Done");
    setLocationRelativeTo(null); //Center window on open
    setSize(WIDTH, HEIGHT);
    setDefaultCloseOperation( JFrame.DO_NOTHING_ON_CLOSE );
    getContentPane().setBackground(Color.LIGHT_GRAY);
    addWindowListener( new CheckOnExit() );
    setLayout( new BorderLayout() );

    JPanel buttonPanel = new JPanel();
    buttonPanel.setBackground(Color.LIGHT_GRAY);

    JButton newTask = new JButton("New Task");
    newTask.addActionListener( new NewTask() );
    newTask.setPreferredSize(new Dimension(130, 40));
    buttonPanel.add(newTask);

    JButton editTask = new JButton("Edit Task");
    editTask.addActionListener( new EditTask() );
    editTask.setPreferredSize(new Dimension(130, 40));
    buttonPanel.add(editTask);

    JButton deleteTask = new JButton("Delete Task");
    deleteTask.addActionListener( new DeleteTask() );
    deleteTask.setPreferredSize(new Dimension(130, 40));
    buttonPanel.add(deleteTask);

    add(buttonPanel, BorderLayout.SOUTH);


    /* TBD Box to display matters TBD */
    JPanel tbdPanel = new JPanel( new FlowLayout() );


    JPanel tbdItem = new JPanel( new FlowLayout() ); //

    JLabel heading = new JLabel("Heading");
    JLabel dets = new JLabel("The Body of the text goes here");

    // Make heading bold -- find out if there's a better way to do this
    Font font = heading.getFont();
    Font boldFont = new Font(font.getFontName(), Font.BOLD, font.getSize());
    heading.setFont(boldFont);      

    tbdItem.add(heading);   
    tbdItem.add(dets);      
    tbdPanel.add(tbdItem);

    add( tbdPanel, BorderLayout.CENTER ); // Add TBD Panel to CENTER of JFrame

Many thanks. And I apologise if the solutions is on this site somewhere. I honestly couldn't find it if it is.

Community
  • 1
  • 1
destro
  • 35
  • 4
  • See [Should I avoid the use of set(Preferred|Maximum|Minimum)Size methods in Java Swing?](http://stackoverflow.com/q/7229226/418556) (Yes.) – Andrew Thompson Jul 10 '14 at 11:46
  • what layout did you expect? heading on top and label on bottom? in that case you should use a BorderLayout in the internal JPanel – mxb Jul 10 '14 at 11:50

4 Answers4

2

The behaviour you describing as not natural to FlowLayout is indeed, the actual way how FlowLayout works. By default FlowLayout adds component right in the top middle of the Container, as you try to add more components, it shifts the previous added components to the left side, but as the row is filled, it move the new components to the next row, again starting the whole procedure from the middle of the next row.

How you want to make this use case work, is by using a nested layout

Some points to remember, before venturing into the world of Swing:

  1. Try not to use setXxXSize() methods randomly in the code. Instead let LayoutManager so choosen worry about that aspect. This thread regarding should I avoid the use of setXxXSize() methods, is a good starting point on the topic
  2. Since this is Swing, try to use frame.pack(), instead of setting manual sizes on it. This way the GUI will look more appealing, given the Swing use to calculate everything, how the GUI should appear on the screen, in a manner, different from what an arbitrary value can do

You might have to take the insight to various layouts, and use them together, or individually to accomplish the needs.

A small program:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class ToBeDone {

    private JButton newTaskButton;
    private JButton editTaskButton;
    private JButton deleteTaskButton;

    private static final int GAP = 5;

    private void displayGUI() {
        JFrame frame = new JFrame("Swing Worker Example");
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

        JPanel contentPane = new JPanel();
        contentPane.setLayout(new BorderLayout(GAP, GAP));

        JPanel centerPanel = new JPanel();
        JPanel labelPanel = new JPanel();
        labelPanel.setLayout(new GridLayout(2, 1, GAP, GAP));
        JLabel headingLabel = new JLabel(
            "<html><h1>Heading</h1><html>", JLabel.CENTER);
        JLabel subHeadingLabel = new JLabel(
            "<html><h2>The body of the text goes here</h2>" +
                                        "</html>",JLabel.CENTER);
        labelPanel.add(headingLabel);
        labelPanel.add(subHeadingLabel);
        centerPanel.add(labelPanel);
        contentPane.add(centerPanel, BorderLayout.CENTER);

        JPanel buttonPanel = new JPanel();
        newTaskButton = new JButton("New Task");
        editTaskButton = new JButton("Edit Task");
        deleteTaskButton = new JButton("Delete Task");
        buttonPanel.add(newTaskButton);
        buttonPanel.add(editTaskButton);
        buttonPanel.add(deleteTaskButton);
        contentPane.add(buttonPanel, BorderLayout.PAGE_END);

        frame.setContentPane(contentPane);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                new ToBeDone().displayGUI();
            }
        };
        EventQueue.invokeLater(runnable);
    }
}
Community
  • 1
  • 1
nIcE cOw
  • 24,468
  • 7
  • 50
  • 143
1

you tbdPanel is set to FlowLayout and when you are heading and dets to this panel, they will be added in a row. That might be why you have them side by side. Try using another layout for tbdPanel, like BorderLayout

1

The cause of your problem is that tbdItem is being given a new FlowLayout() and this will layout items horizontally if there is space (which there is)

Layout managers in Swing can be a painful learning curve - especially the most configurable of them; GridBagLayout.

I would suggest starting with learning how BorderLayout works first and use lots of them (in a hierarchy) to wrap the items you need. So instead, you will need to do something like...

JPanel tbdItem = new JPanel( new BorderLayout() );
...
tbdItem.add(heading, BorderLayout.NORTH);
tbdItem.add(dets, BorderLayout.SOUTH);
  • I upvoted this a long time back, though, just a word of caution here, since Java version 1.4, Java is encouraging the use of `PAGE_START/LINE_START/LINE_END/PAGE_END` instead of `NORTH/EAST/WEST/SOUTH` – nIcE cOw Jul 10 '14 at 12:55
1

It looks like creating these kinds of simple layouts (FlowLayout, GridLayout) was a disservice to the users. People who start learning layout managemet naturally begin with these simple managers not realizing that they cannot do much in practice. And they are confused and struggle. The answer lies in more powerful layout managers like GridBagLayout, GroupLayout, or MigLayout (external). I always advocate for the MigLayout because it is very flexible and easy to use.

For illustration, I have created an example with the MigLayout manager:

package com.zetcode;

import java.awt.EventQueue;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import net.miginfocom.swing.MigLayout;


public class MigLayoutToBeDone extends JFrame {

    public MigLayoutToBeDone() {

        initUI();

        setTitle("To Be Done");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    private void initUI() {

        setLayout(new MigLayout("ins 15", "[grow]", "[][]30:push[]"));

        JLabel heading = new JLabel("Heading");
        JLabel dets = new JLabel("The body of the text goes here");

        add(heading, "center, gapbottom 15,  wrap");
        add(dets, "center, wrap");

        JButton newTask = new JButton("New Task");
        JButton editTask = new JButton("Edit Task");
        JButton deleteTask = new JButton("Delete Task");

        add(newTask, "split 3, center, sgx");
        add(editTask, "sgx");
        add(deleteTask, "sgx");

        pack();

    }

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                MigLayoutToBeDone ex = new MigLayoutToBeDone();
                ex.setVisible(true);
            }
        });
    }
}

The layout consists of one column; the column is stretched to occupy the whole window area. The two labels are centered horizontally within their cells of the column. Below the labels, there is a greedy gap that takes all the available space and thus pushing the three buttons to the bottom. Finally, the buttons are placed into three subcells of a cell, they are centered and made to have equal size.

ToBeDone example screenshot

Jan Bodnar
  • 10,969
  • 6
  • 68
  • 77