Why is my JLabel not showing up

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

Basics

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 Questions, 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

QuizMaster

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());
            }

        }

    }

}

Leave a Comment