I'm trying to change the style of the string based on the status of two check boxes. One is boldCheck and the other is italicCheck. However when I run the program it draws the default string in the paintComponent and the two checkboxes are not working? and there is actual problems in the code itself but the problem seems the way I made this code. Any help on fixing this issue will be much appreciated.
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
public class JavaTest {
public static void main(String[] args) {
JFrame window = new JFrame("HomeWork");
DrawMessage message = new DrawMessage();
window.add(message);
window.setVisible(true);
window.setSize(600,300);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
static class DrawMessage extends JPanel{
private static final JCheckBox boldCheck = new JCheckBox("Bold");
private static final JCheckBox italicCheck = new JCheckBox("Italic");
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setFont(new Font("TimesRoman", Font.PLAIN, 20));
g.setColor(Color.blue);
g.drawString("Welcome to java Programing", 40, 40);
setBackground(Color.yellow);
add(boldCheck);
add(italicCheck);
CheckBoxHandler handler = new CheckBoxHandler();
boldCheck.addItemListener(handler);
italicCheck.addItemListener(handler);
}
static private class CheckBoxHandler extends DrawMessage implements ItemListener{
private int valBold = Font.PLAIN;
private int valItalic = Font.PLAIN;
#Override
public void itemStateChanged(ItemEvent e) {
if(e.getSource() == boldCheck){
valBold = boldCheck.isSelected() ? Font.BOLD: Font.PLAIN;
}
if(e.getSource() == italicCheck){
valItalic = italicCheck.isSelected() ? Font.ITALIC : Font.PLAIN;
}
DrawMessage obj = new DrawMessage();
obj.setFont(new Font("TimesRoman", valBold + valItalic, 20));
}
}
}
}
I believe the following code achieves what you desire.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.WindowConstants;
public class BoldItal extends JPanel implements ItemListener, Runnable {
private JCheckBox boldCheckBox;
private JCheckBox italicCheckBox;
private JFrame frame;
public BoldItal() {
setPreferredSize(new Dimension(600, 300));
setBackground(Color.yellow);
}
#Override
public void run() {
showGui();
}
#Override
public void itemStateChanged(ItemEvent e) {
repaint();
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int style;
if (boldCheckBox.isSelected()) {
style = Font.BOLD;
if (italicCheckBox.isSelected()) {
style += Font.ITALIC;
}
}
else {
if (italicCheckBox.isSelected()) {
style = Font.ITALIC;
}
else {
style = Font.PLAIN;
}
}
g.setFont(new Font("TimesRoman", style, 20));
g.setColor(Color.blue);
g.drawString("Welcome to java Programing", 40, 40);
}
private JPanel createCheckBoxes() {
JPanel checkBoxesPanel = new JPanel();
boldCheckBox = new JCheckBox("Bold");
boldCheckBox.addItemListener(this);
italicCheckBox = new JCheckBox("Italic");
italicCheckBox.addItemListener(this);
checkBoxesPanel.add(boldCheckBox);
checkBoxesPanel.add(italicCheckBox);
return checkBoxesPanel;
}
private void showGui() {
frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.add(createCheckBoxes(), BorderLayout.PAGE_START);
frame.add(this, BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new BoldItal());
}
}
You want a repaint to occur whenever you change the selection in one of the check boxes. Hence you add an ItemListener to each check box. Then, in method paintComponent() you set the Font style according to the values of the two check boxes.
You don't change the background color, hence no need to set it in method paintComponent(). Just set it once. I chose to set it in the class constructor, but that is not mandatory. It can also be set, for example, in method showGui().
I made a few changes to your code.
I started the application with a call to the SwingUtilities invokeLater method. This method ensures that the Swing comp[onents are created and executed on the Event Dispatch Thread.
I created two JPanels, one for the checkbox buttons and one to draw the text. Generally, it's not a good idea to put Swing components on a drawing JPanel.
I made the valBold and valItalic fields global since they're set in the controller class and used in the drawing panel class.
The drawing panel draws the text. Period. The controller class will adjust the global Font fields.
I made the drawing panel class and the item listener class public inner classes.
Here's the complete runnable code.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class DrawStringGUI implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new DrawStringGUI());
}
private DrawMessage message;
private JCheckBox boldCheck;
private JCheckBox italicCheck;
private int valBold = Font.PLAIN;
private int valItalic = Font.PLAIN;
#Override
public void run() {
JFrame window = new JFrame("HomeWork");
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.add(createCheckBoxPanel(), BorderLayout.BEFORE_FIRST_LINE);
message = new DrawMessage();
window.add(message, BorderLayout.CENTER);
window.pack();
window.setVisible(true);
}
private JPanel createCheckBoxPanel() {
JPanel panel = new JPanel();
CheckBoxHandler handler = new CheckBoxHandler();
boldCheck = new JCheckBox("Bold");
boldCheck.addItemListener(handler);
panel.add(boldCheck);
italicCheck = new JCheckBox("Italic");
italicCheck.addItemListener(handler);
panel.add(italicCheck);
return panel;
}
private void repaint() {
message.repaint();
}
public class DrawMessage extends JPanel {
private static final long serialVersionUID = 1L;
public DrawMessage() {
this.setBackground(Color.YELLOW);
this.setPreferredSize(new Dimension(350, 100));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int style = valBold | valItalic;
g.setFont(new Font("TimesRoman", style, 20));
g.setColor(Color.BLUE);
g.drawString("Welcome to Java Programing", 40, 40);
}
}
public class CheckBoxHandler implements ItemListener {
#Override
public void itemStateChanged(ItemEvent event) {
if (event.getSource() == boldCheck) {
valBold = boldCheck.isSelected() ? Font.BOLD : Font.PLAIN;
}
if (event.getSource() == italicCheck) {
valItalic = italicCheck.isSelected() ? Font.ITALIC : Font.PLAIN;
}
DrawStringGUI.this.repaint();
}
}
}
Related
I have a button. I want to change the background after I click on it. My problem here is the button auto call paintComponent(). How can prevent this? I expect after clicking the button the button will be blue, but it will still be red.
package test;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class ButtonDemo extends JButton implements ActionListener{
public ButtonDemo() {
this.setText("BUTTON TEXT");
this.addActionListener(this);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
setBackground(Color.RED);
}
public static void main(String[] args){
JFrame frame = new JFrame();
JPanel contentPane = new JPanel();
frame.setContentPane(contentPane);
contentPane.add(new ButtonDemo());
frame.setSize(500, 500);
frame.setVisible(true);
}
#Override
public void actionPerformed(ActionEvent e) {
this.setBackground(Color.BLUE);
}
}
My personal gut feeling is that JButton is probably not suited to your desired goal.
Essentially, you want to control when and how the "selected" state of the piece is changed.
Personally, I would have some kind of controller which monitored the mouse events in some way (probably having the piece component delegate the event back to the controller) and some kind of model which control when pieces become selected, this would then notify the controller of the state change and it would make appropriate updates to the UI.
But that's a long process to setup. Instead, I'm demonstrating a simple concept where a component can be selected with the mouse, but only the controller can de-select. In this example, this will allow only a single piece to be selected
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.LineBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new GridLayout(5, 5));
ChangeListener listener = new ChangeListener() {
private PiecePane selectedPiece;
#Override
public void stateChanged(ChangeEvent e) {
if (!(e.getSource() instanceof PiecePane)) { return; }
PiecePane piece = (PiecePane) e.getSource();
// Want to ignore events from the selected piece, as this
// might interfer with the changing of the pieces
if (selectedPiece == piece) { return; }
if (selectedPiece != null) {
selectedPiece.setSelecetd(false);
selectedPiece = null;
}
selectedPiece = piece;
}
};
for (int index = 0; index < 5 * 5; index++) {
PiecePane pane = new PiecePane();
pane.addChangeListener(listener);
add(pane);
}
}
}
public class PiecePane extends JPanel {
private boolean selecetd;
private Color selectedBackground;
private Color normalBackground;
private MouseListener mouseListener;
public PiecePane() {
setBorder(new LineBorder(Color.DARK_GRAY));
mouseListener = new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
setSelecetd(true);
}
};
setNormalBackground(Color.BLUE);
setSelectedBackground(Color.RED);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(50, 50);
}
#Override
public void addNotify() {
super.addNotify();
addMouseListener(mouseListener);
}
#Override
public void removeNotify() {
super.removeNotify();
removeMouseListener(mouseListener);
}
public void addChangeListener(ChangeListener listener) {
listenerList.add(ChangeListener.class, listener);
}
public void removeChangeListener(ChangeListener listener) {
listenerList.remove(ChangeListener.class, listener);
}
protected void fireSelectionChanged() {
ChangeListener[] listeners = listenerList.getListeners(ChangeListener.class);
if (listeners.length == 0) {
return;
}
ChangeEvent evt = new ChangeEvent(this);
for (int index = listeners.length - 1; index >= 0; index--) {
listeners[index].stateChanged(evt);
}
}
public boolean isSelected() {
return selecetd;
}
public void setSelecetd(boolean selecetd) {
if (selecetd == this.selecetd) { return; }
this.selecetd = selecetd;
updateSelectedState();
fireSelectionChanged();
}
public Color getSelectedBackground() {
return selectedBackground;
}
public void setSelectedBackground(Color selectedBackground) {
this.selectedBackground = selectedBackground;
updateSelectedState();
}
public Color getNormalBackground() {
return normalBackground;
}
public void setNormalBackground(Color normalBackground) {
this.normalBackground = normalBackground;
updateSelectedState();
}
protected void updateSelectedState() {
if (isSelected()) {
setBackground(getSelectedBackground());
} else {
setBackground(getNormalBackground());
}
}
}
}
I created a toggle button.
You set the primary color and the alternate color in the class constructor.
When you call the switchColors method, the JButton background changes from the primary color to the alternate color. When you call the switchColors method again, the JButton background changes from the alternate color to the primary color.
In the following example, I put the switchColors method in the actionListener so you can see the color change. Each time you left-click on the JButton, the background color changes.
You would call the switchColors method when you want the JButton background to change from blue to red, and again when you want the JButton background to change from red to blue. It's under your control.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class ButtonDemo extends JButton
implements ActionListener {
private static final long serialVersionUID = 1L;
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame("Button Demo");
frame.setDefaultCloseOperation(
JFrame.EXIT_ON_CLOSE);
JPanel contentPane = new JPanel();
contentPane.setLayout(new BorderLayout());
frame.setContentPane(contentPane);
contentPane.add(new ButtonDemo(Color.BLUE,
Color.RED));
frame.setSize(300, 300);
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
});
}
private boolean primaryBackground;
private Color primaryColor;
private Color alternateColor;
public ButtonDemo(Color primaryColor,
Color alternateColor) {
this.primaryColor = primaryColor;
this.alternateColor = alternateColor;
this.primaryBackground = true;
this.setText("BUTTON TEXT");
this.setBackground(primaryColor);
this.addActionListener(this);
}
public void switchColors() {
primaryBackground = !primaryBackground;
Color color = primaryBackground ? primaryColor :
alternateColor;
this.setBackground(color);
}
#Override
public void actionPerformed(ActionEvent e) {
switchColors();
}
}
If you want to change the background for a short while you can do it with swing Timer:
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class ButtonDemo extends JButton implements ActionListener{
private static final int DELAY = 600; //milliseconds
private final Timer timer;
public ButtonDemo() {
this.setText("BUTTON TEXT");
this.addActionListener(this);
Color defaultCloor = getBackground();
timer = new Timer(DELAY, e-> setBackground(defaultCloor));
timer.setRepeats(false);
}
public static void main(String[] args){
JFrame frame = new JFrame();
JPanel contentPane = new JPanel();
frame.setContentPane(contentPane);
contentPane.add(new ButtonDemo());
frame.setSize(300, 200);
frame.setVisible(true);
}
#Override
public void actionPerformed(ActionEvent e) {
timer.stop();
this.setBackground(Color.RED);
timer.start();
}
}
How can I specify the coordinates of the following panel, instead of having it aligned to the center.
I have tried a lot and used different layouts, but still couldn't get it to work. Please help me solving this problem. Thanks!
Here is my code..
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Lesson2 extends JFrame {
private static final long serialVersionUID = -198253288329146091L;
private JPanel contentPane;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
Lesson2 frame = new Lesson2();
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the frame.
*/
public Lesson2() {
contentPane = new JPanel();
setContentPane(contentPane);
JPanel panel = new JPanel() {
private static final long serialVersionUID = -5974584127539186578L;
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.GREEN);
g.fillRect(0, 0, 500, 500);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(500, 500);
}
};
contentPane.add(panel);
}
}
Here is an example of how it looks now
https://prnt.sc/moe3al
Here is the final code edited using a nulled layout with a set size
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Lesson1 extends JFrame {
private static final long serialVersionUID = -198253288329146091L;
private JPanel contentPane;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
Lesson1 frame = new Lesson1();
frame.setSize(1000, 1000);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the frame.
*/
public Lesson1() {
contentPane = new JPanel();
setContentPane(contentPane);
contentPane.setLayout(null);
JPanel panel = new JPanel() {
private static final long serialVersionUID = -5974584127539186578L;
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.GREEN);
g.fillRect(0, 0, 500, 500);
}
};
panel.setLayout(null);
panel.setSize(500, 500);
contentPane.add(panel);
}
}
All Swing/AWT containers use FlowLayout as the default layout manager, which results in the component being centered. If you want more control, you can use Absolute Positioning by calling contentPane.setLayout(null) and setting the component's coordinates via panel.setBounds(), but be aware that it may not handle window resize elegantly.
So I am working on a project here that requires a custom JLayeredPane - like class.
It has two members 'ground' and 'foreground' that are JPanel and an background (Image) member.
The way it was supposed to show was that the background Image should have been drawn and then all the components of the ground on top of it and then foreground's components at the apex. So foreground covers up ground which covers up background. Background should be shown only at places that do not have a Component in ground and foreground or where there is transparency in the JPanels.
It's paint function goes like this:
#Override
public void paint(Graphics g){
g.drawImage(background, 0, 0, null);
ground.paint(g.create());
foreground.paint(g.create());
g.dispose();
}
But nothing like that happens. Just the background image gets painted and nothing else shows.
I have used System.out.println() function to check that ground and foreground actually hold components and they do. But they just don't show.
Can anyone help me here?
The most significant issue is you're not calling super.paint, which is preventing what ever was previously painted to the Graphics context from been cleared or any child components from been painted.
For painting the background, you should be using paintComponent, which is used to paint the background of the component.
If you need to paint under the child components, but above the background, you should still use paintComponent, but paint the background first and then the next layer. The components will be painted after paintComponent.
Painting over components is actually more complex
Take a closer look at Custom Painting and Painting in Swing and AWT
Updated based on code snippets
In Screen which extends from Container, you are doing...
#Override
public void paint(Graphics g) {
super.paint(g);
GraphicsUtilities.drawPictureTiled(background, g);
paintComponents(g);
g.dispose();
}
Don't call paintComponents, super.paint has already done this.
Don't call dispose on a Graphics context you don't create
Based on the rest of the example code I have, you should be extending from JPanel and overriding paintComponent instead. This will allow you to put under the component layer
Because GroundPanel and ForeGroundPanel are both JPanels, there's no need to ever paint them yourself. In fact, you could simply use OverlayLayout or even a GridBagLayout and add them directly to the NestedScreen which is itself a container...
So, I stripped down you example code so I could get it working with the missing code as an example. I got a little more fancy and simply made a JPanel to act as the pause screen
This is all done by simply overlaying the components on top of each other using a GridBagLayout
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test1001 {
public static void main(String[] args) {
new Test1001();
}
public Test1001() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
try {
NestedScreen screen = new NestedScreen();
screen.setBackgroundLayer(ImageIO.read(getClass().getResource("/Sky.png")));
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(screen);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException exp) {
exp.printStackTrace();
}
}
});
}
public interface GraphicsEngineComponents {
}
public class NestedScreen extends Screen implements GraphicsEngineComponents {
GroundPanel ground;
ForeGroundPanel foreground;
private PausePane pausePane;
public NestedScreen() {
ground = new GroundPanel();
foreground = new ForeGroundPanel();
pausePane = new PausePane();
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.weightx = 1;
gbc.weighty = 1;
gbc.fill = GridBagConstraints.BOTH;
add(pausePane, gbc);
add(foreground, gbc);
add(ground, gbc);
MouseAdapter handler = new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
pausePane.setVisible(!pausePane.isVisible());
}
};
addMouseListener(handler);
foreground.addMouseListener(handler);
ground.addMouseListener(handler);
}
public GroundPanel getGroundLayer() {
return ground;
}
public ForeGroundPanel getForegroundLayer() {
return foreground;
}
public void setBackgroundLayer(BufferedImage background) {
super.setBackgroundLayer(background);
}
public class GroundPanel extends JPanel {
public GroundPanel() {
setOpaque(false);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.GREEN);
g.fillRect(0, getHeight() - 200, getWidth(), 200);
}
}
public class PausePane extends JPanel {
private JLabel label;
public PausePane() {
setVisible(false);
setOpaque(false);
setBackground(new Color(0, 0, 0, 128));
setLayout(new GridBagLayout());
label = new JLabel("Paused");
label.setHorizontalAlignment(JLabel.CENTER);
label.setVerticalAlignment(JLabel.CENTER);
Font font = label.getFont();
font = font.deriveFont(Font.BOLD, 48f);
label.setFont(font);
label.setForeground(Color.WHITE);
add(label);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(getBackground());
g.fillRect(0, 0, getWidth(), getHeight());
}
}
public class ForeGroundPanel extends JPanel {
private BufferedImage pony;
public ForeGroundPanel() {
setOpaque(false);
try {
pony = ImageIO.read(getClass().getResource("/Pony.png"));
} catch (IOException ex) {
ex.printStackTrace();
}
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (pony != null) {
int x = (getWidth() - pony.getWidth()) / 2;
int y = getHeight() - 200 - (pony.getHeight() / 2);
g.drawImage(pony, x, y, this);
}
}
}
}
public class Screen extends JPanel implements GraphicsEngineComponents {
private BufferedImage background;
public Screen() {
}
#Override
public String toString() {
return "Screen{" + "background=" + background + '}';
}
public BufferedImage getBackgroundPicture() {
return background;
}
#Override
public Dimension getPreferredSize() {
return background == null ? super.getPreferredSize() : new Dimension(background.getWidth(), background.getHeight());
}
protected void setBackgroundLayer(BufferedImage background) {
if (background != null && background.getHeight() != 0 && background.getWidth() != 0) {
this.background = background;
}
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (background != null) {
g.drawImage(background, 0, 0, this);
}
}
}
}
Take a look at Painting in AWT and Swing and Performing Custom Painting to understand how painting works in Swing.
A basic idea would be to avoid using all these compound or nested components and instead, create an engine that can paint the layers directly onto a Graphics context, maybe even painting to a BufferedImage which you can the paint onto a single component...
There are a few ways you go about doing this. I'll just introduce one way.
Create a background panel where you paint the background image (in the example below, it is BackgroundPanel with the image only being the forresty background). Set that panel as the content pane to the frame.
Create another ground panel where you can also paint something (in the example below if it the GroundPanel with only the image of bugs bunny painted.
Create your foreground panel and add it to the ground panel. You can add your foreground components to it. (in the example below the foreground image is the grassy hill, and I also add a button to it
All the panels' opaque property should be set to false, as to allow the panel behind it to be shown under any transparency.
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
public class ThreeTier {
public static final int DIM_WIDTH = 600;
public static final int DIM_HEIGHT = 400;
private BufferedImage backgroundImage;
private BufferedImage groundImage;
private BufferedImage foregroundImage;
public ThreeTier() {
initImages();
JFrame frame = new JFrame();
frame.setContentPane(new BackgroundPanel());
frame.add(new GroundPanel());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private void initImages() {
try {
backgroundImage = ImageIO.read(getClass().getResource("/resources/background.png"));
foregroundImage = ImageIO.read(getClass().getResource("/resources/foreground.png"));
groundImage = ImageIO.read(getClass().getResource("/resources/bugsBunny.png"));
} catch (IOException ex) {
Logger.getLogger(ThreeTier.class.getName()).log(Level.SEVERE, null, ex);
}
}
class BackgroundPanel extends JPanel {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(backgroundImage, 0, 0, DIM_WIDTH, DIM_HEIGHT, this);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(ThreeTier.DIM_WIDTH, ThreeTier.DIM_HEIGHT);
}
}
class GroundPanel extends JPanel {
private static final String RIGHT_ACTION = "rightAction";
private int imageX = 50;
private int imageY = 140;
public GroundPanel() {
setOpaque(false);
add(new ForegroundPanel());
InputMap im = getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
im.put(KeyStroke.getKeyStroke("RIGHT"), RIGHT_ACTION);
getActionMap().put(RIGHT_ACTION, new AbstractAction() {
public void actionPerformed(ActionEvent e) {
if (imageX >= DIM_WIDTH) {
imageX = 0 - groundImage.getWidth();
repaint();
} else {
imageX += 10;
repaint();
}
}
});
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(groundImage, imageX, imageY, groundImage.getWidth(), groundImage.getHeight(), this);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(ThreeTier.DIM_WIDTH, ThreeTier.DIM_HEIGHT);
}
}
class ForegroundPanel extends JPanel {
public ForegroundPanel() {
setOpaque(false);
setLayout(new FlowLayout(FlowLayout.TRAILING, 10, 0));
JButton button = new JButton("I'm in the Foreground!");
add(button);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(foregroundImage, 0, 0, DIM_WIDTH, DIM_HEIGHT, this);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(ThreeTier.DIM_WIDTH, ThreeTier.DIM_HEIGHT);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new ThreeTier();
}
});
}
}
UPDATE
Of course you could always just use the JLayeredPane. That's what it's for. See this example
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Image;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.border.LineBorder;
public class LayeredPaneDemo {
public static final int DIM_WIDTH = 600;
public static final int DIM_HEIGHT = 400;
public LayeredPaneDemo() {
ContainerPanel container = new ContainerPanel();
JLabel title = new JLabel("Lame Google Map");
title.setFont(new Font("verdana", Font.BOLD, 36));
title.setHorizontalAlignment(JLabel.CENTER);
JPanel panel = new JPanel(new GridBagLayout());
panel.add(container);
JFrame frame = new JFrame();
frame.add(panel);
frame.add(title, BorderLayout.NORTH);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new LayeredPaneDemo();
}
});
}
public class ContainerPanel extends JPanel {
public ContainerPanel() {
JLayeredPane layeredPane = new JLayeredPane();
layeredPane.setPreferredSize(new Dimension(DIM_WIDTH, DIM_HEIGHT));
BackgroundPanel bg = new BackgroundPanel();
GroundPanel gg = new GroundPanel();
ForegroundPanel fg = new ForegroundPanel();
bg.setBounds(0, 0, DIM_WIDTH, DIM_HEIGHT);
layeredPane.add(bg, new Integer(1));
gg.setBounds(0, 0, DIM_WIDTH, DIM_HEIGHT);
layeredPane.add(gg, new Integer(2));
fg.setBounds(0, 0, DIM_WIDTH, DIM_HEIGHT);
layeredPane.add(fg, new Integer(3));
setLayout(new GridBagLayout());
add(layeredPane);
setBorder(new LineBorder(Color.BLUE, 10));
}
}
public class ForegroundPanel extends JPanel {
public ForegroundPanel() {
JPanel buttonPanel = new JPanel(new GridLayout(3, 3));
buttonPanel.setOpaque(false);
buttonPanel.add(new JLabel());
buttonPanel.add(new JButton("UP"));
buttonPanel.add(new JLabel());
buttonPanel.add(new JButton("Left"));
buttonPanel.add(new JLabel());
buttonPanel.add(new JButton("Right"));
buttonPanel.add(new JLabel());
buttonPanel.add(new JButton("Down"));
buttonPanel.add(new JLabel());
FlowLayout flow = (FlowLayout) getLayout();
flow.setAlignment(FlowLayout.TRAILING);
flow.setHgap(0);
flow.setVgap(0);
add(buttonPanel);
setOpaque(false);
}
}
public class GroundPanel extends JPanel {
Image image = null;
public GroundPanel() {
try {
image = ImageIO.read(getClass().getResource("/resources/california.png"));
} catch (IOException ex) {
Logger.getLogger(LayeredPaneDemo.class.getName()).log(Level.SEVERE, null, ex);
}
setOpaque(false);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0, 0, DIM_WIDTH, DIM_HEIGHT, this);
}
}
public class BackgroundPanel extends JPanel {
public BackgroundPanel() {
setLayout(new GridBagLayout());
setBackground(Color.WHITE);
try {
Image img = ImageIO.read(getClass().getResource("/resources/google.jpg"));
add(new JLabel(new ImageIcon(img)));
} catch (IOException ex) {
Logger.getLogger(LayeredPaneDemo.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}
I want to change my componentstyle by using UIManager.
For example:
I click on a Button and the Button foreground changes from black to green. The same for a JCheckbox.....
In my example the changes just work for the Button.gradient.... I get no update for Button.foreground and no update for the JCheckbox!
Here my UIManagerClass:
package components;
import java.awt.Color;
import java.util.ArrayList;
import javax.swing.SwingUtilities;
public class OwnUiManager {
ButtonDemo Bd;
OwnUiManager(ButtonDemo aThis) {
Bd = aThis;
}
public void setNormal() {
ArrayList<Object> gradients = new ArrayList();
gradients.add(0.3);
gradients.add(0.0);
gradients.add(new Color(221, 232, 243));
gradients.add(new Color(255, 255, 255));
gradients.add(new Color(184, 207, 229));
javax.swing.UIManager.put("RadioButton.background", Color.PINK);
javax.swing.UIManager.put("Button.gradient", gradients);
javax.swing.UIManager.put("Button.foreground", Color.PINK);
SwingUtilities.updateComponentTreeUI(Bd);
}
public void setNightVision() {
System.out.println("Tes");
ArrayList<Object> gradients = new ArrayList();
gradients.add(0.18f);
gradients.add(0.17f);
gradients.add(Color.BLACK);
gradients.add(Color.DARK_GRAY);
gradients.add(Color.DARK_GRAY);
javax.swing.UIManager.put("RadioButton.background", Color.GRAY);
javax.swing.UIManager.put("Button.gradient", gradients);
javax.swing.UIManager.put("Button.foreground", Color.red);
SwingUtilities.updateComponentTreeUI(Bd);
}
}
and here my Buttondemo/Main-Class:
package components;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
public class ButtonDemo extends JPanel
implements ActionListener {
protected JButton b1,b2;
private JRadioButton b3;
public ButtonDemo() {
b1 = new JButton("ON");
b1.addActionListener(this);
add(b1);
b2 = new JButton("OFF");
b2.addActionListener(this);
add(b2);
//For Testing the Style
b3=new JRadioButton("Test");
add(b3);
}
public void actionPerformed(ActionEvent e) {
OwnUiManager test = new OwnUiManager(this);
if (e.getSource().equals(b1)) {
test.setNormal();
} else {
test.setNightVision();
}
}
private static void createAndShowGUI() {
JFrame frame = new JFrame("ButtonDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
ButtonDemo newContentPane = new ButtonDemo();
newContentPane.setOpaque(true); //content panes must be opaque
frame.setContentPane(newContentPane);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
JCheckBox / JRadioButton has Icon
have to change own Icon to concrete JCheckBox / JRadioButton or put then to the UIManager, then apply for whole JVM instance
for more infos have to check UIManager Defaults and Key Bindings by camickr
most of your potential issues is descibed in Creating a custom button in Java with JButton
I'm French so my English is quite bad but I have a real problem with java:
I'm trying to show an Image built manually with some fillRect & co.
It works. Next, I want to update this image as a function of the text I enter in the text field. And there is the problem: it doesn't change anything, and if I manually rescale the window of the JFrame, the image shows totally deformed or scaled badly.
I'm a beginner and I have difficulties with GUI, moreso when I want to couple it with Images.
Can you help me? Why doesn't it work as I want? This is my code below, a bit long but actually quite simple ! Thanks :)
I've changed it a bit, this is the 2e VERSION.
Now the problem s that I can't change a condition in order to modify the color of a simple rectangle, try "lol" in the entry field !
CODE VERSION 2
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Color;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.ParseException;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.JLabel;
import javax.swing.JButton;
import javax.swing.ImageIcon;
public class Fenetre extends JFrame {
private JFrame frame;
private JPanel container = new JPanel();
private JTextField jtf;
private JLabel label = new JLabel("Commandes ");
private JButton b = new JButton ("OK");
private Graphics graph;
private Image img;
private JLabel screen;
private boolean color;
/**
* Constructeur de l'objet
*/
public Fenetre(){
color = true;
frame = new JFrame();
frame.setTitle("Animation");
frame.setSize(1000, 400);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
container.setBackground(Color.white);
container.setLayout(new BorderLayout());
JPanel top = new JPanel();
jtf = new JTextField();
jtf.setPreferredSize(new Dimension(800, 30));
b.addActionListener(new BoutonListener());
frame.setContentPane(top);
frame.setVisible(true);
paintComponent(graph);
screen = new JLabel( new ImageIcon(img));
top.add(screen);
top.add(label);
top.add(jtf);
top.add(b);
frame.setContentPane(top);
}
class BoutonListener implements ActionListener{
public void actionPerformed(ActionEvent e) {
if(jtf.getText().equals("lol")) lol();
System.out.println("Entry : " + jtf.getText());
}
}
public void paintComponent(Graphics g)
{
if(img == null) {
img = frame.createImage(1000,300);
g = img.getGraphics();
}
g.setColor(Color.white);
g.fillRect(0,0,1000,300);
if(color) g.setColor(Color.orange); else g.setColor(Color.blue);
g.fillRect(8,25,200,100);
g.setColor(Color.green);
g.drawString("Text",10,10);
}
public void lol()
{
if(color) color = false; else color = true;
}
}
PREVIOUS CODE
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Color;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.ParseException;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.JLabel;
import javax.swing.JButton;
import javax.swing.ImageIcon;
public class Fenetre extends JFrame {
private JPanel container = new JPanel();
private JTextField jtf;
private JLabel label = new JLabel("Commandes ");
private JButton b = new JButton ("OK");
private Graphics g;
private Image img;
private JLabel screen;
/**
* Constructeur de l'objet
*/
public Fenetre(){
this.setTitle("Animation");
this.setSize(1000, 400);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);
container.setBackground(Color.white);
container.setLayout(new BorderLayout());
JPanel top = new JPanel();
jtf = new JTextField();
jtf.setPreferredSize(new Dimension(800, 30));
b.addActionListener(new BoutonListener());
this.setContentPane(top);
this.setVisible(true);
paint(g);
screen = new JLabel( new ImageIcon(img));
top.add(screen);
top.add(label);
top.add(jtf);
top.add(b);
this.setContentPane(top);
}
class BoutonListener implements ActionListener{
public void actionPerformed(ActionEvent e) {
System.out.println("Entry : " + jtf.getText());
if(jtf.getText().equals("lol")) lol();
}
}
#Override
public void paint(Graphics g)
{
if(img == null) {
img = createImage(1000,300);
g = img.getGraphics();
}
g.setColor(Color.white);
g.fillRect(0,0,1000,300);
g.setColor(Color.orange);
g.fillRect(8,25,200,100);
g.setColor(Color.green);
g.drawString("Text",10,10);
}
#Override
public void update(Graphics g)
{
g.setColor(Color.blue);
g.fillRect(8,25,300,100);
}
public void lol()
{
g.setColor(Color.blue);
g.fillRect(8,25,200,100);
}
}
I see several problems in your code:
You are confusing your g member variable with the g parameter of the paint method. When lol is called, g is null and you get a NullPointerException
You should never grab a hold on Graphics (only in really rare cases). Instead you override properly paintComponent() and you use the Graphics parameter to draw what you want. When you want to update the component, you call repaint()
Don't override paint, but override paintComponent()
Don't override paint of JFrame. Use a dedicate component for that. No need to use a JLabel for that, a simple JComponent is enough.
Don't extend JFrame if you are not extending its functionality.
After adding components to the component hierarchy, call revalidate()
Fix those issues and come back with another question if you still have problems.
You should probably consider reading this tutorial and the few next steps. It will show you basic examples of things similar to what you are trying to do.
EDIT:
I took your V2 code and patched it as I could. This is very far from perfect but you should get the gist of how you can do this:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
public class Fenetre extends JComponent {
private boolean color;
/**
* Constructeur de l'objet
*/
public Fenetre() {
color = true;
setPreferredSize(new Dimension(1000, 300));
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.white);
g.fillRect(0, 0, 1000, 300);
if (color) {
g.setColor(Color.orange);
} else {
g.setColor(Color.blue);
}
g.fillRect(8, 25, 200, 100);
g.setColor(Color.green);
g.drawString("Text", 10, 10);
}
public void lol() {
if (color) {
color = false;
} else {
color = true;
}
repaint();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
initUI();
}
});
}
protected static void initUI() {
JFrame frame = new JFrame();
frame.setTitle("Animation");
frame.setSize(1000, 400);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
JPanel container = new JPanel();
final JTextField jtf = new JTextField();
final Fenetre fenetre = new Fenetre();
JLabel label = new JLabel("Commandes ");
JButton b = new JButton("OK");
container.setBackground(Color.white);
container.setLayout(new BorderLayout());
JPanel top = new JPanel();
jtf.setPreferredSize(new Dimension(800, 30));
class BoutonListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
if (jtf.getText().equals("lol")) {
fenetre.lol();
}
System.out.println("Entry : " + jtf.getText());
}
}
b.addActionListener(new BoutonListener());
top.add(fenetre);
top.add(label);
top.add(jtf);
top.add(b);
top.revalidate();
frame.setContentPane(top);
frame.setVisible(true);
}
}
Your Swing graphics programming has several significant problems, and I urge you to go through the tutorials to learn how to do this better. For example, you are
calling the paint method directly -- something you should almost never do except in very special situations (this is not one of them)
Drawing directly in the JFrame's paint(...) method. Instead you will want to draw in the paintComponent(...) method override of a class derived from JComponent such as JPanel.
Calling update unnecessarily as if this were an AWT program. You don't do this in Swing unless you're changing the Look & Feel.
Again, take a look at the tutorials on this -- you will not regret doing this, trust me.
edit -- too slow! 1+ to Guillaume