Solution:
Yes it is wrong. Never, never, never, NEVER read in files/images within a painting method. This method must be blindingly fast and must involve painting only, and doing this, reading in a file, will needlessly slow down your GUI to a crawl. And why do this? You can read the image in once in a constructor or method and then reuse the image object in paintComponent as many times as needed.
As an aside, you're probably better off obtaining the image as a resource and not a file.
Thanks, Hovercraft Full Of Eels
I am creating a very simple version of 'Fruit Ninja'. A game where some fruit flies onto the screen and the user has to cut it in two.
I am using a JLabel with ImageIcon for the fruit. I use a swing Timer for movement.
It works great, with animations too, but here is the tricky part. When I add a background image, the animations are verry laggy.
The question is: How can I add a background image, while the swing animations won't lose performance?
I added some code below.
Tim
My JPanel where I draw my background image:
public class PlayingField extends javax.swing.JPanel {
public PlayingField()
{
this.setBounds(0, 50, 500, 500);
this.setLayout(null);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
BufferedImage img = null;
try {
img = ImageIO.read(new File(System.getProperty("user.dir")+"/assets/background.png"));
} catch (IOException e)
{
}
g.drawImage(img, 0, 0, null);
}
}
The objects I'm drawing on them are just simple JLabels with ImageIcons.
I have a timer that handles the animation:
public class GameTimer extends javax.swing.Timer implements ActionListener {
GameController gameController;
public GameTimer(GameController gameController) {
super(delay, null);
this.addActionListener(this);
this.gameController = gameController;
}
#Override
public void actionPerformed(ActionEvent e)
{
gameController.moveObject();
}
}
and last but not least the code that moves the object:
public void moveObject()
{
activeObject.setPositionY((activeObjectView.getPositionY()+1));
playingField.repaint();
}
Yes it is wrong. Never, never, never, NEVER read in files/images within a painting method. This method must be blindingly fast and must involve painting only, and doing this, reading in a file, will needlessly slow down your GUI to a crawl. And why do this? You can read the image in once in a constructor or method and then reuse the image object in paintComponent as many times as needed.
As an aside, you're probably better off obtaining the image as a resource and not a file.
Related
I created a Jframe using netbeans drag-n-drop designer. After asking here , I can make the color changing works after changing the color of some item from recList and paint(g) on a new thread of my Draw class.
Now I want to add another JComponent like DrawCar that will add the car image to the Jframe. I want this new JComponent because I don't want to re-render the "car" if the squares in the background change color.
So I created the DrawCar with paint() method like below:
public void paint(Graphics g) {
//This to make the (0,0) at the bottom-left not top-left as default.
Graphics2D g2 = (Graphics2D)g;
AffineTransform at = g2.getTransform();
at.translate(0, getHeight());
at.scale(1, -1);
g2.setTransform(at);
//Below is to draw the car
try {
image = ImageIO.read(new File("car.png"));
} catch (IOException ex) {
Logger.getLogger(Car.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
}
g.drawImage(image, 245, 0, null);
}
If I put these code to render the car in the paint() method of Draw class, it works, so no problem with this code!
In the Container (the class with the GUI) class, I have a button handler. I want the car appear when I click that button, so I tried to add to the event handler with
private void starter_btnActionPerformed(java.awt.event.ActionEvent evt) {
Thread reDraw = new Thread(new Runnable() {
#Override
public void run() {
//draw1 below is the instance of "Draw" class
//draw1.paint2(draw1.getGraphics()); //This code works with repainting the square with new color as mentioned before
DrawCar draw2 = new DrawCar();
repaint();
revalidate();
}
});
reDraw.start();
}
but it won't work, I don't know what I missed here. Maybe some ways to add the DrawCar to the current JFrame?
Thank you for your time!
EDIT:
I just make a simple project to make it clear. I created a JFrame named Test and drop in it a button that will show the picture when I click on it. It's all auto-generated codes.
Now I create a new class call MyClass
public class MyClass extends JComponent{
private BufferedImage image;
MyClass(){
try {
image = ImageIO.read(new File("D://pic.jpg"));
} catch (IOException ex) {
Logger.getLogger(this.getName()).log(java.util.logging.Level.SEVERE, null, ex);
}
}
public void paintComponent(Graphics g){
super.paintComponent(g);
g.drawImage(image, 50, 50, null);
}
}
And in the Test, the event handler of the button is like this:
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
// TODO add your handling code here:
MyClass mc = new MyClass();
add(mc);
repaint();
revalidate();
}
I clicked it and nothing happens. The picture is supposed to shows up on click. How do I make it happens? Thank you
EDIT 2:
This is the image of what I want to achieve, it's the little green car on the bottom, it should appears only when I click "Start!"
I see quite a few general problems with your code, including:
Never override the paint method, but rather a JPanel or JComponent's paintComponent method.
Almost always call the super's method.
Never read in a file from within a painting method.
Your code flagrantly violates Swing threading rules -- most all Swing calls should be made on the Swing event thread, not off of it as you're doing.
Better to create and post your Minimal, Complete, and Verifiable example or sscce as this way we can run your actually functioning code.
As for your specific problem, you're creating a DrawCar component instance, but you never appear to add it to your GUI, and so it will never show since it is never part of a top-level window such as a JFrame or JApplet or JDialog.
re comments:
Just though paint() and paintComponent() are the same, just one calls another...
No they're not. The paint(...) method is the more "primitive" of the two as it's based in the old AWT code base. It does not automatically do double buffering (unlike paintComponent) and is responsible for painting the component, the component's borders, and any child components held by the component. For the latter reason, you want to avoid overriding it since if you mess it up, you risk mispainting more than just the component.
Back to my problem, I make it like this DrawCar draw2 = new DrawCar(); add(draw2); draw2.setVisible(true); revalidate(); repaint(); I don't know if I missed the "prefix" before add, the code is auto-generated so I can't find any JFrame frame = new JFrame(); to make frame.add(draw2) like others do with their code Any chance that we will find some logical problem?
You've got major structural problems as your new DrawCar class again is nowhere to be seen in the main GUI.
So I'm guessing that your main GUI already has a DrawCar component held within it, and that you're goal is to get this to draw a car on button push (you never say!). If so, then why not have the DrawCar hold a JPanel and simply set its Icon with your image. Myself, I'd read the image in at program start up, and also flip it at at that time, if that were my goal.
Again, if you're still having problems, then isolate the problem down to the smallest compilable runnable code, your mcve and let us test it.
I have a little issue with my GUI in NetBeans. I draw images (dots) when a user clics in a JPanel at the mouse clic location. This part works just fine. I store each image locations in two different ArrayList that contains the X location and Y location. Now what I want to do is to delete the latest image drawn in the Panel after a button is clicked. So what I did is remove the last index of both ArrayList, and then call repaint() to draw all the images from the locations in both X and Y ArrayList (code below).
What is weird is that I need to resize the GUI (put it in full screen or just change its' size) in order for the drawn images to show up again in the JPanel otherwise, the panel remains empty.
Here's the parts of code that are affected :
public void paint(Graphics g) {
super.paint(g);
for(int i=0;i<=listePointsX.size()-1;i++) {
try{
BufferedImage icon = ImageIO.read(getClass().getResourceAsStream("/myimage.png"));
Graphics graphe = jPanel1.getGraphics();
graphe.setColor(Color.BLACK);
graphe.drawImage(icon, this.listePointsX.get(i),this.listePointsY.get(i), rootPane);
}catch(Exception e1){
}
}
private void jButtonUndoActionPerformed(java.awt.event.ActionEvent evt) {
if(listePointsX.size()>0){
int lastObject= listePointsX.size();
listePointsX.remove(lastObject-1);
listePointsY.remove(lastObject-1);
jPanel1.repaint();
}
else{
}
}
Any idea what I need to do to some kind of "refresh" the whole thing? Am I doing something wrong? Tried searching about that but did not find anyting...
Graphics graphe = jPanel1.getGraphics(); is NOT how painting should work, instead, you should have overriden the panel's paintComponent method and painted the points within in.
See Painting in AWT and Swing and Performing Custom Painting for more details about how painting works in Swing
Instead, your panel should be doing ALL the work, managing the points in the ArrayList and painting them. You parent component "might" have the ability to add or remove points if that meets your design requirements, but the core responsibility remains with the panel.
Avoid performing any long running or block operations within the paint methods, they should run as fast as possible. Since the image never changes, you should simply load once (either when the class is constructed or when you first need the image) and keep using the same reference.
Alright it worked just fine now. I had to do it the way you told me up here. I created a new class that extends jPanel (below). Then in my main Form, had to create an object of this class. Whenever a user makes a click, it will call this Drawing class object and add an item to the ArrayList (this object manages everything regarding created points... It looks like this :
public class MyDrawingClass extends JPanel {
ArrayList<Integer> arrayListPointX = new ArrayList<>();
ArrayList<Integer> arrayListPointY = new ArrayList<>();
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
try{
BufferedImage icon = ImageIO.read(getClass().getResourceAsStream("/images/dot.png"));
g.setColor(Color.BLACK);
if(arrayListPointX.size()<=0){
...
}
else{
for(int i=0;i<listePointsX.size();i++){
g.setColor(Color.BLACK);
g.drawImage(icon, listePointsX.get(i), listePointsY.get(i), rootPane);
}
}
}catch(Exception e){
...
}
}
So if I want to "undo", let's say my object of "MyDrawingClass" is called "draw". I can do : draw.arrayListPointX.remove(draw.arrayListPointX.size()-1); and call repaint(); to display remaining points.
Thanks for your tips appreciate it ! :)
The goal here is that I can click a color and shape, then two points on the figuresPanel. That colored shape is filled in on the figuresPanel, and I can add shapes on top of each other etc. I also need the current date to be displayed in the corner of the figuresPanel, on top of all the painted shapes. I am able to create shapes (though they always cover the date) no problem, but whenever I resize or minimize the window, all the painted shapes disappear. I've tried implementing quite a few different methods from Graphics (update, paint, redraw, etc.) and haven't found one that corrects this issue. I thought paintComponent() would take care of all that, but apparently not. Feel free to point out anything that could be implemented in a better way, always happy to learn something new.
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
drawFigures();
figuresPanel.add(dateLabel, c);
}
#Override
public Dimension getPreferredSize() {
return (new Dimension(800, 600));
}
private void addFigure(Figure figure) {
figureList.add(figure);
listArea.append(figure.toString() + "\n");
figure.fill(getGraphics());
}
private void drawFigures() {
for (Figure f : figureList) {
f.fill(getGraphics());
}
}
I would suggest a few things that might help you.
In your method drawFigures() instead of using getGraphics(), why don't you pass the instance of Graphics in paintComponentMethod as a parameter to the method and use it to draw the object. This will make sure that you are drawing on the same instance of the Graphics that you are using to paint.
In your method addFigure(Figure figure) I would suggest you remove the line figure.fill(getGraphics());, to draw the object completely. Instead in your mouseClicked(MouseEvent event) method after your switch statement insert:
revalidate();
repaint();
This should actually take care of the drawing of the objects are done exactly on the same Graphics object that is being used to display the components.
I hope that this shall be helpful.
I have a panel in which i draw a number of graphics objects on using the paintComponent method and the Graphics methods to draw.
I need to create a button, that you click and then it clears the panel.
example :
JButton clear = new JButton("Clear");
public void actionPerformed(ActionEvent e){
if(e.getSource()==clear){
//button code here
}
}
What i need is the code that goes inside that IF statement.
The paintComponent method will get called everytime a repaint is called and this method draw what you write in it everytime.If you want to clear a screen of object then you must remove the object(a button for example) from the panel/frame on the button click function.
It is not a good practice to write logic code in the paintComponent method with boolean etc.
Do something like this:
private boolean clear = false;
public void paintComponent(Graphics g) {
super.paintComponent(g);
if(clear)
return;
}
// all your graphics here
}
public void actionPerformed(ActionEvent e){
if(e.getSource()==clearButton){
clear = true;
repaint();
}
}
Don't forget to change back the clear flag, at some point of the program, when you want to allow drawing.
Check out Custom Painting Approaches for the two common ways of doing custom painting:
paint objects contained in a List, in which case you would clear the List
paint onto a BufferedImage, in which case you would clear the BufferedImage
The examples code shows how "Clear" would be done in both cases.
I'm attempting to code a simple animation or physics example in a Java Swing application. I have the actual windows application open and working, but I can't figure out how to actually draw my shapes, and how I'd format the code for calculations between frames, that sort of stuff.
I've read some stuff about over riding a paint method, but I don't know what that means, and I don't believe I'm using it in the code I'm using right now. This is my code:
public class Physics extends JFrame{
public Physics() {
initUI();
}
private void initUI() {
JPanel panel = new JPanel();
getContentPane().add(panel);
panel.setLayout(null);
final JLabel label = new JLabel("Hi, press the button to do something");
label.setBounds(20, 0, 2000, 60);
final JButton submitButton = new JButton("Start");
submitButton.setBounds(20, 150, 80, 20);
submitButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
//Put button code here later
}
});
panel.add(label);
panel.add(submitButton);
setTitle("Program");
setSize(300, 250);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
Physics ex = new Physics();
ex.setVisible(true);
}
});
}
}
So I have some blank space above my button where I'd like to draw maybe a square or circle moving across the screen to start off with, once I get that down I can start getting into the more advanced stuff. Any hints on how to do that would be appriciated :D
Thanks!
"I've read some stuff about over riding a paint method, but I don't know what that means"
So you've overridden actionPerformed, so you know what an #Override is. As you'll notice from the ActionListener, you never actually explicitly call actionPerformed, but whatever you put in the there, still get's used. That's because the ActionListener implicitly call it for you.
The same is true with painting. In the Swing painting process, there is a paint chain that Swing uses to paint components. Along the way paint is called somewhere. So just like actionPerformed, you can override paint and it will get implicitly called for you.
#Override
public void paint(Graphics g) {
super.paint(g);
}
The Graphics object passed to the method is the graphics context that Swing will use for the painting. You can look at the Graphics API to see the methods you can use. You can use drawOval to draw a circle
#Override
public void paint(Graphics g) {
super.paint(g);
g.drawOval(x, y, width, height);
}
Now here's the thing. You don't actually want to override paint. In the tutorials linked above, some of the examples will use applets and override paint, but you shouldn'y paint on top level containers like JFrame or JApplet. Instead paint on a JPanel or JComponent and just add it the JFrame. When you do paint on JPanel or JComponent, you'll instead override paintComponent (which also gets called along the paint chain), instead of paint
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawOval(x, y, width, height);
}
You see how I used variables for the drawOval method. The x is the x location from the top-let of the screen, and y and the y point. width and height are width and height of the circle. The great thing about using variables is that their values can be changed at runtime.
That's where the animation comes to play. As pointed out, you an use a javax.swing.Timer
The basic construct is
public Timer(int delay, ActionListener listener) {
}
The delay is the milliseconds to delay each call to the listener. The listener will have your actionPerformed call back that will do what's inside, every delay milliseconds. So what you can do, is just change the x from the drawOval and repaint(), and it will animate. Something like
Timer timer = new Timer(40, new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
x += 5;
repaint();
}
});
timer.start();
The timer code you can just put in the constructor. That's probably simplest explanation I can give. Hope it helps.
Don't forget the to see Custom Painting and Grapics2D for more advance topics on graphics. Also see some example of timers and animation here and here and here and here and here
Also avoid using null layouts. See Laying out Components Within a Container to learn how to use layout managers, as should be done with Swing apps.
Take a look at the Swing tutorial on Custom Painting.
The example shows you how to do painting. If you want animation, then you would use a Swing Timer to schedule the animation. The tutorial also has a section on How to use a Swing Timer.
Put the two tutorial together and you have a solution.
There are any number of ways to achieve this.
Start by taking a look at:
Performing Custom Painting
2D Graphics
For details about how painting in Swing is done.
Animation is not as simple as just pausing a small period of time and then repainting, theres acceleration and deceleration and other concepts that need to be considered.
While you could write your own, that's not a small task, a better solution might be to use a pre-existing engine, for example...
Then take a look at:
Timing Framework
Trident
java-universal-tween-engine
Which are all examples of animation engines in Swing. While I prefer the Timing Framework as it provides me with a lower level API, this is a personal opinion. Both Trident and the JUWE seem to be geared more towards component/property based animation (which the Timing Framework can do if you want to build some of the feature sets up)
I created a simple animation with two rockets blasting off. The full eclipse project is here: https://github.com/CoachEd/JavaExamples/tree/master/RaceToSpace. Here's a screenshot: