2

I'm trying to code a layout for a small phonebook app using Java Swing. I came across a problem with size I cannot fix. I want filters (displayed under search results) to be wide enough to show whole title when search phrase is short. Here's what it looks like:

sample filters

As you can see name is shortened. Surname is carrying longer text and is displayed just as I want.

Filter class:

import javax.swing.*;
import javax.swing.border.CompoundBorder;
import java.awt.*;

public class Filter
{
    private JLabel label;
    private JPanel filterPane;

    Filter(String name)
    {
        // Filter text
        label = new JLabel();
        label.setForeground(new Color(0xAA0000));

        // Closing button
        JButton closeButton = new JButton("X");
        closeButton.setFont(new Font("Dialog", Font.BOLD, 20));
        closeButton.setMargin(new Insets(0, 0, 0, 0));

        filterPane = new JPanel();
        filterPane.setLayout(new BoxLayout(filterPane, BoxLayout.X_AXIS));
        // Frame with title + 50px padding on the right side for the next filter
        filterPane.setBorder(new CompoundBorder(
                BorderFactory.createEmptyBorder(0, 0, 0, 50),
                BorderFactory.createTitledBorder(name)));
        filterPane.add(closeButton);
        filterPane.add(Box.createRigidArea(new Dimension(5, 0)));
        filterPane.add(label);
        filterPane.setVisible(false);
    }

    public void setValue(String value) {
        if (value == null || value.isEmpty())
            filterPane.setVisible(false);
        else
        {
            this.label.setText(value);
            filterPane.setVisible(true);
        }
    }

    public JPanel getFilterPane() {
        return filterPane;
    }
}

Main class:

import javax.swing.*;
import java.awt.*;
import java.lang.reflect.InvocationTargetException;

public class TestWindow
{
    private static void init()
    {
        JFrame frame = new JFrame("Stackoverflow test window");
        frame.setSize(new Dimension(480, 320));
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);

        // Main panel - 20 px padding from each side
        JPanel pane = new JPanel();
        pane.setLayout(new BorderLayout());
        pane.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));

        // Create panel for filters
        JPanel filterPane = new JPanel();
        filterPane.setLayout(new BoxLayout(filterPane, BoxLayout.X_AXIS));
        filterPane.setPreferredSize(new Dimension(0, 60));

        // Add sample filters
        Filter filter = new Filter("name");
        filter.setValue("i");
        filter.getFilterPane().setAlignmentY(Component.BOTTOM_ALIGNMENT);
        filterPane.add(filter.getFilterPane());

        filter = new Filter("surname");
        filter.setValue("loooooong text");
        filter.getFilterPane().setAlignmentY(Component.BOTTOM_ALIGNMENT);
        filterPane.add(filter.getFilterPane());

        // Add everything to main panel
        pane.add(new JTextArea("Nothing fancy here, just to fill out space"), BorderLayout.CENTER);
        pane.add(filterPane, BorderLayout.SOUTH);
        frame.setContentPane(pane);
        frame.setVisible(true);
    }

    public static void main (String [] args) throws InterruptedException, InvocationTargetException {
        SwingUtilities.invokeLater(TestWindow::init);
    }
}

I tried setMinimumSize and setPreferredSize both for label and filterPane in Filter constructor, but it didn't help. Could you help me please?

PKua
  • 463
  • 3
  • 15
  • 2
    How are you adding the panes to the parent window? What layout are you using? Please [edit] the question and add the applicable code. – RealSkeptic Dec 28 '16 at 10:29
  • Thanks, you're right. Done. – PKua Dec 28 '16 at 10:49
  • 2
    Instead of setting sizes, override the `getXxxSize` of the class. Post a [mcve]. – user1803551 Dec 28 '16 at 11:22
  • [`JComponent.setMinimumSize`](https://docs.oracle.com/javase/8/docs/api/javax/swing/JComponent.html#setMinimumSize-java.awt.Dimension-), tried that? – TT. Dec 28 '16 at 11:22
  • Yes, both for filterPane and label. Didn't work. @user1803551 You mean rewrite my question applying those rules? – PKua Dec 28 '16 at 11:38
  • @PKua Write a small (minimal) program that reproduces the issue, all in one class, that people can copy/paste and fiddle around with in their own programming environment. That's what's meant by MCVE or SSCCE. – TT. Dec 28 '16 at 11:44
  • Yes, exactly what I mean. We want to be able to copy-paste the code into our IDEs, run it and see the problem. – user1803551 Dec 28 '16 at 11:48
  • @user1803551 Thanks, done, tried to do my best. I hope everything is clear now. – PKua Dec 28 '16 at 12:06
  • So the problem is with the title border. A simple search gives previous questions [here](http://stackoverflow.com/questions/6407897/title-truncation-in-jpanel-titledborder-java-swing), [here](http://stackoverflow.com/questions/36405080/why-is-my-titled-border-panel-so-small), [here](http://stackoverflow.com/questions/15260484/java-swing-how-to-change-the-font-size-on-a-jpanels-titledborder), [here](http://stackoverflow.com/questions/5744611/title-collapse-in-titledborder) and an openJDK report [here](https://bugs.openjdk.java.net/browse/JDK-4994231). – user1803551 Dec 28 '16 at 13:22

1 Answers1

5

The problem is actually composed of two problems.

  1. When using TitledBorder, the component does not take the size of the title into account when calculating its preferred size.
  2. When using BoxLayout, the preferred width is ignored, unless you add "horizontal glue" after the component.

So, to fix the first problem, you need to override the getPreferredSize() method of JPanel. But for this to work, you also need to bypass the second problem by adding glue.

To override the getPreferredSize(), you can use something like:

class MinSizePanel extends JPanel {

    public Dimension getPreferredSize() {
        Dimension original = super.getPreferredSize();
        TitledBorder titleBorder = (TitledBorder)((CompoundBorder)getBorder()).getInsideBorder();
        int width = (int)Math.max( original.getWidth(), 60 + (int)titleBorder.getMinimumSize(this).getWidth());
        return new Dimension( width, (int)original.getHeight() );
    }
}

This gets the TitledBorder from the CompoundBorder, gets its minimum size (this is a method that takes the title width into consideration), adds some extra for the empty border and insets (you can add the appropriate calculations, I did a shortcut and just used 60...), and uses that (if it's more than the width of the component as-is).

Instead of

filterPane = new JPanel();

Use

filterPane = new MinSizePanel();

(You should really move all the rest of the construction of the panel into that class as well).

And in the TestWindow class, after the last

    filterPane.add(filter.getFilterPane());

Also add

    filterPane.add(Box.createHorizontalGlue());
RealSkeptic
  • 33,993
  • 7
  • 53
  • 79
  • Thanks a lot, it solved the problem. So `BoxLayout` ignores the preferred width of the container it's bound to unless there is a glue as well? – PKua Dec 28 '16 at 15:51
  • 1
    @PKua It ignores the preferred width of the components, not the preferred width of the container. I think it is trying to make the layout compact, like a line of text that's justified to the left. Adding the glue causes it to calculate a different max width and change its policy. There could be other ways to achieve the same effect. – RealSkeptic Dec 28 '16 at 16:42