Width of components in SpringLayout after UI-Update equals 0 - java

TL;DR
On different DPI settings, components of my application can overlap. To get rid of that I want to check the components width, which always equals 0 after updating the UI. Simplyfied code at the end of the question.
The base problem is that on different DPI settings different parts of my program is the one that reaches farthest to the right. On higher DPI its a dynamically generated label and on lower DPI a fixed width textfield.
I am working with a SpringLayout to deal with possible changing DPI settings. With the above described I have the problem that on specific settings components overlap.
To overcome that, I wanted to check whether the left edge of the one component is further left than the right edge of the other component +10 pixels for more space.
if (labelRight.getX() < (labellist.get(0).getX() + labellist.get(0).getWidth+10)){
//Use one SpringLayout setting for labelRight
} else //Use another SpringLayout setting for labelRight
I assume the problem is that I check the width of the components after they were generated. The user may choose from different settings which will change parts of the panels content. labellist contains the rightmost labels from that area.
I made up a small testprogram to show the problem width the width check:
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SpringLayout;
import javax.swing.UIManager;
public class testsforSO extends JFrame {
private static final long serialVersionUID = -857761460788731909L;
ArrayList<JLabel> lbls = new ArrayList<>();
int lblCount = 6;
JPanel panel = new JPanel();
SpringLayout sl_panel = new SpringLayout();
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
testsforSO frame = new testsforSO();
frame.pack();
frame.setVisible(true);
frame.setSize(500, 500);
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public testsforSO() {
getContentPane().add(panel);
updateUI();
JButton incr = new JButton("Increment here!");
incr.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
for (int i=0; i<=lbls.size()-1; i++){
panel.remove(lbls.get(i));
}
lblCount++;
lbls.clear();
updateUI();
panel.repaint();
panel.revalidate();
System.out.println("Width of slimest label: " + lbls.get(0).getWidth());
System.out.println("Width of widest label : " + lbls.get(lbls.size()-1).getWidth());
}
});
panel.add(incr);
sl_panel.putConstraint(SpringLayout.NORTH, incr, 10, SpringLayout.NORTH, panel);
sl_panel.putConstraint(SpringLayout.EAST, incr, -10, SpringLayout.EAST, panel);
panel.setLayout(sl_panel);
}
private void updateUI(){
for (int i = 0; i <= lblCount; i++) {
String abc = new String();
for (int j = 0; j <= i; j++) {
abc += "xx";
}
JLabel bfrLbl = new JLabel(abc);
lbls.add(bfrLbl);
panel.add(bfrLbl);
}
for (int i = 0; i <= lbls.size() - 1; i++) {
if (i > 0)
sl_panel.putConstraint(SpringLayout.NORTH, lbls.get(i), 8, SpringLayout.SOUTH, lbls.get(i - 1));
else
sl_panel.putConstraint(SpringLayout.NORTH, lbls.get(i), 8, SpringLayout.NORTH, panel);
}
}
}
When pressing the button, the UI gets updated with one more label at the bottom. The SpringLayout is fitted accordingly and the panel gets revalidated after. However the output of the width is 0. Why is it and what can I do against that?

After further investigation I found a way to correctly read the width of the component:
sl_panel.getConstraint(SpringLayout.WIDTH, lbls.get(0)).getValue();
Does the trick. Seems to be a very massive line compared to
lbls.get(0).getWidth();
though.

Related

Setting JPanel background while using BoxLayout

