Java Swing - JPanel and GridLayout Margins/Padding - java

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).

Related

BoxLayout artifact at 125% display scaling

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?

Java swing GUI absolute positioning

I know that absolute positioning is not recommended, but I need to show my labels randomly scattered as well as randomly changing their positions.
I have researched how to use setBounds but it doesn't seem to work. The following code shows the labels in a Flow Layout, and when I use setLayout(null) it shows a blank frame.
public class GUI extends JFrame{
device mobiles[];
device station;
JPanel pane= new JPanel();
public GUI()
{
setTitle("communication is Key");
setSize(1000, 1000);
setResizable(false);
setDefaultCloseOperation(EXIT_ON_CLOSE);
pane.setBackground(Color.WHITE);
int x=0; int y=0;
mobiles= new device[10];
for (int i = 0; i < 10; i++) {
x=randInt();
y=randInt();
mobiles[i]= new device(1,x,y);
pane.add(mobiles[i]);
}
x=randInt();
y=randInt();
station = new device(0,x,y);
pane.add(station);
this.add(pane);
}
and this is class "devices" that extends JLabel
public class device extends JLabel{
ImageIcon mob = new ImageIcon("mob.png");
ImageIcon tow = new ImageIcon("tower.png");
public device(int num, int x, int y)
{ if(num==1)
this.setIcon(mob);
else this.setIcon(tow);
this.setBounds(x, y, 3, 7);
}
}
any help in finding out what the problem is, would be be appreciated.
The following code shows the labels in a Flow Layout, and when I use setLayout(null) it shows a blank frame.
The layout manager sets the size and location of the component.
If you don't use the layout manager, then you are responsible for set the size and location of each component.
Typically I would set the size to equal to the components preferred size.
Also, did you display the x/y value that are randomly generated? Maybe the values are larger than the size of the panel.
and when I use setLayout(null) it shows a blank frame.
What layout is set to null? The panel of the frame. Your code doesn't use the above method. Post the code that you use to cause the problem. We don't want to guess what you may or may not be doing.
thanks to #CasparNoree ... the answer suggested was to initialize the Japnel from the start:
JPanel pane = new JPanel(null);
When you set the layout to null you can set the bounds manually with coordinates.
JFrame jf = new JFrame();
JPanel p = new JPanel();
JButton jb = new JButton();
// this is where you make it so setting the bounds actually does something
p.setLayout(null);
jb.setBounds(100,100,100,100);
p.add(jb);
jf.add(p);
jf.setVisible(true);

Colouring in a JPanel using mouseClicked - Error

Good day,
I might've been a bit vague with the title of my question, but I hope this will explain. The scenario is quite basic - I have a JFrame, in which I have an array of JPlanes. The idea is that when I click on one of them, upon clicking in should become black. Here is the code:
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.*;
import javax.swing.border.Border;
public class PixelArt {
JFrame frame;
Border blackline;
JPanel squares[][] = new JPanel[100][100];
int x;
int y;
public PixelArt() {
frame = new JFrame("Pixel Art");
frame.setSize(1000, 1000);
frame.setLayout(new GridLayout(100, 100));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
for (int i = 0; i < 100; i++) {
for (int j = 0; j < 100; j++) {
x = i;
y = j;
squares[i][j] = new JPanel();
squares[i][j].setBorder(BorderFactory.createDashedBorder(null));
squares[i][j].addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
x = e.getX();
y = e.getY();
squares[x][y].setBackground(Color.black);
}
});
frame.add(squares[i][j]);
}
}
frame.setVisible(true);
}
public static void main(String[] args) {
new PixelArt();
}
}
The actual problem is that this code does not do what I explained above. It does color in one of the JPlane's black when clicked, but within a 9x9 area starting from the upper corner of the grid. I do not have an explanation for this. The problem seems to be in the following 2 lines:
x = e.getX();
y = e.getY();
One of my guesses is that I have some kind of an offset in the coordinate system, but then this does not explain why regardless on which JPanel I press, the JPanels colored are only in the upper 9x9 area.
Does anyone have a clue how I could fix the problem I described above? If something is unclear in my explanation, please ask. Thank you in advance.
Here is a screenshot of the working code:
you can detect clicked panel using event.getSource() like follow example code
public void mouseClicked(MouseEvent e) {
JPanel panel = (JPanel)e.getSource();//
panel.setBackground(Color.black);
}
the problem is you can't use x,y directly as indexes to array element. because panels has a width and borders too.if you go with x y you have to make some mathematics logic. for example you click middle of your first jpanel in the grid so let's assume x and y coordinate is about 10px but in your code you call [10][10] Janel but actually you should call [0][0].
also as #Cr0w3 says you add listners to all panels.so if you clicked middle of first grid cell or last grid cell there is no difference in x,y.
but if you make a mathematical logic to detect clicked element you need to take in to account your frame/main panel width(also have to update when risize) and border thickness.
also do you really want to do this using 10000 panels ? you may need use a one panel and override paint component method.10000 panels isn't effective for this kind of thing.if you resize or click quickly on panels you will see it take a lot of time. so you may need to draw graphics on a jpanel .see this example
I think you should not add the listener to the panels themselves, but to the frame.
Because the X and Y coordinates might be relative to the panel size (e.g., upper corner of the panel returns 1/1, thus you apply the color to the panel at [1][1] even though you click the panel at [50][50].
If you use the frame for listening at the point you will receive 50/50 as coordinates.
Unfortunately I cannot comment, because of my low reputation but I hope I could help a little.
If you want to add a listener to the panel do not use the coordinates and just apply color to the clicked panel without listening to the coordinates. Using e.getSource() should help you in that case.

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).

