简体   繁体   中英

JPanel listeners and threads issues

Here is the code for displaying circles with varying radius on a panel inside a frame with a given delay rate, but the code is showing the final output not the intermediate stages ie, the circles are not appearing one by one but all the circles are coming at once as a final output. There may be some errors related to button action listeners and panel threads. The code is taking initial circle radius and the total number of iterations (the total number of circles to be displayed), radius of each next circle gets incremented by 10.

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class ControlCircle extends JFrame {
  private JButton jbtEnlarge = new JButton("Start");
  private JButton jbtShrink = new JButton("Stop");
  private CirclePanel canvas = new CirclePanel();

  private int radius = 0;
  private int iter;

  public ControlCircle() {
    JPanel panel = new JPanel();
    JPanel jp = new JPanel();
    jp.setPreferredSize(new Dimension(300, 0));
    panel.add(jbtEnlarge);
    panel.add(jbtShrink);

    this.add(jp, BorderLayout.WEST);
    this.add(canvas, BorderLayout.CENTER);
    this.add(panel, BorderLayout.SOUTH);

    final JTextField f1 = new JTextField(8),f2 = new JTextField(8);

    jp.setLayout(new FlowLayout(FlowLayout.RIGHT, 50, 30));
    jp.add(new JLabel("Radius"));
    jp.add(f1);
    jp.add(new JLabel("Iteration"));
    jp.add(f2);

    f1.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        radius = Integer.parseInt(new String(f1.getText()));
      }
    });

    f2.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        iter = Integer.parseInt(new String(f2.getText()));
      }
    });

    jbtEnlarge.addActionListener(new EnlargeListener());
    jbtShrink.addActionListener(new ShrinkListener());
  }

  public static void main(String[] args) {
    JFrame frame = new ControlCircle();

    frame.setTitle("ControlCircle");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setSize(800, 600);
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
  }

  class EnlargeListener implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      canvas.enlarge();
    }
  }

  class ShrinkListener implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      //canvas.shrink();
    }
  }

  class CirclePanel extends JPanel {
    private int r = radius;

    public void enlarge() {
      //radius += 2;

      repaint();
    }

    public void shrink() {
      radius -= 2;

      repaint();
    }

    protected void paintComponent(Graphics g) {
      super.paintComponent(g);

      for (int i = 0; i < iter; i++) {
        g.drawOval(getWidth() / 2 - r, getHeight() / 2 - r, 2 * r, 2 * r);

        try {
          Thread.sleep(100);
        } catch (Exception exp) {
        }

        r = r + 10;
      }

      r = 0;
    }
  }
}

The problem you're having is far to common.

Swing is a single threaded framework. This means that all UI related interactions must occur within the context of this thread (AKA the Event Dispatching Thread).

The EDT is responsible for, amongst other things, dispatching repaint requests. If any part of your code stops this thread (block I/O, time consuming process, Thread.sleep ), the EDT will be unable to process any new events.

Have a read through Concurrency in Swing for more details.

You now face two issues...

  1. You can't block the EDT
  2. You can't update the UI from any thread other then the EDT.

Luckily, there are a number of solutions. The simplest is using a javax.swing.Timer .

This timer triggers it's tick events within the EDT but waits within it's own thread...

在此处输入图片说明

import com.sun.org.apache.bcel.internal.generic.LSTORE;
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Composite;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Droplets {

    public static void main(String[] args) {
        new Droplets();
    }

    public Droplets() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Test");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new DropletPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }
    protected static final int MAX_RADIUS = 50;
    protected static final int GROWTH_RATE = 1;

    public class DropletPane extends JPanel {

        private List<Droplet> droplets;

        public DropletPane() {
            droplets = new ArrayList<>(25);
            addMouseListener(new MouseAdapter() {
                @Override
                public void mousePressed(MouseEvent e) {
                    droplets.add(new Droplet(e.getPoint()));
                }
            });

            Timer timer = new Timer(40, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    for (Droplet droplet : droplets.toArray(new Droplet[droplets.size()])) {
                        droplet.grow();
                        if (droplet.getRadius() >= MAX_RADIUS) {
                            droplets.remove(droplet);
                        }
                    }
                    repaint();
                }
            });
            timer.setRepeats(true);
            timer.setCoalesce(true);
            timer.start();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            Composite comp = g2d.getComposite();
            for (Droplet droplet : droplets) {

                float alpha = 1f - ((float) droplet.getRadius() / (float) MAX_RADIUS);
                g2d.setComposite(AlphaComposite.SrcOver.derive(alpha));
                Point p = droplet.getLocation();
                int radius = droplet.getRadius();
                g2d.drawOval(p.x - (radius / 2), p.y - (radius / 2), radius, radius);
                g2d.setComposite(comp);

            }
            g2d.dispose();
        }
    }

    public class Droplet {

        private Point p;
        private int radius;

        public Droplet(Point p) {
            this.p = p;
        }

        public Point getLocation() {
            return p;
        }

        public int getRadius() {
            return radius;
        }

        public void grow() {
            radius += GROWTH_RATE;
            if (radius > MAX_RADIUS) {
                radius = MAX_RADIUS;
            }
        }
    }
}

