I am calling this method called check in one of my abstract classes but for some reason the JLabel (problem) I am adding to the JPanel (panel) is not showing up. Why is this
The problem you're having is you're blocking the Event Dispatching Thread, prevent the UI from been updated or any new events from been processed...
It starts here...
for(int i = 0; i < 15; i++)
{
//...
//Check to see if user has enetered anything
// And is compounded here
while(!answered)
{
Thread.sleep(duration);
//...
}
You're clearly thinking in a procedural manner (like you would for a console program), but this isn't how GUIs work, GUIs are event driven, something happens at some point in time and you respond to it.
My suggestion is to investigate Swing Timer
, which will allow you to schedule a call back at some, point in the future and perform some action when it is triggered, which can be used to modify the UI, as its executed within the context of the EDT.
See Concurrency in Swing and How to use Swing Timers for more details
I'd also recommend that you take a look at CardLayout
, as it might make easier to change the between different views
See How to Use CardLayout for more details
I work very much to the principle of "Code to interface not implementation" and the Model-View-Controller. These basically encourage your to separate and isolate responsibility, so a change in one part won't adversely affect another.
It also means you can plug'n'play implementations, decoupling the code and making it more flexible.
Start with the basic, you need something that has some text (the question), a correct answer and some "options" (or incorrect answers)
public interface Question {
public String getPrompt();
public String getCorrectAnswer();
public String[] getOptions();
public String getUserResponse();
public void setUserResponse(String response);
public boolean isCorrect();
}
So, pretty basic. The question has a prompt, a right answer, some wrong answers and can manage the user response. For ease of use, it also has a isCorrect
method
Now, we need an actual implementation. This is a pretty basic example, but you might have a number of different implementations and might even include generics for the type of answers (which I've assumed as String
for argument sake)
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());
}
}
Okay, that's all fine and all, but what does this actually do for us? Well, know you can create a simple component whose sole job is to present the question to the user and handle their response...
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());
}
}
}
This makes for a nice re-usable component, one which can handle a bunch of questions and not care.
Now, we need some way to manage multiple questions, a quiz!
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();
}
}
}
Okay, this is a little more complicated, but the basics are, it manages which question is currently presented to the user, manages the time and allows the user to navigate to the next question if they want to.
Now, it's easy to get lost in the detail...
This part of the code actually set's up the main "view", using a 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");
The start
button, "end screen" and each individual QuestionPane
are added to the panelOfQuestions
, which is managed by a CardLayout
, this makes it easy to "flip" the views as required.
I use a simple method to move to the next question
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();
}
}
This basically increments a counter and checks to see if we've run out of questions or not. If we have, it disables the next button and shows the "last" view to the user, if not, it moves to the next question view and restarts the timeout timer.
Now, here comes the "magic"...
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 + ")");
}
}
});
The Swing Timer
acts a pseudo loop, meaning that it will call the actionPerformed
method on a regular bases, just like for
or while
loop would, but it does it in such away that it doesn't block the EDT.
This example adds a little more "magic" in that it acts as a count down timer, it checks how long the question has been visible to the user and presents a count down until it will automatically move to the next question, when the duration
is greater then or equal to the timeOut
(5 seconds in this example), it calls the nextQuestion
method
But how do you use it you ask? You create a List
of Question
s, create an instance of the QuizPane
and add that to some other container which is displayed on the screen, for example...
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);
}
});
}
}
And finally, because I know you'll want one, a fully runable example
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());
}
}
}
}
If you used Jframe for this application, just check to see if you added the panel to the frame, you've just added the label to the panel, just check to see if you've added the panel to the Jframe, otherwise it won't show up