BoxLayout doesn't respect glue when JLabel contains HTML - java

I have a component I've written that uses BoxLayout to layout some JLabels horizontally, followed by glue (I'd like the JLabels' width to remain fixed). For example:
I need to add two lines of text to each JLabel, so I'm using a bit of HTML. As soon as I added the HTML, the BoxLayout stopped respecting the glue. I get something like:
I can get around this by specifying that the maximum size should be equal to the preferred size (specifying preferred size has no effect). Is this the correct approach? Is there some explanation for why glue seems to be ignored when there's HTML in my JLabels?
The MWE:
import javax.swing.*;
import java.awt.*;
public class LabelBreak extends JFrame {
JPanel panel;
public LabelBreak() {
setTitle("Frame");
panel = new MyPanel();
panel.setPreferredSize(new Dimension(500, 100));
add(panel);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
JFrame frame = new LabelBreak();
frame.pack();
frame.setVisible(true);
});
}
private static class MyPanel extends JPanel {
private MyPanel() {
super();
this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
JPanel header = new JPanel();
header.setLayout(new BoxLayout(header, BoxLayout.X_AXIS));
//JLabel label = new JLabel("One");
JLabel label = new JLabel("<html>One<br>is<br>the<br>loneliest<br>number</html>");
label.setBorder(BorderFactory.createLineBorder(Color.RED, 2));
header.add(label);
label = new JLabel("Two");
label.setBorder(BorderFactory.createLineBorder(Color.RED, 2));
header.add(label);
header.add(Box.createHorizontalGlue());
this.add(header);
}
}
}

Is there some explanation for why glue seems to be ignored when there's HTML in my JLabels?
A BoxLayout will respect the maximum size (and minimum size) for components.
For normal text the maximum size would be the preferred size of the component, so the glue works as expected.
It looks like the calculation for the maximum size is different for HTML vs regular text.
I can get around this by specifying that the maximum size should be equal to the preferred size
Yes, this is a reasonable approach, but I would override the getMaximumSize() method to simply return the getPreferredSize() value.
#Override
public Dimension getMaximumSize()
{
return getPreferredSize();
}
This way if you change the HTML it will still work.

BoxLayout respects maximum size of JLabel so you have to set it.
To control width between two labels you can use Box.createHorizontalStrut(width).
Note that two labels are centered in header. I would use GridLayout to place components in one row with different space between columns.
private MyPanel() {
super();
this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
JPanel header = new JPanel();
header.setLayout(new GridLayout(1,4,20,0));
// JLabel label = new JLabel("One");
JLabel label = new JLabel("<html>One<br>is<br>the<br>loneliest<br>number</html>");
label.setBorder(BorderFactory.createLineBorder(Color.RED, 2));
label.setMaximumSize(label.getPreferredSize());
header.add(label);
// header.add(Box.createHorizontalStrut(10));
label = new JLabel("Two");
label.setBorder(BorderFactory.createLineBorder(Color.RED, 2));
header.add(label);
// header.add(Box.createHorizontalGlue());
this.add(header);
}

Related

Forcing minimum JPanel size

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:
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?
The problem is actually composed of two problems.
When using TitledBorder, the component does not take the size of the title into account when calculating its preferred size.
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());

Java - How to change components height using BoxLayout?

So, I have a tiny GUI program and I decided to use the BoxLayout to display the components from top to bottom. Everything works fine but I'm not able to change the height of my JButtons. I tried many things like setPreferredSize() but then i had the problem that the width isn't correct, as well. Using setMaximumSize() sets the width like i want to but the height still doensn't change. Maybe some of you could help me :) Thanks
public class SimpleSkinViewer extends JPanel implements ActionListener{
private final Dimension boxDimension = new Dimension(320, 320);
private final Dimension buttonDimension = new Dimension(320, 60);
private final Dimension spaceDimension = new Dimension(0, 5);
private JLabel imagebox;
private JButton loadButton;
private JButton changeButton;
private JButton downloadButton;
public SimpleSkinViewer() {
super();
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
imagebox = new JLabel("");
imagebox.setIcon(new ImageIcon(loadImage("http://skins.minecraft.net/MinecraftSkins/AvarionDE.png")));
loadButton = new JButton("Load Skin");
changeButton = new JButton("Change Skin");
downloadButton = new JButton("Download");
//add listeners
loadButton.addActionListener(this);
changeButton.addActionListener(this);
downloadButton.addActionListener(this);
//dimensions
imagebox.setMaximumSize(boxDimension);
loadButton.setMaximumSize(buttonDimension);
changeButton.setMaximumSize(buttonDimension);
downloadButton.setMaximumSize(buttonDimension);
add(imagebox);
add(Box.createRigidArea(spaceDimension));
add(loadButton);
add(Box.createRigidArea(spaceDimension));
add(changeButton);
add(Box.createRigidArea(spaceDimension));
add(downloadButton);
}
#Override
public void actionPerformed(ActionEvent arg0) {
}
//and other stuff.....
public static void main (String[] args) {
JFrame frame = new JFrame("Avarion's Simple Skin Viewer");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(new SimpleSkinViewer());
frame.setLocationRelativeTo(null);
frame.pack();
frame.setVisible(true);
}
}
You need Box.createVerticalGlue()
Change
add(changeButton);
add(Box.createRigidArea(spaceDimension));
with
add(changeButton);
add(Box.createVerticalGlue());
Then you can use .setPreferredSize(new Dimension(x,y)); and buttons will adapt to your layout
From the docs for BoxLayout
When a BoxLayout lays out components from top to bottom, it tries to
size each component at the component's preferred height.
For a top-to-bottom box layout, the preferred width of the container
is that of the maximum preferred width of the children. If the
container is forced to be wider than that, BoxLayout attempts to size
the width of each component to that of the container's width (minus
insets). If the maximum size of a component is smaller than the width
of the container, then X alignment comes into play.
So, you can set both the maximumSize and preferredSize to get the desired size.
loadButton.setMaximumSize(buttonDimension);
loadButton.setPreferredSize(buttonDimension);

