Make the animation faster when there are thousands of components - java

I am trying to hide a JSplitPane with animation. By hide, I mean to setDividerLocation(0) so its left component is invisible (technically it is visible, but with zero width):
public class SplitPaneTest {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
JPanel leftPanel = new JPanel(new BorderLayout());
leftPanel.setBorder(BorderFactory.createLineBorder(Color.green));
JPanel rightPanel = new JPanel(new GridLayout(60, 60));
for (int i = 0; i < 60 * 60; i++) {
// rightPanel.add(new JLabel("s"));
}
rightPanel.setBorder(BorderFactory.createLineBorder(Color.red));
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, rightPanel);
frame.add(splitPane);
JButton button = new JButton("Press me to hide");
button.addActionListener(e -> hideWithAnimation(splitPane));
leftPanel.add(button, BorderLayout.PAGE_START);
frame.setMaximumSize(new Dimension(800, 800));
frame.setSize(800, 800);
frame.setLocationByPlatform(true);
frame.setVisible(true);
});
}
private static void hideWithAnimation(JSplitPane splitPane) {
final Timer timer = new Timer(10, null);
timer.addActionListener(e -> {
splitPane.setDividerLocation(Math.max(0, splitPane.getDividerLocation() - 3));
if (splitPane.getDividerLocation() == 0)
timer.stop();
});
timer.start();
}
}
If you run it, will see that everything seems good, and the animation runs smooth.
However, in the real application the right of the JSplitPane is a JPanel with CardLayout and each card has a lot of components.
If you uncomment this line in order to simulate the number of components:
// rightPanel.add(new JLabel("s"));
and re-run the above example, you will see that the animation no longer runs smoothly. So, the question is, is is possible to make it smooth(-ier)?
I have no idea how to approach a solution - if any exists.
Based on my research, I registered a global ComponentListener:
Toolkit.getDefaultToolkit()
.addAWTEventListener(System.out::println, AWTEvent.COMPONENT_EVENT_MASK);
and saw the tons of events that are being fired. So, I think the source of the problem is the tons of component events that are being fired for each component. Also, it seems that components with custom renderers (like JList - ListCellRenderer and JTable - TableCellRenderer), component events are firing for all of the renderers. For example, if a JList has 30 elements, 30 events (component) will be fired only for it. It also seems (and that's why I mentioned it) that for CardLayout, events are taking place for the "invisible" components as well.
I know that 60*60 might sound crazy to you, but in a real application (mine has ~1500) as it makes sense, the painting is heavier.

I know that 60*60 might sound crazy to you, but in a real application (mine has ~1500) as it makes sense, the painting is heavier.
The layout manager is invoked every time the divider location is changed which would add a lot of overhead.
One solution might be to stop invoking the layout manager as the divider is animating. This can be done by overriding the doLayout() method of the right panel:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class SplitPaneTest2 {
public static boolean doLayout = true;
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
JPanel leftPanel = new JPanel(new BorderLayout());
leftPanel.setBorder(BorderFactory.createLineBorder(Color.green));
JPanel rightPanel = new JPanel(new GridLayout(60, 60))
{
#Override
public void doLayout()
{
if (SplitPaneTest2.doLayout)
super.doLayout();
}
};
for (int i = 0; i < 60 * 60; i++) {
rightPanel.add(new JLabel("s"));
}
rightPanel.setBorder(BorderFactory.createLineBorder(Color.red));
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, rightPanel);
frame.add(splitPane);
JButton button = new JButton("Press me to hide");
button.addActionListener(e -> hideWithAnimation(splitPane));
leftPanel.add(button, BorderLayout.PAGE_START);
frame.setMaximumSize(new Dimension(800, 800));
frame.setSize(800, 800);
frame.setLocationByPlatform(true);
frame.setVisible(true);
});
}
private static void hideWithAnimation(JSplitPane splitPane) {
SplitPaneTest2.doLayout = false;
final Timer timer = new Timer(10, null);
timer.addActionListener(e -> {
splitPane.setDividerLocation(Math.max(0, splitPane.getDividerLocation() - 3));
if (splitPane.getDividerLocation() == 0)
{
timer.stop();
SplitPaneTest2.doLayout = true;
splitPane.getRightComponent().revalidate();
}
});
timer.start();
}
}
Edit:
I was not going to include my test on swapping out the panel full of components with a panel that uses an image of components since I fell the animation is the same, but since it was suggested by someone else here is my attempt for your evaluation:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.awt.image.*;
public class SplitPaneTest2 {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
JPanel leftPanel = new JPanel(new BorderLayout());
leftPanel.setBorder(BorderFactory.createLineBorder(Color.green));
JPanel rightPanel = new JPanel(new GridLayout(60, 60));
for (int i = 0; i < 60 * 60; i++) {
rightPanel.add(new JLabel("s"));
}
rightPanel.setBorder(BorderFactory.createLineBorder(Color.red));
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, rightPanel);
frame.add(splitPane);
JButton button = new JButton("Press me to hide");
button.addActionListener(e -> hideWithAnimation(splitPane));
leftPanel.add(button, BorderLayout.PAGE_START);
frame.setMaximumSize(new Dimension(800, 800));
frame.setSize(800, 800);
frame.setLocationByPlatform(true);
frame.setVisible(true);
});
}
private static void hideWithAnimation(JSplitPane splitPane) {
Component right = splitPane.getRightComponent();
Dimension size = right.getSize();
BufferedImage bi = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = bi.createGraphics();
right.paint( g );
g.dispose();
JLabel label = new JLabel( new ImageIcon( bi ) );
label.setHorizontalAlignment(JLabel.LEFT);
splitPane.setRightComponent( label );
splitPane.setDividerLocation( splitPane.getDividerLocation() );
final Timer timer = new Timer(10, null);
timer.addActionListener(e -> {
splitPane.setDividerLocation(Math.max(0, splitPane.getDividerLocation() - 3));
if (splitPane.getDividerLocation() == 0)
{
timer.stop();
splitPane.setRightComponent( right );
}
});
timer.start();
}
}

