使用线程 repaint() 组件

Posted

技术标签:

【中文标题】使用线程 repaint() 组件【英文标题】:Using a thread to repaint() components 【发布时间】:2018-05-03 04:02:41 【问题描述】:

我的线程似乎从未启动过。无论是那个还是 run 方法实际上都没有做任何事情,为此我无法解释。

我的按钮StartRace 上有一个侦听器,它应该启动线程,该线程将增加每个矩形的长度,直到其中一个长到足以被宣布为获胜者(通过超过窗口的宽度,250 像素)。

我将所有组件最初绘制到屏幕上,但从未重新绘制它们。我调用该方法的方式有问题吗?我是否有嵌套在其他不应该嵌套的类?

//--------------------------------------------------------------
//    Start a race between blue and red, track the winner
//    Use threads to manage each rectangle's movement
//    Allow for user interaction, like stopping and starting
//--------------------------------------------------------------


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

public class ConcurrencyRace extends JFrame
 
   private ConcurrencyPanel panel = new ConcurrencyPanel(); 
   private JButton startRace = new JButton("Start The Race!");
   private JButton stopRace = new JButton("Stop The Race!");
   private JLabel winnerText = new JLabel("Winner: ");
   private int blueDraw = 5, redDraw = 5;
   private Random rn = new Random();

   //-----------------------------------------------------------------
   //  Creates and displays the main program frame.
   //-----------------------------------------------------------------

   public ConcurrencyRace() 
       super("Concurrency");
       setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
       Container cp = getContentPane();
       cp.add(panel, BorderLayout.CENTER);
       JPanel p = new JPanel();
       p.add(startRace);
       p.add(stopRace);
       cp.add(p, BorderLayout.NORTH);
       cp.add(winnerText, BorderLayout.SOUTH);
       pack();
       setVisible(true);
   

   public static void main (String[] args)
   
       ConcurrencyRace tRun = new ConcurrencyRace();
       tRun.setVisible(true);
   

   private class ConcurrencyPanel extends JPanel 
   
       public class runnerThread extends Thread 
           @Override
           public void run() 
               while (blueDraw < 250 && redDraw < 250) 

                   panel.validate();
                   panel.repaint();

                   try 
                       Thread.sleep(200);
                    catch (InterruptedException e) 
               
           
       

       public ConcurrencyPanel ()
       
           setPreferredSize(new Dimension(600,250));

       

       private class ButtonListener implements ActionListener 

           runnerThread rectDraw = new runnerThread();

           //--------------------------------------------------------------
           //  Starts the thread to draw each rectangle ("racer")
           //--------------------------------------------------------------
           public void actionPerformed (ActionEvent event)
           
               if (event.getSource() == startRace)                    
                   rectDraw.start();
               
           
       
       @Override
       public void paintComponent (Graphics page) 

           super.paintComponent(page);
           page.setColor(Color.blue);
           page.fillRect(0,80,blueDraw,20);

           page.setColor(Color.red);
           page.fillRect(0,120,redDraw,20);

           blueDraw += rn.nextInt(10) + 1;
           redDraw += rn.nextInt(10) + 1;

           page.dispose();
           
   

【问题讨论】:

Swing 操作只能发生在 AWT 事件派发线程中;见docs.oracle.com/javase/tutorial/uiswing/concurrency。不要创建新线程,而是使用javax.swing.Timer。此外,不要在不是您创建的 Graphics 对象上调用 dispose() — 传递给绘制方法的 Graphics 实例由 AWT/Swing 系统管理。 停止再发同样的问题——这是对这个系统的滥用,对在这里提供帮助的志愿者不公平。 【参考方案1】:

主要

您永远不会在任何一个按钮上添加ActionListener,因此当它们被激活时没有任何响应

另外

    状态管理无处不在。 blueDrawredDraw 应该是 ConcurrencyPanel 的实例字段。 不要在任何绘制方法中更新 UI 的状态(或 UI 依赖的变量)。绘制方法应该绘制状态,而不是改变它。更新blueDrawredDraw 应该在特定的方法中完成,需要更新时可以调用。