Which Layout Manager Should I use to achieve the following?

I have a JFrame and three JPanels. On the frame I used BorderLayout. At the CENTER of the frame I have put outerPanel. On my outerPanel I have used MigLayout. The two other panels are added on to the outerPanel. These two panels are of equal size and their widths add up to the width of the outerPanel - I wanted the outerPanel to be divided into two halves. Below is the code for this:
public class ControlPanel extends JFrame {
// components
public JPanel outerPanel;
public JPanel innerPanel1;
public JPanel innerPanel2;
public ControlPanel() {
this.createUI();
}
public void createUI() {
// form properties
this.setSize(new java.awt.Dimension(300, 300));
this.setVisible(true);
this.setLayout(new java.awt.BorderLayout());
this.outerPanel = new JPanel();
this.outerPanel.setPreferredSize(new java.awt.Dimension(260, 250));
this.outerPanel.setLayout(new net.miginfocom.swing.MigLayout());
this.outerPanel.setBorder(BorderFactory.createEtchedBorder());
this.add(new javax.swing.JLabel("North"), BorderLayout.NORTH);
this.add(this.outerPanel, BorderLayout.CENTER);
this.innerPanel1 = new JPanel();
this.innerPanel1.setPreferredSize(new java.awt.Dimension(130, 150));
this.innerPanel1.setLayout(new net.miginfocom.swing.MigLayout());
this.innerPanel1.setBorder(BorderFactory.createTitledBorder("Panel1"));
this.innerPanel2 = new JPanel();
this.innerPanel2.setPreferredSize(new java.awt.Dimension(130, 150));
this.innerPanel2.setLayout(new net.miginfocom.swing.MigLayout());
this.innerPanel2.setBorder(BorderFactory.createTitledBorder("Panel2"));
this.outerPanel.add(this.innerPanel1);
this.outerPanel.add(this.innerPanel2);
this.pack();
}
public static void main(String[] args) {
ControlPanel cp = new ControlPanel();
}
}
Problem: When I run my program, the GUI that appears before I resize the window is fine; but when I resize the window -enlarging it, innerPane1 and innerPanel2 remains of the same size without resizing to occupy the space available.
Question: How do we make the two panels , innerPannel1 and innerPanel2, resize at the same time with the window so that they can share equally the available space? Any particular Layout Manager that can be used to divide a panel into two equal halves that can resize at the same time with the window?
Images Showing the output.
Before resizing - the GUI looks well and the panels have correct size.
After resizing -the GUI is distorted and the panels doesn't change size.
I suggest you use new GridLayout(1, 2). This will split the panel in 1 row and 2 (equally sized) columns.
So, simply changing
this.outerPanel = new JPanel();
to
this.outerPanel = new JPanel(new GridLayout(1, 2));
should do.

How to remove the padding between in the JPanel still using a flow layout?