#GeorgeZ. I think the concept presented by #camickr has to do with when you actually do the layout. As an alternative to overriding doLayout, I would suggest subclassing the GridLayout to only lay out the components at the end of the animation (without overriding doLayout). But this is the same concept as camickr's.
Although if the contents of your components in the right panel (ie the text of the labels) remain unchanged during the animation of the divider, you can also create an Image of the right panel when the user clicks the button and display that instead of the actual panel. This solution, I would imagine, involves:
A CardLayout for the right panel. One card has the actual rightPanel contents (ie the JLabels). The second card has only one JLabel which will be loaded with the Image (as an ImageIcon) of the first card.
As far as I know, by looking at the CardLayout's implementation, the bounds of all the child components of the Container are set during layoutContainer method. That would probably mean that the labels would be layed out inspite being invisible while the second card would be shown. So you should probably combine this with the subclassed GridLayout to lay out only at the end of the animation.
To draw the Image of the first card, one should first create a BufferedImage, then createGraphics on it, then call rightPanel.paint on the created Graphics2D object and finally dispose the Graphics2D object after that.
Create the second card such that the JLabel would be centered in it. To do this, you just have to provide the second card with a GridBagLayout and add only one Component in it (the JLabel) which should be the only. GridBagLayout always centers the contents.
Let me know if such a solution could be useful for you. It might not be useful because you could maybe want to actually see the labels change their lay out profile while the animation is in progress, or you may even want the user to be able to interact with the Components of the rightPanel while the animation is in progress. In both cases, taking a picture of the rightPanel and displaying it instead of the real labels while the animation takes place, should not suffice. So it really depends, in this case, on how dynamic will be the content of the rightPanel. Please let me know in the comments.
If the contents are always the same for every program run, then you could probably pre-create that Image and store it. Or even, a multitude of Images and store them and just display them one after another when the animation turns on.
Similarly, if the contents are not always the same for every program run, then you could also subclass GridLayout and precalculate the bounds of each component at startup. Then that would make GridLayout a bit faster in laying out the components (it would be like encoding a video with the location of each object), but as I am testing it, GridLayout is already fast: it just calculates about 10 variables at the start of laying out, and then imediately passes over to setting the bounds of each Component.
Edit 1:
And here is my attempt of my idea (with the Image):
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.IntBinaryOperator;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSplitPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class SplitPaneTest {
//Just a Timer which plays the animation of the split pane's divider going from side to side...
public static class SplitPaneAnimationTimer extends Timer {
private final JSplitPane splitPane;
private int speed, newDivLoc;
private IntBinaryOperator directionf;
private Consumer<SplitPaneAnimationTimer> onFinish;
public SplitPaneAnimationTimer(final int delay, final JSplitPane splitPane) {
super(delay, null);
this.splitPane = Objects.requireNonNull(splitPane);
super.setRepeats(true);
super.setCoalesce(false);
super.addActionListener(e -> {
splitPane.setDividerLocation(directionf.applyAsInt(newDivLoc, splitPane.getDividerLocation() + speed));
if (newDivLoc == splitPane.getDividerLocation()) {
stop();
if (onFinish != null)
onFinish.accept(this);
}
});
speed = 0;
newDivLoc = 0;
directionf = null;
onFinish = null;
}
public int getSpeed() {
return speed;
}
public JSplitPane getSplitPane() {
return splitPane;
}
public void play(final int newDividerLocation, final int speed, final IntBinaryOperator directionf, final Consumer<SplitPaneAnimationTimer> onFinish) {
if (newDividerLocation != splitPane.getDividerLocation() && Math.signum(speed) != Math.signum(newDividerLocation - splitPane.getDividerLocation()))
throw new IllegalArgumentException("Speed needs to be in the direction towards the newDividerLocation (from the current position).");
this.directionf = Objects.requireNonNull(directionf);
newDivLoc = newDividerLocation;
this.speed = speed;
this.onFinish = onFinish;
restart();
}
}
//Just a GridLayout subclassed to only allow laying out the components only if it is enabled.
public static class ToggleGridLayout extends GridLayout {
private boolean enabled;
public ToggleGridLayout(final int rows, final int cols) {
super(rows, cols);
enabled = true;
}
#Override
public void layoutContainer(final Container parent) {
if (enabled)
super.layoutContainer(parent);
}
public void setEnabled(final boolean enabled) {
this.enabled = enabled;
}
}
//How to create a BufferedImage (instead of using the constructor):
private static BufferedImage createBufferedImage(final int width, final int height, final boolean transparent) {
final GraphicsEnvironment genv = GraphicsEnvironment.getLocalGraphicsEnvironment();
final GraphicsDevice gdev = genv.getDefaultScreenDevice();
final GraphicsConfiguration gcnf = gdev.getDefaultConfiguration();
return transparent
? gcnf.createCompatibleImage(width, height, Transparency.TRANSLUCENT)
: gcnf.createCompatibleImage(width, height);
}
//This is the right panel... It is composed by two cards: one for the labels and one for the image.
public static class RightPanel extends JPanel {
private static final String CARD_IMAGE = "IMAGE",
CARD_LABELS = "LABELS";
private final JPanel labels, imagePanel; //The two cards.
private final JLabel imageLabel; //The label in the second card.
private final int speed; //The speed to animate the motion of the divider.
private final SplitPaneAnimationTimer spat; //The Timer which animates the motion of the divider.
private String currentCard; //Which card are we currently showing?...
public RightPanel(final JSplitPane splitPane, final int delay, final int speed, final int rows, final int cols) {
super(new CardLayout());
super.setBorder(BorderFactory.createLineBorder(Color.red));
spat = new SplitPaneAnimationTimer(delay, splitPane);
this.speed = Math.abs(speed); //We only need a positive (absolute) value.
//Label and panel of second card:
imageLabel = new JLabel();
imageLabel.setHorizontalAlignment(JLabel.CENTER);
imageLabel.setVerticalAlignment(JLabel.CENTER);
imagePanel = new JPanel(new GridBagLayout());
imagePanel.add(imageLabel);
//First card:
labels = new JPanel(new ToggleGridLayout(rows, cols));
for (int i = 0; i < rows * cols; ++i)
labels.add(new JLabel("|"));
//Adding cards...
final CardLayout clay = (CardLayout) super.getLayout();
super.add(imagePanel, CARD_IMAGE);
super.add(labels, CARD_LABELS);
clay.show(this, currentCard = CARD_LABELS);
}
//Will flip the cards.
private void flip() {
final CardLayout clay = (CardLayout) getLayout();
final ToggleGridLayout labelsLayout = (ToggleGridLayout) labels.getLayout();
if (CARD_LABELS.equals(currentCard)) { //If we are showing the labels:
//Disable the laying out...
labelsLayout.setEnabled(false);
//Take a picture of the current panel state:
final BufferedImage pic = createBufferedImage(labels.getWidth(), labels.getHeight(), true);
final Graphics2D g2d = pic.createGraphics();
labels.paint(g2d);
g2d.dispose();
imageLabel.setIcon(new ImageIcon(pic));
imagePanel.revalidate();
imagePanel.repaint();
//Flip the cards:
clay.show(this, currentCard = CARD_IMAGE);
}
else { //Else if we are showing the image:
//Enable the laying out...
labelsLayout.setEnabled(true);
//Revalidate and repaint so as to utilize the laying out of the labels...
labels.revalidate();
labels.repaint();
//Flip the cards:
clay.show(this, currentCard = CARD_LABELS);
}
}
//Called when we need to animate fully left motion (ie until reaching left side):
public void goLeft() {
final JSplitPane splitPane = spat.getSplitPane();
final int currDivLoc = splitPane.getDividerLocation(),
minDivLoc = splitPane.getMinimumDividerLocation();
if (CARD_LABELS.equals(currentCard) && currDivLoc > minDivLoc) { //If the animation is stopped:
flip(); //Show the image label.
spat.play(minDivLoc, -speed, Math::max, ignore -> flip()); //Start the animation to the left.
}
}
//Called when we need to animate fully right motion (ie until reaching right side):
public void goRight() {
final JSplitPane splitPane = spat.getSplitPane();
final int currDivLoc = splitPane.getDividerLocation(),
maxDivLoc = splitPane.getMaximumDividerLocation();
if (CARD_LABELS.equals(currentCard) && currDivLoc < maxDivLoc) { //If the animation is stopped:
flip(); //Show the image label.
spat.play(maxDivLoc, speed, Math::min, ignore -> flip()); //Start the animation to the right.
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
JPanel leftPanel = new JPanel(new BorderLayout());
leftPanel.setBorder(BorderFactory.createLineBorder(Color.green));
int rows, cols;
rows = cols = 60;
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
final RightPanel rightPanel = new RightPanel(splitPane, 10, 3, rows, cols);
splitPane.setLeftComponent(leftPanel);
splitPane.setRightComponent(rightPanel);
JButton left = new JButton("Go left"),
right = new JButton("Go right");
left.addActionListener(e -> rightPanel.goLeft());
right.addActionListener(e -> rightPanel.goRight());
final JPanel buttons = new JPanel(new GridLayout(1, 0));
buttons.add(left);
buttons.add(right);
frame.add(splitPane, BorderLayout.CENTER);
frame.add(buttons, BorderLayout.PAGE_START);
frame.setSize(1000, 800);
frame.setMaximumSize(frame.getSize());
frame.setLocationByPlatform(true);
frame.setVisible(true);
splitPane.setDividerLocation(0.5);
});
}
}

