2016-04-13 66 views
1

我意識到您需要使用事件調度線程來避免死機和死鎖,但我在這裏遇到了凍結問題。由於封裝EDT,我懷疑我有這個問題。當掃描儀等待用戶輸入時GUI凍結

這與主靜方法的類:

@SuppressWarnings("serial") 
public class WelcomeScreen extends JFrame implements ActionListener { 

/* 
* initialize variables 
*/ 
private JButton btnSubmit; 
private JTextField nameInput; 
private JLabel enterName; 

public WelcomeScreen() { 

    /* 
    * build the welcome frame with all components 
    */ 
    setAlwaysOnTop(true); 
    setDefaultCloseOperation(EXIT_ON_CLOSE); 
    setLocationRelativeTo(null); 
    setResizable(false); 
    setBounds(0, 0, 514, 256); 
    getContentPane().setLayout(null); 

    // welcome label 
    JLabel label_welcome = new JLabel("<html>Welcome to <br/>Game</html>"); 
    label_welcome.setHorizontalAlignment(SwingConstants.CENTER); 
    label_welcome.setBounds(159, 11, 190, 32); 
    getContentPane().add(label_welcome); 

    // create the warrior button 
    btnSubmit = new JButton("Start"); 
    btnSubmit.setBounds(209, 129, 89, 23); 
    btnSubmit.addActionListener(this); 
    btnSubmit.setFocusable(false); 
    getContentPane().add(btnSubmit); 

    enterName = new JLabel("Enter Player Name:"); 
    enterName.setBounds(50, 60, 150, 50); 
    getContentPane().add(enterName); 

    nameInput = new JTextField(); 
    nameInput.setBounds(212, 70, 125, 25); 
    getContentPane().add(nameInput); 

    // set frame visible 
    setVisible(true); 

} 

/* 
* action listener 
*/ 
public void actionPerformed(ActionEvent evt) { 

    if (evt.getSource().equals(btnSubmit)) { 
     dispose(); 
     new Core(); 
    } 

} 

/* 
* <<<<<MAIN STATIC METHOD>>>>> 
*/ 
public static void main(String[] args) { 

    // safely start the welcome frame 
    SwingUtilities.invokeLater(new Runnable() { 
     public void run() { 
      new WelcomeScreen(); 
     } 
    }); 

} 
} 

而這是一個擴展的JFrame第二類。這是凍結的GUI。如果您查看gameEngine()方法,您會注意到String input = keyboard.next();,這是造成凍結。如果該行被註釋掉,GUI將顯示兩個字符串「test1」和「test2」。

@SuppressWarnings("serial") 
public class Core extends JFrame { 

/* 
* initialize variables 
*/ 
private Scanner keyboard; 
private JTextArea textArea; 

public Core() { 

    /* 
    * create the frame that displays output messages 
    */ 
    setBounds(0, 0, 800, 400); 
    setResizable(false); 
    setLocationRelativeTo(null); 
    setAlwaysOnTop(true); 
    setDefaultCloseOperation(EXIT_ON_CLOSE); 
    getContentPane().setLayout(null); 

    JPanel panel = new JPanel(); 
    panel.setBounds(10, 11, 774, 349); 
    panel.setLayout(null); 
    getContentPane().add(panel); 

    textArea = new JTextArea(); 
    JScrollPane scroll = new JScrollPane(textArea); 
    scroll.setBounds(0, 0, 774, 349); 
    textArea.setBounds(10, 11, 443, 279); 
    panel.add(scroll); 

    // THIS ISN'T EVEN SHOWING UP 
    appendMessage("test1"); 

    // set frame visible 
    setVisible(true); 

    // NEITHER IS THIS 
    appendMessage("test2"); 

    /* 
    * start game engine 
    */ 
    gameEngine(); 

} 

/* 
* start gameEngine 
*/ 
public void gameEngine() { 

    // instantiate scanner 
    keyboard = new Scanner(System.in); 

    // <<<<<<<<<<<<<<<THIS HERE IS CAUSING THE GUI TO FREEZE>>>>>>>>>>>>>>> 
    String input = keyboard.next(); 

} 

// append a message to the GUI 
public void appendMessage(String s) { 
    textArea.append("> " + s + "\n"); 
} 

} 
+2

[併發中的擺動](http://docs.oracle.com/javase/tutorial/uiswing/concurrency/)的原因。現在,你需要問「爲什麼?」你是否試圖在GUI中從控制檯獲取用戶輸入? – MadProgrammer

+2

避免使用'null'佈局,像素完美的佈局是現代UI設計中的幻想。影響組件的個體大小的因素太多,其中沒有一個可以控制。 Swing旨在與佈局經理一起工作,放棄這些將導致問題和問題的終結,您將花費越來越多的時間來嘗試糾正 – MadProgrammer

+0

@MadProgrammer長話短說,我正在研究一種簡單的基於文本的方法,基於遊戲。我發現使JTextField「行爲」等同於控制檯輸入非常困難。該程序不會等待JTextField輸入。這就是爲什麼在這種情況下,用戶在控制檯中鍵入命令,並在GUI中顯示消息。 – Dragneel

回答

5

您的問題是,掃描儀阻塞Swing事件線程,阻止您的GUI繪製和與用戶交互。一個簡單的解決方案是在後臺線程中執行掃描程序交互。更好的解決方案是完全擺脫Scanner(System.in),並通過GUI獲得所有用戶交互。控制檯I/O和GUI I/O不應混用。

使用一個JTextField和佈局管理器來獲取和輸入響應的一個簡單的例子:

import java.awt.BorderLayout; 
import java.awt.event.ActionEvent; 
import java.awt.event.KeyEvent; 

import javax.swing.*; 

@SuppressWarnings("serial") 
public class Fu22GamePanel extends JPanel { 
    private static final int COLS = 50; 
    private static final int ROWS = 25; 
    private static final int GAP = 3; 
    private JTextField entryField = new JTextField(COLS); 
    private JTextArea textArea = new JTextArea(ROWS, COLS); 
    private String playerName; 

    public Fu22GamePanel(String name) { 
     this.playerName = name; 
     textArea.setFocusable(false); 
     textArea.setWrapStyleWord(true); 
     textArea.setLineWrap(true); 
     JScrollPane scrollPane = new JScrollPane(textArea); 
     scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); 

     EntryAction entryAction = new EntryAction("Submit", KeyEvent.VK_S); 
     entryField.setAction(entryAction); 
     JPanel bottomPanel = new JPanel(); 
     bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.LINE_AXIS)); 
     bottomPanel.add(entryField); 
     bottomPanel.add(new JButton(entryAction)); 

     setBorder(BorderFactory.createEmptyBorder(GAP, GAP, GAP, GAP)); 
     setLayout(new BorderLayout(GAP, GAP)); 
     add(scrollPane); 
     add(bottomPanel, BorderLayout.PAGE_END); 
    } 

    private class EntryAction extends AbstractAction { 
     public EntryAction(String name, int mnemonic) { 
      super(name); 
      putValue(MNEMONIC_KEY, mnemonic); 
     } 

     @Override 
     public void actionPerformed(ActionEvent e) { 
      String text = entryField.getText(); 
      entryField.setText(""); 
      textArea.append("> " + text + "\n"); 
      entryField.requestFocusInWindow(); 
     } 
    } 

    private static class WelcomePanel extends JPanel { 
     private JTextField playerNameField = new JTextField(10); 

     public WelcomePanel() { 
      int wpGap = 20; 
      setBorder(BorderFactory.createEmptyBorder(wpGap, wpGap, wpGap, wpGap)); 
      add(new JLabel("Enter Player Name:")); 
      add(playerNameField); 
     } 

     public String getPlayerName() { 
      return playerNameField.getText(); 
     } 
    } 

    private static void createAndShowGui() { 
     WelcomePanel welcomePanel = new WelcomePanel(); 
     int response = JOptionPane.showConfirmDialog(null, welcomePanel, "Welcome to the Game", JOptionPane.OK_CANCEL_OPTION); 
     if (response != JOptionPane.OK_OPTION) { 
      return; 
     } 

     String name = welcomePanel.getPlayerName(); 

     Fu22GamePanel mainPanel = new Fu22GamePanel(name); 

     JFrame frame = new JFrame("Game -- Player: " + name); 
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     frame.getContentPane().add(mainPanel); 
     frame.pack(); 
     frame.setLocationRelativeTo(null); 


     frame.setVisible(true); 
    } 

    public static void main(String[] args) { 
     SwingUtilities.invokeLater(() -> { 
      createAndShowGui(); 
     }); 
    } 
} 
+0

您建議將掃描器移動到單獨的線程。我可以將整個GUI移動到單獨的線程嗎? – Dragneel

+0

@ LawrenceLelo:請重新閱讀答案 - 我建議**不要**移動掃描儀,我建議**擺脫掃描儀**。你不需要它,如果你保留它,它只會讓你失望。 –

+0

我指的是你的'簡單解決方案',而不是'更好的解決方案'。 – Dragneel

3

使用JTextField作爲輸入,夫婦與ActionListener來,你可以適當的響應事件,如輸入鍵。

使用JTextArea作爲輸出...

Bad Adventure

import java.awt.BorderLayout; 
import java.awt.Dimension; 
import java.awt.EventQueue; 
import java.awt.Graphics; 
import java.awt.Graphics2D; 
import java.awt.event.ActionEvent; 
import java.awt.event.ActionListener; 
import javax.swing.JFrame; 
import javax.swing.JPanel; 
import javax.swing.JScrollPane; 
import javax.swing.JTextArea; 
import javax.swing.JTextField; 
import javax.swing.UIManager; 
import javax.swing.UnsupportedLookAndFeelException; 

public class NastyAdventure { 

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

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

       JFrame frame = new JFrame("Testing"); 
       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
       frame.add(new TestPane()); 
       frame.pack(); 
       frame.setLocationRelativeTo(null); 
       frame.setVisible(true); 
      } 
     }); 
    } 

    public class TestPane extends JPanel { 

     private JTextArea output; 
     private JTextField input; 

     private boolean isDead = false; 

     public TestPane() { 
      setLayout(new BorderLayout()); 
      output = new JTextArea(10, 30); 
      input = new JTextField(20); 
      add(new JScrollPane(output)); 
      add(input, BorderLayout.SOUTH); 

      output.setEditable(false); 
      output.append("A bear appears on the trial in front of you\n"); 
      output.append("What do you do?"); 

      input.addActionListener(new ActionListener() { 
       @Override 
       public void actionPerformed(ActionEvent e) { 
        input.setText(null); 
        if (!isDead) { 
         output.append("\nThe bear eats you, you die\n"); 
         output.append("The adventure is over, the bear wins\n"); 
        } else { 
         output.append("You're dead, you can't do anything\nThe adventure is over, the bear wins\n"); 
        } 
        isDead = true; 
       } 
      }); 
     } 

    } 

} 

看一看How to Use Text FieldsHow to Use Text AreasHow to Write an Action ListenersWriting Event Listeners更多細節

你也可能會想看看Model-View-Controller這將幫助你保持遊戲的狀態從UI

+0

您的時間非常感謝。我對你演示的概念有充分的理解。但舉個例子,比方說,遊戲的功能更強大,因爲它允許用戶輸入許多其他觸發不同事情的不同命令。例如,用戶可以輸入_run_調用一種方法從熊身上撤退,或者用戶可以輸入_drink藥水_調用另一種方法來重新生成健康。現在我的印象是代碼不能**等待**和**監聽**在** JTextField **中鍵入的命令。 – Dragneel

+0

舉一個例子:http://pastebin.com/MYz9Vpp0 – Dragneel

+2

它「依賴」,代碼將「等待」,因爲它沒有被執行。當調用'actionPerformed'時,解析輸入並決定應該發生什麼,所以當用戶輸入「run」並按下[Enter]時,actionPerformed方法解析JTextField的文本並詢問模型執行該操作。該模型可能會更新它的狀態,並將其報告回視圖,並更新文本區域,並重新開始循環 – MadProgrammer