Swing Draw 1px border lines between components in a GridBagLayout component - java

I have components laid out in a GridBagLayout. I want a 1px black line between all the components, as well as around the JPanel itself.
Currently, I am using a MatteBorder to do this. The parent component has a 1px MatteBorder on the top and left edges. Each child component has a 1px MatteBorder on the right and bottom edges. The horizontal and vertical gaps are zero on the GridBagLayout.
This mostly works, except I'm getting occasional gaps where the child borders meet the parent border.
I suspect this is due to a rounding/floating point inaccuracy in the distribution of extra space to the child components.
Is there a better way to achieve this look?
Attached is a simpler example:
public class SSBGuiTest extends JDialog {
public SSBGuiTest(Frame owner) {
super(owner);
initComponents();
}
public SSBGuiTest(Dialog owner) {
super(owner);
initComponents();
}
private void initComponents() {
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
wrapperPanel = new JPanel();
panelWithTopLeftMatteBorder = new JPanel();
panel1 = new JPanel();
label1 = new JLabel();
panel2 = new JPanel();
label2 = new JLabel();
panel3 = new JPanel();
label3 = new JLabel();
//======== this ========
Container contentPane = getContentPane();
contentPane.setLayout(new BorderLayout());
//======== wrapperPanel ========
{
wrapperPanel.setBorder(new EmptyBorder(15, 15, 15, 15));
wrapperPanel.setLayout(new BorderLayout());
//======== panelWithTopLeftMatteBorder ========
{
panelWithTopLeftMatteBorder.setBorder(new MatteBorder(1, 1, 0, 0, Color.black));
panelWithTopLeftMatteBorder.setLayout(new GridBagLayout());
((GridBagLayout)panelWithTopLeftMatteBorder.getLayout()).columnWidths = new int[] {0, 0};
((GridBagLayout)panelWithTopLeftMatteBorder.getLayout()).rowHeights = new int[] {0, 0, 0, 0};
((GridBagLayout)panelWithTopLeftMatteBorder.getLayout()).columnWeights = new double[] {1.0, 1.0E-4};
((GridBagLayout)panelWithTopLeftMatteBorder.getLayout()).rowWeights = new double[] {1.0, 1.0, 1.0, 1.0E-4};
//======== panel1 ========
{
panel1.setBorder(new MatteBorder(0, 0, 1, 1, Color.black));
panel1.setLayout(new BorderLayout());
//---- label1 ----
label1.setHorizontalAlignment(SwingConstants.CENTER);
label1.setText("label1");
panel1.add(label1, BorderLayout.CENTER);
}
panelWithTopLeftMatteBorder.add(panel1, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
new Insets(0, 0, 0, 0), 0, 0));
//======== panel2 ========
{
panel2.setBorder(new MatteBorder(0, 0, 1, 1, Color.black));
panel2.setLayout(new BorderLayout());
//---- label2 ----
label2.setHorizontalAlignment(SwingConstants.CENTER);
label2.setText("label2");
panel2.add(label2, BorderLayout.CENTER);
}
panelWithTopLeftMatteBorder.add(panel2, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0,
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
new Insets(0, 0, 0, 0), 0, 0));
//======== panel3 ========
{
panel3.setBorder(new MatteBorder(0, 0, 1, 1, Color.black));
panel3.setLayout(new BorderLayout());
//---- label3 ----
label3.setHorizontalAlignment(SwingConstants.CENTER);
label3.setText("label3");
panel3.add(label3, BorderLayout.CENTER);
}
panelWithTopLeftMatteBorder.add(panel3, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0,
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
new Insets(0, 0, 0, 0), 0, 0));
}
wrapperPanel.add(panelWithTopLeftMatteBorder, BorderLayout.CENTER);
}
contentPane.add(wrapperPanel, BorderLayout.CENTER);
pack();
setLocationRelativeTo(getOwner());
// JFormDesigner - End of component initialization //GEN-END:initComponents
}
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables
private JPanel wrapperPanel;
private JPanel panelWithTopLeftMatteBorder;
private JPanel panel1;
private JLabel label1;
private JPanel panel2;
private JLabel label2;
private JPanel panel3;
private JLabel label3;
// JFormDesigner - End of variables declaration //GEN-END:variables
}
Which looks like this:

I think I'd use BorderFactory.createLineBorder(color, thickness) rather than ...matteBorder, since LineBorder seems to be a bit closer to what you're looking to do. As a convenience, you could also use LineBorder.createBlackLineBorder().
If the outsides of some components aren't quite hitting the insides of their containers, check that the containers (i.e. outer components) don't have non-zero insets set!
An alternate solution might be to let your background container have a black background and 1 pixel insets, and place non-bordered components on it with 1 pixel gaps between them. That should result in very precise black lines everywhere you don't have a component on top, and also eliminates the problem of multiple borders meeting up and resulting in a double-width border.
Finally, it looks to me like you're using components to draw tables. Do you need these components to have behavior of some sort, or would it be fine to really just put tables on your components? You could do this pretty conveniently using e.g. JLabels JTextPanels and setting their text properties to HTML for whatever you'd like to display. Java incorporates a fairly sophisticated HTML layout engine which even handles a goodly subset of CSS, either inline or even from a href'd file.

Related

JScrollBar Layout Manager and SpringLayout Manager not working together

public void createSpringLayout(SpringLayout spring, JLabel label, JScrollPane scrollPane, JPanel buttonPanel) {
spring.putConstraint(SpringLayout.NORTH, label, 10, SpringLayout.NORTH, this);
spring.putConstraint(SpringLayout.WEST, label, 10, SpringLayout.WEST, this);
spring.putConstraint(SpringLayout.NORTH, scrollPane, 10, SpringLayout.SOUTH, label);
spring.putConstraint(SpringLayout.WEST, scrollPane, 0, SpringLayout.WEST, label);
spring.putConstraint(SpringLayout.EAST, scrollPane, -10, SpringLayout.EAST, this);
spring.putConstraint(SpringLayout.NORTH, buttonPanel, Spring.constant(10, 30, 30), SpringLayout.SOUTH, scrollPane);
spring.putConstraint(SpringLayout.SOUTH, buttonPanel, -10, SpringLayout.SOUTH, this);
spring.putConstraint(SpringLayout.WEST, buttonPanel, 0, SpringLayout.WEST, label);
}
The two Jpanels with all the stuff on them are using the same superclass for better looks.
However as you can see if the contents of the scrollpane are to wide the scrollpane just uses extra space in the bottom to create a horizontal scrollbar.
Even if I tell the Springlayout that the spring between the scrollpane and the buttonPanel can be between 10 and 30.
I think that first the SpringLayoutManager is called to layout its components and then the ScrollPane comes along and notices that its displayed Components do not fit in the Viewport and creates a Scrollbar, which the SpringLayoutManager is unaware of.
I can't find any solution tha tell the ScrollPane beforehand to calculate its needed Size or for it to just dont use more Space than it has from the beginning.
Do not pass negative numbers to putConstraint.
From the documentation of putConstraint:
Links edge e1 of component c1 to edge e2 of component c2, with a fixed distance between the edges.
The pad value is not an absolute pixel offset, it is the amount of padding (the distance) between the component and the linked edge. It should always be positive.
The same javadoc also describes the pad parameter as:
pad - the fixed distance between dependent and anchor
SpringLayout is complex, which means it’s easy to make mistakes with it. You can easily accomplish the same layout with a BorderLayout:
setLayout(new BorderLayout());
add(label, BorderLayout.PAGE_START);
add(scrollPane, BorderLayout.CENTER);
add(buttonPanel, BorderLayout.PAGE_END);
label.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0));
buttonPanel.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0));
this.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));

Gridbaglayout in ScrollPane in Splitpane

I have a ScrollPane in a SplitPane and the ScrollPane contains a Gridbag layout panel.
I like to add horizontal filling Buttons to it.
Setting weightx and filling does not help at all.
I already tried it by using the dividers property listener, but nothing works.
Thanks for your answers!
Should be no problem:
// ScrollPane in a SplitPane
var scrollPane = new JScrollPane();
var splitPane = new JSplitPane();
splitPane.setLeftComponent(scrollPane);
splitPane.setRightComponent(new JLabel("dummy"));
// ScrollPane contains a Gridbag layout panel
var gridBagLayout = new GridBagLayout();
var panel = new JPanel(gridBagLayout);
scrollPane.setViewportView(panel);
// add horizontal filling Buttons
var gbc = new GridBagConstraints(
0, RELATIVE, 1, 1, 1.0, 0.0,
CENTER, HORIZONTAL, new Insets(2, 2, 2, 2), 0, 0);
panel.add(new JButton("first"), gbc);
panel.add(new JButton("second"), gbc);
JOptionPane.showMessageDialog(null, splitPane);

GridBagLayout anchor & JScrollPane issues