Related

GridLayout is showing odd behavior

I am trying to create a grid comprised of 100 squares. My code below is extremely buggy and I am not sure why.
import javax.swing.*;
import javax.swing.border.Border;
import java.awt.*;
public class snake extends JFrame
{
public static void main(String[] args)
{
Border whiteLine = BorderFactory.createLineBorder(Color.white);
//-----------FRAME
JFrame frame = new JFrame();
frame.setSize(1000,1000);
frame.setTitle("Snake");
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
frame.getContentPane().setBackground(Color.black);
frame.setVisible(true);
frame.setLayout(new GridLayout(10,10));
//-----------FRAME
//-----------PANELS
Dimension panelDimension = new Dimension(20,20);
int counter = 0;
JPanel[][] p = new JPanel[10][10];
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 10; j++)
{
p[i][j] = new JPanel();
//p[i][j].setPreferredSize(panelDimension);
p[i][j].setBackground(Color.red);
//p[i][j].setLocation(490,490);
p[i][j].setBorder(whiteLine);
p[i][j].setVisible(true);
frame.getContentPane().add(p[i][j]);
counter+=1;
}
}
System.out.println("counter: " + counter);
}
}
When I run the code like this it shows a grid comprised of 2 columns the first column has 7 rows and the second column has 6. Sometimes it even shows other incorrect numbers of columns and rows. I am not sure why it doesn't create a grid of 10 rows 10 columns.
You've got several problems including:
Calling setVisible(true) on the JFrame before adding components, before calling pack() on the top-level window. This can lead to wonky positioned components within our GUI's or even GUI's that remain empty
Not calling pack() on the JFrame after adding components and before setting it visible
Setting the size of the JFrame. Let the layout managers, containers and components do this for you (which is what calling pack() is for)
Setting it to a bad size, a "perfect square", one that ignores the menu bar that the OS adds,
...
For example:
package foo01;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.*;
#SuppressWarnings("serial")
public class SnakePanel extends JPanel {
private static final int CELL_WIDTH = 80;
private static final Dimension CELL_DIMENSION = new Dimension(CELL_WIDTH, CELL_WIDTH);
private static final int COLUMNS = 10;
private static final int GAP = 2;
private static final Color BG_COLOR = Color.WHITE;
private static final Color CELL_COLOR = Color.RED;
public SnakePanel() {
setBackground(BG_COLOR);
// add a white line around the grid
setBorder(BorderFactory.createEmptyBorder(GAP, GAP, GAP, GAP));
// create a grid with gaps that show the background (white) color
setLayout(new GridLayout(COLUMNS, COLUMNS, GAP, GAP));
for (int row = 0; row < COLUMNS; row++) {
for (int col = 0; col < COLUMNS; col++) {
JPanel cell = new JPanel(); // create a new cell
cell.setPreferredSize(CELL_DIMENSION); // cheating here. Better to override getPreferredSize()
cell.setBackground(CELL_COLOR);
add(cell);
// give the cell JPanel some simple behavior:
cell.addMouseListener(new MyMouse(col, row));
}
}
}
private class MyMouse extends MouseAdapter {
private int col;
private int row;
public MyMouse(int col, int row) {
this.col = col;
this.row = row;
}
#Override
public void mousePressed(MouseEvent e) {
System.out.printf("Mouse pressed row and column: [%d, %d]%n", row, col);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
// create the main JPanel
SnakePanel snakePanel = new SnakePanel();
// create the JFrame
JFrame frame = new JFrame("Snake");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// add the main JPanel to the JFrame
frame.add(snakePanel);
// pack the JFrame -- tells the layout managers to do their things
frame.pack();
// if we want to center the GUI:
frame.setLocationRelativeTo(null);
// only *now* do we display the GUI
frame.setVisible(true);
});
}
}
Some notes on the code:
Any code within the Runnable passed into the SwingUtilities.invokeLater(...) method is called on the Swing event thread, which is a wise thing to do when creating a Swing GUI
SwingUtilities.invokeLater(() -> {
// ....
});
First, create the main JPanel that is held by the JFrame:
SnakePanel snakePanel = new SnakePanel();
Then create the JFrame, add that JPanel and call pack(). The pack call tells the layout managers to do there thing, to lay out components within containers, to size things based on their preferred sizes and their layouts:
JFrame frame = new JFrame("Snake");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(snakePanel);
frame.pack();
if we want to center the GUI:
frame.setLocationRelativeTo(null);
only now do we display the GUI
frame.setVisible(true);

