I have a panel on which I want to draw stuff.
Painting on it when it is beeing created is no problem.
canvas = new Panel() {
public void paint(Graphics g) {
g.setColor(Color.WHITE);
g.drawLine(0, 0, 10, 10);
}
};
But then I want to draw on it during runtime.
By instinct, I've created something like this:
Graphics g = canvas.getGraphics();
g.setColor(Color.GREEN);
g.drawLine(10, 10, 20, 20);
canvas.paint(g);
Sadly, this doesn't work.
This is probably a simple question but I cannot find a satisfying result by searching.
So how can I do what I want to do?
Sorry for the question above.
I just added the paint code on a button click event and it works.
It just doesn't work on the windowOpened event of the parent frame.
Any ideas why?
The problem is that the paint() method can be called at any time whenever the window system (or OS) decides that the particular graphical component needs to be repainted on screen. This may happen at any moment (most often when resizing, moving, switching windows, etc). To see how often it happens just add a log message at the beginning of paint() method. If you paint something on canvas just once it's very likely that it's painted, but then another repaint request comes from OS/window system and your green line gets "overdrawn" by object's paint() .
So the answer is that any custom painting should be done in paint(). You can add extra attributes to your subclass (eg. boolean drawGreenLine), check it in paint() and take any appropriate action, eg:
class MyPanel extends JPanel {
boolean drawGreenLine;
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.WHITE);
g.drawLine(0, 0, 10, 10);
if (drawGreenLine) {
g.setColor(Color.GREEN);
g.drawLine(10, 10, 20, 20);
}
}
};
EDIT: As suggested by #MadProgrammer the example has been changed to override paintComponent(). This way the component is only responsible for drawing itself (and not any children or borders).
try g.dispose() to release the GraphicsContext's ressources
Related
So I'm working on a somewhat interactive painting of a chess game. Basically, for now, there are only 2 buttons: next move and previous move. The next move button adds 1 to the int variable moveNum, and the previous move button subtracts 1 from the int variable moveNum. Basically like this:
int moveNum = 0;
public void actionPerformed(ActionEvent e)
{
if(e.getActionCommand().equals("Next Move"))
{
moveNum++;
Display.instance.repaint();
}
else if(e.getActionCommand().equals("Previous Move"))
{
moveNum--;
Display.instance.repaint();
}
}
I then just have a ton of graphics, one for each value of moveNum. It looks somewhat like this:
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
g2d.setColor(Color.BLACK);
if (moveNum == 0)
{
g2d.fill(new Rectangle2D.Double(0, 0, 10, 10));//just for simplicity
}
else if (moveNum == 1)
{
g2d.fill(new Rectangle2D.Double(0, 0, 10, 10));
g2d.fill(new Rectangle2D.Double(0, 0, 20, 10)); //again for simplicity
}
else if (moveNum ==2)
{
g2d.fill(new Rectangle2D.Double(0, 0, 10, 10));
g2d.fill(new Rectangle2D.Double(0, 0, 20, 10));
g2d.fill(new Rectangle2D.Double(0, 0, 30, 10)); //simplicity
}
...
}
But the graphics are not changing when I press the buttons. What could I be missing?
Update: I have tried to call Display.instance.repaint() after the moveNum stuff, but now I get no graphics at all. What could be the problem?
Update #2: The paint component is being executed because when I put System.out.println("Test"); to the method, it is being printed. I also now see the infinite recursion problem (not sure how I did not see it before). But even after fixing it, I still don't get the graphics I want. I realized that I can get some graphics when I go full-screen for some reason. But the buttons still don't change the graphics. Using the System.out.println("Test"); method, I found that "Test" is printed once when I run the program, two more times when I go full screen and get graphics, and 12 more times when I press the next button. I'm not really sure what's going on there, are my buttons set up incorrectly?
Final update: I do not have a MRE/SSCE, but I have figured out my problem. I have realized I'm one of the biggest idiots in the universe and my problem was capitalization. My buttons were not working because I capitalized the wrong thing in both of them in the segment e.getActionCommand().equals("Next Move")). Anyways, thanks a ton for your help. I don't think I would have found the infinite recursion problem, so special thanks for helping with that.
Try adding component.repaint() after moveNum++; and after moveNum--;.
When component is the component that you want to update.
Also, remove the repaint() inside paintComponent(Graphics g)
I'm pretty new to Java and the GUI world. Right now I'm trying to create a really basic space shooter. To create it I started creating a JFrame, in which I've later on put a personal extension of a JPanel called GamePanel, on which I'm now trying to display all my components. Until here it's all pretty clear, the problem comes now: I have my GamePanel in which I display my player, and on the KeyEvent of pressing S the player should shoot the Bullets. I've managed the bullets as an Array, called Shooter[], of Bullet Objects, created by myself this way:
public class Bullet implements ActionListener{
Timer Time = new Timer(20, this);
private int BulletY = 430;
public int PlayerX;
public Rectangle Bound = new Rectangle();
public Bullet(int playerx) {
this.PlayerX = playerx;
Time.start();
}
public void draw(Graphics g){
g.setColor(Color.RED);
g.fillRect(PlayerX + 2, BulletY, 3, 10);
g.dispose();
}
#Override
public void actionPerformed(ActionEvent e) {
if (Time.isRunning()) {
BulletY = BulletY - 5;
Bound = new Rectangle (PlayerX + 2, BulletY, 3, 10);
}
}
}
I thought that calling the draw method in the GamePanel's paint() method would have allowed me to display both all the bullets shot and the player. What actually happens is that at the start it seems allright, but when I press S the player disappears and just one bullet is shot. Can you explain me why? This is how my paint() method looks like:
public void paint(Graphics g) {
g.setColor(Color.BLACK);
g.fillRect(0, 0, 500, 500);
for(int i = 0; i < BulletsCounter; i++) {
Shooter[i].draw(g);
}
g.setColor(Color.RED);
g.fillRect(PlayerX, PlayerY, 20, 20);
//System.out.println("Here I should have painted the player...");
g.dispose();
}
BulletsCounter is a counter I've created to avoid any NullPointerExceptions in painting the whole array, it increases when S is pressed and so another bullet of the array is initialized and shot.
Thank you for your patience, I'm new to the site, so warn me for any mistake.
You've several significant problems, the biggest given first:
You're disposing a Graphics object given to you by the JVM. Never do this as this will break the painting chain. Instead, only dispose of a Graphics object that you yourself have created.
You're drawing within paint which is not good for several reasons, but especially bad for animation since you don't have automatic double buffering of the image
You don't call the super painting method within your override and thus don't allow the JPanel to do house-keeping painting.
Recommendations:
Don't dispose of the Graphics object, not unless you, yourself, create it, for example if you extract one from a BufferedImage.
Override the JPanel's paintComponent method, not its paint method to give you double buffering and smoother animation.
And call super.paintComponent(g) first thing in your override to allow for housekeeping painting
in my application I have a cross road picture in the background and I want to draw traffic lights on the top of it (black rectangle with 3 circles)
The problem is, I cannot see the rectangle at all, as if it was under the image or something. And if I switch the order in which the items are painted, I get all black image.
Do you have any idea how this can be solved?I am new to graphics and searched similar questions, but none helped me.
Thank you.
public MainFrame() throws HeadlessException {
super("semafor");
crossroad = new ImageIcon("cross.png");
initFrame();
initComponents();
sem1 = new Semafor(true, 100, 100);
add(sem1);
repaint();
setVisible(true);
}
//here I paint the image
#Override
public void paint(Graphics g) {
super.paint(g);
g.drawImage(crossroad.getImage(), 0, 45, this);
}
//and in class Semafor i paint the actual traffic lights
#Override
public void paint(Graphics g) {
g.setColor(Color.black);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.darkGray);
//and then the circles
}
The first thing I'm noticing is that you are calling <unknown>.getWidth() and <unknown>.getHeight() for the rectangle size. If it's covering the entire image, this suggests that it is getting that width and height from the panel it is being drawn on.
A simple stack trace,
(new Exception).printStackTrace();
or
Thread.dumpStack();
will tell you as much. You could also query the width and height with a System.out call to verify that you're getting the values you're expecting, or, if this really gets out of control, learn to use JUnit and the assert statement. Honestly, though, it looks like you're just accidentally calling the wrong method.
I have been working on a basic Java Swing application, that paints some objects into a opaque JPanel. I have been coding this app on a University MAC-PC. Yesterday I tested the program on my macbook air(after exporting the project), the behaviour of the application totally change.
The logic of the application is as follows, a Jframe that contains a JLayeredPaneL with Jpanel, in each Jpanel I paint some objects.The application is working correctly on the University laptop.
1)The JPanel is not longer transparent
//Creating Layered Panel
JLayeredPane lpane = new JLayeredPane();
lpane.setBounds(0, 0, screenSize.width, screenSize.height);
this.add(lpane, BorderLayout.CENTER);
lpane.setBounds(0, 0, screenSize.width, screenSize.height);
//creating Jpanel
myGlassPane = new JPanel()
myGlassPane.setBackground(new Color(0, 255, 0, 0));
myGlassPane.setBounds(0, 0, 400, 400);
myGlassPane.setOpaque(true);
myGlassPane.setVisible(true);
//adding item
lpane.add(myGlassPane);
this.setBackground(new Color(0, 255, 0, 0));//makes JFrame invisible
this.setVisible(true);
this.setResizable(false);
2) The JPanel does not remember what is previously drawn(it actually creates two Jpanel one what is being drawn at the moment and what was previously drawn)
I paint lines, whenever a new line is added to an array I call paintAgain(), here is the code of the paintComponent
public void paintLines(Point p)
{
arrayLines.add(p);
repaint();
//Only the point is displayed the other points are not visible,
//the other points are in another JPANEL?
}
public void delete()
{
delete = true;
arrayLines.clear();
repaint();
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
if(delete)
{
g.clearRect(0,0,screenSize.width,screenSize.height);
delete =false;
}
else
{
g2d = (Graphics2D) g.create();
g2d.setStroke(new BasicStroke(10));
g2d.setColor(pickedColor);
//It just paints the new lines, it does not iterate through all the points
g2d.draw(new Line2D.Float(arrayLines.get(arraySize-1).getx1(), arrayLines.get(arraySize-1).gety1(),arrayLines.get(arraySize-1).getx2(),arrayLines.get(arraySize-1).gety2()));
}
}
I do not know why the behaviour of the program changes. Maybe the JRE version that i'm using?I have literally no idea since, it has never happened before
Thank you in advance
In your paintComponent method, your code as written just draws one line, and so one line is all that you see. Instead you need to use a for loop to iterate through your Point collection, arrayLines, drawing a line between points.
// note that i *must* start at 1, not at 0
for (int i = 1; i < arrayLines.size(); i++) {
int x1 = arrayLines.get(i - 1).x;
int y1 = arrayLines.get(i - 1).y;
int x2 = arrayLines.get(i).x;
int y2 = arrayLines.get(i).y;
g2.drawLine(x1, y1, x2, y2);
}
Also, where do you set delete to true ever?
Edit
Regarding your comments:
...The paint method is an example, I call paintLines() several times.
This won't matter if paintComponent draws one and only one line.
Do I have to repaint everything , everytime i make a modification in the array?
At this point, probably, yes. Later consider doing your static background drawing to a BufferedImage and then displaying that in the paintComponent almost first thing, right after the super.paintComponent(g) call, and then drawing your non-static, your moving sprites directly in paintComponent. If you know for a fact that you've only altered a portion of a component, you can call one of the repaint(...) overload methods that suggest repainting a rectangular area of the component.
The problem is this code is working in another computer and in mine it misbehaves.
The issue for me is that I have no idea why the code would have a prayer of working on another system since it is broken code.
One of the other issues you are having is the fact that you are using an alpha based color as the background to an opaque component...
myGlassPane = new JPanel()
myGlassPane.setBackground(new Color(0, 255, 0, 0));
myGlassPane.setBounds(0, 0, 400, 400);
myGlassPane.setOpaque(true);
myGlassPane.setVisible(true);
//adding item
lpane.add(myGlassPane);
Swing only knows how to paint opaque or transparent components and makes these decisions based on the opaque state of the components.
When transparent, the API knows that it must first prepare the graphics context properly and secondly, paint all components that might be beneath this one.
When using an alpha based background color, the component is unable to clear the Graphics context for painting (as filling with a transparent color doesn't do anything), this tends to mean that the Graphics context still contains what ever was painted to it previously (as the Graphics context is a shared resource).
Instead, remove...
myGlassPane.setBackground(new Color(0, 255, 0, 0));
and use
myGlassPane.setOpaque(false);
which will give you the same effect.
It will also mean you won't need
g.clearRect(0,0,screenSize.width,screenSize.height);
and can simply remove the all the elements from the arrayLines instead, which will give you the same effect...just longer lasting...
I think I have figured out where my problem is coming from. when I comment out repaint() I get what I would normally get. I screen with a racetrack on it that I created using rectangles along with 2 cars. Obviously nothing looks like it moves because I commented out repaint(). If I run it as is my program does one of two things. It either doesn't work or it blinks about 3 to 5 times and then stops. if I comment out the sleep() the program just keeps blinking till I exit. The cars move like they are supposed to. I don't know what I'm doing wrong. I can also include the code for the actually track if you would like. It is just way to long and I think I narrowed it down to this.
private class MoveTwo extends Thread{
public void run(){
//infinite loop
while(true){
try{
repaint();//Refresh Screen
if(playerTwoSpeed<=5) playerTwoSpeed+=.2;//slow acceleration
playerTwo.y-=playerTwoSpeed;
Thread.sleep(100);//Delay
} catch(Exception e){
break;//Stop if there is an error
}
}
}
}
Here you go:
public void paint(Graphics g){
super.paint(g);
g.setColor(Color.DARK_GRAY);
g.fillRect(0, 0, WIDTH, HEIGHT);
//Turn Border green when we draw.
g.setColor(Color.GREEN);
//fill rectangles.
g.fillRect(left.x, left.y, left.width, left.height);
g.fillRect(right.x, right.y, right.width, right.height);
g.fillRect(top.x, top.y, top.width, top.height);
g.fillRect(bottom.x, bottom.y, bottom.width, bottom.height);
g.fillRect(center.x, center.y, center.width, center.height);
g.fillRect(obstacle.x, obstacle.y, obstacle.width, obstacle.height);
g.fillRect(obstacle2.x, obstacle2.y, obstacle2.width, obstacle2.height);
g.fillRect(obstacle3.x, obstacle3.y, obstacle3.width, obstacle3.height);
g.fillRect(obstacle4.x, obstacle4.y, obstacle4.width, obstacle4.height);
g.fillRect(obstacle5.x, obstacle5.y, obstacle5.width, obstacle5.height);
g.setColor(Color.WHITE);//Change color to white.
g.fillRect(outerStart.x, outerStart.y, outerStart.width, outerStart.height);
g.fillRect(innerStart.x, innerStart.y, innerStart.width, innerStart.height);
g.setColor(Color.YELLOW);
g.fillRect(finish.x, finish.y, finish.width, finish.height);
//Player one is blue
g.setColor(Color.BLUE);
g.fill3DRect(playerOne.x, playerOne.y, playerOne.width, playerOne.height, true);
g.setColor(Color.RED);
g.fill3DRect(playerTwo.x, playerTwo.y, playerTwo.width, playerTwo.height, true);
}
You probably use an override of a paint method from a JFrame subclass. This leads to blinks on many OS as JFrame are heavy components. The solution is to use an override of a JComponent (typically a JPanel)'s paintComponent method. The code would be the same but would integrate much more smoothly in Swing framework.
Also, you should consider using a better control over your thread, a while truc loop is...difficult to stop. :)
You should use a boolean flag and a method to set it to true or false.
Regards,
Stéphane
You should use double-buffering in order to get smooth animations.
JComponent.setDoubleBuffered(boolean flag);