I have the following class
public class Demo {
private JFrame mainFrame;
static public Color BGCOLOUR1 = new Color(240, 240, 240);
public Demo() {
mainFrame = new JFrame("Demo");
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainFrame.setLayout(new BorderLayout());
mainFrame.setSize(900, 800);
JPanel centralPanel = new JPanel();
centralPanel.setOpaque(true);
centralPanel.setBackground(BGCOLOUR1);
centralPanel.setLayout(new GridBagLayout());
centralPanel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20),
BorderFactory.createTitledBorder("Panel")));
JPanel insidePanel = new JPanel();
insidePanel.setOpaque(true);
insidePanel.setBackground(BGCOLOUR1);
insidePanel.setLayout(new BorderLayout());
insidePanel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder("Inside panel"),
BorderFactory.createEmptyBorder(10, 10, 10, 10)));
JTextArea insidePanelText = new JTextArea(6, 50);
insidePanelText.setLineWrap(true);
insidePanel.add(insidePanelText);
centralPanel.add(insidePanel, new GridBagConstraints(0, 0, 1, 1, 1, 0, GridBagConstraints.NORTH,
GridBagConstraints.HORIZONTAL, new Insets(10, 10, 10, 10), 0, 0));
JScrollPane scrollPane = new JScrollPane(centralPanel);
mainFrame.add(scrollPane);
}
Why is the inside panel positioned in the centre of the centralPanel (vertically) when I set the GridBagConstraints anchor to NORTH? I would like it positioned at the top.
Also, if I add the centralPanel in a JScrollPane before adding it to the mainFrame as per the example I can resize the application larger just fine, but as soon as I resize it smaller (even though it is still larger than I originally started it) a scroll bar appears. How can I prevent that from happening?
Edit: To illustrate the scrolling problem (I packed the frame when I took these screens):
When I start the application it has no scrollbars
I resize the window larger, and then smaller again. As soon as I make it smaller, the scrollbar appears.
As far as GridBagLayout is concerned, based on the properties you've supplied, it is been laid out to the NORTH of the cell.
GridBagLayout works mostly to the preferred size of the components and calculates the positions of each component around the center of the parent container.
If, instead of:
new GridBagConstraints(0, 0, 1, 1, 1, 0, GridBagConstraints.NORTH,
GridBagConstraints.HORIZONTAL, new Insets(10, 10, 10, 10), 0, 0)
I use something like:
new GridBagConstraints(0, 0, 1, 1, 1, 1, GridBagConstraints.NORTH,
GridBagConstraints.HORIZONTAL, new Insets(10, 10, 10, 10), 0, 0)
it will produce the desired result.
Basically, by using gridy and setting it to 1, we're asking GridBagLayout to give all the remaining vertical space to this cell, but because we're not filling the cell, the contents is pushed to the NORTH of the cell
Also, if I add the centralPanel in a JScrollPane before adding it to the mainFrame as per the example I can resize the application larger just fine, but as soon as I resize it smaller (even though it is still larger than I originally started it) a scroll bar appears. How can I prevent that from happening?
I couldn't really replicate this particular problem, I could use either pack or setSize and resized the window smaller to its "packed" size and the scroll bars would appear, but once I resized the window beyond the "packed" size the scroll bars would disappear

Java JList GUI formatting

