JPanel监听器和线程问题

时间:2013-05-31 12:32:57

标签: java multithreading swing paint event-listener

这是用于在具有给定延迟率的帧内的面板上显示具有变化半径的圆的代码,但是代码显示最终输出而不是中间阶段,即,圆圈不是逐个出现而是全部圈子一下子作为最终输出。可能存在与按钮动作侦听器和面板线程相关的一些错误。代码采用初始圆半径和迭代总数(要显示的圆的总数),每个下一个圆的半径增加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;
    }
  }
}

2 个答案:

答案 0 :(得分:2)

你遇到的问题很普遍。

Swing是一个单线程框架。这意味着所有与UI相关的交互必须在此线程的上下文中发生(AKA事件调度线程)。

除其他事项外,EDT负责调度重绘请求。如果代码的任何部分停止此线程(阻止I / O,耗时的进程Thread.sleep),则EDT将无法处理任何新事件。

详细阅读Concurrency in Swing

你现在面临两个问题......

  1. 你不能阻止EDT
  2. 您无法从EDT以外的任何线程更新UI。
  3. 幸运的是,有很多解决方案。最简单的是使用javax.swing.Timer

    这个计时器触发它在EDT中的滴答事件,但在它自己的线程中等待......

    enter image description here

    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;
                }
            }
        }
    }
    

    扩展示例

    当您单击“开始”按钮时,此示例将以随机间隔(每个液滴之间)创建随机数量的液滴。您可以多次按启动键,它会使输出结合起来。

    enter image description here

    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);
                }
            }
        }
    }
    

    动画基础

    你需要做三件事来动画。

    • 开始状态
    • 目标状态
    • 增量或时间范围。

    (您还需要一些方法来存储当前状态)

    起始状态和目标状态是不言自明的,它们描述了您现在所处的位置以及您想要更改的位置。

    增量将是在每个“时间间隔”(或刻度)应用于当前状态的数量,直到达到增量。

    时间范围是您想要用于从开始状态移动到结束状态的时间量。

    delta方法是更简单的方法,但不像时间范围方法那样灵活......

    一旦你设置了这些基本元素,就需要某种定时触发的“滴答”,它允许你计算当前状态,这是从开始状态到目标状态的线性移动( delta)或随时间变化的进展(时间范围)

    最后的全面工作返工

    除了你试图在paint方法中阻止EDT并且没有遵循Swing的Initial Thread要求之外,我发现唯一的另一个重要问题是你对radius的依赖和iter值。

    基本上,除非你按下 Enter 键,否则它们永远不会被设置......我不是。

    此示例使用您发布的代码和第一个示例中的想法......

    enter image description here

    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();
            }
        }
    }
    

    此代码基于对问题的描述,通过在Swing中纠正动画中的常见错误,但是您的一些代码对我来说没有意义(即enlargeshrink)所以我专注于你提供的描述。

答案 1 :(得分:-1)

我们的想法是在用作画布的面板上控制绘图动画,其中包含按钮StartStop,我添加了Continue和{{ 1}}其他控件可以更好地解释这个想法。这些按钮控制动画线程的执行,从而在绘图表面上绘制圆圈。绘图表面我作为内部类分开,只有绘制任何执行的功能。另一个想法是采用这种方法逐渐绘制圆圈,直到它完成绘图,从而使用增量绘画。

我使用了上面的代码并稍微改了一下以支持我的想法。如果您需要更多且通常更好的示例,请查看此article

代码在下面,我没有对它进行足够的改进以具有生产明智的外观和感觉,但仅用于演示目的。

Reset