Функция вызывается дважды без причины

1

Im делает выражение викторины. И когда я нажимаю клавишу внутри представления, модель обновляется через контроллер. Но функция в моделях сначала называется 1 раз. 2 раза в следующий раз я нажимаю клавишу, 3 раза в следующий раз,... и т.д.

Я абсолютно не знаю, как это происходит. Я попытался отладить его, и он не вызван из нескольких мест. Только с 1 места. Это почти похоже на то, что ключевые события укладываются в стек и продолжают вызывать метод.

Вот мой код нескольких классов.

Вид викторины, где собираются ключевые события:

/**
 * This class will show the questions and react on the keyboard events
 * @author Matthias Claes
 *
 */
public class QuizView extends JPanel implements View {
private Observable $model;
private Controller $controller;

private Question $question;

private boolean $isPressed; /* To check if we already listened to a key press event */


public QuizView(Observable model, Controller controller){
    setFocusable(true);

    $model = model;

    if (controller == null)
        $controller = new QuizController(model);
    else
        $controller = controller;

    $question = null;   
    $isPressed = false;

    /* Add a keylistener for every team */
    addKeyListener(new KeyAdapter() {
        public void keyTyped(KeyEvent e) {
            int teamSize; /* team size*/
            teamSize = ((QuizModel) getModel()).getTeams().size();
            if (Character.getNumericValue(e.getKeyChar()) <= teamSize) { /* If you pressed a number under the teamsize we listen to it, and there hasn't been pressed before */

                buttonPressed(Character.getNumericValue(e.getKeyChar()));

                // Else ignore the key stroke
            }
        }
    }); 

}

/**
 * If a button gets pressed we call this function
 * @param team the team that pressed their button
 */
protected void buttonPressed(int team) {
    /* Check if a button is pressed */
    if(((QuizModel) getModel()).getTeamPressed() > 0)
        $isPressed = true;
    else
        $isPressed = false;

    /* If there hasn't been pressed yet and the question is not null */
    if(!$isPressed && $question != null){
        minimizeFrame(); /* Minimize the frame */

        /* If this question has a media path we need to pause the audio/video, we also check if the user has installed vlcplayer and selected the right path */
        if($question.getMediaPath() != null && QuizSoftwareModel.$vlcPath != null)
            ((QuizController)getController()).pause(); /* Pause the video */

        /* Give a pop up message to the admin that a team has pushed their button */
        ((QuizController)getController()).showScoreView(team);
    }

}

public void update(Observable arg0, Object arg1) {
    $question = (Question) arg1;

    System.out.println("CALLED");

    if(((QuizModel) getModel()).getMaximize()) /* Only maximize the JFrame when needed */
        maximizeFrame(); /* Maximize the frame first */     

    repaint();
}


/**
 * Maximize the parent JFrame so the teams can see the question
 */
protected void maximizeFrame() {
    JFrame topFrame = (JFrame) SwingUtilities.getWindowAncestor(this);
    topFrame.setState(JFrame.MAXIMIZED_BOTH);

}

/**
 * Minimize the parent JFrame so the teams can't see the question anymore 
 */
protected void minimizeFrame() {
    JFrame topFrame = (JFrame) SwingUtilities.getWindowAncestor(this);
    topFrame.setState(JFrame.ICONIFIED);

}

В представлении отображается всплывающее окно с кнопкой, чтобы перейти к следующему вопросу или воспроизвести тот же вопрос, когда ответ неверен. Нажатие кнопки возобновления создает проблему.

public class GiveScoreView extends JFrame implements View {
Observable $model;
Controller $controller;

private Question $question; /* Saves the question that is passed by the update */

/*GUI elements */
ArrayList<JLabel> $answerLabels;
ArrayList<JCheckBox> $checkBoxes;
JLabel $questionLabel;
JLabel $teamPressedLabel;

/* 2 buttons to resume/end the question */
JButton $resumeButton;
JButton $calculateButton;

public GiveScoreView(Observable model, Controller controller) {

    setModel(model);
    setController(controller);

    $answerLabels = new ArrayList<JLabel>();
    $checkBoxes = new ArrayList<JCheckBox>();
    $questionLabel = new JLabel();

    $teamPressedLabel = new JLabel();

    $resumeButton = new JButton("Resume question"); /* TODO messagebundle */
    $calculateButton = new JButton("Calculate score"); /* TODO messagebundle */


    initializeFrame();
}

/**
 * Initializes the frame
 */
private void initializeFrame() {
    setTitle("Give a score to the teams"); /* TODO languagebundle, Change the title of the frame */

    getContentPane().setLayout(new GridBagLayout());/* Set the layout to gridbaglayout */

    addWindowListener(new WindowAdapter() {
        @Override
        public void windowClosing(WindowEvent e) {
            dispose();
        }
    });

    pack();
    setVisible(false); /* Don't display it on default */

}

@Override
public void update(Observable arg0, Object arg1) {
    $question = (Question) arg1;

    /* Now we need to display this frame */
    if(((QuizModel) getModel()).getDisplayScoreView()){
        $teamPressedLabel.setText("Team " + Integer.toString(((QuizModel) getModel()).getTeamPressed()) + " is ready to answer!"); /* TODO messagebundle */
        $teamPressedLabel.setFont(new Font("Serif", Font.PLAIN, 34)); /* Change the font */
        displayScoreView();
        setVisible(true); /* Now display the JFrame */
    }
}


private void displayScoreView() {
    Multipleanswer multipleanswerQuestion; /* a multiple answer question to display the multipleanswer questions */
    Multiplechoice multiplechoiceQuestion; /* a multiple choice question to display the multiplechoice questions */
    ArrayList<String> answers = null;

    GridBagConstraints c = new GridBagConstraints();

    /* Set the position of the JFrame so it centered */
    Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();

    // Determine the new location of the window
    int w = getSize().width;
    int h = getSize().height;
    int x = (dim.width - w) / 2;
    int y = (dim.height - h) / 2;
    int i = 0;

    // Move the window
    setLocation(x - 150, y - 150);

    /* Set size */
    setSize(550, 300);


    /* If the question isn't empty */
    if (!($question == null)) {
        c.anchor = GridBagConstraints.NORTHWEST; 

        $questionLabel.setText($question.getQuestion()); /* Set the text of the JLabel to the question itself */
        $questionLabel.setFont(new Font("Serif", Font.PLAIN, 26)); /* Change the font */

        getContentPane().add($teamPressedLabel,c); /* Add the label to the JFrame, the team that has pressed it button */

        c.weighty = 1.0;

        /* Display the question under the team pressed text */
        c.gridx = 0;
        c.gridy = 1;

        getContentPane().add($questionLabel,c); /* Add the label to the JFrame, the question itself */

        /* If the type of the question is multipleanswer */
        if ($question.getType() == QuestionType.MULTIPLEANSWER) {

            /* Cast it to multipleanswer question */
            multipleanswerQuestion = (Multipleanswer) $question;

            /* Get the answers */
            answers = multipleanswerQuestion.getAnswers();
        } else if ($question.getType() == QuestionType.MULTIPLECHOICE) {

            /* Cast it to multiplechoice question */
            multiplechoiceQuestion = (Multiplechoice) $question;

            /* Get the answers */
            answers = multiplechoiceQuestion.getAnswers();
        }

        /* Speed questions don't show answers so we only display answers if it not a speed question */
        if ($question.getType() != QuestionType.SPEED) {
            /* Make a JLabel and JCheckBox for each answer */
            for (i = 0; i < answers.size(); i++) {
                $answerLabels.add(new JLabel(answers.get(i))); /* Make a new JLabel with answer string as text */
                $checkBoxes.add(new JCheckBox()); /* Make a new JCheckBox */

                $answerLabels.get(i).setFont(new Font("Serif", Font.PLAIN, 16));
                c.gridx = 0;
                c.gridy = i + 2;
                getContentPane().add($answerLabels.get(i),c); /* Add the label to the JFrame */
                c.gridx = 1;
                c.gridy = i + 2;
                getContentPane().add($checkBoxes.get(i),c); /* Add the checkbox to the JFrame */

            }
        }

        /* Add actionlisteners to the buttons */
        $resumeButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if($question.getMediaPath() != null && QuizSoftwareModel.$vlcPath != null) /* If there is an audio/video piece */
                    ((GiveScoreController)getController()).resumeMP(); /* Resume the mediaplayer if there is an audio/video piece */

                ((GiveScoreController)getController()).resumeQuestion(); /* Resume the question */
                closeFrame(); /* Close the frame */
            }
        });