I am developing a simple application, and am currently working on the gui design using Swing. In my program I have a JPanel which I would like to have a background color black like so:
JPanel playerPanel = new JPanel();
playerPanel.setOpaque(true);
playerPanel.setBackground(Color.BLACK);
This code works fine. However, the problem is when I assign a Layout Manager to the panel:
JPanel playerPanel = new JPanel();
playerPanel.setOpaque(true);
playerPanel.setBackground(Color.BLACK);
playerPanel.setLayout(new BoxLayout(playerPanel, BoxLayout.PAGE_AXIS));
For some reason, this makes the black color of the panel go away. This happens no matter where I place the .setLayout(...) command, before or after the .setBackground(...) and .setOpaque(true).
Why is this, and how do I work around this? How do I keep a black JPanel that uses a BoxLayout manager?
Verify that your panel's content is not obscuring the altered background. Resize the example below, which I've artificially enlarged, to see the effect.
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.util.Random;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
/**
* #see https://stackoverflow.com/a/57785802/230513
*/
public class BoxTest {
public static final Random random = new Random();
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
new BoxTest().create();
}
});
}
void create() {
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
panel.setBackground(Color.BLACK);
panel.add(Box.createVerticalGlue());
for (int i = 0; i < 4; i++) {
panel.add(new VariablePanel());
panel.add(Box.createVerticalGlue());
}
JFrame f = new JFrame("BoxTest");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(panel);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
f.setSize(f.getWidth(), f.getHeight() + 64);
}
}
/**
* A VariablePanel has a label showing its current size,
* as well as a variable number of text items.
*/
class VariablePanel extends JPanel {
private static final String text =
"Sed ut perspiciatis unde omnis iste natus error sit.";
private final JLabel sizeLabel = new JLabel("Size:");
public VariablePanel() {
this.setLayout(new GridLayout(0, 1));
this.add(sizeLabel);
int count = BoxTest.random.nextInt(5) + 1;
for (int i = 0; i < count; i++) {
this.add(new JLabel(text));
}
this.addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent e) {
int w = e.getComponent().getWidth();
int h = e.getComponent().getHeight();
sizeLabel.setText("Size: " + w + "\u00d7" + h);
}
});
}
}
Swing components (except JLabel) are opaque by default. This means:
you don't need playerPanel.setOpaque(true)
most components you add to the panel will be opaque and cover the background of your playerPanel.
Also, the BoxLayout respects the maximum size of any component you add to the panel. So if you add a component:
like a JButton which has a defined maximum size, you will see the button on top of the playerPanel and the background will surround the button.
like a JPanel, which does not have a defined maximum size, the panel will be resized to fill the entire area of the playerPanel and you won't see the background of the playerPanel.
If you want to see the background of the playerPanel show through a component added to the playerPanel, then you need to use setOpaque(false) on the component. For example:
JPanel child = new JPanel();
child.setOpaque( false );
playerPanel.add( child );

Why is my JFrame window doing that?(check description)