I am having trouble formatting the GUI for a program I am developing. I need to have four buttons with a JList above it. My JList keeps appearing besides all of my buttons instead of above them. Could anyone point me in the correct direction to solve my problem?
import java.awt.BorderLayout;
import java.awt.GridLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
public class Test extends JPanel {
private static final long serialVersionUID = 1L;
private JList jlist;
public static void main(String[] args) {
JFrame boxOptions = new JFrame("Calculator");
boxOptions.setSize(0,0);
boxOptions.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
boxOptions.setResizable(false);
boxOptions.setLayout(new BorderLayout());
boxOptions.add(new Test(), BorderLayout.CENTER);
boxOptions.pack();
boxOptions.setLocationRelativeTo(null);
boxOptions.setVisible(true);
}
public Test(){
setLayout(new GridLayout(1, 4, 5, 5));
add(new JButton("add"));
add(new JButton("Check In"));
add(new JButton("Check Out"));
add(new JButton("Delete"));
String[] Titles = {"one", "two", "three"};
jlist = new JList(Titles);
jlist.setVisibleRowCount(3);
add(new JScrollPane(jlist), BorderLayout.NORTH);
}
}
You're setting the layout of your Test JPanel class to GridLayout, and then trying to add a component to it using BorderLayout.NORTH specifier -- which is not kosher as that specifier only works for a BorderLayout and is meaningless for your GridLayout-using Test JPanel.
Solution: nest JPanels. Have an outer JPanel use a BorderLayout, and add the list's JScrollPane to this outer JPanel using the BorderLayout.NORTH specifier, and then create an inner JPanel that uses GridLayout, that holds your JButtons and add it to the main outer JPanel in the BorderLayout.CENTER position.
Most important though: read the Swing tutorials which you can find here, in particular the section on using the layout managers, since there is no need to guess at this stuff.
First of all , the BorderLayout Parameters use only when the layout is BorderLayout.
You can use GridBagLayout Instead. Sample code is below
setLayout(new GridBagLayout());
add(new JButton("add"), new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
add(new JButton("Check In"), new GridBagConstraints(1, 1, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
add(new JButton("Check Out"), new GridBagConstraints(2, 1, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
add(new JButton("Delete"), new GridBagConstraints(3, 1, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
String[] Titles = {"one", "two", "three"};
jlist = new JList(Titles);
jlist.setVisibleRowCount(3);
add(new JScrollPane(jlist), new GridBagConstraints(0, 0, 4, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
Is this you expected ?

How to get GridBagLayout to respect minimum size of a button or panel?

In the following GridBagLayout code, I'm expecting the specified minimum size of JButton btn2 to be respected when the JFrame is resized to be made smaller. But when I make the JFrame smaller, the btn2 gets smaller than its minimum size and then vanishes.
Can someone point me in the right direction of what I'm doing wrong? Maybe I have to set the minimum size of the JPanel that contains the buttons?
Any help is appreciated, thanks!
JFrame frame = new JFrame();
frame.setSize(400,300);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel();
panel.setMinimumSize(new Dimension(400,300));
panel.setBackground(Color.RED);
panel.setLayout(new GridBagLayout());
GridBagConstraints gbc = null;
JButton btn1 = new JButton("btn1");
btn1.setPreferredSize(new Dimension(150,50));
btn1.setMinimumSize(new Dimension(150,50));
gbc = new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,
GridBagConstraints.NORTHWEST, GridBagConstraints.NONE,
new Insets(0,0,0,0), 0, 0);
panel.add(btn1, gbc);
JButton btn2 = new JButton("btn2");
btn2.setPreferredSize(new Dimension(150,150));
btn2.setMinimumSize(new Dimension(150,150));
gbc = new GridBagConstraints(1, 0, 1, 1, 1.0, 1.0,
GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH,
new Insets(0,0,100,100), 0, 0);
panel.add(btn2, gbc);
frame.getContentPane().add(panel);
frame.setVisible(true);
In addition to Andrew Thompson's answer:
The GBL DOES NOT respect the minimum size set on a component if the component's preferred size can and will be respected.
The GBL DOES respect the minimum size set on a component when the component's preferred size cannot be respected, that is: the component prefers to be of size (w,h) but the GBL sees this is impossible due to lack of space, and therefore it falls back on the advertised minimum size of the component.
It is therefore very much possible to make your component suddenly become larger than it was before when you downsize the containing frame. You only have to set a minimum size on it that is larger than the preferred size. So you are making the frame smaller, and suddenly your component becomes larger. That is... weird but possible :) All hail GBL.
Here is an example of a panel that implements this behavior (GBC is an easier class to use than the original GridBagConstraints):
private static JPanel getPanel() {
JPanel panel = new JPanel(new GridBagLayout());
final JTextField textField = new JTextField("test");
final String text = "text";
final JLabel label = new JLabel(text);
final JButton button = new JButton(new AbstractAction("Enlarge label!") {
private String actText = text;
#Override
public void actionPerformed(ActionEvent arg0) {
actText = actText + text;
label.setText(actText);
}
});
GBC g;
g = new GBC().locate(0, 0).weight(0.0, 0.0).align(GBC.WEST, GBC.HORIZONTAL, null);
textField.setMinimumSize(new Dimension(200,20));
panel.add(textField, g);
g = new GBC().locate(1, 0).weight(1.0, 0.0);
panel.add(label, g);
g = new GBC().locate(0, 1).weight(1.0, 1.0).span(2, 1);
panel.add(button, g);
return panel;
}
In general, I would say to not use components that must be fixed in size within a GBL. Wrap it inside other panels with different layout managers.
If you really want to use your fixed-sized component within a GBL, override its getPreferredSize() method and return the size you want. But I prefer to ignore these methods.
Maybe I have to set the minimum size of the JPanel that contains the buttons?
AFAIR GBL was notorious for ignoring sizing hints. No, a correction on that. To get sensible resizing of components within GBL, use the GridBagConstraints with appropriate values. Beware though, the behavior of the layout to not display any component that would be forced to less than its minimum size.
I would pack() the frame then set the minimum size on the frame. Here is how it might look, changing the last line to..
frame.pack();
frame.setVisible(true);
frame.setMinimumSize(frame.getSize());
Given the layout though, I would tend to put btn1 into the PAGE_START of the BorderLayout of a panel that is then added to the LINE_START of another BL. btn2 would go in the CENTER of the outer BL.

Categories

Resources