所有这一切让我相信你最好使用 Swing Timer

概念上...

你可以做这样的事情......

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class ConcurrencyRace 

    //-----------------------------------------------------------------
    //  Creates and displays the main program frame.
    //-----------------------------------------------------------------
    public ConcurrencyRace() 
        SwingUtilities.invokeLater(new Runnable() 
            @Override
            public void run() 
                Timer timer = new Timer(200, null);
                JFrame frame = new JFrame();
                frame.add(new ButtonPane(timer), BorderLayout.NORTH);
                frame.add(new RacePane(timer));
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            
        );
    

    public static void main(String[] args) 
        new ConcurrencyRace();
    

    public class ButtonPane extends JPanel 

        private JButton startRace = new JButton("Start The Race!");
        private JButton stopRace = new JButton("Stop The Race!");

        public ButtonPane(Timer timer) 
            startRace.addActionListener(new ActionListener() 
                @Override
                public void actionPerformed(ActionEvent e) 
                    timer.start();
                
            );

            stopRace.addActionListener(new ActionListener() 
                @Override
                public void actionPerformed(ActionEvent e) 
                    timer.stop();
                
            );

            setLayout(new GridBagLayout());
            add(startRace);
            add(stopRace);
        

    

    private class RacePane extends JPanel 

        private int blueDraw = 5, redDraw = 5;
        private Random rn = new Random();

        public RacePane(Timer timer) 
            timer.addActionListener(new ActionListener() 
                @Override
                public void actionPerformed(ActionEvent e) 
                    if (updateState()) 
                        ((Timer)e.getSource()).stop();
                    
                
            );
        

        protected boolean updateState() 
            blueDraw += rn.nextInt(10) + 1;
            redDraw += rn.nextInt(10) + 1;
            repaint();

            return blueDraw >= getWidth() || redDraw >= getWidth();
        

        @Override
        public Dimension getPreferredSize() 
            return new Dimension(600, 250);
        

        @Override
        public void paintComponent(Graphics page) 
            System.out.println(">>");
            super.paintComponent(page);
            page.setColor(Color.blue);
            page.fillRect(0, 80, blueDraw, 20);

            page.setColor(Color.red);
            page.fillRect(0, 120, redDraw, 20);
        
    

这将Timer 保持为中心概念,在按钮和比赛面板之间共享。

我没有添加对生成获胜者通知的支持,这将通过传递给RacePane 的简单观察者模式来完成

【讨论】:

我无法确定在哪里添加 ButtonListener。如果我尝试在 ConcurrencyPanel() 构造函数中这样做,它会引发一堆错误。如果我尝试在 ConcurrencyRace 构造函数中执行此操作,它将无法识别 ButtonListener 类。至于blueDrawredDraw 你是说在ConcurrencyPanel 中声明它们并使用一种方法来更新它们的值?我会在Timer 的每个勾号之后这样做吗? @mad:可能会帮助吸血鬼,但 1+ 是您通常的好答案。 谢谢。你的编辑绝对对我有帮助,而且非常有意义。我会复习一下,以便更清楚地理解这个概念。 在屏幕上打印获胜者通知非常容易,但我怎么能在停止按钮的右侧添加标签呢?我想更新它而不是打印通知,但我不知道如何从 RacePane 设置该标签的文本 @Steven 正如我所说,您需要某种观察者模式,RacePane 然后会向相关方生成状态已更改的通知。如果您有兴趣,ActionListenerTimer 是工作中观察者模式的示例

以上是关于使用线程 repaint() 组件的主要内容,如果未能解决你的问题,请参考以下文章

Java AWT - repaint()方法线程调度

QWidget::repaint:更新进度条时检测到递归重绘

使用带有 actionPerformed 的 repaint() 方法

QWidget::repaint: Recursive repaint detected

如何在游戏循环中使用 repaint() 方法

repaint和reflow