        $calculateButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if($question.getMediaPath() != null && QuizSoftwareModel.$vlcPath != null) /* If there is an audio/video piece */
                    ((GiveScoreController)getController()).closeMP(); /* Close the mediaplayer */
                ((GiveScoreController)getController()).nextQuestion(); /* Go on to the next question */
                closeFrame(); /* Close the frame */
            }
        });
        /* Place the buttons */
        c.gridx = 0;
        c.gridy = i + 2;


        getContentPane().add($resumeButton);

        c.gridx = 1;
        c.gridy = i + 2;

        getContentPane().add($calculateButton);
    }
}

Контроллер объекта giveScoreView:

/**
 * This class represents the controller of the GiveScoreView
 * @author Matthias Claes
 *
 */
public class GiveScoreController extends AbstractController {

public GiveScoreController(Observable model) {
    super(model);
}

/**
 * Resume the question so other teams can answer it
 */
public void resumeQuestion() {
    ((QuizModel) getModel()).resumeQuestion(); /* Resume the question */

}

}

Модель викторины:

/**
 * The model of the quiz
 * @author Matthias Claes
 *
 */

public class QuizModel extends Observable {
private ArrayList<Question> $questions;
private ArrayList<Team> $teams;
private Question $currentQuestion;
private MediaPlayer $mp;
private int $i; /* The position in the questions, at which question we are at the moment */
private int $teamPressed; /* The team that pressed their button */
private boolean $running;
private boolean $displayScoreView;
private boolean $maximize; /* A boolean that tells the QuizView if we need to maximize this JFrame again */


/**
 * Constructor for QuizModel
 * @param questions a list of questions
 */
public QuizModel(ArrayList<Question> questions){

    $mp = new MediaPlayer(this, null); /* null -> Give the default controller as parameter */
    this.addObserver($mp);

    $teams = new ArrayList<Team>();
    if (questions != null) {
        $questions = questions;
    }
    else 
        $questions = new ArrayList<Question>();

    $currentQuestion = null;

    $teamPressed = 0; /* Default value*/
    $maximize = false; /* Default value */

    $i = 0;
    $running = false;
    $displayScoreView = false;
}




/**
 * Starts the quiz
 */
public void start() {

    if (initialized()) {// quiz properly initialized
        $running = true;
        nextQuestion();

    }
    else {
        // TODO what happens when the quiz isn't properly initialized yet
    }
}


/**
 * Will create a view where the admin can choose the score if the answer was right, resume if the answer was false.
 * @param i the team that pushed the button 
 */
public void showScoreView(int i) {
    $teamPressed = i;
    setDisplayScoreView(true);

    /* Update the view */
    System.out.println("show score view");
    setChanged();
    notifyObservers($currentQuestion);

}

/**
 * Get the boolean displayScoreView
 * @return returns true of false, depending on the displayScoreView boolean
 */
public boolean getDisplayScoreView(){
    return $displayScoreView;
}

/**
 * Set the $displayScoreView boolean
 * @param displayScoreView the new value of $displayScoreView
 */
public void setDisplayScoreView(boolean displayScoreView){
    $displayScoreView = displayScoreView;
}


/**
 * Goes to the next question in the quiz
 */
public void nextQuestion(){
    if($running == true && $i < $questions.size()){
        $currentQuestion = $questions.get($i);

        $i++;
        /* This question has a mediaPath but the user doesn't have vlc installed or selected the wrong folder, so they can't use this question, so we go to the next one */
        if($currentQuestion.getMediaPath() != null && QuizSoftwareModel.$vlcPath == null){
            JOptionPane.showMessageDialog(null, QuizSoftwareModel.$messages.getString("videoDisplayError"));
            nextQuestion(); /* go the next question */

        }
        /* Display the question */ 
        else{
            System.out.println("Diplay first question");
            setChanged();
            notifyObservers($currentQuestion);
        }

    } /* End of the quiz */
    else{
        /* Show winner TODO */
    }

}


public Question getCurrentQuestion() {
    return $currentQuestion;
}

public int getI() {
    return $i;
}


/**
 * Resume the question
 */
public void resumeQuestion() {
    $teamPressed = 0; /* Reset the team that has pressed so other teams can press aswell */
    $maximize = true; /* Maximize the screen again */
    $displayScoreView = false;

    /* TODO SAVE THE TEAM INTO AN ARRAY SO THIS TEAM CAN'T PRESS AGAIN */
    System.out.println("RESUME");
    this.setChanged();
    this.notifyObservers($currentQuestion);
}


/**
 * Return the $maximize boolean
 * @return returns the $maximize boolean
 */
public boolean getMaximize() {
    return $maximize;
}

}