Here's the portion of my java application GUI that I have a question about.
What this GUI consists is a blue JPanel(container) with default FlowLayout as LayoutManager that contains a Box which contains two JPanels(to remove the horizontal spacing or i could have used setHgaps to zero for that matter instead of a Box) that each contains a JLabel.
Here's my code for creating that part of the GUI.
private void setupSouth() {
final JPanel southPanel = new JPanel();
southPanel.setBackground(Color.BLUE);
final JPanel innerPanel1 = new JPanel();
innerPanel1.setBackground(Color.ORANGE);
innerPanel1.setPreferredSize(new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT));
innerPanel1.add(new JLabel("Good"));
final JPanel innerPanel2 = new JPanel();
innerPanel2.setBackground(Color.RED);
innerPanel2.setPreferredSize(new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT));
innerPanel2.add(new JLabel("Luck!"));
final Box southBox = new Box(BoxLayout.LINE_AXIS);
southBox.add(innerPanel1);
southBox.add(innerPanel2);
myFrame.add(southPanel, BorderLayout.SOUTH);
}
My question is how would i get rid the vertical padding between the outer JPanel(the blue one) and the Box?
I know this is padding because i read on Difference between margin and padding? that "padding = space around (inside) the element from text to border."
This wouldn't work because this has to due with gaps(space) between components.- How to remove JPanel padding in MigLayout?
I tried this but it didn't work either. JPanel Padding in Java
You can just set the gaps in the FlowLayout, i.e.
FlowLayout layout = (FlowLayout)southPanel.getLayout();
layout.setVgap(0);
The default FlowLayout has a 5-unit horizontal and vertical gap. Horizontal doesn't matter in this case as the BorderLayout is stretching the panel horizontally.
Or simple initialize the panel with a new FlowLayout. It'll be the same result.
new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));
Edit:
"I tried that, didn't work.."
Works for me...
Setting the gap ↑ Not setting the gap ↑
import java.awt.*;
import javax.swing.*;
public class Test {
public void init() {
final JPanel southPanel = new JPanel();
FlowLayout layout = (FlowLayout)southPanel.getLayout();
layout.setVgap(0);
southPanel.setBackground(Color.BLUE);
final JPanel innerPanel1 = new JPanel();
innerPanel1.setBackground(Color.ORANGE);
innerPanel1.add(new JLabel("Good"));
final JPanel innerPanel2 = new JPanel();
innerPanel2.setBackground(Color.RED);
innerPanel2.add(new JLabel("Luck!"));
final Box southBox = new Box(BoxLayout.LINE_AXIS);
southBox.add(innerPanel1);
southBox.add(innerPanel2);
southPanel.add(southBox); // <=== You're also missing this
JFrame myFrame = new JFrame();
JPanel center = new JPanel();
center.setBackground(Color.yellow);
myFrame.add(center);
myFrame.add(southPanel, BorderLayout.SOUTH);
myFrame.setSize(150, 100);
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
myFrame.setLocationByPlatform(true);
myFrame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable(){
public void run() {
new Test().init();
}
});
}
}
Note: Always post a runnable example (as I have done) for better help. You say it doesn't work, but it always works for me, so how would we know what you're doing wrong without some code that will run and demonstrate the problem?

Java: Extra space in AbsoluteLayout

Is it possible to have some extra space around the edges of a JFrame that uses AbsoluteLayout? When I have a button as the downwardsmost component on the JFrame, it gets positioned right up against the bottom edge of the JFrame window, and it looks bad. I would like to know if there's a way to add a little extra space between components and the edge of the JFrame while using AbsoluteLayout.
Suggestions:
When you add a component to a JFrame, you're actually adding it to the JFrame's contentPane. To give the contentPane a "buffer" border, consider giving it an EmptyBorder(...) with the parameters being int constants for the amount of border desired around the component.
Avoid using "absolute" layouts for anything, and especially for placing components at easy to place locations for the layout managers, such as at the bottom of the GUI.
For example, note in the GUI created in the code below how the center and bottom JPanel's don't go out to the edge of the GUI because of the empty border:
import java.awt.BorderLayout;
import java.awt.Dimension;
import javax.swing.*;
public class ButtonAtBottom {
private static void createAndShowGui() {
JPanel bottomPanel = new JPanel();
bottomPanel.add(new JButton("Bottom Button"));
bottomPanel.setBorder(BorderFactory.createTitledBorder("Bottom Panel"));
JPanel centerPanel = new JPanel();
centerPanel.setBorder(BorderFactory.createTitledBorder("Center Panel"));
JPanel mainPanel = new JPanel(new BorderLayout());
mainPanel.add(centerPanel, BorderLayout.CENTER);
mainPanel.add(bottomPanel, BorderLayout.PAGE_END);
// **** here I add the border to the mainPanel which I'll
// make into the contentPane
int eb = 25;
mainPanel.setBorder(BorderFactory.createEmptyBorder(eb, eb, eb, eb));
// don't set the preferredSize per Kleopatra, but am doing it
// here simply to make code shorter for this sscce
mainPanel.setPreferredSize(new Dimension(500, 400));
JFrame frame = new JFrame("ButtonAtBottom");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
You can use Box.createRigidArea(dimensions) to create an empty space that you can add below the button.
Set an empty border on your content panel where SIZE is the amount of padding you want.
JFrame frame = new JFrame();
JPanel panel = new JPanel(null);
panel.setBorder(BorderFactory.createEmptyBorder(SIZE,SIZE,SIZE,SIZE);
frame.setContentPane(panel);
//The rest
The arguments are for top, left, bottom and right padding so if you want different paddings on each edge, you can set it accordingly.

Categories

Resources