BoxLayout artifact at 125% display scaling - java

This simple Java swing BoxLayout UI apparently results in certain pixels not being drawn (resulting in artifacts/garbage) when my display scaling is set to 125% (Windows 10):
import javax.swing.*;
public class Test {
public static void main(String[] args) {
JPanel container = new JPanel();
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
for(int i = 1; i <= 3; i++) panel.add(new JButton("Button " + i));
JFrame frame = new JFrame();
container.add(panel);
frame.add(container);
frame.pack();
frame.setVisible(true);
}
}
Result:
There is a one pixel gap between Button 2 and Button 3. I'm not greatly concerned with the gap itself, but the pixels in this gap (within the width of the buttons) are filled with garbage. In the screenshot, this appears to be a misaligned piece of a button, but in general this can change whenever this area is repainted (such as after hovering Button 2), often coming from a recently-repainted component (often more obvious in a more complex application). Presumably this is just uninitialized data due to some kind of dimension mismatch triggered by display scaling and particular position values.
Last tested with openjdk-14.0.2. (Presumably this can only happen since JEP 263 added "HiDPI Graphics" support in Java 9.)
Am I doing something wrong? Is there a way to avoid this problem or work around it? Is this a problem in BoxLayout or is it a more fundamental problem? Basically, what's going on?

Related

How can get components to stretch across an entire row using BoxLayout?

I'm looking at the How To Use BoxLayout documentation, which clearly says that
What if none of the components has a maximum width? In this case, if
all the components have identical X alignment, then all components are
made as wide as their container.
Let's assume we're adding a lot of JButton instances to a JPanel. If the maximum width of these buttons are none AND we invoke setAlignmentX(Component.LEFT_ALIGNMENT) on all of these buttons - then each of these buttons should stretch across its entire row. The documentation even illustrates this using the below picture.
I can't get this to work!
I've tried doing setMaximumSize(null) and setMaximumSize(new Dimension(-1,-1)) and setMaximumSize(new Dimension(0,0)) on the buttons but nothing gives me the described behaviour.
What excactly does the documentation mean when it says :
What if none of the components has a maximum width?
What is a maximum width of none?
The best I've been able to produce is the below. Reading the documentation I would expect that the buttons should be able to stretch across their entire rows. I know I can use other layout managers as well for this, but I would like to achieve this with BoxLayout (granted the documentation is right / I've understood the documentation right).
public class CustomList extends JPanel {
private final Box box = Box.createVerticalBox();
public CustomList() {
for (int i = 0; i < 10; i++) {
JButton b = new JButton("Button item" + i);
//b.setMaximumSize(new Dimension(0,0));
b.setAlignmentX(Component.LEFT_ALIGNMENT);
box.add(b);
}
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
add(box, BorderLayout.CENTER);
}
public static void main(String[] args) {
CustomList l = new CustomList();
l.setSize(200, 200);
l.setBackground(Color.red);
JFrame frame = new JFrame("Vertical Box");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(l, BorderLayout.CENTER);
frame.setSize(300, 200);
frame.setVisible(true);
}
}
Your buttons actually have a maximum width.
What you can do is create JPanel objects with BorderLayout in your loop, add each button to each panel (to BorderLayout.CENTER, which is the default anyway).
BorderLayout.CENTER doesn't care about the maximum size of its child Component, so you end up with a JPanel whose whole content is filled by a JButton.
Since the JPanel itself has a huge default maximum size of new Dimension(Short.MAX_VALUE, Short.MAX_VALUE) (this is width=32767,height=32767 !!) which is the default maximum size of Component, you will get the expected result :
public CustomList() {
for (int i = 0; i < 10; i++) {
JPanel panel = new JPanel(new BorderLayout());
JButton b = new JButton("Button item" + i);
//b.setMaximumSize(new Dimension(0,0));
b.setAlignmentX(Component.LEFT_ALIGNMENT);
panel.add(b);
box.add(panel);
}
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
add(box, BorderLayout.CENTER);
}

JPanel taking up more space than it needs. How can I shrink it?