Результат, который я получаю, выглядит следующим образом:
Первый вопрос
НАЗЫВАЕТСЯ
показать оценку
НАЗЫВАЕТСЯ
ПРОДОЛЖИТЬ
НАЗЫВАЕТСЯ
показать оценку
НАЗЫВАЕТСЯ
ПРОДОЛЖИТЬ
НАЗЫВАЕТСЯ
ПРОДОЛЖИТЬ
НАЗЫВАЕТСЯ

Таким образом, метод возобновления в викторине называется многократным вызовом.

Я не знаю, может ли кто-нибудь помочь мне с этим, но уже спасибо. Мои извинения за очень длинный пост, но я не знаю, как показать это другим вам.

  • 8
    Почему вы используете $ для всех ваших имен переменных? Это действительно плохой стиль Java.
  • 0
    Это похоже на ситуацию, когда отладчик был бы действительно полезен ...
Показать ещё 7 комментариев
Теги:
debugging
keypress
key-events

2 ответа

2
Лучший ответ

Внутри метода displayScoreView() вы добавляете ActionListener к кнопке, на которую ссылается $resumeButton которая в конечном итоге вызовет метод resumeQuestion().

Поскольку displayScoreView() - это метод экземпляра, который может быть вызван несколько раз (и кажется, что он будет), в то время как $resumeButton содержит один и тот же экземпляр кнопки, все время жизни экземпляра GiveScoreView могут быть несколькими слушателями, делающими одно и то же (например, invoke resumeQuestion()), зарегистрированного на этой кнопке.