Extended Example

This example will, when you click the "Start" button, create a random number of droplets at a random interval (between each droplet). You can press start multiple times and it will compound the output.

在此处输入图片说明

import static droplets.Droplets.MAX_RADIUS;
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Composite;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Droplets02 {

    public static void main(String[] args) {
        new Droplets02();
    }

    public Droplets02() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Test");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new DropletPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }
    protected static final int MAX_RADIUS = 50;
    protected static final int GROWTH_RATE = 1;

    public interface Pool {

        public void addDroplet(Droplet droplet);

        public Dimension getSize();
    }

    public class DropletPane extends JPanel implements Pool {

        private List<Droplet> droplets;
        private Timer timer;

        public DropletPane() {

            setLayout(new GridBagLayout());
            JButton button = new JButton("Start");
            button.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    new DropletWorker(DropletPane.this).execute();
                }
            });
            add(button);

            droplets = new ArrayList<>(25);
            timer = new Timer(40, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    if (!droplets.isEmpty()) {
                        for (Droplet droplet : droplets.toArray(new Droplet[droplets.size()])) {
                            droplet.grow();
                            if (droplet.getRadius() >= MAX_RADIUS) {
                                droplets.remove(droplet);
                            }
                        }
                        if (droplets.isEmpty()) {

                            ((Timer) e.getSource()).stop();

                        }
                        repaint();
                    }
                }
            });
            timer.setRepeats(true);
            timer.setCoalesce(true);

        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            Composite comp = g2d.getComposite();
            for (Droplet droplet : droplets) {

                float alpha = 1f - ((float) droplet.getRadius() / (float) MAX_RADIUS);
                g2d.setComposite(AlphaComposite.SrcOver.derive(alpha));
                Point p = droplet.getLocation();
                int radius = droplet.getRadius();
                g2d.drawOval(p.x - (radius / 2), p.y - (radius / 2), radius, radius);
                g2d.setComposite(comp);

            }
            g2d.dispose();
        }

        @Override
        public void addDroplet(Droplet droplet) {
            if (!timer.isRunning()) {
                timer.start();
            }
            droplets.add(droplet);
        }
    }

    public class Droplet {

        private Point p;
        private int radius;

        public Droplet(Point p) {
            this.p = p;
        }

        public Point getLocation() {
            return p;
        }

        public int getRadius() {
            return radius;
        }

        public void grow() {
            radius += GROWTH_RATE;
            if (radius > MAX_RADIUS) {
                radius = MAX_RADIUS;
            }
        }
    }

    public class DropletWorker extends SwingWorker<Void, Droplet> {

        private Pool pool;

        public DropletWorker(Pool pool) {
            this.pool = pool;
        }

        public Pool getPool() {
            return pool;
        }

        protected int random(int minRange, int maxRange) {
            return minRange + (int) (Math.round(Math.random() * (maxRange - minRange)));
        }

        @Override
        protected Void doInBackground() throws Exception {

            int dropCount = random(1, 100);
            Pool pool = getPool();
            Dimension size = pool.getSize();
            for (int index = 0; index < dropCount; index++) {
                Thread.sleep(random(10, 1000));
                int x = random(0, size.width);
                int y = random(0, size.height);
                Droplet droplet = new Droplet(new Point(x, y));
                publish(droplet);
            }

            return null;
        }

        @Override
        protected void process(List<Droplet> chunks) {
            for (Droplet droplet : chunks) {
                getPool().addDroplet(droplet);
            }
        }
    }
}

Animation Basics

You need three things to perform animation.

  • A Start state
  • A Target state
  • A delta or time range.

(You also need some way to store the current state)

The start and target states are self explanatory, they describe where you are now and where you want to change to.

The delta would be the amount to apply to the current state at each "time interval" (or tick) until you reach the delta.

Or

The time range would be the amount of time you want to use to move from the start state to the end state.

The delta approach is the simpler mechanism, but isn't nearly as flexible as the time range approach...