UPDATE: I have received justified criticism for posting non working code. I've taken that to heart and am updating this post with a complete working example. I'm also updating the description accordingly:
I have a very simple java swing GUI whose components take up what looks to be an equal amount of vertical (Y) space as is used by the largest Y extent component, but completely unnecessarily so. I have tried to shrink those components that don't need that much vertical space using preferredSize hints but to no avail.
The basic layout is simple: There's a main window and three vertical panels. The layout is a simple GridLayout (and I would prefer to keep it that way, unless someone shows me what I need cannot be done with GridLayout). All three panels seem to be occupying the same amount of vertical space, even though in the case of the sliders, this is massive waste of space. How can I get each of the sub-panes to only use as much space as they each need? i.e. I would like the two slider windows to be only as tall as the sliders and their description need to be.
The code:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
class test {
public static void main(String[] arg) {
JFrame mainWindow = new JFrame();
JSlider slider1 = new JSlider(0,100,50);
JSlider slider2 = new JSlider(0,100,50);
JPanel pnlSlider1 = new JPanel();
pnlSlider1.setLayout(new GridLayout(1,1)); // 1 row, 1 column
pnlSlider1.add(new JLabel("Description for slider1"));
pnlSlider1.add(slider1);
JPanel pnlSlider2 = new JPanel();
pnlSlider2.setLayout(new GridLayout(1,1)); // 1 row, 1 column
pnlSlider2.add(new JLabel("Description for slider2"));
pnlSlider2.add(slider2);
// label should now be to the left of slider
String content = "<html>Some rather long winded HTML content</html>";
JEditorPane ep = new JEditorPane("text/html", content);
// this is the main window panel
JPanel panel = new JPanel();
panel.setLayout(new GridLayout(3,1)); // 3 rows, 1 column
panel.add(ep);
panel.add(pnlSlider1);
panel.add(pnlSlider2);
// tie it all together and display the window
mainWindow.setPreferredSize(new Dimension(300, 600));
mainWindow.setLocation(100, 100);
mainWindow.getContentPane().add(panel);
mainWindow.pack();
mainWindow.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
mainWindow.setVisible(true);
}
}
(removed rant about not having seen any GUI coding advances in 30 years as that's not pertinent to the problem and likely won't be solved in this post either)
..components take up what looks to be an equal amount of vertical (Y) space as is used by the largest Y extent component, but completely unnecessarily so.
Yes, that is the way GridLayout is designed to work.
Use a GridBagLayout or BoxLayout or GroupLayout instead, each of which can do a single column or row of components of variable size (width and height).

Java swing GUI creation