Обратите внимание: если вам сложно узнать, что делает на самом деле код, вы должны подумать о его очистке...

  • 0
    Большое спасибо, это сработало, я даже не знал, что это возможно: /.
0

Такое поведение, которое вы испытываете, предполагает, что вы добавляете новых слушателей к входам каждый раз, когда программа реагирует. Попробуйте переместить этот кодовый блок:

        /* Add actionlisteners to the buttons */
        $resumeButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if($question.getMediaPath() != null && QuizSoftwareModel.$vlcPath != null) /* If there is an audio/video piece */
                    ((GiveScoreController)getController()).resumeMP(); /* Resume the mediaplayer if there is an audio/video piece */

                ((GiveScoreController)getController()).resumeQuestion(); /* Resume the question */
                closeFrame(); /* Close the frame */
            }
        });

        $calculateButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if($question.getMediaPath() != null && QuizSoftwareModel.$vlcPath != null) /* If there is an audio/video piece */
                    ((GiveScoreController)getController()).closeMP(); /* Close the mediaplayer */
                ((GiveScoreController)getController()).nextQuestion(); /* Go on to the next question */
                closeFrame(); /* Close the frame */
            }
        });

Для внутри конструктора, ниже вашего кода:

    $resumeButton = new JButton("Resume question"); /* TODO messagebundle */
    $calculateButton = new JButton("Calculate score"); /* TODO messagebundle */

Таким образом, вы создаете только один слушатель для каждой кнопки.

  • 1
    Спасибо, сэр, это сработало :)

Ещё вопросы

Сообщество Overcoder
Наверх
Меню