Once you have these basic elements set up, you need some kind of "tick" that is triggered at regular intervals which allows you to calculate the current state, which is either a linear movement from the start state to the target state (delta) or a progression of change of over time (time range)

A final, full working rework

Apart from you're attempt to block the EDT within the paint method and failing to following the Initial Thread requirements of Swing, the only other, significant, problem I found was your reliance on the radius and iter values.

Basically, these were never getting set UNLESS you pressed the Enter key...which I wasn't.

This example uses the code that you posted and the ideas from the first example...

在此处输入图片说明

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class ControlCircles extends JFrame {

    private JButton jbtEnlarge = new JButton("Start");
    private JButton jbtShrink = new JButton("Stop");
    private CirclePanel canvas = new CirclePanel();
    private JTextField f1 = new JTextField(8);
    private JTextField f2 = new JTextField(8);

    public ControlCircles() {
        JPanel panel = new JPanel();
        JPanel jp = new JPanel();
        jp.setPreferredSize(new Dimension(300, 0));
        panel.add(jbtEnlarge);
        panel.add(jbtShrink);

        this.add(jp, BorderLayout.WEST);
        this.add(canvas, BorderLayout.CENTER);
        this.add(panel, BorderLayout.SOUTH);


        jp.setLayout(new FlowLayout(FlowLayout.RIGHT, 50, 30));
        jp.add(new JLabel("Radius"));
        jp.add(f1);
        jp.add(new JLabel("Iteration"));
        jp.add(f2);

        jbtEnlarge.addActionListener(new EnlargeListener());
        jbtShrink.addActionListener(new ShrinkListener());
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new ControlCircles();

                frame.setTitle("ControlCircle");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setSize(800, 600);
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }

        });
    }

    class EnlargeListener implements ActionListener {

        public void actionPerformed(ActionEvent e) {
            int radius = Integer.parseInt(f1.getText());
            int iter = Integer.parseInt(f2.getText());
            canvas.start(radius, iter);

        }

    }

    class ShrinkListener implements ActionListener {

        public void actionPerformed(ActionEvent e) {
            //canvas.shrink();
        }

    }

    class CirclePanel extends JPanel {

        private int radius;
        private int iterations;

        private int iteration;

        private List<Integer> circles;
        private Timer timer;

        public CirclePanel() {
            circles = new ArrayList<>(25);
            timer= new Timer(100, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    iteration++;
                    if (iteration < iterations) {
                        circles.add(radius);
                        radius += 10;
                    } else {
                        ((Timer)e.getSource()).stop();
                    }
                    repaint();
                }
            });
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            int width = getWidth() - 1;
            int height = getHeight()- 1;
            g.drawRect(0, 0, width, height);
            for (Integer radius : circles) {
                int x = (width - radius) / 2;
                int y = (height - radius) / 2;
                g.drawOval(x, y, radius, radius);
            }
        }

        public void start(int radius, int iter) {
            timer.stop();
            circles.clear();
            this.radius = radius;
            iterations = iter;
            iteration = 0;
            System.out.println("radius = " + radius);
            System.out.println("iterations = " + iterations);
            timer.start();
        }
    }
}

This code works based on the description of your problem by correcting the common mistakes with animation in Swing but some of your code didn't quite make sense to me (ie enlarge and shrink ) so I focused on the description your provided.

The idea is to control the drawing animation on the panel used as a canvas with the buttons Start , Stop and I added Continue and Reset additional controls to better explain the idea. These buttons control the animation thread execution thus drawing circles on the drawing surface. the drawing surface I separated as inner class that has only function to draw whatever performed. Another idea that the approach is taken to draw the circles one by one incrementally until it finishes drawing thus used incremental painting.

I have used the code from the above and changed it a little to support my ideas. If you need more and usually better examples look at this article .

The code is below, I didn't polish it enough to have a production wise look and feel but for demonstration purpose only.

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;

public class ControlCircle extends JFrame implements Runnable {
  private JButton jbtStart = new JButton("Start");
  private JButton jbtStop = new JButton("Stop");
  private JButton jbtContinue = new JButton("Continue");
  private JButton jbtReset = new JButton("Reset");
  private CirclePanel canvas = new CirclePanel();

  private JTextField f1;
  private int radius = 0;

  private JTextField f2;
  private int iter;

  protected boolean  incrementalPainting;

  /**
   * Flag indicates that a thread is suspended
   */
  private boolean suspended = false;


  /**An instance of the class Thread.*/
  private Thread thread = null;

