I have 2 JPanels in a JTabbedPane and when update(g) is called on a panel inside the first panel (Its an animation) even if the second panel is the selected panel(i.e the one you can see) the updated panel appears on the screen. Why is this and how can i circumvent this behaviour?
The update() method of JComponent "doesn't clear the background," so you may need to do that explicitly. Typical examples of JTabbedPane don't usually require using update(). Perhaps an sscce showing your usage might help.
Addendum 1: It's not clear why you are calling update(). Below is a simple animation that does not exhibit the anomaly.
Addendum 2: See Painting in AWT and Swing: paint() vs. update(). You may want to use repaint() in actionPerformed() instead.
import java.awt.*;
import java.awt.event.*;
import java.util.Random;
import javax.swing.*;
public class JTabbedTest {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
//#Override
public void run() {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JTabbedPane jtp = new JTabbedPane();
jtp.setPreferredSize(new Dimension(320, 200));
jtp.addTab("Reds", new ColorPanel(Color.RED));
jtp.addTab("Greens", new ColorPanel(Color.GREEN));
jtp.addTab("Blues", new ColorPanel(Color.BLUE));
f.add(jtp, BorderLayout.CENTER);
f.pack();
f.setVisible(true);
}
});
}
private static class ColorPanel extends JPanel implements ActionListener {
private final Random rnd = new Random();
private final Timer timer = new Timer(1000, this);
private Color color;
private int mask;
private JLabel label = new JLabel("Stackoverflow!");
public ColorPanel(Color color) {
super(true);
this.color = color;
this.mask = color.getRGB();
this.setBackground(color);
label.setForeground(color);
this.add(label);
timer.start();
}
//#Override
public void actionPerformed(ActionEvent e) {
color = new Color(rnd.nextInt() & mask);
this.setBackground(color);
}
}
}
when update(g) is called on a panel
inside the first panel (Its an
animation)
Overriding the update(...) method is an old AWT trick and should never be used with Swing.
Read the section from the Swing tutorial on Custom Painting for the proper way to do this. And for animation read up on "How to Use Swing Timer" found in the tutorial as well.
Related
I am trying to make a panel showing a printed statement "Hello World!" and an OK button. Neither will show up on the panel and I have no idea why. I started with a block of code that was supposed to create just a blank popup. The blank popup worked great. I can't add the string or button and see them. I have tried calling paintComponent. I have tried adding the content to the panel. Does anyone know what I am missing?
Here is my code
package painting;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class SwingPaintDemo1 {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
private static class SwingPaintDemo extends JPanel{
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawString("Hello World!", 20,30);
}
}
private static void createAndShowGUI() {
System.out.println("Created GUI on EDT? "+
SwingUtilities.isEventDispatchThread());
JFrame f = new JFrame("Swing Paint Demo");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(250,250);
f.setVisible(true);
JButton okbutton = new JButton("OK");
ButtonHandler listener = new ButtonHandler();
okbutton.addActionListener(listener);
SwingPaintDemo displayPanel = new SwingPaintDemo();
JPanel content = new JPanel();
content.setLayout(new BorderLayout());
content.add(displayPanel, BorderLayout.CENTER);
content.add(okbutton, BorderLayout.SOUTH);
}
private static class ButtonHandler implements ActionListener{
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
}
}
You forgot to add the JPanel to the JFrame. Just add the following line at the bottom of your createAndShowGUI() method:
f.add(content);
I would also recommend moving your f.setVisible(true); line to the bottom of the method just to be safe. When you make the frame visible, the component tree is set up to take into account all the components added to the JFrame. If you add more components after that, you will need to do either manually revalidate the tree or do something that triggers an automatic revalidation. I'm assuming you're not revalidating your tree anywhere, so you should move f.setVisible(true); to after all the components are added.
I have been struggling with this for some time. At first, I only used ActionListener, then I added the paintComponent, but I have no idea what to put there. I read some tutorials and used their code as an example, but it still doesn't work. Right now, the end result is the same as it was without PaintComponent.
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
public class Scream extends JPanel {
private JButton button = new JButton("OK");
private Color screenColor;
private JPanel panel = new JPanel();
private JFrame frame;
private Dimension screenSize;
private ImageIcon image;
private JLabel label = new JLabel(image);
private int x;
private int y;
private boolean mouseClicked;
public Scream() {
button.addActionListener(new ActionListener(){
#Override
public void actionPerformed(ActionEvent e ) {
if (e.getSource() == button) {
mouseClicked = true;
frame.getContentPane().add(label);
frame.setSize(image.getIconWidth(), image.getIconHeight());
panel.repaint();
}
}
});
frame = new JFrame ("Existential angst");
screenColor = new Color(150, 100, 0);
panel.setBackground( screenColor );
frame.add(button, BorderLayout.PAGE_END);
frame.add(panel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(1300, 700);
frame.setVisible(true);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
image.paintComponent(this, g, 1300, 700);
}
public static void main (String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
Scream scream = new Scream();
}
});
}
}
If you are trying to dynamically add an image to a panel then you need to add the label to the panel. There is no need for any custom painting.
The basic code for adding components to a visible GUI is:
panel.add(...);
panel.revalidate();
panel.repaint();
Also, don't attempt to set the size of the frame to the size of the image. A frame contains a titlebar and borders. Instead you can use frame.pack();
I noticed a couple of issues:
image is never initialized to anything so it is null, effectively making the label empty. I assume maybe your example was just incomplete?
Once I initialized the image to something, your example still did not work. Turns out adding label without specifying any constraint basically does nothing (I assume since adding a component to a border layout without a constraint puts it in the center where panel already is). When I added the label to BorderLayout.NORTH, everything worked (though resizing the frame to the size of the image makes it only partially visible since the frame includes the OK button)
I want to put my background image at the very bottom in this frame, and the button on top. However the code I wrote below doesn't work. Can anyone see where the problems are?
Another thing is that even though I set the location for my button, it keep showing at the top center on the frame.
Please ignore the comment lines. (I was just guessing, and hoping them will work, but they don't apparently.)
public class Menu extends JFrame{
private JLayeredPane pane;
private JLayeredPane pane2;
public Menu(){
final JFrame f = new JFrame("Chinese Chess");
JButton play = new JButton("Play vs. AI");
f.setLayout(new FlowLayout());
f.setLocationRelativeTo(null);
f.setSize(800, 800);
f.setVisible(true);
f.setResizable(false);
//f.pack();
pane = new JLayeredPane();
pane2 = new JLayeredPane();
f.add(pane);
f.add(pane2);
//background image
JLabel background = new JLabel(new ImageIcon("res/img/background.png"));
background.setLocation(0, 0);
background.setSize(800, 800);
pane.add(background, JLayeredPane.FRAME_CONTENT_LAYER);
pane2.add(play, JLayeredPane.DEFAULT_LAYER);
//pane.moveToBack();
//button PlayAI
play.setLocation(500,500);
play.setPreferredSize(new Dimension(100,50));
//f.setLayout(new FlowLayout());
//frame menu
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//f.getContentPane().add(play);
play.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
new PlayAI();
}
});
}
public static void main(String[] args){
new Menu();
}
Problems/Solutions:
setLocation(...) and setBounds(...) types of calls are ignored by most layout managers. The only way to use them is to set the layout of the container to null via .setLayout(null);
But having said that, 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.
So in sum -- don't do this, don't use null layouts or setBounds, but rather nest JPanels, each using its own layout manager, and thereby create easy to maintain and decent GUI's.
If you want an image to be in the background, then draw it in a JPanel that you use as a container for your GUI components by drawing it in the JPanel's paintComponent(Graphics g) method as has been demonstrated in many many similar questions on this site -- I'll find you some of mine in a second.
If you add any JPanels on top of this image drawing JPanel, be sure that you can see through them by calling setOpaque(false) on these overlying JPanels. Otherwise you'll cover up the image.
Your code has two JFrames when only one is needed. Get rid of the one you don't use.
You call setVisible(true) too early on the JFrame, before components have been added to the GUI -- don't. Call it only after adding everything to the GUI so all display OK.
You're creating two JLayedPanes, and completely covering one by the other by adding them to the JFrame without understanding how the JFrame's BorderLayout handles added components.
I suggest that you not even use one JLayeredPane but instead draw in the JPanel as noted above, and use that as your container.
Your code looks to be opening a completely new GUI window when the play button is pressed, and if so, this can get annoying to the user fast. Consider swapping views instead with a CardLayout.
For example:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.*;
// extend JPanel so you can draw to its background
#SuppressWarnings("serial")
public class Menu2 extends JPanel {
private BufferedImage bgImage = null; // our background image
private JButton playButton = new JButton(new PlayVsAiAction("Play Vs. AI", KeyEvent.VK_P));
public Menu2(BufferedImage bgImage) {
this.bgImage = bgImage;
setLayout(new GridBagLayout()); // center our button
add(playButton);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (bgImage != null) {
g.drawImage(bgImage, 0, 0, this);
}
}
// to size our GUI to match a constant or the image.
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
// if you want to size it based on the image
if (bgImage != null) {
int width = bgImage.getWidth();
int height = bgImage.getHeight();
return new Dimension(width, height);
} else {
return super.getPreferredSize();
}
// if you want to size the GUI with constants:
// return new Dimension(PREF_W, PREF_H);
}
private class PlayVsAiAction extends AbstractAction {
public PlayVsAiAction(String name, int mnemonic) {
super(name); // have our button display this name
putValue(MNEMONIC_KEY, mnemonic); // alt-key to press button
}
#Override
public void actionPerformed(ActionEvent e) {
// TODO code to start program
}
}
private static void createAndShowGui() {
BufferedImage img = null;
String imagePath = "res/img/background.png";
try {
// TODO: fix this -- use class resources to get image, not File
img = ImageIO.read(new File(imagePath));
} catch (IOException e) {
e.printStackTrace();
System.exit(-1);
}
Menu2 mainPanel = new Menu2(img);
JFrame frame = new JFrame("Chinese Chess");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
createAndShowGui();
});
}
}
Apart from the solution above... you should create and launch your swing application this way:
public static void main(String[] args) {
//Schedule a job for the event-dispatching thread:
//creating and showing this application's GUI.
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
private static void createAndShowGUI() {
// Instantiate your JFrame and show it
}
I am new to Java. I am trying to make text appear on a JLabel after a virtual button is clicked. However, I can't seem to find a solution to this. When I use the if statement it won't work. How can I make the text appear after the button was pressed?
import java.awt.event.*;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JButton;
import javax.swing.JPanel;
import java.awt.Graphics;
public class autos extends JLabel implements ActionListener
{
private static final long serialVersionUID = 1L;
int now=0;
public autos(){
JLabel l=new JLabel("");
JFrame f=new JFrame("the title");
JPanel p=new JPanel();
JButton b=new JButton("click");
f.setBounds(400,500,400,500);
f.setVisible(true);
p.add(b);
f.add(p);
b.addActionListener(this);
p.setVisible(true);
p.add(l);
f.add(l);
if(now==1)
{
l.setText("hello");
l.setOpaque(true);
}
p.setBounds(200,200,200,200);
l.setBounds(100,100,100,100);
l.setOpaque(true);
f.setDefaultCloseOperation(f.EXIT_ON_CLOSE);
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
g.drawRect(200,300,89,90);
g.drawString("buv",80,80);
repaint();
}
public static void main(String[] args)
{
new autos();
}
#Override
public void actionPerformed(ActionEvent e) {
now=1;
System.out.println("worked");
System.out.println(now);
}
}
You are setting up your label in your constructor code, which executes before the event handler which sets the now variable to 1.
What you can do is to move this code:
l.setText("hello");
l.setOpaque(true);
To here:
#Override
public void actionPerformed(ActionEvent e) {
now=1;
System.out.println("worked");
System.out.println(now);
l.setText("hello");
l.setOpaque(true);
}
This is a minimally working example of updating the text on button click. See the comments in code for the variety of changes.
import java.awt.event.*;
import javax.swing.*;
/* There is no need to extend label here. */
// public class autos extends JLabel implements ActionListener
public class autos implements ActionListener {
private static final long serialVersionUID = 1L;
int now = 0;
// this is now a class attribute, accessible to any method of this class.
JLabel l;
public autos() {
// this no longer declares a local variable, but instead
// creates an instance of the class attribute.
l = new JLabel("");
JFrame f = new JFrame("the title");
JPanel p = new JPanel();
JButton b = new JButton("click");
f.setBounds(400, 500, 400, 500); // this needs fixing!
f.setVisible(true);
p.add(b);
f.add(p);
b.addActionListener(this);
p.setVisible(true);
p.add(l);
f.add(l);
p.setBounds(200, 200, 200, 200); // this needs fixing!
l.setBounds(100, 100, 100, 100); // this needs fixing!
l.setOpaque(true);
f.setDefaultCloseOperation(f.EXIT_ON_CLOSE);
}
/* I cannot tell what this was trying to achieve, but whatever it was,
this was the wrong way to go about it. Never call repaint() from within
the paintComponent method as this creates an infinite loop! */
/*
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawRect(200, 300, 89, 90);
g.drawString("buv", 80, 80);
repaint();
}
*/
public static void main(String[] args) {
// Swing GUIs should be created and updated on the EDT
new autos();
}
#Override
public void actionPerformed(ActionEvent e) {
/* This logic is questionable, but it denpends on what you are trying
to achieve here, something I'm not clear on. */
now = 1;
if (now == 1) {
l.setText("hello");
l.setOpaque(true);
}
System.out.println("worked");
System.out.println(now);
}
}
Edit
You added two comments at the setBounds part saying this needs fixing!. There I tried to resize the JPanel and JLabel but it's obvious it doesn't work.
How should I proceed here?
Here are some 'copy/paste comments' I regularly use:
Java GUIs have to work on different OS', screen size, screen resolution etc. using different PLAFs in different locales. As such, they are not conducive to pixel perfect layout. Instead use layout managers, or combinations of them along with layout padding and borders for white space.
See Should I avoid the use of set(Preferred|Maximum|Minimum)Size methods in Java Swing? (Yes.)
Provide ASCII art or a simple drawing of the intended layout of the GUI at minimum size, and if resizable, with more width and height.
Now, to expand on those comments for this use-case:
That is advice I offer for people trying to make a null layout, but it also applies here.
This is relevant because there is one component (the custom painted one, if it exists) that needs to #Override the getPreferredSize(..) method to return a size as a suggestion to the layout managers or for packing the top level container.
The 3rd comment is because it is hard to advise how to code this GUI without knowing the end effect that is required.
Of course, I should point out: Each SO thread is expected to be a single, specific question. I really should have told you to start a new question on the other matters, but let it slide for this edit.
I am trying to change the background color of my tabs in a JTabbedPane. I tried JTabbedPane.setBackgroudAt(0, Color.GRAY) and JTabbedPane.setBackgroud(Color.GRAY) and the foreground too, but nothing happens. I changed the background of the panel inside the tab, still nothing.
Edit 1: I'm using UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel"); if this can help with the solution
Edit 2: Link to a example, https://www.dropbox.com/s/0krn9vikvkq46mz/JavaApplication4.rar
You can change the background color of the tab using setBackgroundAt(), as shown here.
You can change the background color of the tab's content using setBackground(), as shown here. Typically you have to do this on the tab's content, as the enclosing JTabbedPane background color is obscured by the content.
If you still have trouble, please edit your question to include an sscce based on either example that exhibits the problem you envounter.
Addendum: Combining the methods is also possible:
import java.awt.*;
import java.awt.event.*;
import java.util.Random;
import javax.swing.*;
public class JTabbedTest {
private static JTabbedPane jtp;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jtp = new JTabbedPane();
jtp.setPreferredSize(new Dimension(320, 200));
jtp.addTab("Reds", new ColorPanel(0, Color.RED));
jtp.setBackgroundAt(0, Color.RED);
jtp.addTab("Greens", new ColorPanel(1, Color.GREEN));
jtp.setBackgroundAt(1, Color.GREEN);
jtp.addTab("Blues", new ColorPanel(2, Color.BLUE));
jtp.setBackgroundAt(2, Color.BLUE);
f.add(jtp, BorderLayout.CENTER);
f.pack();
f.setVisible(true);
}
});
}
private static class ColorPanel extends JPanel implements ActionListener {
private final Random rnd = new Random();
private final Timer timer = new Timer(1000, this);
private Color color;
private Color original;
private int mask;
private JLabel label = new JLabel("Stackoverflow!");
private int index;
public ColorPanel(int index, Color color) {
super(true);
this.color = color;
this.original = color;
this.mask = color.getRGB();
this.index = index;
this.setBackground(color);
label.setForeground(color);
this.add(label);
timer.start();
}
#Override
public void actionPerformed(ActionEvent e) {
color = new Color(rnd.nextInt() & mask);
this.setBackground(color);
jtp.setBackgroundAt(index, original);
}
}
}
most of method for JTabbedPane is protected in the API, and not accesible from Swing methods
have to look for Custom XxxTabbedPaneUI, override these methods, and could be accesible from outside
correct way would be to implement Custom Look & Feel only, part of them override JTabbedPane
example for Custom XxxTabbedPaneUI
You should consider using a Look and Feel that does what you want, or failing that, changing the default UIManger settings for a JTabbedPane:
UIManager.put("TabbedPane.background", Color.GRAY);
If you opt for the latter, it must be done before you create your GUI.
For more on this, please see Rob Camick's excellent blog on the subject: UIManager Defaults.
Edit: I was wrong. It should be:
UIManager.put("TabbedPane.unselectedBackground", Color.GRAY);
But note that this may not work with every Look and Feel.
It may be a problem that there is nothing added yet to the tab.
Try setting the content manager of the content panel to BorderLayout, adding a JPanel with BorderLayout. Center and then coloring that panel.