【问题标题】:Difficulty using threads with Swing在 Swing 中使用线程有困难
【发布时间】:2013-11-03 12:58:25
【问题描述】:
package combatframe;

import javax.swing.*;
import java.awt.event.*;
import static java.lang.Math.PI;
import javax.swing.border.*;

public class CombatFrame extends JFrame
{
    String[] Ships = {"Battleship", "Cruiser", "Frigate"};
    JComboBox attackCombo = new JComboBox(Ships);
    JComboBox targetCombo = new JComboBox(Ships);
    JButton calculate = new JButton();
    JPanel mainPanel = new JPanel();
    JPanel outPanel = new JPanel();
    JPanel shipPanel = new JPanel();
    JTextArea outText = new JTextArea("Results: ", 11, 30);
    JTextArea attackStats = new JTextArea("Attacker stats.", 10,10);
    JTextArea targetStats = new JTextArea("Target stats.", 10,10);

    public static void main(String[] args) 
    {
        CombatFrame combatFrame = new CombatFrame();
    }

    public CombatFrame()
    {
        this.setSize(500,500);
        this.setTitle("OTG Combat Simulator");
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        EventListener c1 = new EventListener();
        EventListener c2 = new EventListener();
        EventListener calc = new EventListener();

        calculate.setText("Calculate!");

        Border b1 = BorderFactory.createTitledBorder("Result");
        outPanel.setBorder(b1);        
        Border b2 = BorderFactory.createTitledBorder("Ship Classes");
        shipPanel.setBorder(b2);

        mainPanel.add(shipPanel);
        mainPanel.add(outPanel);
        shipPanel.add(attackStats);
        shipPanel.add(attackCombo);
        shipPanel.add(targetCombo);
        shipPanel.add(targetStats);
        outPanel.add(calculate);

        outPanel.add(outText);

        attackCombo.addActionListener(c1);
        attackCombo.setSelectedItem("Battleship");
        targetCombo.addActionListener(c2);
        targetCombo.setSelectedItem("Battleship");

        calculate.addActionListener(calc);

        this.add(mainPanel);
        this.setVisible(true);

    } 

    public double hitProbability(double factor)
    {
        double hitCount = (double)(Math.random() * 1.001 * factor );
        return hitCount;
    }

    public double Combat(double[] turnArray)
    {
        double rRange = turnArray[0];
        double rAngular = turnArray[1];
        double rRadius = turnArray[2];
        double damage = turnArray[3];

        double appliedDamage = 0;

        int[] attHit = 
        {
            0, 0, 0
        };

        int[] strikes =
        {
            0, 0, 0, 0, 0
        };

        double[] damageMod =
        {
            0,0.4,0.75,1.0,1.25
        };

        double roll1, roll2, roll3;

        roll1 = hitProbability(rRadius);
        roll2 = hitProbability(rAngular);
        roll3 = hitProbability(rRange);

        if (roll1 <=1)
            attHit[0]++;
        if (roll2 <=1)
            attHit[1]++;
        if (roll3 <=1)
            attHit[2]++;

        switch (attHit[0]+attHit[1]+attHit[2])
        {
            case 3:
                double wrecker = Math.random();
                if (wrecker >= 0.95 - Math.random())
                    strikes[4]++;
                else
                    strikes[3]++;
                break;
            case 2:
                strikes[2]++;
                break;
            case 1:
                strikes[1]++;
                break;
            case 0:
                strikes[0]++;
                break;
        }

        for (int x=0; x<5; x++)
        {
        appliedDamage += strikes[x]*Damage*
                (Math.random()+Math.random())*damageMod[x];
        }


        return appliedDamage;
    }