Java - Swing library - JComboBox on top of JLabel with image - invisible JLabel [duplicate]

This question already has answers here:
How to set a background picture in JPanel
(3 answers)
JFrame not showing a picture
(1 answer)
JPanel Image Background Overlapping Other JPanel
(2 answers)
More than one JPanel in a Frame / having a brackground Image and another Layer with Components on the top
(3 answers)
How to show a label as the frame background in java? [closed]
(3 answers)
Closed 5 years ago.
It's my first post here, trynna be quick.
I have a small JFrame window with JLabel with image as a background to said JFrame. JFrame also has 2 JButtons. To this point it worked well, until I decided to add JComboBox. Result of this action is now when I run my JFrame it shows blank, no background image from JLabel, no ComboBox visible, JButtons are shown tho. When I resize even a little this window, JLabel background image appears and everything is fine but it should be without resizing. What am I missing here? I'm very fresh with swing and am doing "game" for my java class project. Here is screenshots and code:
Running
After resizing
public class View_Startup extends JFrame {
JLabel lBackground;
JButton bStart,bExit;
JComboBox cbResolutions;
ImageIcon iBackground,iStart,iExit;
Image icon;
String resolutions[] = {"1280x720 HD","1366x768 WXGA","1600x900HD+","1920x1080 fullHD"};
public View_Startup() {
iBackground = new ImageIcon("xdddddddddd/resolution_background.jpg");
iStart = new ImageIcon("xddddddddddd/iStart2.png");
iExit = new ImageIcon("xdddddddddd/iExit2.png");
this.setSize(656,399);
this.setTitle("xddddddddd");
this.icon = Toolkit.getDefaultToolkit().getImage("C:xdddddddddd\\images.jpg");
this.setIconImage(icon);
this.setVisible(true);
this.setLayout(null);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice defaultScreen = ge.getDefaultScreenDevice();
Rectangle rect = defaultScreen.getDefaultConfiguration().getBounds();
int x=(int) rect.getMaxX();
int y=(int) rect.getMaxY();
this.setLocation(x/2-328,y/2-199);
bStart = new JButton(iStart);
bStart.setBounds(490,240,150,50);
bStart.setOpaque(false);
bStart.setContentAreaFilled(false);
//bStart.setBorderPainted(false);
add(bStart);
bExit = new JButton(iExit);
bExit.setBounds(490,300,150,50);
bExit.setOpaque(false);
bExit.setContentAreaFilled(false);
//bExit.setBorderPainted(false);
add(bExit);
cbResolutions = new JComboBox(resolutions);
cbResolutions.setBounds(490,180,150,50);
add(cbResolutions);
lBackground = new JLabel(iBackground);
lBackground.setBounds(0,0,640,360);
add(lBackground);
}
}
Several problems here:
You're using null layouts, a dangerous and kludgy thing to do
You're calling setVisible(true) on the JFrame before you've fully constructed it. Only call this after adding all components.
You're using a JLabel as a background image displayer, but not using it also as a container to hold components.
Instead consider
Using a JPanel's paintComponent method to display the background image
Add this JPanel to your JFrame's contentPane or make it the contentPane
Give it a decent layout (and if need be, add nested non-opaque JPanels each with their own layouts if you need complex layouts)
Call .setVisible(true) on your JFrame after adding all components.
Something like this:
can be created with this code:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.*;
public class LayoutExample extends JPanel {
private static final long serialVersionUID = 1L;
private BufferedImage background;
private JButton startButton = new JButton("Start");
private JButton exitButton = new JButton("Exit");
private JComboBox<String> combo = new JComboBox<>(new String[] {"1280 x 780 HD"});
public LayoutExample(BufferedImage background) {
this.background = background;
JPanel rightLowerPanel = new JPanel(new GridLayout(0, 1, 5, 5));
rightLowerPanel.setOpaque(false);
rightLowerPanel.add(combo);
rightLowerPanel.add(startButton);
rightLowerPanel.add(exitButton);
JPanel rightPanel = new JPanel(new BorderLayout());
rightPanel.setOpaque(false);
rightPanel.add(rightLowerPanel, BorderLayout.PAGE_END);
setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
setLayout(new BorderLayout());
add(rightPanel, BorderLayout.LINE_END);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (background != null) {
g.drawImage(background, 0, 0, this);
}
}
#Override
public Dimension getPreferredSize() {
if (background != null) {
int w = background.getWidth();
int h = background.getHeight();
return new Dimension(w, h);
}
return super.getPreferredSize();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
private static void createAndShowGui() {
String imgPath = "https://pbs.twimg.com/media/DRHUe_tV4AA96G4.jpg";
BufferedImage img = null;
try {
URL imageUrl = new URL(imgPath);
img = ImageIO.read(imageUrl);
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
LayoutExample mainPanel = new LayoutExample(img);
JFrame frame = new JFrame("LayoutExample");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
}

FlowLayout without linebreaks and/or in vertical direction

This is a follow-up to this question. Any viable answer will also answer that one.
What layout may be used with as little modification as possible to replicate the aligning nature of a FlowLayout, but never linebreak and also be available in a from-top-to-bottom flavour?
The obvious candidate, BoxLayout, does not work nicely with JPanels. Consider the following two examples:
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
class App
{
public static void main(String[] args)
{
JFrame window = new JFrame();
Box box = new Box(BoxLayout.Y_AXIS);
for(int i = 0; i < 5; ++i)
{
JLabel label = new JLabel("XX");
box.add(label);
}
box.add(Box.createVerticalGlue());
window.add(box);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setVisible(true);
}
}
This will properly display a vertical line of labels, beginning at the top and stretching as far towards the bottom as the labels take space. Good.
Modifying this, however, just a tiny bit:
public static void main(String[] args)
{
JFrame window = new JFrame();
Box box = new Box(BoxLayout.Y_AXIS);
for(int i = 0; i < 5; ++i)
{
JLabel label = new JLabel("XX");
JPanel panel = new JPanel();
panel.add(label);
box.add(panel);
}
box.add(Box.createVerticalGlue());
window.add(box);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setVisible(true);
}
This will stretch all components of the Box to the same height, placing the labels far away from each other. Bad.
Overriding the JPanel's getPreferredSize and getMaximumSize methods (with getMinimumSize) has no effect and would be a bad way to fix it, because it relied on the components rather than the container and its layout.
Addendum:
Here is an already pretty successful attempt using GroupLayout. Unfortunately it did not seem to occur to the designer that among DEFAULT_SIZE and PREFERRED_SIZE a choice MINIMUM_SIZE would have been a good idea.
Furthermore if it is possible to invert the sequence of GroupLayout.SequentialGroup, the API is no help to figure out how. I for one certainly have no clue how to even extend that class.
import java.awt.Component;
import java.awt.ComponentOrientation;
import java.awt.Container;
import javax.swing.GroupLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
class LineLayout extends GroupLayout
{
public LineLayout(Container owner, int axis)
{
super(owner);
this.direction = axis;
this.direction |= owner.getComponentOrientation() != ComponentOrientation.LEFT_TO_RIGHT
? LineLayout.RIGHT_TO_LEFT : LineLayout.LEFT_TO_RIGHT;
this.setupGroups();
}
public LineLayout(Container owner, int axis, int orientation)
{
super(owner);
this.direction = axis;
this.direction |= orientation;
this.setupGroups();
}
#Override // to replicate FlowLayout functionality : this method is called from owner.add
public void addLayoutComponent(Component component, Object constraints)
{
if(constraints == null)
{
// REALLY surprised that this works, considering that overriding the JPanel's
// getMaximumSize method with getPreferredSize had no effect
this.horizontal.addComponent(component, GroupLayout.DEFAULT_SIZE,
GroupLayout.DEFAULT_SIZE,
GroupLayout.PREFERRED_SIZE);
this.vertical.addComponent (component, GroupLayout.DEFAULT_SIZE,
GroupLayout.DEFAULT_SIZE,
GroupLayout.PREFERRED_SIZE);
}
// TODO: else
}
protected void setupGroups()
{
super.setAutoCreateGaps(false); // does nothing
if((this.direction & LineLayout.AXIS) == LineLayout.Y_AXIS)
{
this.horizontal = super.createParallelGroup();
this.vertical = (this.direction & LineLayout.ORIENTATION) == LineLayout.RIGHT_TO_LEFT
? this.createSequentialInvertedGroup() : super.createSequentialGroup();
}
else
{
this.horizontal = (this.direction & LineLayout.ORIENTATION) == LineLayout.RIGHT_TO_LEFT
? this.createSequentialInvertedGroup() : super.createSequentialGroup();
this.vertical = super.createParallelGroup();
}
super.setHorizontalGroup(this.horizontal);
super.setVerticalGroup (this.vertical);
}
// How!?
// protected LineLayout.SequentialInvertedGroup createSequentialInvertedGroup() { return new LineLayout.SequentialInvertedGroup(); }
protected GroupLayout.SequentialGroup createSequentialInvertedGroup() { return super.createSequentialGroup(); } // placeholder
protected int direction;
protected GroupLayout.Group horizontal;
protected GroupLayout.Group vertical;
// not sure how reliable the constant field values of BoxLayout are, whether it's smart to assume them unchanging over the ages
public static final int AXIS = 0b1;
public static final int X_AXIS = 0b0; // = BoxLayout.X_AXIS;
public static final int Y_AXIS = 0b1; // = BoxLayout.Y_AXIS;
public static final int ORIENTATION = 0b10;
public static final int LEFT_TO_RIGHT = 0b00; // also top to bottom
public static final int RIGHT_TO_LEFT = 0b10; // also bottom to top
// No idea how; only has "add" methods; cannot actually do anything with the added components!?
//protected static class SequentialInvertedGroup extends GroupLayout.SequentialGroup
//{}
}
class Applikation
{
public static void main(String[] args)
{
JFrame window = new JFrame();
JPanel box = new JPanel();
box.setLayout(new LineLayout(box, LineLayout.Y_AXIS));
for(int i = 0; i < 5; ++i)
{
JLabel label = new JLabel("XX");
JPanel panel = new JPanel();
panel.add(label);
box.add(panel);
}
window.add(box);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setVisible(true);
}
}
If you try this out, you will note that there are still notable border spaces between the "XX" labels, taking up about 2/3 of an extra label per gap. While already much better than in the BoxLayout example, I do not think there is a good way to improve this spacing further.
private static int MAX_HEIGHT = 40;
private static final Dimension DIMENSION = new Dimension(Integer.MAX_VALUE, MAX_HEIGHT);
public static void main(String[] args)
{
JFrame window = new JFrame();
Box box = new Box(BoxLayout.Y_AXIS){
private static final long serialVersionUID = 1L;
#Override
public Component add(Component comp) {
comp.setMaximumSize(DIMENSION);
return super.add(comp);
}
};
for(int i = 0; i < 5; ++i)
{
JLabel label = new JLabel("XX");
JPanel panel = new JPanel();
panel.add(label);
box.add(panel);
}
window.add(box);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.pack();
window.setVisible(true);
}
You are using a Box for adding your components into. And the Documentation says:
a Box can use only a BoxLayout.
Now lets look into the Documentation for BoxLayout. It says:
BoxLayout pays attention to a component's requested minimum, preferred, and maximum sizes.
Now we have found the reason for the different outputs of your two examples. In your first example you are adding JLabels directly to your Box. Since they have a default maximumSize depending on their content they are not scaled by the Box.
In your second example you are adding JPanels to the Box that have your JLabels in it. A JPanel does not have a default maximumSize and so it is scaled by the Box.
So if you want to get the same output with JPanels as without you need your JPanels to have a maximumSize depending on their content means the JLabels.
So you could set a maximumSize manually. Something like that:
panel.setMaximumSize(new Dimension(100,20));
Or you use a different LayoutManager with your JPanels. One that calculates its size depending on its components. One that pays attention to a component's requested minimum, preferred, and maximum sizes.
Does this sound familiar to you? Right its from the Documentation of BoxLayout. So try to use a BoxLayout on your JPanels and you will get exactly the same result as your first example.
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

Why won't my content pane show up on my JFrame?

This is my graphics class, but when I create and add a JPanel as a content pane to it nothing shows up. I have done many tests to see if my content pane is visible but it still will not show.
import java.awt.Color;
import java.awt.Font;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class GraphicsMain extends JFrame{
public static final long serialVersionUID = 7610350056926018727L;
static GraphicsMain frame = new GraphicsMain();
static final int WIDTH = 1024, HEIGHT = 768;
static Listener listener = new Listener();
public static void init() {
createGUI();
}
public static void createGUI() {
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.setFocusable(true);
frame.setLayout(null);
frame.setResizable(false);
frame.setSize(WIDTH, HEIGHT);
frame.setTitle("Game of Life");
frame.setContentPane(frame.createMainPanel());
frame.setVisible(true);
}
public JPanel createMainPanel() {
JPanel totalGUI = new JPanel();
totalGUI.setSize(HEIGHT, WIDTH);
totalGUI.setBackground(Color.red);
totalGUI.setLayout(null);
JPanel buttonPanel = createButtonPanel();
totalGUI.add(buttonPanel);
totalGUI.setVisible(true);
System.out.println("Is returning!");
return totalGUI;
}
public JPanel createButtonPanel() {
JPanel buttonPanel = new JPanel();
buttonPanel.setSize(WIDTH, HEIGHT);
buttonPanel.setLocation(0, 0);
Font buttonFont = new Font("Button Font", Font.PLAIN, 12);
JButton goButton = createButton("Go", WIDTH/16, HEIGHT/12, 10, 10, listener, buttonFont, Color.black);
buttonPanel.add(goButton);
JButton clearButton = createButton("Clear", WIDTH/16, HEIGHT/12, 10 + HEIGHT/12, 10, listener, buttonFont, Color.black);
buttonPanel.add(clearButton);
JButton exitButton = createButton("Exit", WIDTH/16, HEIGHT/12, 10 + 2*HEIGHT/12, 10, listener, buttonFont, Color.black);
buttonPanel.add(exitButton);
return buttonPanel;
}
public JButton createButton(String text, int width, int height, int x, int y, ActionListener listener, Font font, Color color) {
JButton button = new JButton(text);
button.setSize(width, height);
button.setLocation(x, y);
button.addActionListener(listener);
button.setFont(font);
button.setForeground(Color.red);
return button;
}
}
this class is called by
import javax.swing.SwingUtilities;
public class GameOfLifeMain {
static boolean running = true;
static int UPS = 60;
static GameOfLifeMain main = new GameOfLifeMain();
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
main.init();
long startTime = System.nanoTime();
double ns = 1000000000.0 / UPS;
double delta = 0;
long secondTimer = System.nanoTime();
while(running) {
long now = System.nanoTime();
delta += (now - startTime) / ns;
startTime = now;
while(delta >= 1) {
main.update();
delta--;
}
main.render();
if(System.nanoTime() - secondTimer > 1000000000) {
secondTimer += 1000000000;
}
}
}
});
}
public void init() {
GraphicsMain.init();
}
public void update() {
}
public void render() {
//GraphicsMain.render();
}
}
Your problems are (at least) two fold:
As #Radiodef astutely notes, you're calling a long-running loop on the Swing event thread. Since this thread is completely responsible for the rendering of your GUI and the interacting with the user, your GUI becomes completely frozen.
You are using null layouts which make you completely responsible for the positioning and size of all components added to the null-layout using component.
I suggest:
As #Radiodef suggests, do your long-running loop in a background thread such as a SwingWorker. This will allow easier interaction between your GUI and the background process. His link is a good one: Concurrency in Swing.
Use nested JPanels, each one using its own layout to achieve the layout desired, one that will run well on any platform and on any video card setting.
Better to override your drawing JPanels getPreferredSize(...) method to have it sized correctly.
Setting a JPanel to opaque via setOpaque(true) will not help since JPanels are already opaque by default.
Edit
I stand corrected on the last point per Jan Bodnar:
"However, the default value for this property on most standard JComponent subclasses (such as JButton and JTree) is look-and-feel dependent." For example, the JPanel of the GTK look and feel is not opaque by default.
Thanks, Jan
Call setOpaque(true) on your Panel, then the background will be painted. I also noticed that you mixed up WIDTH and HEIGHT when defining the size of your panel (this might be an error).