I'm developing a Java GUI and I need:
A label in first row(only one label).
Starting 2nd row need to add say 100 buttons which extends to multiple lines(width shouldn't go beyond the visible screen)
In a new line one more Label
From next line say 100 buttons which extends to multiple lines(width shouldn't go beyond the visible screen)...
[OPTIONAL] If the components exceeds JFrame height then need a scroll facility to the main window (only vertical)
I have a strange results with flow layout, sometimes it stick to visible width, sometimes it sets even 500 buttons in a single row.
I have tried every layout and also multipanes. Still no luck.
Please guide.. just need an idea, No need of code
Updated with code: Sorry guys, that was my first question to stackoverflow
Thanks for prompt response
Infact i tried many, here is a simple one.
setLayout(new FlowLayout());
setTitle("JAVA GUI");
setSize(500,500);
setVisible(true);
add(new JLabel("row 1"));
JPanel panel1 = new JPanel(new FlowLayout());
for(int i=0;i<200;i++){
panel1.add(new JButton("b"+i));
}
add(panel1);
Here the panel1 is appearing in a sigle row which goes beyond the visible part of the screen.
I think this can be solved by setting maximumsize to Jframe, but no idea how to set its size to FULL SCREEN.
You can try MigLayout.
http://www.miglayout.com/
Also this question is not really a question for stack overflow. A good way to ask your question would be to post your code and tell us what is wrong with it and what it is supposed to do.
While this is not the norm for 'good' stackOverflow questions, I don't have any problem with it myself. Some people cannot deal with anything except code. I would suggest that, if you're going to post code, that you take the trouble to post code that will compile, run, and demonstrate your situation. It really helps those of us out here understand what you're seeing and what you're trying to do.
You talk about "rows"; be aware that rows and columns are terms used with things like GridLayout and GridBagLayout, but I don't think they're appropriate for what you describe.
In your description, you don't say what you want scrolled. It would appear you want the entirety of the UI scrolled, I'll assume that for now.
I would try a JPanel with BoxLayout, oriented vertically, for the overall main UI. You will put some things into that:
The first JPanel.
Another JPanel, set with FlowLayout, holding the first bunch of buttons.
Another JPanel with the next JLabel
And a fourth JPanel, set with FlowLayout, holding the second bunch of buttons.
Now, I would put the top-level panel into a JScrollPane, and then put that into the CENTER section of a Frame (with its default BorderLayout), and see what happens. To tell the truth, I'm not sure, but these are the things I would start with.
I cannot tell, without running code, why you get odd behavior sometimes.
As said in a previous comment, using a ContentPane is the way to go. Here is a working example of what you want:
public class Test {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setTitle("JAVA GUI");
JPanel panel1 = new JPanel();
panel1.setLayout(new BoxLayout(panel1, BoxLayout.Y_AXIS));
int nbLines = 10;
for (int i = 0; i < nbLines; i++) {
JPanel linePanel = new JPanel(new FlowLayout());
linePanel.add(new JLabel("row " + i));
for(int j = 0; j < 50; j++) {
linePanel.add(new JButton("b" + j));
}
panel1.add(linePanel);
}
frame.setContentPane(panel1);
//frame.setSize(500, 500);
frame.setExtendedState(frame.getExtendedState() | JFrame.MAXIMIZED_BOTH);
frame.pack();
frame.setVisible(true);
}
}
And here is what I get:
If you want to have left-aligned buttons you can use:
JPanel linePanel = new JPanel(new FlowLayout(FlowLayout.LEFT));

BoxLayout refuses to honor preferred size of JButton

I have been working on a small project that is supposed to simulate a gambling game. Unfortunately, I ran into some odd issues while working with BoxLayout. To the best of my knowledge, LayoutManagers usually honor any component's preferred size. However, in the below code, BoxLayout does not.
Here is my code thus far:
import java.awt.*;
import javax.swing.*;
public class Main
{
public static void main(String[] args)
{
JFrame.setDefaultLookAndFeelDecorated(true);
JFrame frame = new JFrame("Suit-Up");
frame.setContentPane(makeGUI());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(900,450);
frame.setLocationRelativeTo(null);
frame.setResizable(false);
frame.setVisible(true);
}
public static JPanel makeGUI()
{
JPanel main = new JPanel();
main.setMinimumSize(new Dimension(900,450));
main.setBackground(Color.red);
JPanel infoPanel = new JPanel();
infoPanel.setLayout(new BoxLayout(infoPanel, BoxLayout.LINE_AXIS));
infoPanel.setPreferredSize(new Dimension(900,60));
infoPanel.setBackground(Color.green);
main.add(infoPanel);
JPanel infoText = new JPanel();
infoText.setLayout(new BoxLayout(infoText, BoxLayout.PAGE_AXIS));
infoPanel.add(infoText);
JPanel moneyText = new JPanel();
moneyText.setLayout(new BoxLayout(moneyText, BoxLayout.LINE_AXIS));
infoText.add(moneyText);
JPanel lastGameText = new JPanel();
lastGameText.setLayout(new BoxLayout(lastGameText, BoxLayout.LINE_AXIS));
infoText.add(lastGameText);
JButton playAgain = new JButton("Play Again ($20)");
playAgain.setPreferredSize(new Dimension(200,60));
infoPanel.add(playAgain);
JButton finish = new JButton("End Session");
finish.setPreferredSize(new Dimension(200,60));
infoPanel.add(finish);
JPanel cardPanel = new JPanel();
cardPanel.setLayout(new BoxLayout(cardPanel, BoxLayout.LINE_AXIS));
main.add(cardPanel);
return main;
}
}
Despite specifying preferred sizes for both JButtons, they do not change their sizes. I have tried setMaximumSize() and setMinimumSize() as well, but neither had any effect.
Am I overlooking something obvious, or is this a limitation of BoxLayout?
"To the best of my knowledge, LayoutManagers usually honor any component's preferred size" - That's actually not true. preferred/min/max size are just "hints" that layout managers MAY use to determine how best to layout there contents. Layout managers are allowed to simply ignore them if they want to.
From the JavaDocs
BoxLayout attempts to arrange components at their preferred widths
(for horizontal layout) or heights (for vertical layout). For a
horizontal layout, if not all the components are the same height,
BoxLayout attempts to make all the components as high as the highest
component. If that's not possible for a particular component, then
BoxLayout aligns that component vertically, according to the
component's Y alignment. By default, a component has a Y alignment of
0.5, which means that the vertical center of the component should have the same Y coordinate as the vertical centers of other components with
0.5 Y alignment.
Similarly, for a vertical layout, BoxLayout attempts to make all
components in the column as wide as the widest component. If that
fails, it aligns them horizontally according to their X alignments.
For PAGE_AXIS layout, horizontal alignment is done based on the
leading edge of the component. In other words, an X alignment value of
0.0 means the left edge of a component if the container's ComponentOrientation is left to right and it means the right edge of
the component otherwise.

Java Swing - JPanel and GridLayout Margins/Padding

I'm working on building a chess game in Java, and I'm currently having a bit of trouble getting the GUI exactly the way I want it with Swing. I'm using a GridLayout to organize a grid of 8x8 ChessButtons (which override the JButton so that I can store extra information inside of them such as coordinates). Originally, the ChessButtons wouldn't appear unless I moused over them, but I solved that problem by placing each ChessButton inside a separate JPanel and setting each button's setPreferredSize() to a set height and width.
Now, my problem is that there seems to be a small margin or padding above (and/or below?) each button. I've made sure to set setHgap(0) and setVgap(0) for the GridLayout, so I'm pretty sure the mysterious margin is coming from either the buttons or the JPanels. But, I can't seem to get rid of them, and they seem to be causing each ChessButton to shift a little bit up/down whenever I mouse of them.
I realize this description of the problem might be a little hard to visualize, so I've taken a screenshot (using JButtons rather than ChessButtons so the gaps are slightly easier to recognize): http://img3.imageshack.us/img3/6656/jbuttonmargins.png
Here is the code I used to initialize each ChessButton:
chessBoard = new JPanel(new GridLayout(8, 8, 0, 0));
chessBoard.setBorder(BorderFactory.createEmptyBorder());
for (int i = 0; i <= 65; i++) {
//Create a new ChessButton
ChessButton button = new ChessButton("hi");
button.setBorder(BorderFactory.createEmptyBorder());
button.setPreferredSize(new Dimension(75, 75));
button.setMargin(new Insets(0, 0, 0, 0));
//Create a new JPanel that the ChessButton will go into
JPanel buttonPanel = new JPanel();
buttonPanel.setPreferredSize(new Dimension(75, 75));
buttonPanel.setBorder(BorderFactory.createEmptyBorder());
buttonPanel.add(button);
//Add the buttonPanel to the grid
chessBoard.add(buttonPanel);
}
So, how can I get rid of these vertical spaces between buttons? I'm relatively new to Swing, so I'm sorry if the answer is extremely obvious, but I'd appreciate any help anyone might have to offer! Thanks in advance!
Don't add an empty border; do use setBorderPainted(false).
Addendum: As #camickr notes, the panel's layout may include default gaps. The example below uses no-gap GridLayout accordingly.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
/** #see http://stackoverflow.com/questions/4331699 */
public class ButtonBorder extends JPanel {
private static final int N = 8;
private static final int SIZE = 75;
public ButtonBorder() {
super(new GridLayout(N, N));
this.setPreferredSize(new Dimension(N * SIZE, N * SIZE));
for (int i = 0; i < N * N; i++) {
this.add(new ChessButton(i));
}
}
private static class ChessButton extends JButton {
public ChessButton(int i) {
super(i / N + "," + i % N);
this.setOpaque(true);
this.setBorderPainted(false);
if ((i / N + i % N) % 2 == 1) {
this.setBackground(Color.gray);
}
}
}
private void display() {
JFrame f = new JFrame("ButtonBorder");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new ButtonBorder().display();
}
});
}
}
Originally, the ChessButtons wouldn't appear unless I moused over them, but I solved that problem by placing each ChessButton inside a separate JPanel and setting each button's setPreferredSize() to a set height and width
That is not the proper solution. There is no reason to use a JPanel to hold the buttons. In fact, this is probably the cause of the problem. The buttons should show up when you add them to a GridLayout. If they don't show up its probably because you added the buttons to the GUI after making the GUI visible. Components should be added to the GUI BEFORE it is made visible.
Now, my problem is that there seems to be a small margin or padding above (and/or below?) each button
I don't understand why there also isn't a horizontal gap. When you create a JPanel, by default it uses a FlowLayout which also contains a horizontal/vertical gap of 5 pixels. So I understand why you might have the vertical gap of 10 pixels. I don't understand why there is no horizontal gap.
If you need more help post your SSCCE demonstrating the problem. And the SSCCE should use regular JButtons. Get the basics working with standard components before you start playing with custom components. That way you know if the problem is with your custom code or not.
Try adding chessBoard.setPreferredSize(600, 600) to create a JPanel for the board that only has room to fit the buttons (8 buttons each way * 75 size each way on the buttons).

Categories