Hi i wanna ask about Swing timer and Canvas. Im doing simple animation for changing color of objects. I made it with Thread.sleep but JFrame was unresponsive while repainting so i change it to Swing Timer. But now when i start the animation its doing nothing timer is working but objects on canvas dont change color.
Here is my function to animate change of colors im using it in overide paint function of canvas
private void paintSearch(Vector<NodeGraph2D> vector,Graphics graphics2D) {
if (!vector.isEmpty()) {
final int[] k = {0};
Timer timer = new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (repaint) {
if (k[0] == vector.size())
return;
if (vector == pruferTreeNodes) {
vector.elementAt(k[0]).draw((Graphics2D) graphics2D);
} else {
graphics2D.setColor(Color.GREEN);
((Graphics2D) graphics2D).fill(vector.elementAt(k[0]));
graphics2D.setColor(Color.BLACK);
((Graphics2D) graphics2D).drawString(vector.elementAt(k[0]).getNodeGraph().getName(), vector.elementAt(k[0]).getX1() + 15, vector.elementAt(k[0]).getY1() + 25);
}
k[0] += 1;
}
}
});
timer.start();
}
}
Do you think my usage of timers is bad ? Thanks for response. :)
When doing custom painting in Swing it is a good idea to subclass JPanel (it could be an anonymous class) and to store painting-related data in attributes somewhere accessible to the panel.
Your timer would not do any painting but rather manipulate the painting-related data. You should never attempt to do any painting on a graphics object outside of the EventDispatcherThread of Swing or outside of the paintComponent methods of JComponents. (refer to the documentation of these methods for further information)
Here is an example of how custom painting with a timer manipulating color could look like:
public static void main(String[] args) {
EventQueue.invokeLater(Example::new);
}
// this is the painting-related data that is being manipulated by the timer
private int currentColorIndex;
public Example() {
JFrame frame = new JFrame("Custom Painting");
frame.setSize(640, 480);
frame.setLocationRelativeTo(null);
Color[] allColors = {Color.RED, Color.BLUE, Color.GREEN,
Color.YELLOW, Color.ORANGE, Color.MAGENTA};
JPanel myCustomPanel = new JPanel() {
#Override
protected void paintComponent(Graphics g) {
// here the painting related data is being used by the custom JPanel implementation
g.setColor(allColors[currentColorIndex]);
g.fillRect(0, 0, getWidth(), getHeight());
}
};
frame.setContentPane(myCustomPanel);
Timer timer = new Timer(100, e -> {
// the timer does not use any graphics objects, etc, but rather manipulates our painting-related data
currentColorIndex = (currentColorIndex + 1) % allColors.length;
// whenever the painting-related data has changed we need to call repaint() on our custom JPanel implementation
myCustomPanel.repaint();
});
timer.setRepeats(true);
timer.start();
frame.setVisible(true);
}
Related
I'm having trouble getting smooth and fluid motion with java Swing. Using this code, which involves using a BufferedImage to draw graphics to before applying it to the panel, gives me results with shaky motion and stuttering. How can I fix this?
public class SmoothMotion extends JPanel {
public static final int size = 800;
public static int pos = 0;
public static void main(String[] args) {
JPanel panel = new SmoothMotion();
JFrame frame = new JFrame("Smooth As");
frame.setSize(size, size);
frame.add(panel);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Timer timer = new Timer(17, new ActionListener() {
public void actionPerformed(ActionEvent e) {
SmoothMotion.updateItems();
panel.repaint();
}
});
timer.start();
}
public static void updateItems() {
pos += 4;
pos = pos > 750 ? 0 : pos;
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLACK);
g.fillRect(0, 0, size, size);
g.setColor(Color.RED);
g.fillRect(pos > 375 ? 750 - pos : pos, 100, 50, 50);
}
}
don't override paint(). Custom painting is done by overriding paintComponent(...)
there is no need for the BufferedImage. Swing is double buffered by default. Just invoke super.paintComponent(graphics) first to clear the background and then do your custom painting.
the painting method should NOT change properties of the class. You should have a method like updatePos(…) to update the position.
The ActionLIstener would invoke the updatePos() which would then invoke repaint().
See: get width and height of JPanel outside of the class for an example that demonstrates many of the above suggestions.
I would like to draw some circles every second and delete them all (or at least one) from the panel.
Here is the exisiting code:
public class DrawShape {
ShapePanel panel;
public DrawShape(ShapePanel panel) {
this.panel = panel;
Timer t = new Timer();
t.schedule(new TimerTask() {
long startTime = System.currentTimeMillis();
int secondsToRun = 3;
#Override
public void run() {
if (System.currentTimeMillis() - startTime > secondsToRun * 1000) {
panel.deleteCircle();
System.out.println("\t" + panel.circles.size());
cancel();
} else {
panel.addCircle(new Circle((int) (Math.random() * 200), (int) (Math.random() * 200)));
System.out.println(panel.circles.size());
}
}
}, 0, 1000);
}
}
If the time is greater than 3 seconds, delete all circles, else continue to draw circles on screen.
Here is the ShapePanel class:
public class ShapePanel extends JPanel {
public List<Circle> circles = new LinkedList<Circle>();
public ShapePanel() {
// Setting up the frame
JFrame frame = new JFrame();
frame.setSize(500, 500);
frame.setVisible(true);
frame.setBackground(Color.black);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(this); // adding the panel to the frame
}
public void addCircle(Circle circle) {
circles.add(circle);
this.repaint();
}
public void deleteCircle() {
// circles.remove(circles.get(0));
circles.clear(); //remove them all
this.repaint();
}
#Override
public void paint(Graphics g) {
for (Circle c : circles) {
g.setColor(Color.GREEN);
g.drawOval(c.x, c.y, Circle.radius * 2, Circle.radius * 2);
}
}
}
When I call deleteCircle(), the circles should be removed from the list and repaint. I should end up with a blank screen with no circles in it. I think repaint doesn't work in this case.
P.S.: first time asking a question, so sorry if it's long one :D
So, two things jump out at me immediately.
Using java.util.Timer
Swing is NOT thread safe and since java.util.Timer runs in it's own thread, updating the UI (or in this case, something the UI relies on) could cause you random issues.
I'd consider using a javax.swing.Timer instead, as it is triggered inside the Event Dispatching Queue (and is generally simpler to use)
Not calling super.paint
You've failed to take into consideration what paint does and failed to take over it's responsibilities, which, in this case, would be to "prepare" the Graphics context for painting.
Graphics is a shared resource, which is passed to all the components been updated in the paint pass. This means it will contain what ever was previously painted to it.
Two recommendations:
As a general recommendation, prefer overriding paintComponent instead of paint (paint does a lot of important jobs, so unless you're willing to do them, it's generally to high up in the paint chain)
Call super.paintComponent first, before doing any custom painting.
I would, highly, recommend reading:
Performing Custom Painting
Painting in AWT and Swing
and
Concurrency in Swing
How to Use Swing Timers
I'm following along in a (dated) Java book, and this project is supposed to "animate" a circle moving across the screen. However, when the program is run, the circle remains in one spot. My code looks identical to the book's. Am I forgetting something? Am I calling repaint() at the wrong time?
public class Animation
{
JFrame f;
int x, y;
public static void main(String [] args)
{
Animation a = new Animation();
a.go();
}
public void go()
{
f=new JFrame();
myPanel p=new myPanel();
f.getContentPane().add(p);
f.setSize(300, 300);
f.setVisible(true);
for(int i=0; i<=50; i++)
{
p.repaint();
x++;
y++;
}
}
class myPanel extends JPanel
{
public void paintComponent(Graphics g)
{
super.paintComponent(g);
g.setColor(Color.GREEN);
g.fillOval(x, y, 40, 40);
}
}
}
So, immediately, two things jump out at me.
It's possible that the loop which is updating the values is NOT running on the Event Dispatching Thread, which could be leading to dirty read/writes
repaint can consolidate requests to reduce the amount of work placed on the Event Queue/Event Dispatching Thread. Since there is not "artificial" delay in the loop, it's possible that all your requests are been reduced to a single update pass by the RepaintManager
The first thing I would do is isolate the responsibility for the management of the position of the oval, because in your current code, it could be updated from anywhere, which is just a mess
class MyPanel extends JPanel {
private Point posy = new Point(0, 0);
public Point getPosy() {
return posy;
}
public void move() {
Point posy = getPosy();
posy.x++;
posy.y++;
repaint();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.GREEN);
g.fillOval(posy.x, posy.y, 40, 40);
}
}
Next, I'd ensure that the context of the UI is been modified from within the EDT...
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
Animation a = new Animation();
a.go();
}
});
}
and finally, I'd make use of Swing Timer to act as a pseudo loop for the animation...
public void go() {
f = new JFrame();
MyPanel p = new MyPanel();
f.getContentPane().add(p);
f.setSize(300, 300);
f.setVisible(true);
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
p.move();
}
});
timer.start();
}
There are a number of reasons for using a Swing Timer in this context. First, the "ticks" are executed ON the EDT, making it safe to update the UI from within and it won't block the UI while it's "waiting" between ticks
I would recommend having a look at:
Painting in AWT and Swing which will give you a better understanding into how painting actually works
Concurrency in Swing as it will give you a heads up into some of the pitfalls in trying to do more then one thing within the GUI
How to Use Swing Timers because they are really useful for this kind of thing
I'm trying to draw an animation in a JPanel by displaying all the frames as BufferedImage objects and using a Thread to invoke g.drawImage in the JPanels paintComponent(Graphics g) method for each frame, with sleeping in between. My understanding is that invoking g.drawImage from anywhere, as long as g is the Graphics object from the paintComponent, should cause the pixels in the JPanel to be updated, but there is no change in the JPanel. Is that not how Graphics.drawImage works, or is it an issue with using another Thread, or something else all together? An abbreviated version of my code is below, with unnecessary bits removed
class Example extends JPanel{
public Dimension getPreferredSize(){
return new Dimension(500, 500);
}
public void paintComponent(Graphics g){
super.paintComponent(g);
draw(g);
}
public void draw(Graphics g){
BufferedImage temp1;
BufferedImage temp2;
try{
temp1 = ImageIO.read(new File("C:\\Users\\Owner\\Desktop\\test1.png"));
temp2 = ImageIO.read(new File("C:\\Users\\Owner\\Desktop\\test2.png"));
}catch(IOException e){
temp1 = null;
temp2 = null;
}
final BufferedImage image1 = temp1;
final BufferedImage image2 = temp2;
Thread drawThread = new Thread(new Runnable(){
public void run(){
g.drawImage(image1, 0, 0, null);
try{
Thread.sleep(100);
}catch(InterruptedException e){
// omitted
}
g.drawImage(image2, 0, 0, null);
try{
Thread.sleep(100);
}catch(InterruptedException e){
// omitted
}
}
});
drawThread.start();
}
public static void main(String[] args){
SwingUtilities.invokeLater(new Runnable(){
public void run(){
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new B());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}
Your current painting code is wrong. A painting method is for painting only. You would NEVER start a Thread from a painting method. You can't control when the painting method is invoked and every time the method is invoked you would start another Thread.
The Graphics object should only be used as a short duration object that exists for the duration of the painting method. You should not attempt to keep a reference to the object indefinitely.
I'm trying to draw an animation
If you want to do animation then you use a Swing Timer to schedule the animation.
So you should have the image as a property of the class. Then when the Timer fires you change the image property and invoke repaint() on the panel.
Here is my code: I took out some stuff that I felt wasn't necessary. I might've took out some brackets too, but I'm just trying to show the content I have.
What happens is, when I run the program, the background image paints (it's a PNG in resources), and only ONE button appears (my PLAY button), which is the first button - it's auto-selected.
I actually have four buttons but I've only included PLAY and INSTRUCTIONS in my code. The other three don't show up unless I mouse over them. I know it's probably something weird with the paint method, but I don't know how to fix it.
If I select a different button and minimize the window then open it again, that selected button is the only one that appears. I have to mouse over to get the other buttons to appear.
I've added super.paint() to the paint method too and I get all my buttons but the background is grey.
I think the problem is super.paint() paints all my buttons, and g.drawImage(bg, 0, 0, null) only paints my background and I can't do one without painting over the other.
Sorry if this was a mess. I'm new at Java and I have trouble articulating what I'm trying to say.
public class MainMenu extends JFrame {
private JPanel contentPane;
/**
* Launch the application.
*/
//variables
public static Image bg;
public static void main(String[] args) {
MainMenu mainFrame = new MainMenu();
mainFrame.setSize(FRAME_WIDTH, FRAME_HEIGHT);
mainFrame.setResizable(false);
mainFrame.setLocationRelativeTo(null);
mainFrame.setTitle ("Zumby");
mainFrame.setLayout(null);
// Loads the background image and stores in bg object.
try {
bg = ImageIO.read(new File("zumby.png"));
} catch (IOException e) {
}
mainFrame.setVisible(true);
}
/**
* Overrides the paint method.
* MONDAY
*/
public void paint(Graphics g)
{
// Draws the img to the BackgroundPanel.
System.out.println("paint");
g.drawImage(bg, 0, 0, null);
}
/**
*/
public MainMenu() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 800, 500);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
contentPane.setOpaque(false);
setContentPane(contentPane);
contentPane.setLayout(null);
//create buttons
JButton btnPlay = new JButton("PLAY");
btnPlay.setBackground(Color.BLACK);
btnPlay.setForeground(Color.WHITE);
btnPlay.setFont(font);
btnPlay.setBorder(border);
btnPlay.setFocusPainted(false);
//if "Play" is clicked
btnPlay.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent click) {
setVisible(false);
new GamePlay(); //opens up GamePlay window
}
});
btnPlay.setBounds(600, 64, 141, 61);
contentPane.add(btnPlay);
JButton btnInstructions = new JButton("INSTRUCTIONS");
btnInstructions.setBounds(600, 160, 141, 61);
btnInstructions.setBackground(Color.BLACK);
btnInstructions.setFocusPainted(false);
// btnInstructions.setEnabled(true);
contentPane.add(btnInstructions);
repaint();
pack();
setVisible(true);
}
}
Swing uses a "layering" concept for it's painting...
paint calls paintComponent, paintBorder and paintChildren. By overriding paint and failing to call super.paint, you've prevented the component from painting it's various layers.
In Swing, it is preferred to use paintComponent to provide custom painting, which allows you to paint underneath any other components that might be added to the component.
public class TestPaint01 {
public static void main(String[] args) {
new TestPaint01();
}
public TestPaint01() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private Image backgroundImage;
public TestPane() {
try {
BufferedImage background = ImageIO.read(new File("/path/to/image.jpg"));
//backgroundImage = background.getScaledInstance(-1, background.getHeight() / 4, Image.SCALE_SMOOTH);
backgroundImage = background;
} catch (IOException ex) {
ex.printStackTrace();
}
setLayout(new GridBagLayout());
add(new JButton("Hello"));
}
#Override
public Dimension getPreferredSize() {
return backgroundImage == null ? super.getPreferredSize() : new Dimension(backgroundImage.getWidth(this), backgroundImage.getHeight(this));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int x = (getWidth() - backgroundImage.getWidth(this)) / 2;
int y = (getHeight() - backgroundImage.getHeight(this)) / 2;
g.drawImage(backgroundImage, x, y, this);
}
}
}
You might find A Closer look at the Paint Mechanism and Painting in AWT and Swing informative.
I think it's because you're overriding the paint method. It's better to override repaint, then call super.repaint(); Like this:
public void repaint(Graphics g)
{
super.repaint(g);
// Draws the img to the BackgroundPanel.
System.out.println("paint");
g.drawImage(bg, 0, 0, null);
}
Then the components get redrawn as well.
But if all you want to do is show an image as the background see here.
You're overriding paint() but don't call super.paint(). So the normal painting of the components done by the JFrame's paint() method implementation is not executed.
Since you are using Swing and JFrame the painting mechanism used to override paintComponent not paint that is usually used with applets or AWT.