  public ControlCircle() {
    JPanel panel = new JPanel();
    JPanel jp = new JPanel();
    jp.setPreferredSize(new Dimension(300, 0));
    panel.add(jbtStart);
    panel.add(jbtStop);
    panel.add(jbtContinue);
    panel.add(jbtReset);

    this.add(jp, BorderLayout.WEST);
    this.add(canvas, BorderLayout.CENTER);
    this.add(panel, BorderLayout.SOUTH);

    f1 = new JTextField(8);
    f2 = new JTextField(8);

    jp.setLayout(new FlowLayout(FlowLayout.RIGHT, 50, 30));
    jp.add(new JLabel("Radius"));
    jp.add(f1);
    jp.add(new JLabel("Iteration"));
    jp.add(f2);


    jbtStart.addActionListener(new StartListener());
    jbtStop.addActionListener(new StopListener());
    jbtContinue.addActionListener(new ContinueListener());
    jbtReset.addActionListener(new ResetListener());
  }

  public static void main(String[] args) {
    JFrame frame = new ControlCircle();

    frame.setTitle("ControlCircle");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setSize(800, 600);
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
  }

  class StartListener implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      if (thread == null) {
        repaint();
        startThread();
      }
    }
  }

  class StopListener implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      if (thread != null){
        mySuspend();
      }
    }
  }

  class ContinueListener implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      myResume();
    }
  }

  class ResetListener implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      if (thread != null) {
        stopThread();
      }
      repaint();
    }

  }

  /**
   * my Suspend
   */
  private void mySuspend() {
    System.out.println("mySyspend()");
    suspended = true;
  }

  /**
   * my Resume
   */
  private synchronized void myResume(){
    System.out.println("myResume()");
    suspended = false;
    notifyAll();
  }

  public void run(){
    System.out.println("run() - started");

    Thread me = Thread.currentThread();
    while (thread == me) {
      radius = Integer.parseInt(f1.getText());
      iter = Integer.parseInt(f2.getText());
      for (int i = 0; i < iter; i++) {
        if (thread == null) return;
        incrementalPainting = true;
        myRepaint();
        try {
          Thread.sleep(1000);
        }
        catch(InterruptedException e){}
        radius += 10;
      }
      if(thread != null) thread = null; // exiting while
    }
    System.out.println("run() - exiting");
  }

  /**
   * start Thread
   */
  private void startThread(){
    System.out.println("startThread()");
    if(thread == null){
      thread = new Thread(this);
      thread.start();
    }
  }

  /**
   *  stop Thread
   */
  private synchronized void stopThread() {
    System.out.println("stopThread()");
    thread = null; // exiting from while
    if (suspended) {
      suspended = false;
      notify();
    }
  }

  /**
   * This is called from the run method to invoke painting.
   */
  private void myRepaint() {
    System.out.println("myRepaint()");
    incrementalPainting = true;
    repaint();
    synchronized (this) {
      while (incrementalPainting) {
        System.out.println("wait while incremental painting");
        try {
          wait();
        } catch (InterruptedException e) {
          System.out.println("interrupted");
        }
      }
    }

    suspend();
  }
  /**
   * This method should place somewhere when run() has started. Perfectly
   * when repaint() performed.
   */
  private void suspend(){
    System.out.println("suspend()");
    synchronized (this) {
      while (suspended) {
        System.out.println("wait while suspended");
        try {
          wait();
        } catch (InterruptedException e) {
          System.out.println("interrupted");
        }
      }
    }

  }

  public synchronized void myPaint(Graphics g) {
    if (g == null){
      if (incrementalPainting){
        incrementalPainting = false;
        notifyAll();
      }
      return;
    }
    if (incrementalPainting){
      myDraw(g);
      incrementalPainting = false;
      notifyAll();

    }
    else {
      myDraw(g);
    }
  }

  public void myDraw(Graphics g){
    g.drawOval(getWidth() / 2 - radius, getHeight() / 2 - radius, 2 * radius, 2 * radius);
  }

  protected final class CirclePanel extends JPanel {
    //Offscreen buffer of this canvas
    private BufferedImage backBuffer = null;

    public void paintComponent (Graphics g) {
      System.out.println("incrementalPainting="+incrementalPainting);
      // First paint background
      super.paintComponent(g);
      Dimension d = this.getSize();
      if (! incrementalPainting)
        backBuffer = (BufferedImage) this.createImage(d.width, d.height);
      Graphics2D g2 = backBuffer.createGraphics();
      if (! incrementalPainting){
        g2.setColor(Color.WHITE);
        g2.fillRect(0,0, d.width, d.height);
      }
      myPaint(g2);
      g.drawImage(backBuffer, 0, 0, this);

    }

  }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM