'How to create a pagination in Java swing?
Hello I am trying to create an examination system in Java swing and I am trying to change from question to question on the same frame once a user provides a good answer and clicks on a Next button.
Here is an example of what I am trying to do:
public static int i = 1;
btnEnter.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if(e.getSource().equals(btnEnter)) {
frame.setVisible(false);
JFrame exam = new JFrame("Exam has started.");
exam.setVisible(true);
exam.setLayout(null);
exam.setExtendedState(JFrame.MAXIMIZED_BOTH);
try {
String query = "SELECT * FROM questions WHERE QuestionId = " + i;
PreparedStatement pstmt = ConnectDb.getConnection().prepareStatement(query);
ResultSet rslt = pstmt.executeQuery();
if (rslt.next()) {
// Question label
JLabel Questionlbl = new JLabel();
Questionlbl.setForeground(Color.BLACK);
Questionlbl.setFont(new Font("Arial", Font.PLAIN, 25));
Questionlbl.setBounds(40, 90, 900, 95);
// some more components here
Questionlbl.setText("Question " + i + " " + rslt.getString(2));
btnNext.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if(e.getSource().equals(btnNext)) {
//Verify answer, increase counter and update query here...
SwingUtilities.updateComponentTreeUI(exam);
exam.invalidate();
exam.validate();
exam.repaint();
}
}
});
}
catch (Exception ex) {
JOptionPane.showMessageDialog(null,ex,"Error",JOptionPane.ERROR_MESSAGE);
exam.setVisible(true);
}
});
Technically, I had a main frame with a button (e.g btnEnter) which I could click to access another frame containing MCQ questions retrieved from a database. Now, once I click on the Next button (e.g btnNext) I guess it should be able to verify the answer (based on a radio button selected by the user) and at the same time increase the counter or index everytime a good answer is provided by the user and clicks on the button on the frame.
The problem is when I click on the Next button it only verifies the first answer of the first question and it does not display the next question with its options (radiobuttons) on the same Jframe (e.g exam). To sum up, I cannot increase the counter from the next button to update the SQL query so that it points to the next record.
I previously tried to use loops but in vain. I guess maybe I've been doing it wrong.
Does anyone knows how to exactly do this?
Solution 1:[1]
Let's try and decouple the code and make use of the Single Responsibility Principle and make use things like delegation and dependency injection.
First, we're going to need some kind of "container" class which manages a single question and answer, for example...
public interface QuestionAnswer {
public String getQuestion();
public String[] getPrompts();
public void setUserAnswer(int answer);
public int getUserAnswer();
public boolean isAnsweredCorrectly();
}
nb: I'm assuming a multiple choice style question/answer workflow
Now, we need some kind class which acts as our data source, through which we can load questions
public interface QuestionAnswerDataSource {
public int getCurrentQuestion();
public int getQuestionCount() throws QuestionAnswerDataSourceException;
public boolean hasMoreQuestions();
public QuestionAnswer getNextQuestion() throws QuestionAnswerDataSourceException;
}
Why make use of interface like this? You'll see why in a minute, but first, lets create a SQL database based implementation of the data source
public class DefaultQuestionAnswer implements QuestionAnswer {
private String question;
private String[] prompts;
private int answer;
private int userAnswer;
public DefaultQuestionAnswer(String question, String[] prompts, int answer) {
this.question = question;
this.prompts = prompts;
this.answer = answer;
}
@Override
public String getQuestion() {
return question;
}
@Override
public String[] getPrompts() {
return prompts;
}
@Override
public void setUserAnswer(int answer) {
if (answer >= 0 && answer < getPrompts().length) {
userAnswer = answer;
}
}
public int getUserAnswer() {
return userAnswer;
}
protected int getAnswer() {
return answer;
}
@Override
public boolean isAnsweredCorrectly() {
return getUserAnswer() == getAnswer();
}
}
public class SQLQustionAnswerDataSource implements QuestionAnswerDataSource {
private int currentQuestion = 0;
private int questionCount;
private Connection connection;
public SQLQustionAnswerDataSource(Connection connection) throws QuestionAnswerDataSourceException {
this.connection = connection;
questionCount = getQuestionCount();
}
protected Connection getConnection() {
return connection;
}
public boolean hasMoreQuestions() {
return currentQuestion + 1 < questionCount;
}
@Override
public int getCurrentQuestion() {
return currentQuestion;
}
@Override
public int getQuestionCount() throws QuestionAnswerDataSourceException {
String query = "SELECT count(*) FROM questions";
int count = 0;
try (PreparedStatement pstmt = getConnection().prepareStatement(query); ResultSet rslt = pstmt.executeQuery()) {
if (rslt.next()) {
count = rslt.getInt(1);
}
} catch (SQLException ex) {
throw new QuestionAnswerDataSourceException(ex);
}
return count;
}
public QuestionAnswer getNextQuestion() throws QuestionAnswerDataSourceException {
if (!hasMoreQuestions()) {
throw new QuestionAnswerDataSourceException("No more questions");
}
currentQuestion++;
String query = "SELECT * FROM questions WHERE QuestionId = ?";
QuestionAnswer qa = null;
try (PreparedStatement pstmt = getConnection().prepareStatement(query)) {
pstmt.setInt(1, getCurrentQuestion());
try (ResultSet rslt = pstmt.executeQuery()) {
if (rslt.next()) {
// Load the prompts which are presented the user ... ie
// multiple choices
String prompts[] = new String[0];
// Load the index of the correct answer
int correctAnswer = 0;
qa = new DefaultQuestionAnswer(rslt.getString(2), prompts, correctAnswer);
} else {
throw new QuestionAnswerDataSourceException("Question not found");
}
}
} catch (SQLException ex) {
throw new QuestionAnswerDataSourceException(ex);
}
return qa;
}
}
nb: I don't have a SQL database to test against, so you'll have to fill out the core details
The above demonstrations a basic concept of a question/answer data source which is backed by a SQL database.
The good thing about this is, anywhere QuestionAnswerDataSource is expected, we can make use of it, for example, through the UI...
public class QuestionAnswerPane extends JPanel {
private QuestionAnswerDataSource dataSource;
private QuestionAnswer questionAnswer;
private JLabel questionNumberLabel;
private JLabel questionLabel;
private JPanel promptsPane;
private JLabel scoreLabel;
private JButton nextButton;
private int correctAnswers = 0;
public QuestionAnswerPane(QuestionAnswerDataSource dataSource) {
this.dataSource = dataSource;
setLayout(new GridBagLayout());
questionNumberLabel = new JLabel("Question #");
questionLabel = new JLabel("...");
scoreLabel = new JLabel("0");
nextButton = new JButton("Next >");
promptsPane = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.anchor = GridBagConstraints.LINE_START;
gbc.weightx = 1;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.insets = new Insets(4, 4, 4, 4);
add(questionNumberLabel, gbc);
gbc.gridy++;
add(questionLabel, gbc);
gbc.gridy++;
gbc.fill = GridBagConstraints.BOTH;
gbc.weighty = 1;
add(promptsPane, gbc);
gbc.gridy++;
gbc.fill = GridBagConstraints.NONE;
gbc.weighty = 0;
gbc.anchor = GridBagConstraints.LINE_END;
add(nextButton, gbc);
gbc.gridy++;
add(scoreLabel, gbc);
nextQuestion();
nextButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
nextQuestion();
}
});
}
public QuestionAnswerDataSource getDataSource() {
return dataSource;
}
protected void nextQuestion() {
QuestionAnswer currentQA = getCurrentQuestionAnswer();
if (currentQA != null && currentQA.isAnsweredCorrectly()) {
correctAnswers++;
scoreLabel.setText(Integer.toString(correctAnswers));
}
if (getDataSource().hasMoreQuestions()) {
try {
currentQA = getDataSource().getNextQuestion();
questionNumberLabel.setText(Integer.toString(getDataSource().getCurrentQuestion()) + "/" + Integer.toString(getDataSource().getQuestionCount()));
setQuestionAnswer(currentQA);
} catch (QuestionAnswerDataSourceException ex) {
ex.printStackTrace();
JOptionPane.showMessageDialog(this, "Could not get next question");
setQuestionAnswer(null);
}
} else {
setQuestionAnswer(null);
}
nextButton.setEnabled(false);
}
protected QuestionAnswer getCurrentQuestionAnswer() {
return questionAnswer;
}
public void setQuestionAnswer(QuestionAnswer questionAnswer) {
this.questionAnswer = questionAnswer;
updateQAState();
}
protected void updateQAState() {
QuestionAnswer currentQuestionAnswer = getCurrentQuestionAnswer();
QuestionAnswerDataSource dateSource = getDataSource();
promptsPane.removeAll();
if (currentQuestionAnswer == null) {
questionNumberLabel.setText(null);
questionLabel.setText(null);
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
promptsPane.add(new JLabel("The quiz is over"), gbc);
String message = "You scored " + Integer.toString(correctAnswers);
try {
message += " out of " + Integer.toString(dateSource.getQuestionCount());
} catch (QuestionAnswerDataSourceException exp) {
exp.printStackTrace();
}
promptsPane.add(new JLabel(message), gbc);
} else {
questionNumberLabel.setText("Question " + (dateSource.getCurrentQuestion() + 1));
questionLabel.setText(currentQuestionAnswer.getQuestion());
ActionListener listener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() instanceof JRadioButton) {
JRadioButton btn = (JRadioButton) e.getSource();
Object indexValue = btn.getClientProperty("PromptIndex");
if (indexValue instanceof Integer) {
int index = (int) indexValue;
getCurrentQuestionAnswer().setUserAnswer(index);
nextButton.setEnabled(true);
}
}
}
};
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.weightx = 1;
gbc.fill = gbc.HORIZONTAL;
ButtonGroup bg = new ButtonGroup();
String[] prompts = questionAnswer.getPrompts();
for (int index = 0; index < prompts.length; index++) {
JRadioButton btn = new JRadioButton(prompts[index]);
btn.putClientProperty("PromptIndex", index);
btn.addActionListener(listener);
promptsPane.add(btn, gbc);
}
}
}
}
The core functionality revolves around the nextQuestion and updateUIState methods. These are responsible for loading the next question, if one exists and updating the UI correspondingly.
Now, as I said, I don't have a SQL database to test against, so, instead, I created my own "test" datasource...
public class TestQuestionAnswerDataSource implements QuestionAnswerDataSource {
private int currentQuestion = -1;
private List<QuestionAnswer> questionAnswers;
public TestQuestionAnswerDataSource() {
questionAnswers = new ArrayList<>(8);
questionAnswers.add(new DefaultQuestionAnswer("What is always coming, but never arrives?", new String[]{"Post", "Tomorrow", "Political promises", "Public transport"}, 1));
questionAnswers.add(new DefaultQuestionAnswer("What can be broken, but is never held?", new String[]{"Light speed", "Your back", "Fingers", "A promise"}, 3));
questionAnswers.add(new DefaultQuestionAnswer("If a plane crashes on the border between the United States and Canada, where do they bury the survivors?", new String[]{"United States", "Canada", "Under the rug", "Survivors are not buried"}, 3));
questionAnswers.add(new DefaultQuestionAnswer("Is this a question?", new String[]{"Yes", "No", "Maybe", "Only if this is an answer"}, 3));
}
@Override
public int getCurrentQuestion() {
return currentQuestion;
}
@Override
public int getQuestionCount() throws QuestionAnswerDataSourceException {
return questionAnswers.size();
}
@Override
public boolean hasMoreQuestions() {
return currentQuestion + 1 < questionAnswers.size();
}
@Override
public QuestionAnswer getNextQuestion() throws QuestionAnswerDataSourceException {
if (!hasMoreQuestions()) {
throw new QuestionAnswerDataSourceException("No more questions");
}
currentQuestion++;
return questionAnswers.get(currentQuestion);
}
}
This can then be plugged into the UI and allows for a controlled testing workflow...
QuestionAnswerDataSource dataSource = new TestQuestionAnswerDataSource();
JFrame frame = new JFrame();
frame.add(new QuestionAnswerPane(dataSource));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
Now, when you're ready, you can replace TestQuestionAnswerDataSource with SQLQustionAnswerDataSource and the UI should continue to operate without (?) issue
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | MadProgrammer |
