您遇到的问题是您阻塞了事件调度线程,阻止更新 UI 或处理任何新事件...
从这里开始……
for(int i = 0; i < 15; i++)
{
//...
//Check to see if user has enetered anything
// And is compounded here
while(!answered)
{
Thread.sleep(duration);
//...
}
您显然是在以一种程序化的方式思考(就像您对控制台程序一样),但这不是 GUI 的工作方式,GUI 是事件驱动的,在某个时间点发生某些事情并且您会做出响应。
我的建议是调查 Swing Timer,这将允许您安排在某个时间点的回调,并在触发时执行一些操作,这可用于修改 UI,因为它执行在 EDT 的范围内。
请参阅Concurrency in Swing 和How to use Swing Timers 了解更多详情
我还建议您查看CardLayout,因为它可能更容易在不同视图之间更改
更多详情请见How to Use CardLayout
基础知识
我非常遵守“代码到接口而不是实现”的原则和Model-View-Controller。这些基本上鼓励您分离和隔离责任,因此一个部分的更改不会对另一部分产生不利影响。
这也意味着您可以即插即用实现,解耦代码并使其更加灵活。
从基础开始,你需要一些包含文本(问题)、正确答案和一些“选项”(或错误答案)的东西
public interface Question {
public String getPrompt();
public String getCorrectAnswer();
public String[] getOptions();
public String getUserResponse();
public void setUserResponse(String response);
public boolean isCorrect();
}
所以,非常基本。该问题有提示、正确答案、一些错误答案,并且可以管理用户响应。为了方便使用,它还有一个isCorrect方法
现在,我们需要一个实际的实现。这是一个非常基本的示例,但您可能有许多不同的实现,甚至可能包括用于答案类型的泛型(为了论证,我假设为 String)
public class DefaultQuestion implements Question {
private final String prompt;
private final String correctAnswer;
private final String[] options;
private String userResponse;
public DefaultQuestion(String prompt, String correctAnswer, String... options) {
this.prompt = prompt;
this.correctAnswer = correctAnswer;
this.options = options;
}
@Override
public String getPrompt() {
return prompt;
}
@Override
public String getCorrectAnswer() {
return correctAnswer;
}
@Override
public String[] getOptions() {
return options;
}
@Override
public String getUserResponse() {
return userResponse;
}
@Override
public void setUserResponse(String response) {
userResponse = response;
}
@Override
public boolean isCorrect() {
return getCorrectAnswer().equals(getUserResponse());
}
}
好的,这一切都很好,但这实际上对我们有什么作用?好吧,知道你可以创建一个简单的组件,它的唯一工作就是向用户提出问题并处理他们的响应......
public class QuestionPane extends JPanel {
private Question question;
public QuestionPane(Question question) {
this.question = question;
setLayout(new BorderLayout());
JLabel prompt = new JLabel("<html><b>" + question.getPrompt() + "</b></html>");
prompt.setHorizontalAlignment(JLabel.LEFT);
add(prompt, BorderLayout.NORTH);
JPanel guesses = new JPanel(new GridBagLayout());
guesses.setBorder(new EmptyBorder(5, 5, 5, 5));
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.weightx = 1;
gbc.anchor = GridBagConstraints.WEST;
List<String> options = new ArrayList<>(Arrays.asList(question.getOptions()));
options.add(question.getCorrectAnswer());
Collections.sort(options);
ButtonGroup bg = new ButtonGroup();
for (String option : options) {
JRadioButton btn = new JRadioButton(option);
bg.add(btn);
guesses.add(btn, gbc);
}
add(guesses);
}
public Question getQuestion() {
return question;
}
public class ActionHandler implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
getQuestion().setUserResponse(e.getActionCommand());
}
}
}
这是一个很好的可重用组件,它可以处理一堆问题而不关心。
现在,我们需要一些方法来管理多个问题,一个测验!
public class QuizPane extends JPanel {
private List<Question> quiz;
private long timeOut = 5;
private Timer timer;
private JButton next;
private CardLayout cardLayout;
private int currentQuestion;
private JPanel panelOfQuestions;
private Long startTime;
public QuizPane(List<Question> quiz) {
this.quiz = quiz;
cardLayout = new CardLayout();
panelOfQuestions = new JPanel(cardLayout);
JButton start = new JButton("Start");
start.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
currentQuestion = -1;
nextQuestion();
timer.start();
}
});
JPanel filler = new JPanel(new GridBagLayout());
filler.add(start);
panelOfQuestions.add(filler, "start");
for (int index = 0; index < quiz.size(); index++) {
Question question = quiz.get(index);
QuestionPane pane = new QuestionPane(question);
panelOfQuestions.add(pane, Integer.toString(index));
}
panelOfQuestions.add(new JLabel("The quiz is over"), "last");
currentQuestion = 0;
cardLayout.show(panelOfQuestions, "start");
setLayout(new BorderLayout());
add(panelOfQuestions);
JPanel buttonPane = new JPanel(new FlowLayout(FlowLayout.RIGHT));
next = new JButton("Next");
buttonPane.add(next);
next.setEnabled(false);
add(buttonPane, BorderLayout.SOUTH);
next.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
nextQuestion();
}
});
timer = new Timer(250, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (startTime == null) {
startTime = System.currentTimeMillis();
}
long duration = (System.currentTimeMillis() - startTime) / 1000;
if (duration >= timeOut) {
nextQuestion();
} else {
long timeLeft = timeOut - duration;
next.setText("Next (" + timeLeft + ")");
next.repaint();
}
}
});
}
protected void nextQuestion() {
timer.stop();
currentQuestion++;
if (currentQuestion >= quiz.size()) {
cardLayout.show(panelOfQuestions, "last");
next.setEnabled(false);
// You could could loop through all the questions and tally
// the correct answers here
} else {
cardLayout.show(panelOfQuestions, Integer.toString(currentQuestion));
startTime = null;
next.setText("Next");
next.setEnabled(true);
timer.start();
}
}
}
好的,这有点复杂,但最基本的是,它管理当前向用户呈现的问题、管理时间并允许用户导航到下一个问题(如果他们愿意)。
现在,很容易迷失在细节中……
这部分代码实际上设置了主“视图”,使用CardLayout
panelOfQuestions.add(filler, "start");
for (int index = 0; index < quiz.size(); index++) {
Question question = quiz.get(index);
QuestionPane pane = new QuestionPane(question);
panelOfQuestions.add(pane, Integer.toString(index));
}
panelOfQuestions.add(new JLabel("The quiz is over"), "last");
currentQuestion = 0;
cardLayout.show(panelOfQuestions, "start");
start 按钮、“结束屏幕”和每个单独的QuestionPane 都添加到由CardLayout 管理的panelOfQuestions,这使得根据需要“翻转”视图变得容易。
我用一个简单的方法转到下一个问题
protected void nextQuestion() {
timer.stop();
currentQuestion++;
if (currentQuestion >= quiz.size()) {
cardLayout.show(panelOfQuestions, "last");
next.setEnabled(false);
// You could could loop through all the questions and tally
// the correct answers here
} else {
cardLayout.show(panelOfQuestions, Integer.toString(currentQuestion));
startTime = null;
next.setText("Next");
next.setEnabled(true);
timer.start();
}
}
这基本上增加了一个计数器并检查我们是否已经用完了问题。如果我们有,它会禁用下一个按钮并向用户显示“最后一个”视图,如果没有,它会移动到下一个问题视图并重新启动超时计时器。
现在,“魔法”来了……
timer = new Timer(250, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (startTime == null) {
startTime = System.currentTimeMillis();
}
long duration = (System.currentTimeMillis() - startTime) / 1000;
if (duration >= timeOut) {
nextQuestion();
} else {
long timeLeft = timeOut - duration;
next.setText("Next (" + timeLeft + ")");
}
}
});
Swing Timer 是一个伪循环,这意味着它将在常规基础上调用 actionPerformed 方法,就像 for 或 while 循环一样,但它这样做的方式很遥远,以至于它没有'不要阻止 EDT。
这个例子增加了一点“魔力”,因为它充当倒计时计时器,它检查问题对用户可见的时间并显示倒计时,直到它自动移动到下一个问题,当duration 大于或等于 timeOut(本例中为 5 秒),它调用 nextQuestion 方法
但是你问你如何使用它?您创建Questions 的List,创建QuizPane 的实例并将其添加到屏幕上显示的其他容器中,例如...
public class QuizMaster {
public static void main(String[] args) {
new QuizMaster();
}
public QuizMaster() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
List<Question> quiz = new ArrayList<>(5);
quiz.add(new DefaultQuestion("Bananas are:", "Yellow", "Green", "Blue", "Ping", "Round"));
quiz.add(new DefaultQuestion("1 + 1:", "2", "5", "3", "An artificial construct"));
quiz.add(new DefaultQuestion("In the UK, it is illegal to eat...", "Mince pies on Christmas Day", "Your cousin", "Bananas"));
quiz.add(new DefaultQuestion("If you lift a kangaroo’s tail off the ground...", "It can’t hop", "It will kick you in the face", "Act as a jack"));
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new QuizPane(quiz));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}
最后,因为我知道你会想要一个完全可运行的示例
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.EmptyBorder;
public class QuizMaster {
public static void main(String[] args) {
new QuizMaster();
}
public QuizMaster() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
List<Question> quiz = new ArrayList<>(5);
quiz.add(new DefaultQuestion("Bananas are:", "Yellow", "Green", "Blue", "Ping", "Round"));
quiz.add(new DefaultQuestion("1 + 1:", "2", "5", "3", "An artificial construct"));
quiz.add(new DefaultQuestion("In the UK, it is illegal to eat...", "Mince pies on Christmas Day", "Your cousin", "Bananas"));
quiz.add(new DefaultQuestion("If you lift a kangaroo’s tail off the ground...", "It can’t hop", "It will kick you in the face", "Act as a jack"));
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new QuizPane(quiz));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class QuizPane extends JPanel {
private List<Question> quiz;
private long timeOut = 5;
private Timer timer;
private JButton next;
private CardLayout cardLayout;
private int currentQuestion;
private JPanel panelOfQuestions;
private Long startTime;
public QuizPane(List<Question> quiz) {
this.quiz = quiz;
cardLayout = new CardLayout();
panelOfQuestions = new JPanel(cardLayout);
JButton start = new JButton("Start");
start.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
currentQuestion = -1;
nextQuestion();
timer.start();
}
});
JPanel filler = new JPanel(new GridBagLayout());
filler.add(start);
panelOfQuestions.add(filler, "start");
for (int index = 0; index < quiz.size(); index++) {
Question question = quiz.get(index);
QuestionPane pane = new QuestionPane(question);
panelOfQuestions.add(pane, Integer.toString(index));
}
panelOfQuestions.add(new JLabel("The quiz is over"), "last");
currentQuestion = 0;
cardLayout.show(panelOfQuestions, "start");
setLayout(new BorderLayout());
add(panelOfQuestions);
JPanel buttonPane = new JPanel(new FlowLayout(FlowLayout.RIGHT));
next = new JButton("Next");
buttonPane.add(next);
next.setEnabled(false);
add(buttonPane, BorderLayout.SOUTH);
next.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
nextQuestion();
}
});
timer = new Timer(250, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (startTime == null) {
startTime = System.currentTimeMillis();
}
long duration = (System.currentTimeMillis() - startTime) / 1000;
if (duration >= timeOut) {
nextQuestion();
} else {
long timeLeft = timeOut - duration;
next.setText("Next (" + timeLeft + ")");
next.repaint();
}
}
});
}
protected void nextQuestion() {
timer.stop();
currentQuestion++;
if (currentQuestion >= quiz.size()) {
cardLayout.show(panelOfQuestions, "last");
next.setEnabled(false);
// You could could loop through all the questions and tally
// the correct answers here
} else {
cardLayout.show(panelOfQuestions, Integer.toString(currentQuestion));
startTime = null;
next.setText("Next");
next.setEnabled(true);
timer.start();
}
}
}
public interface Question {
public String getPrompt();
public String getCorrectAnswer();
public String[] getOptions();
public String getUserResponse();
public void setUserResponse(String response);
public boolean isCorrect();
}
public class DefaultQuestion implements Question {
private final String prompt;
private final String correctAnswer;
private final String[] options;
private String userResponse;
public DefaultQuestion(String prompt, String correctAnswer, String... options) {
this.prompt = prompt;
this.correctAnswer = correctAnswer;
this.options = options;
}
@Override
public String getPrompt() {
return prompt;
}
@Override
public String getCorrectAnswer() {
return correctAnswer;
}
@Override
public String[] getOptions() {
return options;
}
@Override
public String getUserResponse() {
return userResponse;
}
@Override
public void setUserResponse(String response) {
userResponse = response;
}
@Override
public boolean isCorrect() {
return getCorrectAnswer().equals(getUserResponse());
}
}
public class QuestionPane extends JPanel {
private Question question;
public QuestionPane(Question question) {
this.question = question;
setLayout(new BorderLayout());
JLabel prompt = new JLabel("<html><b>" + question.getPrompt() + "</b></html>");
prompt.setHorizontalAlignment(JLabel.LEFT);
add(prompt, BorderLayout.NORTH);
JPanel guesses = new JPanel(new GridBagLayout());
guesses.setBorder(new EmptyBorder(5, 5, 5, 5));
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.weightx = 1;
gbc.anchor = GridBagConstraints.WEST;
List<String> options = new ArrayList<>(Arrays.asList(question.getOptions()));
options.add(question.getCorrectAnswer());
Collections.sort(options);
ButtonGroup bg = new ButtonGroup();
for (String option : options) {
JRadioButton btn = new JRadioButton(option);
bg.add(btn);
guesses.add(btn, gbc);
}
add(guesses);
}
public Question getQuestion() {
return question;
}
public class ActionHandler implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
getQuestion().setUserResponse(e.getActionCommand());
}
}
}
}