I'm programming a cookie clicker game remake and when I scale the JFrame window, something white appears. It disappears as soon as you hover the cursor over the button(when refreshes) and I need to fix that, because it does even the same when you launch the game.
Here's a screenshot(UNSCALED | SCALED): http://s3.postimg.org/xomifomhf/bandicam_19.png
this is the whole code of this game:
package cookieclicker.tominocz;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Main {
public static int num1;
static Icon icon1 = new ImageIcon("cookie.png");
static JButton b1 = new JButton(icon1);
static JButton b2 = new JButton("You got " + num1 + " Cookies!");
#SuppressWarnings("serial")
public static void main(String[] args) {
File save = new File(".\\gamesave.cookieclicker");
if (save.exists()) {
loadGame();
}
JFrame f = new JFrame("Cookie Clicker Beta v0.1");
b2.setBackground(Color.cyan);
b2.setPreferredSize(new Dimension(10000, 14));
JPanel buttonPanel1 = new JPanel(new GridLayout(1, 1));
buttonPanel1.setBackground(Color.DARK_GRAY);
b2.setEnabled(false);
b2.setBorder(null);
buttonPanel1.add(b2);
JPanel buttonPanel2 = new JPanel(new GridLayout(1000, 1));
buttonPanel2.setBackground(Color.DARK_GRAY);
buttonPanel2.setEnabled(false);
buttonPanel2.add(new JButton("Grandma"));
buttonPanel2.add(new JButton(""));
buttonPanel2.add(new JButton(""));
JPanel east = new JPanel(new GridBagLayout());
JPanel north = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.anchor = GridBagConstraints.NORTH;
gbc.weighty = 1;
north.add(buttonPanel1, gbc);
east.add(buttonPanel2, gbc);
JPanel center = new JPanel() {
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
};
center.setBorder(BorderFactory.createLineBorder(Color.BLACK));
f.add(east, BorderLayout.EAST);
f.add(north, BorderLayout.NORTH);
f.add(center);
f.pack();
f.setSize(600, 400);
f.setVisible(true);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
b1.setBackground(Color.lightGray);
b1.setBorder(null);
f.add(b1);
b1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Object source = e.getSource();
if (source instanceof JButton) {
addCookies();
}
}
});
}
public static void addCookies() {
saveGame();
b2.setText("You got " + ++num1 + " Cookies!");
if (num1 == 1) {
b2.setText(" You got " + 1 + " Cookie! ");
} else {
b2.setText("You got " + num1 + " Cookies!");
}
System.out.println(num1);
}
public static void saveGame() {
try {
BufferedWriter writer = new BufferedWriter(new FileWriter(
".\\gamesave.cookieclicker"));
writer.write(String.valueOf(1 + num1));
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void loadGame() {
try (BufferedReader br = new BufferedReader(new FileReader(
".\\gamesave.cookieclicker"))) {
String SavedGame;
while ((SavedGame = br.readLine()) != null) {
num1 = Integer.parseInt(SavedGame);
b2.setText("You got " + num1 + " Cookies!");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
And also the other thing is, that the dark grey strip is hiding 3 buttons.
Don't count the 4th one, that's the one showing the ammount of cookies you have :).
Now where could the problem be?
You have two components sharing the CENTRE position of the frame's BorderLayout, center and b1.
b1, been the last component added, is getting the attention of the layout manager and is been laid out when the frame is resized, center is not and is remaining at the last size/position it was set to (because you called pack, which forced the frame to layout it's child components, but then you added b1 after it).
BorderLayout can only manage a single component at each of it's five pre-defined positions
Make a decision about who should be in the centre...
You should also have a read of Should I avoid the use of set(Preferred|Maximum|Minimum)Size methods in Java Swing? and stop messing with the preferred size of your components, let them make their own decisions in combination with appropriate layout managers
I'd also encourage you to move the content of your main method some where else (may be the class's constructor), this way, you fields won't need to be static and it solve a ton of other issues you might have in the future
First of all:
b2.setPreferredSize(new Dimension(10000, 14));
Don't specify preferred sizes for components. Each component is responsible for determining its own preferred size. Let the layout manager determine the size.
JPanel buttonPanel2 = new JPanel(new GridLayout(1000, 1));
Don't use random numbers when defining the GridLayout. If you want one column then just use: new GridLayout(0, 1). Now all components added will be displayed in the first row.
Now for your problem:
f.add(center);
f.pack();
You add an empty panel to the CENTER of the BorderLayout. Then you pack the frame so the panel now has a valid size.
f.add(b1);
But then you add a second component to the "CENTER". However BorderLayout will only manage the size of the last component added.
Swing will paint() the last component added first, so the button is painted, then the panel is painted over top of it.
If you move the mouse over the center, then the mouse event is passed to the button and the rollover logic is invoked so the button is painted.
If you resize the frame, the buttons size is recalculated by the layout manager and components are repainted. Again, the center panel is painted last so you see part of the button with the panel on top.
I don't know why you have the center panel so I can't make a specific suggestion other than to say, get rid of it. Again the main problem is you are trying to add two components to the center. Don't do this!

Drawing a gridline in the same part where I created the JPanel?

I need to build a gridline for a crossword puzzle. I wanted to know if I could do it in the same part where I created the JPanel and it's properties instead of doing it in methods?
class CrosswordWindow extends JFrame {
public JPanel crossPanel;
public CrosswordWindow() {
super("Crossword");
setLayout(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setResizable(false);
/*
bunch of buttons, labels, JLists etc.
*/
int size;
crossPanel = new JPanel();
crossPanel.setBounds(240, 40, 680, 360);
crossPanel.setBackground(Color.white);
crossPanel.setBorder(BorderFactory.createEtchedBorder(1, Color.lightGray, Color.lightGray));
add(crossPanel);
I'm having a tough time trying to draw the grid lines for the crossword.
First off, never do this: setLayout(null);. While null layouts and setBounds() might seem to Swing newbies like the easiest and best way to create complex GUI's, the more Swing GUI'S you create the more serious difficulties you will run into when using them. They won't resize your components when the GUI resizes, they are a royal witch to enhance or maintain, they fail completely when placed in scrollpanes, they look gawd-awful when viewed on all platforms or screen resolutions that are different from the original one.
But more to your original point, my suggestion is not to draw gridlines. Instead consider using a JPanel that holds a GridLayout, you can give it a horizontal and vertical gap of 1, and set is background to black if you want to show gridlines, and then fill it with either JLabels or JTextFields that accept one single char.
There is a multitide of ways that this might be done, one of the simplest I can think of would be to use a component of some kind for each cell and a series of MatterBorders as the "grid lines"
I also agree with HovercraftFullOfEels, avoid using null layouts, pixel perfect layouts are an illusion within modern UI design. There are too many factors which affect the individual size of components, none of which you can control. Swing was designed to work with layout managers at the core, discarding these will lead to no end of issues and problems that you will spend more and more time trying to rectify
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.GridLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.Border;
import javax.swing.border.MatteBorder;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public static class TestPane extends JPanel {
protected static final Border TOP_LEFT = new MatteBorder(1, 1, 1, 0, Color.DARK_GRAY);
protected static final Border TOP_RIGHT = new MatteBorder(1, 1, 1, 1, Color.DARK_GRAY);
protected static final Border BOTTOM_LEFT = new MatteBorder(0, 1, 1, 0, Color.DARK_GRAY);
protected static final Border BOTTOM_RIGHT = new MatteBorder(0, 1, 1, 1, Color.DARK_GRAY);
public TestPane() {
setLayout(new GridLayout(10, 10));
for (int row = 0; row < 10; row++) {
for (int col = 0; col < 10; col++) {
Border border = null;
int index = (row * 10) + col;
if (row == 0) {
if (col == 9) {
border = TOP_RIGHT;
} else {
border = TOP_LEFT;
}
} else if (row == 9) {
if (col == 9) {
border = BOTTOM_RIGHT;
} else {
border = BOTTOM_LEFT;
}
} else if (col == 9) {
border = BOTTOM_RIGHT;
} else {
border = BOTTOM_LEFT;
}
JLabel cell = new JLabel(" ");
cell.setBorder(border);
add(cell);
}
}
}
}
}
Take a look at How to Use Borders, Laying Out Components Within a Container and How to Use GridLayout for more details

how i resize when mouse over using swing?

I'm trying to resize a label on MouseEnter, but on MouseExit, I want it back to the previous state. How would I do this?
I want the label to be bigger when it is moused over, but when the mouse exits, the label will back to normal size.
Can anybody explain to me how to do that?
If it's possible, I want to see the resize slowly.
This is the code:
package kk
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridLayout;
import javax.swing.GroupLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
public class ScrollGroup extends JPanel {
private static final int N = 8;
private static final int NN = N * N;
private static final int GAP = 5;
private static final int SIZE = 100;
public ScrollGroup() {
this.setLayout(new GridLayout(N, N, GAP, GAP));
for (int i = 0; i < NN; i++) {
final JLabel label = new JLabel();
label.setOpaque(true);
label.setBackground(Color.getHSBColor((float) i / NN, 1, 1));
label.setPreferredSize(new Dimension(SIZE, SIZE));
this.add(label);
label.addMouseListener(new MouseAdapter() {
public void mouseEntered(MouseEvent e){
}
public void mouseExited(MouseEvent e) {
}
});
}
}
private void display() {
JFrame f = new JFrame("ScrollGroup");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JScrollPane sp = new JScrollPane(this);
GroupLayout layout = new GroupLayout(f.getContentPane());
f.setLayout(layout);
layout.setAutoCreateGaps(true);
layout.setAutoCreateContainerGaps(true);
layout.setHorizontalGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup().addComponent(sp)));
layout.setVerticalGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup().addComponent(sp)));
f.pack();
f.setSize(N * SIZE, N * SIZE);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new ScrollGroup().display();
}
});
}
}
I'm trying to resize a label in MouseEntred,
Define "resize".
You are adding your JLabels to a panel using a GridLayout. All the labels are already set to the maximum size permitted by the space available to the panel, so what do you expect the resize to do?
If you want it to appear that the label is getting bigger, then maybe you can assign a MatteBorder to each label. You can make the MatteBorder whatever size you want and then set the color equal to the background color of the panel.
If you want to animate then then you can use a Swing Timer. In the mouse#ntered you start the Timer. Every time the Timer fires you change the MatteBorder to be one less pixedl until the size is zero and you stop the Timer. On mouseExited, you just restore the default Border.
See the sections from the Swing tutorial on How to Use Timers and How to Use Borders for more information.

