When I try to draw an oval in JFrame, the paint() method doesn't work, but when I change JFrame to Frame it's fine. Can you please explain this problem?
import java.awt.Graphics;
import java.awt.Window;
import javax.swing.JFrame;
public class Oval extends JFrame
{
public Oval()
{
// this.setLocationRelativeTo(null);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
this.setSize(400, 400);
}
public void paint(Graphics g)
{
g.drawOval(40, 40, 40, 40);
}
public static void main(String []args)
{
new Oval();
}
}
Don't override paint(). Instead add a JPanel to the JFrame and override paintComponent(g)
Call super method. Even if you override paint() call super.paint() (Of course it's better to use paintComponnet())
Well you shouldn't override the paint() method, instead add a JPanel to your JFrame and override the JPanel's paintComponent() method and draw your stuff on the JPanel instead.
Here's a demonstration:
import javax.swing.*;
import java.awt.*;
public class Oval extends JFrame
{
public Oval()
{
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(new GridBagLayout());
OvalPanel ovalPanel = new OvalPanel(100); // SIZE of oval is 100
ovalPanel.setPreferredSize(new Dimension(400, 400));
add(ovalPanel);
pack();
setLocationRelativeTo(null);
setVisible(true);
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
new Oval();
}
});
}
}
class OvalPanel extends JPanel
{
private final int SIZE;
public OvalPanel(final int size)
{
this.SIZE = size;
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g); // Call the super class's paintComponent so that it does its appropriate rendering first.
g.drawOval((getWidth() - SIZE) >> 1, (getHeight() - SIZE) >> 1, SIZE, SIZE); // Center the oval
}
}
Also if you notice, I used SwingUtlities.invokeLater() method and there I called the Oval constructor. That I did because Swing is thread unsafe and so you should call any Swing related methods in the Event Dispatcher Thread which is responsible for handling any Swing related stuff, and that's what the invokeLater() method does.
Note: if you're using JDK 8, you can write the SwingUtilities.invokeLater() statement as:
SwingUtilities.invokeLater(Oval::new); // That's JDK 8 Method reference feature.
You might wonder as to why you shouldn't do stuff directly in the paint() method. The reason has to do with the way paint() method is called. You see, the RepaintManager is responsible for handling the painting of all the Swing based components, and when the paint() method is called, it in turn calls the paintComponent(), paintChildren() and paintBorder() methods. paintComponent() is the best place to handle any component based rendering and that's why its the suggestion. Though, even the following code works:
import javax.swing.*;
import java.awt.*;
public class Oval extends JFrame
{
public Oval()
{
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(400, 400);
setLocationRelativeTo(null);
setVisible(true);
}
public void paint(Graphics g)
{
super.paint(g); // Calling the super class's paint method first.
// so that it handles its rendering.
g.drawOval(40, 40, 40, 40);
}
public static void main(String[] args)
{
new Oval();
}
}
Even the order in which methods are called is important in Swing. Like:
setSize(400, 400); // Setting the size of JFrame first.
setLocationRelativeTo(null); // Locating the JFrame based on its size then.
setVisible(true); // Finally its made visible
All the above work as expected. But the following won't display the results properly:
setVisible(true); // JFrame is already made visible
setLocationRelativeTo(null); // This first locates the JFrame based on its (0, 0) size
setSize(400, 400); // Then its size is increased. Not as expected as the JFrame appears shifted from center.
Related
I'm trying to draw a line in a JFrame, but line isn't drawn.
I tried to use the method setOpaque(true) for contentPane, lblNewLabel and l but nothing changed. I also tried call repaint(); outside this class but the situation is still the same. Here's the code:
public class DrawingClass extends JFrame
{
private JPanel contentPane;
public DrawingClass(int n, int s, int p) {
Line l= new Line();
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
setBounds(700, 300, 480, 640);
contentPane = new JPanel();
contentPane.setOpaque(true);
setResizable(false);
setContentPane(contentPane);
contentPane.setLayout(null);
JLabel lblNewLabel = new JLabel("");
lblNewLabel.setIcon(new ImageIcon("image.png"));
lblNewLabel.setBounds(0, 0, 480, 640);
contentPane.add(lblNewLabel);
l.setBounds(0,0,480,640);
contentPane.add(l);
repaint();
}
class Line extends JPanel
{
public void paintComponent(Graphics g) {
g.setColor(Color.BLUE);
g.fillRect(10, 10, 15, 12);
}
}
}
I expect a little line on the top left of the JFrame, above the background wallpaper, but nothing happen. It shows only the wallpaper.
There are several errors in your code:
You're extending JFrame but you're not changing its behavior, so why are you doing that? JFrame is a rigid component, so it's never a good idea to extend from it, build your GUI based on JPanels instead. See: Extends JFrame vs. creating it inside the program
Don't explicitly set the size of the JFrame, call pack() on it and instead override getPreferredSize from the JPanel, see: Should I avoid the use of set(Preferred|Maximum|Minimum)Size methods in Java Swing?
You don't need to call setOpaque(...) in this case.
Don't use a null-layout, it might lead to strange errors, because null Layout is Evil and frowned upon
We don't have access to your image so we cannot test the ImageIcon and it's also not related to your question. However you should load your images as resources
Don't explicitly set the bounds of each element, this is related to point (4) and you should use a Layout Manager or combinations of them to get your desired GUI.
Don't call repaint() that way, it has no effect, it is supposed to repaint your UI when there's a change in it. However there is no change at the start of your program.
You're breaking the paint-chain by not calling super.paintComponent(...) inside your paintComponent(...) method. Check the Tutorial on Custom Painting in Swing so that you learn how to do it properly
And be careful, as paintComponents(...) (With a trailing s) is different from paintComponent(...) (Look at your title)
So, after doing all of the above changes, we get to this simple program:
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
public class DrawingClass {
private JPanel contentPane;
private JFrame frame;
public static void main(String args[]) {
SwingUtilities.invokeLater(() -> new DrawingClass().createAndShowGUI());
}
public void createAndShowGUI() {
frame = new JFrame(getClass().getSimpleName());
Line line = new Line();
frame.add(line);
frame.setResizable(false);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
class Line extends JPanel {
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLUE);
g.fillRect(10, 10, 15, 12);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(480, 640);
}
}
}
Which produces the following output:
I am attempting to make a simple traffic light frame. This is being made by the main frame TrafficBox:
public class TrafficBox extends JFrame {
public TrafficBox() {
setLayout(new BorderLayout(100,100));
add(new TrafficLight(Color.GREEN), BorderLayout.NORTH);
add(new TrafficLight(Color.ORANGE), BorderLayout.CENTER);
add(new TrafficLight(Color.RED), BorderLayout.SOUTH);
setBounds(100, 100, 800, 600);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TrafficBox();
}
});
}
which then as seen, adds in 3 TrafficLight components which are based on JPanel, and have the following code:
public class TrafficLight extends JPanel {
private final int BALL_DIAMETER = 100;
private Color color;
public TrafficLight(Color color) {
//setSize(BALL_DIAMETER, BALL_DIAMETER);
this.color = color;
new Timer(1000, new ActionListener() {
public void actionPerformed(ActionEvent e) {
repaint();
}
}).start();
}
public void paintComponent(Graphics g) {
g.setColor(color);
g.fillOval(0, 0, BALL_DIAMETER, BALL_DIAMETER);
}
This does sort of what I want, it draws all 3 circles as expected although a majority of the North(green) and south(red) lights are being cut off. I assume this is because the north/south spot are much smaller than the center.
I've tried using setSize(); to set the size of the panels to the size of the circles, however that does not work. Is there a way I can make it so that the full circle will be visible?
So, most layout managers will need some "hints" to be provided by the components in order to know how they want to be laid out.
You will need to override the getPreferredSize and return a size which best meets your needs, for example...
public class TrafficLight extends JPanel {
private final int BALL_DIAMETER = 100;
//...
public Dimension getPreferredSize() {
return new Dimension(BALL_DIAMETER, BALL_DIAMETER);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent();
g.setColor(color);
g.fillOval(0, 0, BALL_DIAMETER, BALL_DIAMETER);
}
Also, paintComponent doesn't ever need to be public no-one else should be calling it and you should call super.paintComponent() before performing any custom painting.
I'd also recommend maybe using GridLayout for this
I'd also argue that TrafficLight doesn't need a Timer of it's own and the should be controlled externally, but that's me
setBounds(100, 100, 800, 600);
Is best avoided, use pack() to size the window to the preferred size of the content and setLocation to position it
Simple fix, needed to be using setPreferredSize() rather than setSize.
public void display() {
pan.repaint();
fen.add(pan);
fen.addKeyListener(this);
fen.setResizable(false);
fen.setTitle("Le Jeu 2D");
img.setText("Coucou");
pan.add(img);
pan.repaint();
pan.setBackground(Color.yellow);
fen.setVisible(true);
fen.setSize(480, 272);
pan.repaint();
fen.revalidate();
}
public void paintComponent(Graphics g) {
System.out.println("zzz");
pan.paint(g);
g.setColor(Color.red);
g.drawRect(10, 10, 10, 10);
}
It doesn't draw anything. Why? I have defined the paint component method, so I don't understand why.
Edit: I edited my code, please take a look
You don't create an instance of your Game class and don't add it to the JFrame.
Here is the Game panel which you are painting on:
Game.java
public class Game extends JPanel {
#Override
public final void paintComponent(final Graphics g) {
super.paintComponent(g);
g.setColor(Color.red);
g.drawRect(10, 10, 10, 10);
}
#Override
public Dimension getMinimumSize() {
return new Dimension(300, 300);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(300, 300);
}
#Override
public Dimension getMaximumSize() {
return new Dimension(300, 300);
}
}
You then need to create an instance of this Game panel and add it to your JFrame:
GameFrame.java
public class GameFrame extends JFrame {
public GameFrame() {
setLocationRelativeTo(null);
setResizable(false);
setTitle("2D Game");
Game game = new Game();
setContentPane(game);
pack();
setVisible(true);
}
}
Then when you create an instance of the JFrame:
Example.java
public class Example {
public static void main(String[] args) {
new GameFrame();
}
}
The panel will be added, and painted:
You never create an instance of the Game class and you never add the Game class to the frame. Even if you did create an instance the size would still be (0, 0) so there would be nothing to paint.
Basically the whole structure of your code is wrong.
I suggest you start over and start with the demo code found in the Swing tutorial on Custom Painting.
The basic structure of your code seems weird.
You could instantiate a JFrame in your main control class, ie. it should be GAME class in this case. Then you can create a new JPanel class and add its object into the JPanel object. Within the JPanel class, you can create all the elements you need and set corresponding parameters. You could also add the event listener in an inner class or a separate class.
I have this little code
public class Test extends JFrame {
public static void main(String[] args) {
new Test();
}
Test() {
getContentPane().add(new MyPanel());
pack();
setVisible(true);
}
private class MyPanel extends JPanel {
#Override
protected void paintComponent(Graphics g) {
setSize(640, 480);
setPreferredSize(new Dimension(640, 480));
setMinimumSize(new Dimension(640, 480));
g.fillRect(20, 20, 50, 50);
}
}
Unfortunately the frame is not resized to the size of the nested panel after calling pack(). I already read the related answers for this topic, but none of them helped. Could you provide me a solution, please?
When pack() is executed the panel is still invisible and its paintComponent() was not executed and as a result setPreferredSize() wasnt executed as well.
But don't call setPreferredSize from paintComponent(). Do your painting in paintComponent nothing else. Avoid putting program logic into that method. Painting operations should be fast and optimized for better performance and user experience. See Performing Custom Painting for more details.
Override panel's getPrefferedSize(), or at least execute setPrefferedSize before pack().
Also see Should I avoid the use of set[Preferred|Maximum|Minimum]Size methods in Java Swing.
public MyPanel() {
setPreferredSize(new Dimension(640, 480));
}
I cannot get this oval to draw on the JFrame.
static JFrame frame = new JFrame("New Frame");
public static void main(String[] args) {
makeframe();
paint(10,10,30,30);
}
//make frame
public static void makeframe(){
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JLabel emptyLabel = new JLabel("");
emptyLabel.setPreferredSize(new Dimension(375, 300));
frame.getContentPane().add(emptyLabel , BorderLayout.CENTER);
frame.pack();
frame.setVisible(true);
}
// draw oval
public static void paint(int x,int y,int XSIZE,int YSIZE) {
Graphics g = frame.getGraphics();
g.setColor(Color.red);
g.fillOval(x, y, XSIZE, YSIZE);
g.dispose();
}
The frame displays but nothing is drawn in it. What am I doing wrong here?
You have created a static method that does not override the paint method. Now others have already pointed out that you need to override paintComponent etc. But for a quick fix you need to do this:
public class MyFrame extends JFrame {
public MyFrame() {
super("My Frame");
// You can set the content pane of the frame to your custom class.
setContentPane(new DrawPane());
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(400, 400);
setVisible(true);
}
// Create a component that you can actually draw on.
class DrawPane extends JPanel {
public void paintComponent(Graphics g) {
g.fillRect(20, 20, 100, 200); // Draw on g here e.g.
}
}
public static void main(String args[]){
new MyFrame();
}
}
However, as someone else pointed out...drawing on a JFrame is very tricky. Better to draw on a JPanel.
Several items come to mind:
Never override paint(), do paintComponent() instead
Why are you drawing on a JFrame directly? Why not extends JComponent (or JPanel) and draw on that instead? it provides more flexibility
What's the purpose of that JLabel? If it sits on top of the JFrame and covers the entire thing then your painting will be hidden behind the label.
The painting code shouldn't rely on the x,y values passed in paint() to determine the drawing routine's start point. paint() is used to paint a section of the component. Draw the oval on the canvas where you want it.
Also, you're not seeing the JLabel because the paint() method is responsible for drawing the component itself as well as child components. Overriding paint() is evil =)
You are overriding the wrong paint() method, you should override the method named paintComponent like this:
#Override
public void paintComponent(Graphics g)
You need to Override an exist paint method that actually up to dates your Frame. In your case you just created your custom new method that is not called by Frame default.
So change your method to this:
public void paint(Graphics g){
}