使用Swing线程的难度

时间:2013-10-24 17:14:50

标签: java multithreading swing jframe swingworker

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 - 在点击按钮后由战斗计算提示时更新 - 以及线程处理所有延迟的计算。 / p>

我知道如果我尝试在同一个线程上运行它们,GUI将简单地冻结,用适用的延迟进行所有计算,然后立即将整个附加的块丢弃到结果字段中。

任何人都可以向我提供任何指示,建议或氰化物吗?什么,任何让痛苦消失的东西......

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

澄清:我最关心的是我无法弄清楚如何实现SwingWorker或类似的多线程进程,以便并行运行GUI和后台计算。

2 个答案:

答案 0 :(得分:1)

我会抓住这个,因为我想我明白你要做什么。也许你想要的是每次点击按钮时创建一个新的runnable:

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(运行)循环并使用标志和/或某种等待/通知方案需要更改参数或计算转弯。

答案 1 :(得分:0)

您可以使用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");
        }
    }
}