Unable to leave vertical gap

import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import java.net.URI;
class MainPageTypo {
JFrame fr;
JButton easy, medium, tough;
JLabel Contact;
MainPageTypo() {
buildGUI();
hookUpEvents();
}
public void buildGUI() {
fr = new JFrame("TypoMaster");
JPanel mainP = new JPanel();
mainP.setLayout(new FlowLayout());
JPanel LevelPanel = new JPanel();
LevelPanel.setLayout(new GridLayout(3, 0, 50, 50));
easy = new JButton("Easy");
medium = new JButton("Medium");
tough = new JButton("Tough");
Contact = new JLabel("Visit my Blog");
fr.add(mainP);
LevelPanel.add(easy);
LevelPanel.add(medium);
LevelPanel.add(tough);
LevelPanel.setBackground(Color.magenta);
LevelPanel.setBorder(BorderFactory.createEmptyBorder(50, 50, 50, 50));
mainP.add(LevelPanel);
mainP.setBackground(Color.lightGray);
fr.setSize(500, 500);
fr.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
fr.setVisible(true);
// fr.setResizable(false);
}
public void hookUpEvents() {
}
public static void main(String args[]) {
new MainPageTypo();
}
}
This is my complete code.I want to leave vertical space from top of JPanel().I am using LevelPanel.setBorder(BorderFactory.createEmptyBorder(50,50,50,50));
but unable to get vertical gap.How can i get this?
From what you are saying I am only guessing that you might be after something you get by setting the border on the main panel:
mainP.setBorder(BorderFactory.createEmptyBorder(50, 50, 50, 50));
Please give us more details. Because you are getting the gap. Maybe draw a quick and nasty picture. :)
Recommendation
Please follow Java naming conventions, i.e. variables names should start from lowercase letter.
Basically, you have to understand which part of the puzzle is responsible for what
setting a Border on a container (here levelPanel) adds space-requirement to the container itself (as #Boro already explained): the LayoutManager apply to that container will layout the children of the container only inside the insets as requested by the border. That's what you are seeing in levelPanel, the red above the first button, below the last button (and to the sides of all buttons)
setting x/y gap properties in a LayoutManager which support this, has effects that are entirely at the decision of the manager itself, no way around reading the api doc of the concrete manager.
API doc for GridLayout:
* In addition, the horizontal and vertical gaps are set to the
* specified values. Horizontal gaps are placed between each
* of the columns. Vertical gaps are placed between each of
* the rows.
API doc for FlowLayout:
* #param hgap the horizontal gap between components
* and between the components and the
* borders of the <code>Container</code>
* #param vgap the vertical gap between components
* and between the components and the
* borders of the <code>Container</code>
From your code, I guess you expected to achieve the GridLayout to have the same gap-behaviour as the FlowLayout :-)
As the LayoutManager of the levelPanel's parent (parent == mainP) is FlowLayout, you can - as an alternative to setting the a Border to mainP - set the gap of the FlowLayout:
mainP.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 50));

Categories