0

I am trying to create a quiz. The quiz starts by reading a file. The file has 6 questions. Each question gets its own card in the cardlayout. Each card has a button to the next CardLayout. Questions 1 - 5 should have a 'next' button that links to the next card. Question 6 should have a button that says 'get results' this will link to a card that displays one of the possible results cards. (these are still in progress, for now I am simply trying to get the button created and am testing it with the .previous() method). As of now each card has a next button and it seems the statement that adds a 'get results' button isn't being reached. take a look.

import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ItemListener;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;

public class UserInterface extends JPanel {
    public static final Object QUESTION = "question";
    // fill label with blank text to expand it
    private JLabel resultLabel = new JLabel(String.format("%150s", " "));

    // CardLayout to allow swapping of question panels
    private CardLayout cardLayout = new CardLayout();
    private JPanel centerPanel = new JPanel(cardLayout);
    private List<String> answers = new ArrayList<>();
    private int currentCard = 0; 
    private QuestionsContainer containers = new QuestionsContainer();

    public UserInterface(QuestionsContainer container) throws FileNotFoundException {
        centerPanel.setBorder(BorderFactory.createLineBorder(Color.BLUE));
        for (Questions question : container.getQuestions()) {
            centerPanel.add(createQPanel(question), null);
            currentCard++;

            JPanel bottomPanel = new JPanel(new BorderLayout());

            if ((currentCard == containers.questionsLength() - 1){
                bottomPanel.add(new JButton(new AbstractAction("Next") {
                   @Override
                   public void actionPerformed(ActionEvent e) {
                    cardLayout.next(centerPanel);

                }
            }),
                BorderLayout.LINE_START);
                add(bottomPanel, BorderLayout.LINE_START);
                bottomPanel.add(resultLabel);

                setLayout(new BorderLayout());
                add(bottomPanel, BorderLayout.PAGE_END);
                add(centerPanel);
        }

                JPanel bottomPanel1 = new JPanel();
                bottomPanel1.add(new JButton(new AbstractAction("Get Results") {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        System.out.println("hello");
                        cardLayout.previous(centerPanel);
// both of these are just to see if this statement is reached
                        bottomPanel.validate();
                        bottomPanel.repaint();
                }
            }),
                BorderLayout.LINE_START);
                add(bottomPanel1, BorderLayout.LINE_START);
                bottomPanel1.add(resultLabel);

                setLayout(new BorderLayout());
                add(bottomPanel1, BorderLayout.PAGE_END);
                add(centerPanel);
        }    
    }

    private JPanel createQPanel(Questions question) {

        JPanel radioPanel = new JPanel(new GridLayout(0, 1));
        radioPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
        radioPanel.add(new JLabel(question.getQuestion()), BorderLayout.PAGE_START);
        ButtonGroup buttonGroup = new ButtonGroup();
        ItemListener myItemListener = new MyItemListener(this);
        for (String answer : question.getAnswer()) {
            JRadioButton answerButton = new JRadioButton(answer);

            answerButton.putClientProperty(QUESTION, question);
            answerButton.addItemListener(myItemListener);
            buttonGroup.add(answerButton);
            radioPanel.add(answerButton);
        }

        JPanel qPanel = new JPanel(new BorderLayout());
        qPanel.add(radioPanel);
        return qPanel;
    }

    public void displayResult(String selectedText) {
        resultLabel.setText(selectedText);
    }
    public void displayFinalResults(){

    }
    public static void createAndShowGui() throws FileNotFoundException {

        UserInterface mainPanel = new UserInterface(new QuestionsContainer());

        JFrame frame = new JFrame("User Interface");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(mainPanel);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

public class QuestionsContainer {

    private List<Questions> questions = new ArrayList<>();
    private int questionCount = 0;  

    QuestionsContainer() throws FileNotFoundException {

        File file = new File("src/house1.txt"); 
        try (Scanner reader = new Scanner(file)) {
            String question = "";
            List<String> answers = new ArrayList<>();

            while (reader.hasNextLine()){


                String line = reader.nextLine();
                if (line.startsWith("QUESTION: ")) {
                    question = line.substring(10);
                } else if (line.startsWith("ANSWER: ")){
                    String answer = line.substring(8);
                    answers.add(answer);
                } else if (line.isEmpty()) {
                    questions.add(new Questions(question, answers));
                    question = "";
                    answers = new ArrayList<>();
                    questionCount++;
                }
            }
        }
    }

    public List<Questions> getQuestions() {
        return questions;
    }
    public int questionsLength(){
        return questions.size();
    }

I've tried making the bottomPanel its own method that returned a bottomPanel. this did not achieve the desired results because it was called in the constructor and I don't think the currentCard variable was adding up every time. Right now this code reads all the questions fine and all the answers fine. But it creates a next button on every single card instead of on the first 5 only. if there's a variable in a weird place or a suspicious print call it's most likely leftover code from a previous test that I forgot to delete/comment out.

Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
  • Your `if-else` statement is wrong, you will need to debug that, but. Instead of trying to hard code this in this manner, you might consider changing tact such that each card is passed some kind of "navigation" model, which provides it with the logic to make determinations about what to do, such as `hasNextQuestion`, `hasPreviousQuestion` and `next`. This way the view can determine if it should display `next` or `results` (and `previous` if it's available) and calling `next` on the navigation controller will make determinations about which view to show next – MadProgrammer Jan 29 '19 at 21:16
  • @MadProgrammer I think I had that thought too re: hasNextQuestion but I checked the Oracle docs for swing and those methods aren't included in the API. –  Jan 29 '19 at 21:22
  • That's right, you'll haver to write that layer yourself - [for example](https://stackoverflow.com/questions/51641629/switching-contentpane-in-real-time/51642080#51642080) and [example](https://stackoverflow.com/questions/31602113/listener-placement-adhering-to-the-traditional-non-mediator-mvc-pattern/31604919#31604919) – MadProgrammer Jan 29 '19 at 21:26
  • @MadProgrammer I thought I was doing that with my current jumble of code. do you have any suggestions off the top of your head on how to do it different? –  Jan 29 '19 at 21:31
  • Make `else if (currentCard == containers.questionsLength() - 1){` the first decision in the `if-else` statement, `if current == end of questions { // results } else { // default operations }`. The point of using a navigation controller is to decouple the logic more – MadProgrammer Jan 29 '19 at 21:33
  • @MadProgrammer just tried that and it didn't work. the currentCard variable is increasing with every click. I don't know why it isn't being caught in the if statement. i'm thinking of going into the array in my answers class and rigging it so it's triggered when there is 1 less than answer than question. –  Jan 29 '19 at 21:42
  • So, your code uses `containers.getQuestionCount()`, you only show the logic for `questionsLength` which makes me wonder what's going on in `getQuestionCount` and its point - consider providing a [mcve] – MadProgrammer Jan 29 '19 at 21:53
  • @MadProgrammer thanks for pointing that out. it's left over from a previous text. it's all questionLength() now. –  Jan 29 '19 at 21:57
  • Based on my limited testing, `containers.questionsLength() > currentCard` can never be `true` - put in some `System.out.println` statements and debug the values – MadProgrammer Jan 29 '19 at 22:00
  • @MadProgrammer made some edits check again –  Jan 29 '19 at 22:04

1 Answers1

0

You seem to be thinking in terms of a procedural process, rather than an event driven process. The state of currentCard will be updated during each loop, it's state is not "stored" between iterations.

This means, as a side effect of BorderLayout, only the last component added to the container will be displayed.

Instead, you need to change your thinking, so that when the ActionListener is triggered, you update the state and make determinations about what should be done, for example

public UserInterface(QuestionsContainer container) throws FileNotFoundException {
    setLayout(new BorderLayout());
    centerPanel.setBorder(BorderFactory.createLineBorder(Color.BLUE));
    for (Questions question : container.getQuestions()) {
        centerPanel.add(createQPanel(question), null);
    }
    add(centerPanel);

    JPanel navigationPane = new JPanel(new GridBagLayout());
    navigationPane.setBorder(new EmptyBorder(8, 8, 8, 8));
    JButton navButton = new JButton("Next");
    navButton.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent evt) {
            if (evt.getActionCommand().equals("Next")) {
                currentCard++;
                if (currentCard == container.questionsLength()) {
                    ((JButton) evt.getSource()).setText("Get results");
                }
                cardLayout.next(centerPanel);
            } else {
                System.out.println("Print the results");
            }
        }
    });
    GridBagConstraints gbc = new GridBagConstraints();
    gbc.anchor = GridBagConstraints.EAST;
    gbc.weightx = 1;
    navigationPane.add(navButton, gbc);
    add(navigationPane, BorderLayout.SOUTH);
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • this is interesting. I just ran this. It only produced get results buttons in the quiz itself and the questions went in reverse. it also printed hello to the console like it was supposed to. and it outputted current card = 1... count =6, next. but for current card = 6 count = 6, it output last. so it's reaching the next statement but not the new Button –  Jan 29 '19 at 22:44
  • I only tested the loop logic - it prints a bunch of `"Next"` statements and finally `"Last"` - so at least I know the code is flowing through the `if` statements – MadProgrammer Jan 29 '19 at 22:50
  • You also seem to be thinking in terms of a "procedural" path, where one action leads to another. The above code will repeatedly add buttons to the `UserInterface` until the method exists, the side effect is, because of `BorderLayout`, it will only show the last thing added to it. – MadProgrammer Jan 29 '19 at 22:56
  • I tried making the buttons in their own method but it had the same results. i'm thinking of trying a GridBagLayout instead of a borderlayout then. also, if the borderlayout only shows the last thing added to it why does it show the next question instead of repeating the same one? –  Jan 29 '19 at 23:14
  • Because you're adding the button to a panel, but because all components have a default size of `0x0`, they are never shown, because the `BorderLayout` only lays out the last one - see the update answer – MadProgrammer Jan 29 '19 at 23:26
  • @user10871827 Worked as expected for me - based on your limited, out-of-context code – MadProgrammer Jan 29 '19 at 23:40