2

I'm trying to create a personality quiz. The idea is that a JPanel will show up with the first question and then once the user selects one of the radio buttons, the second JPanel with the second question will show up. Since I have 5 questions each with 3 answer choices I thought it would be faster and more efficient to create a method that creates radio buttons and adds an ActionListener but I'm having trouble getting the listener to work. Right now to see if it works I'm just trying to change the button text when it is selected.

I tried adding the listener to the button in the createButton method but I haven't had luck. Originally I had it as a parameter in the method but that didn't get the expected result so I tried to create it without the listener as a parameter. Inserting the listener as a parameter didn't change the text.

import java.awt.Container;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.WindowConstants;

public class UserInterface extends ClickListener implements Runnable
{
    private ActionListener listeners;
    private JFrame frame;

    public UserInterface(){
    }
@Override
public void run() {
    frame = new JFrame("title");
    frame.setPreferredSize(new Dimension(1000, 1000));

    frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    createComponents(frame.getContentPane());

    frame.pack();
    frame.setVisible(true);
}
public JFrame getFrame(){
    return frame;
}
private void createComponents(Container container){

    BoxLayout layout = new BoxLayout(container, BoxLayout.Y_AXIS);
    container.setLayout(layout); 

    container.add(QuizIntro());
    container.add(QuestionOne());
    container.add(QuestionOneGroup());
}
    public JLabel QuizIntro(){
    JLabel text = new JLabel("Intro text");
    return text;
} 
public JLabel QuestionOne(){
    JLabel text = new JLabel("1. this is the first question");

    return text;
}
    public JPanel QuestionOneGroup(){
        JRadioButton int1 = createButton("This button was created with my createButton method");
        JRadioButton e1 = new JRadioButton("This button was created without that method");
        JPanel panel = new JPanel();
        panel.add(int1);
        panel.add(e1);
        return panel;
    }
    public JRadioButton createButton(String text){
        JRadioButton b = new JRadioButton(text);
        b.addActionListener(listeners);
        return b;
    }


}

here's my action listener

public class ClickListener implements ActionListener {
    private UserInterface ui; 
    private JRadioButton b; 
    @Override
    public void actionPerformed(ActionEvent ae) {
        if (b.isSelected()){
            b.setText("this works");
        }
    }
}

The actual result is that the button is selected but the text does not change. I'm having trouble figuring out if I'm running the wrong test to see if my listener works or if my listener just does not work.

  • 1
    There is much wrong with this code, including inappropriate inheritance, giving your listener class private variables, variables that remain null and are useless, and adding null variables as a listener where an object belongs. It's quite frankly hard to know what your main problem is. Please consider creating a valid [mcve] and clearly stating/showing what the problem is. If this were my code, I'd start over TBH. – Hovercraft Full Of Eels Jan 05 '19 at 15:07
  • Also: Does the code compile? If so, how does it function well? And how does it malfunction? Are you seeing any exceptions? Are you seeing a NullPointerException, perhaps? Please go through the [ask] for more on how to ask questions that are easier to understand and answer. – Hovercraft Full Of Eels Jan 05 '19 at 15:08
  • @HovercraftFullOfEels if null and useless variable you mean the UI one in the actionlistener that is a mistake from a previous attempt I forgot to comment out. The code compiles with no exceptions. The radio buttons all show up. I'm simply trying to select the first button to see if the button text changes the text. I've tried a bunch of variations with my listener on the button and I'm sure there is some code from a failed attempt left over. what do you mean by adding a null variable as a listener where an object belongs? –  Jan 05 '19 at 15:13
  • @HovercraftFullOfEels my main problem is just trying to determine if the listener is correctly hooked up to the button. –  Jan 05 '19 at 15:14
  • 1
    In your ClickListener class, this is a useless variable: `private JRadioButton b; ` as is `private UserInterface ui;`. Where do you assign a viable object to either variable? How is this possible to do? And again, does your code throw any exceptions when run, when the JRadioButtons are pressed? And again, please post better code, code we can compile, run and test, a [mcve]. I again urge you to read this important link. – Hovercraft Full Of Eels Jan 05 '19 at 15:16
  • As for your direct question, my guess is no, it's not, but again we don't see code where you even create an ActionListener object since your code is so incomplete. – Hovercraft Full Of Eels Jan 05 '19 at 15:16
  • @HovercraftFullOfEels no exceptions are thrown. nothing happens when the radio button is selected other than the button filling in. I'm reading that link now. I'll edit my code to include my UserInterface constructor where I was initializing my listener –  Jan 05 '19 at 15:27
  • 2
    Again, you'll want to add more than just the constructor -- you'll want to post a valid [mcve], a suggestion that I've already made above. I've been on this website for over 5 years, and my experience with these types of questions has shown that a decent and well-crafted [MCVE](https://stackoverflow.com/help/mcve) is your best ticket to getting a quick, decent and comprehensive answer. Again, please read this important link. – Hovercraft Full Of Eels Jan 05 '19 at 15:29
  • @HovercraftFullOfEels look i'm a beginner. and i read that. and i thought i included all the relevant code but i guess i didn't. can you please expand on what you need to see? I can include my createComponent method if you want that successfully adds the buttons. –  Jan 05 '19 at 15:31
  • Understand that we're not asking for the entire program, we want a compilable and runnable representation program, one that demonstrates the problem. This is a new program, one that we can copy and paste into our IDE's (I use Eclipse, but that isn't relevant). You are asking for free help from volunteers, and it will help you immensely if you can make it as easy as possible to understand your code and your problem, and why the code is not working. – Hovercraft Full Of Eels Jan 05 '19 at 15:33
  • For instance, this, `public UserInterface(){ this.listeners = listeners; }` is nonsense as you're just setting the listeners field (which is null) equal to itself since the constructor takes no parameters. – Hovercraft Full Of Eels Jan 05 '19 at 15:35
  • @HovercraftFullOfEels give me a few seconds to add some more relevant code. in the meantime should i get rid of all the code i commented out? I thought that commented out failed attempts might help. –  Jan 05 '19 at 15:35
  • Yes, make your code as clean and as easy to read as possible. I'd get rid of the commented code. If important, show it in another code block, such as "I also tried this... but it didn't work because of ..." – Hovercraft Full Of Eels Jan 05 '19 at 15:36
  • @HovercraftFullOfEels you mentioned inappropriate inheritance earlier. also I believe in one of my failed attempts yesterday I had parameters in my listener's constructor method. are you saying i should remake my listener's constructor with the radio button as a parameter? –  Jan 05 '19 at 15:42
  • 2
    What @HovercraftFullOfEels is trying to say with [mcve] is: 1) Create a new program from scratch that contains only the minimum code to show your issue but that is complete enough for us to copy-paste it on our IDE's and see the same problem as you. Here's an [example](https://stackoverflow.com/a/54043710/2180785) I made for an answer yesterday. If you copy-paste the code you should see the same output I posted in the screenshots without doing anything else than just copy-pasting it. That's what we're expecting from you – Frakcool Jan 05 '19 at 15:51
  • [Here](https://stackoverflow.com/questions/40944066/how-to-set-a-shortcut-key-for-jradiobutton-without-modifiers) is another example of using `JRadioButtons` and with inheritance, it's also an answer from Hovercraft and is kind of similar to what you want to do. – Frakcool Jan 05 '19 at 15:54
  • @HovercraftFullOfEels I added all my components and everything. you should be able to run it by copying and pasting it into eclipse –  Jan 05 '19 at 16:12
  • I appreciate the effort but still, no main method yet, this still `public UserInterface(){ this.listeners = listeners; }` is worthless code (I'm guessing that you want to use a parameter with this constructor??), we don't see where you create or add your ActionListeners.... – Hovercraft Full Of Eels Jan 05 '19 at 16:14
  • .............Keep at it – Hovercraft Full Of Eels Jan 05 '19 at 16:14
  • Also you're using variables that are not declared in the code above, such as `frame` , you've got methods declared to return objects, that return nothing, such as `QuestionOneGroup()` (and should be re-named `questionOneGroup()` to comply with naming conventions),.... we want *real* code, not kind-of, sort-of code. Yes I'm asking a lot, but again, we're volunteers and you're asking for free help debugging, so I'm not asking for too much. – Hovercraft Full Of Eels Jan 05 '19 at 16:20
  • @HovercraftFullOfEels I added all the imports. i copied and pasted it into my IDE and it compiled. let me know if there's anything else. –  Jan 05 '19 at 16:21
  • getting close, but you still have this worthless code: `public UserInterface(){ this.listeners = listeners; }` you do see that it does nothing of use, right? – Hovercraft Full Of Eels Jan 05 '19 at 16:23
  • @HovecraftFullOfEels yeah i realized i didn't copy and paste all the variables. everything should be there. I deleted the useless listener in the constructor. –  Jan 05 '19 at 16:23
  • 1
    Thank you for sticking with this and improving the question. – Hovercraft Full Of Eels Jan 05 '19 at 16:58
  • Yes! I agree with Hovercraft and I upvoted the question for the effort put into creating an MCVE. – Frakcool Jan 05 '19 at 17:06

4 Answers4

4

Your main problem is here:

private ActionListener listeners;

You create an ActionListener but you never call it.

You're also inheriting from ClickListener but never using it.

public class UserInterface extends ClickListener implements Runnable

In your code you call:

b.addActionListener(listeners); //Listeners is null!

So, you're telling your JRadioButton to have a listener that is null.

So, here you have 2 approaches:

  1. Remove private ActionListener listeners; from the top of your program and change:

    b.addActionListener(listeners);
    

    To:

    b.addActionListener(this);
    
  2. Remove extends ClickListener from your class definition and keep:

    b.addActionListener(listeners);
    

    But adding

    listeners = new ClickListener();
    

    Before

    createComponents(frame.getContentPane());
    

IMO I would take the 2nd approach.

BTW for your ActionListener to actually change the text you don't need private variables but rather get the source and cast it. For example:

public void actionPerformed(ActionEvent ae) {
    JRadioButton b = (JRadioButton) ae.getSource();
    b.setText("this works");
}

Forgot to mention, please follow Java naming conventions so that your program is more readable and understandable by everyone who reads it.

  • firstWordLowerCaseVariable
  • firstWordLowerCaseMethod()
  • FirstWordUpperCaseClass
  • ALL_WORDS_UPPER_CASE_CONSTANT

And indent your code correctly as well :)

Frakcool
  • 10,915
  • 9
  • 50
  • 89
2

First of all the GUI should not extend the listener class. Not good. Better to keep them separate and to pass references where needed. For example, if the listener needs a reference to the GUI, pass it in as a paramter

Also, you apparently want to do is to respond to JRadioButton selection immediately with useful behavior. I would use an ItemListener not an ActionListener, since the ItemListener will tell you if the radio button has been selected. Calling getSource() on the ItemEvent will give you the current JRadioButton that was selected.

e.g.,

import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.List;

import javax.swing.*;

@SuppressWarnings("serial")
public class UserInterface2 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);

    public UserInterface2(List<Question> questions) {
        centerPanel.setBorder(BorderFactory.createLineBorder(Color.BLUE));
        for (Question question : questions) {
            centerPanel.add(createQPanel(question), question.getQuestion());
        }

        JPanel bottomPanel = new JPanel(new BorderLayout());
        // add button that allows swapping question panels
        bottomPanel.add(new JButton(new AbstractAction("Next") {

            @Override
            public void actionPerformed(ActionEvent e) {
                cardLayout.next(centerPanel);
            }
        }), BorderLayout.LINE_START);
        bottomPanel.add(resultLabel);

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

    private JPanel createQPanel(Question question) {
        JPanel radioPanel = new JPanel(new GridLayout(0, 1));
        radioPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
        ButtonGroup buttonGroup = new ButtonGroup();
        ItemListener myItemListener = new MyItemListener(this);
        for (String answer : question.getAnswers()) {
            JRadioButton answerButton = new JRadioButton(answer);

            // this is present in case you want to extract the Question
            // object from the JRadioButton, useful for if you want to
            // test if the selected answer is correct
            answerButton.putClientProperty(QUESTION, question);

            // add our listener to the JRadioButton
            answerButton.addItemListener(myItemListener);

            // add to button group so only one can be selected
            buttonGroup.add(answerButton);

            // add to JPanel for display
            radioPanel.add(answerButton);
        }

        JPanel qPanel = new JPanel(new BorderLayout());
        qPanel.add(new JLabel(question.getQuestion()), BorderLayout.PAGE_START);
        qPanel.add(radioPanel);
        return qPanel;
    }

    // public method that the item listener will use to display selection
    public void displayResult(String selectedText) {
        resultLabel.setText(selectedText);
    }

    private static void createAndShowGui() {
        // create mock questions
        // likely this information will be in a text file
        List<Question> questions = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            String question = "Question " + i;
            List<String> answers = new ArrayList<>();
            for (int j = 0; j < 4; j++) {
                answers.add(String.format("Answer [%d %d]", i, j));
            }

            int correctIndex = (int) (Math.random() * answers.size());
            // future iteration will also need correctIndex int
            questions.add(new Question(question, answers, correctIndex));
        }

        UserInterface2 mainPanel = new UserInterface2(questions);

        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 static void main(String[] args) {
        SwingUtilities.invokeLater(() -> createAndShowGui());
    }

}

class MyItemListener implements ItemListener {
    private UserInterface2 ui;

    public MyItemListener(UserInterface2 ui) {
        this.ui = ui;
    }

    @Override
    public void itemStateChanged(ItemEvent e) {
        JRadioButton source = (JRadioButton) e.getSource();

        String selected = "The JRadioButton " + source.getText();
        selected += e.getStateChange() == ItemEvent.SELECTED ? " has been selected"
                : " has been unselected";

        // to get the actual Question object get the client property:
        Question question = (Question) source.getClientProperty(UserInterface2.QUESTION);
        // now can get answer Strings and check if correct one selected        
        System.out.println(question);

        String correctAnswer = question.getAnswers().get(question.getCorrectIndex());;
        if (source.getText().equals(correctAnswer)) {
            selected += " and is correct";
        } else {
            selected += " and is incorrect";
        }

        // tell the GUI to display the result
        ui.displayResult(selected);
    }
}

class Question {
    private String question;
    private List<String> answers;
    private int correctIndex;

    public Question(String question, List<String> answers, int correctIndex) {
        this.question = question;
        this.answers = answers;
        this.correctIndex = correctIndex;
    }

    public String getQuestion() {
        return question;
    }

    public List<String> getAnswers() {
        return answers;
    }

    public int getCorrectIndex() {
        return correctIndex;
    }

    @Override
    public String toString() {
        return "Question [question=" + question + ", correctIndex="
                + correctIndex + "]";
    }

}
Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
0

Since there are many things to be improved in your code, I thought I'll write a sample program to show you how this kind of a thing should be done in Swing. Try it and see. (There are few things we can improve in this sample code as well. But I just wanted to keep things simple and address only the key points.)

import javax.swing.*;
import java.awt.event.*;

public class Questions {

  public static void main(String[] args) {
    JFrame f = new JFrame("Questions");
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    f.getContentPane().add(new QuestionPanel());
    f.setBounds(300, 200, 400, 300);
    f.setVisible(true);
  }
}

class QuestionPanel extends JPanel implements ActionListener {

  private static final String ANSWER_1_TEXT = "Answer 1";
  private static final String ANSWER_2_TEXT = "Answer 2";
  private static final String ANSWER_3_TEXT = "Answer 3";

  private JRadioButton answer1;
  private JRadioButton answer2;
  private JRadioButton answer3;

  QuestionPanel() {

    answer1 = new JRadioButton(ANSWER_1_TEXT);
    answer2 = new JRadioButton(ANSWER_2_TEXT);
    answer3 = new JRadioButton(ANSWER_3_TEXT);

    answer1.addActionListener(this);
    answer2.addActionListener(this);
    answer3.addActionListener(this);

    ButtonGroup buttonGroup = new ButtonGroup();
    buttonGroup.add(answer1);
    buttonGroup.add(answer2);
    buttonGroup.add(answer3);

    setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
    add(answer1);
    add(answer2);
    add(answer3);
  }

  @Override
  public void actionPerformed(ActionEvent e) {

    JRadioButton selectedAnswer = (JRadioButton) e.getSource();
    if (selectedAnswer == answer1) {
      answer1.setText(ANSWER_1_TEXT + " (selected)");
      answer2.setText(ANSWER_2_TEXT);
      answer3.setText(ANSWER_3_TEXT);
    }
    else if (selectedAnswer == answer2) {
      answer1.setText(ANSWER_1_TEXT);
      answer2.setText(ANSWER_2_TEXT + " (selected)");
      answer3.setText(ANSWER_3_TEXT);
    }
    else if (selectedAnswer == answer3) {
      answer1.setText(ANSWER_1_TEXT);
      answer2.setText(ANSWER_2_TEXT);
      answer3.setText(ANSWER_3_TEXT + " (selected)");
    }
  }
}
Prasad Karunagoda
  • 2,048
  • 2
  • 12
  • 16
0

How many programmers do you need to change a lightbulb? 76, 1 to change it and 75 to say how they could've done it better.

You do have bad code practices, but thats usually because of a shaky understanding of the fundamental concepts behind the language design. So I won't comment on how your code is bad for this or that, just explain the basics you should know.

To simplify, an ActionListener is an object that will react on an ActionPerformedEvent. Lets define one, a class called Observer:

Observer has no idea who generated the event, so lets tell him, and if it is a JRadioButton, lets use it as one

public class Observer implements ActionListener{
    @Override
    public void ActionPerformed(ActionEvent ae){
        Object source = ae.getSource();
        if (source instanceof JRadioButton)
            ((JRadioButton) source).setText("this works");
    }

Any of your JRadioButton are objects that generates ActionEvents constantly, but nobody we care about is usually watching, when we add an ActionListener we are basically saying: make this ActionListener object observe my behaviour.

So we need someone to be observed and an observer, lets make those in your UI (or a simplified version of it): Since you are trying to use a global listener, you need to make sure there is one there, observer (our ActionListener) is null for now. Lets instantiate it This time, we know that observer is not null

public class UserInterface implements runnable{
    private ActionListener observer new Observer();
    //...
    public void someMethodToCreateButtons(){
        JRadioButton observableButton = new JRadioButton("Created here");
        observableButton.addActionListener(observer);
    }

And thats it, when you select observableButton, it will change its text to "this works".

Those are the basics, now, I used those names observableButton and Observer for a reason, ActionListeners are based on what is known as the observer design pattern you could pick up a book regarding designs patterns, you wont regret it.

Finally, it seems that you are making things too difficult for yourself, try to simplify your logic. Perhaps making a JPanel containing different sets of buttons and displaying it whenever a condition is true? just spitballing here, but try to keep it simple, hope it helps, good luck!

LeedMx
  • 424
  • 4
  • 19