    private class EventListener implements ActionListener
    {
        @Override
        public void actionPerformed(ActionEvent e)
        {
            if (e.getSource()== attackCombo)
            {
                switch ((String)attackCombo.getSelectedItem())
                {
                    case "Battleship":
                        Attacker attackBS = new Attacker(50000.0, 400.0, 0.01, 45000.0, 400.0,
                                300.0, 10.0, 10000.0, 250.0);
                        break;
                    case "Cruiser":
                        Attacker attackCr = new Attacker(25000.0, 125.0, 0.23, 22500.0, 150.0, 
                                600.0, 5.0, 5000.0, 75.0);
                        break;
                    case "Frigate":
                        Attacker attackFr = new Attacker(2500.0, 40.0, 0.365, 1000.0, 39.0, 
                                900.0, 2.0, 1200.0, 25.0);
                        break;

                }
                attackStats.setText("Optimal: " + Attacker.Optimal 
                        + "\n Weapon Sig: " +Attacker.Attack_Signature 
                        + "\n Tracking: " + Attacker.Tracking
                        + "\n Range: " + Attacker.Range
                        + "\n Ship Sig: " + Attacker.Target_Signature
                        + "\n Velocity: " + Attacker.Orbital
                        + "\n Cycle Time" + Attacker.Period
                        + "\n Health: " + Attacker.Health
                        + "\n Damage: " + Attacker.Damage);
                }

            if (e.getSource()==targetCombo)
            {
                switch ((String)targetCombo.getSelectedItem())
                {
                    case "Battleship":
                        Target targetBS = new Target(50000.0, 400.0, 0.01, 45000.0, 400.0,
                                300.0, 10.0, 10000.0, 250.0);
                        break;
                    case "Cruiser":
                        Target targetCr = new Target(25000.0, 125.0, 0.23, 22500.0, 150.0, 
                                600.0, 5.0, 5000.0, 75.0);
                        break;
                    case "Frigate":
                        Target targetFr = new Target(2500.0, 40.0, 0.365, 1000.0, 39.0, 
                                900.0, 2.0, 1200.0, 25.0);
                        break;
                }
                targetStats.setText("Optimal: " + Target.Optimal 
                    + "\n Weapon Sig: " + Target.Attack_Signature 
                    + "\n Tracking: " + Target.Tracking
                    + "\n Range: " + Target.Range
                    + "\n Ship Sig: " + Target.Target_Signature
                    + "\n Velocity: " + Target.Orbital
                    + "\n Cycle Time" + Target.Period
                    + "\n Health: " + Target.Health
                    + "\n Damage: " + Target.Damage);

            }

        if (e.getSource()==calculate)
        {

                double[] attackArray =
                {//RRange,RAngular,RRadius,Attacker.Damage
                    0,0,0,0
                };

                double[] targetArray =
                {//RRange,RAngular,RRadius,Target.Damage
                    0,0,0,0
                };

                double attackDamage = 0, targetDamage = 0;

                for (int i=1; i<1000; i++)
                {
                    if ((i+1)%Attacker.Period==0)
                    {
                        double rRange = Attacker.Optimal/Target.Range;
                        double rAngular = 
                            ((Math.sin(PI/4)*(Target.Orbital/Target.Range))
                            /Attacker.Tracking);
                        double rRadius = Attacker.Attack_Signature/
                                Target.Target_Signature;

                        attackArray[0] = rRange;
                        attackArray[1] = rAngular;
                        attackArray[2] = rRadius;
                        attackArray[3] = Attacker.Damage;

                        attackDamage += Combat(attackArray);
                        if (attackDamage >= Target.Health)
                        {
                            outText.setText("Attacker wins!");
                            break;
                        }
                    }


                    if (i%Target.Period==0)
                    {
                        double rRange = Target.Optimal/Attacker.Range;
                        double rAngular = 
                            ((Math.sin(PI/4)*(Attacker.Orbital/Attacker.Range))
                            /Target.Tracking);
                        double rRadius = Target.Attack_Signature/
                                Attacker.Target_Signature;

                        targetArray[0] = rRange;
                        targetArray[1] = rAngular;
                        targetArray[2] = rRadius;
                        targetArray[3] = Target.Damage;

                        targetDamage += Combat(targetArray);

                        if (targetDamage >= Attacker.Health)
                        {
                            outText.setText("Target wins!");
                            break;
                        }
                    }
                }
            }
        }
    }
}

我正在尝试构建一个非常简单的战斗模拟框架。它构建了 2 个组合框并根据其中的选择分配统计数据。

然后用户点击计算按钮,然后将结果弹出到结果字段中。这一切都很好。

我希望 GUI 将战斗的每个“回合”附加到结果文本字段 outText 中 - 我想我可以很容易地做到这一点。我希望它在任何有战斗的回合中稍微延迟一下 - 我会在每个 if 语句中使用 Thread.sleep(10) 来处理每艘船的活动转弯。

唯一的问题是我不知道如何将 GUI 保持在一个线程上——当点击按钮后战斗计算提示时更新——以及处理所有延迟计算的线程。

我知道,如果我尝试在同一个线程上同时运行两者,GUI 将简单地冻结,以适当的延迟进行所有计算,然后将整个附加块一次性扔到结果字段中。

谁能给我任何指示、建议或氰化物?任何东西,任何能让疼痛消失的东西......

怀疑 SwingWorker 是个好主意,但仍然无法弄清楚如何在我的代码中实际实现它。

澄清:我最担心的是我不知道如何实现 SwingWorker 或一些类似的多线程进程,以便并行运行 GUI 和后台计算。

【问题讨论】:

  • 好吧,您可能只是要做一个大循环,在每次迭代中运行一圈,最后进入睡眠状态。您可以执行诸如将结果附加到 JTextArea 之类的操作。如果您正在睡觉,则不一定必须为此使用 SwingWorker,尽管 SwingWorker 出于各种原因“更好”。无论哪种方式,您都必须使用 invokeLater 更新 GUI。你能把你的问题缩小一点吗?它是如此开放,我个人不知道除了我的建议之外该说些什么,而且我的建议似乎相当明显。
  • 请遵守 Java 命名约定,变量为小写形式。你的代码很难理解,因为很难分辨什么是变量,什么是类。
  • @BoristheSpider 所说的也是正确的。特别是在 SO 上,您的代码会基于此进行颜色编码。偏离命名约定会使阅读变得更加困难。

标签: java multithreading swing jframe swingworker


【解决方案1】:

我想试一试,因为我想我明白你想要做什么。也许您正在寻找的是每次单击按钮时创建一个新的可运行对象:

class Turn implements Runnable {

    private final Object source;
    private final String attackComboMove;
    private final String targetComboMove;

    public Turn(Object src, String acm, String tcm) {

        source = src;
        attackComboMove = acm;
        targetComboMove = tcm;

    }

    @Override public void run() {

        // may want to disable GUI buttons here

        if (source == attackCombo) {

            switch (attackComboMove) {
                // ...
                // ...
                // ...
            }

            append("attack combo results");

        } else if (source == targetCombo) {

            switch (targetComboMove) {
                // ...
                // ...
                // ...
            }

            append("target combo results");

        } else if (source == calculate) {

            // ...

            for (int i = 1; i < 1000; i++) {

                // ...

                attackDamage += combat(attackArray);

                append("combat results: " + attackDamage + " total damage");

                if (attackDamage >= target.health) {
                    append("Attacker wins!");
                    break;
                }
            }
        }

        // and enable the buttons again when combat is over

    }

    private void append(String text) {

        SwingUtilities.invokeLater(new Runnable() {
            @Override public void run() {
                outTextArea.append("\n" + text);
            }
        });

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {}
    }
}

听起来你只是想要一个攻击发生时的滚动列表,这就是你要做的。 IMO 这是正确的做法,而且是最简单的。

当用户点击你做的按钮时:

new Thread(new Turn(e.getSource(), (String)attackCombo.getSelectedItem(), (String)targetCombo.getSelectedItem())).start();

(如果由于某种原因需要提前结束新线程或更改参数,则为新线程创建一个变量。)

这种模式很容易适应 SwingWorker,如果它是长期运行的,只需扩展 SwingWorker 并做同样的事情,但覆盖 doInBackground() 而不是 run()

否则,如果您打算进行实时模拟并需要一个后台线程,则需要使 run/doInBackground 成为一个 while(running) 循环并在您使用标志和/或某种等待/通知方案时需要更改参数或计算转弯。

【讨论】:

  • 我真的很抱歉 - 或者真的很慢 - 但我实际上无法弄清楚如何将它应用到我自己的代码中。我知道我对 Java 还没有那么好,但我没有意识到我有那么厚 :'( 不过谢谢你的尝试。
  • @Panda 您只需将您正在执行的所有计算插入到 run() 中。所有这一切让您可以安全地 Thread.sleep 关闭事件调度线程。
  • 编辑了我的答案,试图更清楚地说明如何使用它。
  • @BoristheSpider 为什么不呢?原谅我的无知,但我从来没有听说过。 Java API 文档展示了一个例子:docs.oracle.com/javase/7/docs/api/java/lang/Thread.html
  • 来自Javadoc for Runnable - 在大多数情况下,如果您只打算重写 run() 方法而不打算重写其他 Thread 方法,则应使用 Runnable 接口。更重要的是,Thread 作为一个类以非常特殊的方式表现,因此您不能在Thread 的实例上使用waitnotify。如果你扩展Thread,这是一个等待发生的可怕错误。
【解决方案2】:

您可以使用 javax.swing.Timer 来安排某事在未来 N 毫秒内发生。请注意,Java 时间中的10 通常表示10 毫秒,这是一个几乎无法察觉的时间量。如果要休眠或延迟一秒钟,请使用 1000 毫秒、10 秒 = 10000 毫秒等。示例:

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

public class GameFrame extends JFrame
{
    private JPanel _contentPane;
    private GameFrame _mainFrame;
    private JButton _jButton;
    private JTextArea _jTextArea;

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable()
        {
            public void run()
            {
                GameFrame frame = new GameFrame();
                frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
                frame.setSize(640/2, 480/2);
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public GameFrame()
    {
        _mainFrame = this;
        _contentPane = new JPanel();
        _mainFrame.setContentPane(_contentPane);
        _contentPane.setLayout(new BorderLayout());
        _jTextArea = new JTextArea();
        _contentPane.add(_jTextArea, BorderLayout.CENTER);
        _jButton = new JButton("Go");
        _jButton.addActionListener(new GoButtonListener());
        _contentPane.add(_jButton, BorderLayout.SOUTH);
    }

    class GoButtonListener implements ActionListener
    {
        public void actionPerformed(ActionEvent e)
        {
            _jTextArea.append("Please wait...\n");
            Timer timer = new Timer(10000, new LaterListener());
            timer.start();
        }
    }

    class LaterListener implements ActionListener
    {
        public void actionPerformed(ActionEvent e)
        {
            _jTextArea.append("Ten seconds later!\n");
        }
    }
}

【讨论】:

  • 这当然是我所追求的。工作在这一点上招手,但明天我会在我有机会将我的代码重写到这个框架中并看看会发生什么之后回复你。手指交叉!
  • 另外,刚刚发现了布局代码。谢谢!我害怕试图从我的位置移动任何东西,因为它刚刚落在我认为它应该整齐地做的地方......
猜你喜欢
  • 2021-09-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-06
  • 2018-07-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多