How to repaint GUI after a certain element was removed?

I'm making an app where a user would be able to add a button to the screen or remove it (I don't have those options implemented yet). So, for now I'm manually populating it with a for() loop and manually removing one of the buttons. My problem is that after the button has been removed (the removal action in the main()), there's just a blank spot. I want to be able to repaint the screen after I remove one of those buttons. In this example, index 2 (block #3) has been removed, leaving an empty space, where it was before... and I have no idea how to repaint it. I've tried validating or repainting from different places in the program with no success.
Here's the code (P.S. I'm sure my code is not the most efficient way to accomplish what I'm trying to and I'm using setLayout(null), which is not a preferred method, but for now I'm just trying to learn certain things and then expand on that to better myself and my code):
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.LineBorder;
class TestApp extends JFrame{
JFrame frame = new JFrame("Test Program");
ArrayList<JButton> grid = new ArrayList<JButton>();
private int w = 14;
private static int amount = 102;
private static int counter = 0;
//Default Constructor (sets up JFrame)
TestApp(){
frame.setLayout(null);
frame.setPreferredSize(new Dimension(1186, 880));
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
frame.setResizable(false);
paintGrid();
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public void newWindow()
{
JFrame select_win = new JFrame("Selected Frame");
JPanel select_panel = new JPanel();
select_panel.setPreferredSize(new Dimension(600, 800));
select_panel.setBackground(Color.ORANGE);
select_win.add(select_panel);
select_win.pack();
select_win.setResizable(false);
select_win.setVisible(true);
select_win.setLocationRelativeTo(null);
}
private void paintGrid()
{
for(int i = 0, y = 4; i < ((amount / w) + (amount % w)); i++, y += 104)
{
for(int j = 0, x = 4; j < w && (counter < amount); j++, x += 84)
{
addBlock(counter, x, y);
counter++;
}
}
}
//Adds a block
private void addBlock(int index, int x, int y){
int height = 100;
int width = 80;
grid.add(new JButton("counter: " + (counter + 1)));
(grid.get(index)).addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
newWindow();
}
});
}
});
(grid.get(index)).setBorder(new LineBorder(Color.BLACK));
(grid.get(index)).setBackground(Color.YELLOW);
(grid.get(index)).setVisible(true);
(grid.get(index)).setBounds(x, y, width, height);
frame.add(grid.get(index));
}
//Removes a block
private void removeBlock(int index){
frame.remove(grid.get(index));
grid.remove(index);
amount--;
counter--;
}
public static void main(String [] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
TestApp app = new TestApp();
//testing block removal
app.removeBlock(2);
}
});
}
}
The proper way would be: revalidate()
revalidate() method informs the layout manager that this component and all parents above it are marked as needing to be laid out. This means the Layout Manager will try to realign the components. Often used after removing components.
I would think that you will only know this if you are actually using Swing
As you said, is not good to use NullLayout. To fix your problem you only need to do two changes:
Change the layout to FlowLayout on the constructor, like this:
frame.setLayout(new FlowLayout());
Change the setBounds call to a setPreferredSize:
(grid.get(index)).setPreferredSize(new Dimension(width, height));
Now the FlowLayout will automatically align your items and you don't need to worry about it anymore.

Categories