change jlabel with method

I am writing a small program that converts files, and I wanted to have a box pop up that asks the user to please wait while the program loops through and converts all the relevant files, but I am running into a small problem. The box that pops up should have a JLabel and a JButton, while the user is "waiting" I wanted to display a message that says please wait, and a disabled "OK" JButton, and then when its finished I wanted to set the text of the JLabel to let them know that It successfully converted their files, and give them a count of how many files were converted. (I wrote a method called alert that sets the text of the label and enables the button.) The problem is That while the program is running, the box is empty, the Label and the Button are not visible, when it finishes, label appears with the final text that I want and the button appears enabled. I am not sure exactly what is going on, I tried changing the modifiers of the JLabel and JButton several times but I cant seem to get it to work correctly. Here is the code for the box that pops up, any help is greatly appricated.
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class PleaseWait extends javax.swing.JFrame{
private static final int height = 125;
private static final int width = 350;
final static JLabel converting = new JLabel("Please Wait while I convert your files");
private static JButton OK = new JButton("OK");
public PleaseWait(){
// creates the main window //
JFrame mainWindow = new JFrame();
mainWindow.setTitle("Chill For A Sec");
mainWindow.setSize(width, height);
mainWindow.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
// creates the layouts//
JPanel mainLayout = new JPanel(new BorderLayout());
JPanel textLayout = new JPanel(new FlowLayout());
JPanel buttonLayout = new JPanel(new FlowLayout());
// Sets Text //
converting.setText("Please wait while I convert your files");
// disables button //
OK.setEnabled(false);
// adds to the layouts //
textLayout.add(converting);
buttonLayout.add(OK);
mainLayout.add(textLayout, BorderLayout.CENTER);
mainLayout.add(buttonLayout, BorderLayout.SOUTH);
// adds to the frame //
mainWindow.add(mainLayout);
// sets everything visible //
mainWindow.setVisible(true);
}
public static void alert(){
OK.setEnabled(true);
String total = String.valueOf(Convert.result());
converting.setText("Sucsess! " + total + " files Converted");
}
}
Okay here's the issue. You are extending the JFrame . That means your class IS a JFrame.
When you create the PleaseWait frame you don't do anything to it. This is the empty box you are seeing. You are instead creating a different JFrame in your constructor. Remove your mainWindow and instead just use this. Now all of your components will be added to your PleaseWait object. That should fix your blank box issue.
You need an application to create your frame first. This is a simple example of such application.
import javax.swing.UIManager;
import java.awt.*;
public class Application {
boolean packFrame = false;
//Construct the application
public Application() {
PleaseWait frame = new PleaseWait();
//Validate frames that have preset sizes
//Pack frames that have useful preferred size info, e.g. from their layout
if (packFrame) {
frame.pack();
}
else {
frame.validate();
}
//Center the window
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
Dimension frameSize = frame.getSize();
if (frameSize.height > screenSize.height) {
frameSize.height = screenSize.height;
}
if (frameSize.width > screenSize.width) {
frameSize.width = screenSize.width;
}
frame.setLocation((screenSize.width - frameSize.width) / 2, (screenSize.height - frameSize.height) / 2);
frame.setVisible(true);
frame.convert();
}
//Main method
public static void main(String[] args) {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
}
catch(Exception e) {
e.printStackTrace();
}
new Application();
}
}
You have to slightly modify your frame to add controls to the content pane. You can do some work after frame is created, then call alert.
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class PleaseWait extends JFrame {
private static final int height = 125;
private static final int width = 350;
final static JLabel converting = new JLabel();
private static JButton OK = new JButton("OK");
BorderLayout borderLayout1 = new BorderLayout();
JPanel contentPane;
int count;
public PleaseWait(){
contentPane = (JPanel)this.getContentPane();
contentPane.setLayout(borderLayout1);
this.setSize(new Dimension(width, height));
this.setTitle("Chill For A Sec");
this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
// creates the layouts//
JPanel mainLayout = new JPanel(new BorderLayout());
JPanel textLayout = new JPanel(new FlowLayout());
JPanel buttonLayout = new JPanel(new FlowLayout());
// Sets Text //
converting.setText("Please wait while I convert your files");
// disables button //
OK.setEnabled(false);
OK.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
// adds to the layouts //
textLayout.add(converting);
buttonLayout.add(OK);
mainLayout.add(textLayout, BorderLayout.CENTER);
mainLayout.add(buttonLayout, BorderLayout.SOUTH);
// adds to the frame //
contentPane.add(mainLayout);
}
public void convert(){
count = 0;
for (int i = 0; i <10; i++){
System.out.println("Copy "+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
count++;
}
alert();
}
public void alert(){
OK.setEnabled(true);
// String total = String.valueOf(Convert.result());
converting.setText("Sucsess! " + count + " files Converted");
}
}

Categories

Resources