I wrote a program that creates a sudoku board according to the difficulty level chosen by the user. There's a basic GUI using JFrame and JPanel. The board itself is built using a 2D array of JTextFields to allow for editing by the user and I made a table of JButtons representing digits 1-9. I'm trying to make it so when I press a digit button while my cursor is on the relevant text field, it'll input that number to the field. I think there's a problem with how I defined the buttons but would love a hand. By the way, this is my first official stack overflow question so thanks in advance to all who help :)
/*Java Program to solve Sudoku problem using Backtracking*/
import javax.swing.*;
import javax.swing.border.Border;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.*;
public class Solver extends Board {
Solver(int N, int K) {
super(N, K);
}
private static void createWindow() {
JFrame frame = new JFrame("Sudoku");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
createUI(frame);
frame.setSize(250, 80);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private static void createUI(final JFrame frame) {
JPanel panel = new JPanel();
LayoutManager layout = new FlowLayout();
panel.setLayout(layout);
JButton button = new JButton("Play");
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String result = (String) JOptionPane.showInputDialog(
frame,
"Difficulty Glossary:\n\n Hard - 50/81 blank spaces\n Medium - 35/81 blank spaces\n Easy - 20/81 blank spaces\n\nChoose your desired difficulty:\n\tHard: 1\n\tMedium: 2\n\tEasy: 3\nIf your input doesn't match one of these digits, the board generated will be on easy mode.",
"Difficulty Glossary",
JOptionPane.PLAIN_MESSAGE,
null,
null,
"3"
);
optionBoard();
play(Integer.parseInt(result));
}
});
panel.add(button);
frame.getContentPane().add(panel, BorderLayout.CENTER);
}
public static void optionBoard(){
}
public static void play(int level) {
int N = 9, K = 0;
switch (level) {
case 1:
K = 50;
break;
case 2:
K = 35;
break;
default:
K = 20;
break;
}
Solver sudoku = new Solver(N, K);
sudoku.fillValues();
createBoard(sudoku.puzzle);
}
public static void createBoard(int[][] puzzle) {
final Border fieldBorder = BorderFactory.createLineBorder(Color.BLACK);
final JPanel grid = new JPanel(new GridLayout(9, 9));
for (int i = 0; i < 9; i ) {
for (int j = 0; j < 9; j ) {
final JTextField field = new JTextField(2);
if (puzzle[i][j] != 0) {
field.setText(puzzle[i][j] "");
} else {
field.setText("");
}
field.setHorizontalAlignment(JTextField.CENTER); //Center text horizontally in the text field.
field.setBorder(fieldBorder); //Add the colored border.
grid.add(field);
}
}
final JPanel digits = new JPanel(new GridLayout(3, 3));
int num=1;
for (int i = 1; i < 4; i ) {
for (int j = 1; j < 4; j ) {
final JButton digit = new JButton(num "");
num ;
digits.add(digit);
}
}
final JPanel centeredGrid = new JPanel(new GridBagLayout());
centeredGrid.add(digits);
centeredGrid.add(grid);
final JFrame frame = new JFrame("Sudoku Board");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(centeredGrid);
frame.setSize(400,400);
frame.setVisible(true);
JButton button = new JButton("Check");
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
}
});
// centeredGrid.add(button);
}
// Driver code
public static void main(String[] args) {
createWindow();
}
}
I've only provided the relevant GUI class since the mathematical logic part of building the board is solid and works fine. That's what the Board class is.
CodePudding user response:
It sounds like a great project. May I suggest you choose to go with either the JTextField, or JComboBox, to make it easier for you. If you want to use JTextField, you will need to use a parameter check to verify that a number between 1 and 9 was entered. Whereas a JComboBox provides the acceptable values. This way you aren't using one UI to edit another UI before processing the input.
CodePudding user response:
One way to do this could be to:
- Put the JTextFields in an
ArrayList<JTextField>
so that you can get them later as you will likely need access to them during the game - Give the class a field,
private JTextField selectedField = null;
, set initially tonull
. This will point to the last JTextField that has been clicked (the last one to gain focus) - Give the JTextFields FocusListeners when created. If any field gains focus, set the selectedField to refer to it
- In your number JButton's ActionListener, check that selectedField is not null
- If not null, then set the button's actionCommand (its text) to be the selectedField's text.
Also, if you need more help, consider creating and posting a valid
import java.awt.Color;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
public class Solver2 extends JPanel {
private static final float FONT_SIZE = 24f;
private int gridEdgeSize = 9;
private JTextField[][] fieldGrid = new JTextField[gridEdgeSize][gridEdgeSize];
private List<JTextField> fieldList = new ArrayList<>();
private JTextField selectedField = null;
public Solver2() {
JPanel gridPanel = new JPanel(new GridLayout(gridEdgeSize, gridEdgeSize, 2, 2));
gridPanel.setBorder(BorderFactory.createLineBorder(Color.BLACK, 2));
gridPanel.setBackground(Color.BLACK);
for (int i = 0; i < gridEdgeSize; i ) {
for (int j = 0; j < gridEdgeSize; j ) {
JTextField textField = new JTextField(2);
textField.setHorizontalAlignment(SwingConstants.CENTER);
textField.setFont(textField.getFont().deriveFont(Font.BOLD, FONT_SIZE));
textField.setBorder(null);
textField.addFocusListener(new TextFieldFocusListener());
gridPanel.add(textField);
fieldList.add(textField);
}
}
JPanel numberPanel = new JPanel(new GridLayout(0, 3));
for (int i = 1; i < 10; i ) {
String text = String.valueOf(i);
JButton button = new JButton(text);
button.setFont(button.getFont().deriveFont(Font.BOLD, FONT_SIZE));
button.addActionListener(e -> buttonListener(e));
numberPanel.add(button);
}
setLayout(new GridLayout(1, 2));
add(gridPanel);
add(numberPanel);
}
private void buttonListener(ActionEvent e) {
String text = e.getActionCommand();
if (selectedField != null) {
selectedField.setText(text);
}
}
private class TextFieldFocusListener extends FocusAdapter {
@Override
public void focusGained(FocusEvent e) {
selectedField = (JTextField) e.getComponent();
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
Solver2 mainPanel = new Solver2();
JFrame frame = new JFrame("GUI");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
Side Note
If you want to restrict the JTextField from allowing keyboard or copy/paste entry, then one way could be to use a DocumentFilter added to each JTextField's Document.
You could give the class a private boolean field, say,
public class Solver2 extends JPanel {
// .....
private boolean blockTextInput = true;
and a private nested class that extends DocumentFilter. This class will prevent the document from allowing String inserts, removals or replacements as long as blockTextInput is true:
private class MyDocFilter extends DocumentFilter {
@Override
public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr)
throws BadLocationException {
if (blockTextInput) {
return;
}
super.insertString(fb, offset, string, attr);
}
@Override
public void remove(FilterBypass fb, int offset, int length) throws BadLocationException {
if (blockTextInput) {
return;
}
super.remove(fb, offset, length);
}
@Override
public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs)
throws BadLocationException {
if (blockTextInput) {
return;
}
super.replace(fb, offset, length, text, attrs);
}
}
then in the JTextField creation loop, set the filter:
for (int i = 0; i < gridEdgeSize; i ) {
for (int j = 0; j < gridEdgeSize; j ) {
JTextField textField = new JTextField(2);
textField.setHorizontalAlignment(SwingConstants.CENTER);
textField.setFont(textField.getFont().deriveFont(Font.BOLD, FONT_SIZE));
textField.setBorder(null);
textField.addFocusListener(new TextFieldFocusListener());
// ***** here ****
((PlainDocument) textField.getDocument()).setDocumentFilter(new MyDocFilter());
gridPanel.add(textField);
fieldList.add(textField);
}
}
Then change the number JButton's ActionListeners to change blockTextInput before and after setting the JTextField's text:
private void buttonListener(ActionEvent e) {
String text = e.getActionCommand();
if (selectedField != null) {
blockTextInput = false;
selectedField.setText(text);
blockTextInput = true;
}
}
Done
The latest iteration of the program that uses a single FocusListener and a DocumentFilter:
import java.awt.Color;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DocumentFilter;
import javax.swing.text.PlainDocument;
@SuppressWarnings("serial")
public class Solver2 extends JPanel {
private static final float FONT_SIZE = 24f;
private int gridEdgeSize = 9;
private List<JTextField> fieldList = new ArrayList<>();
private JTextField selectedField = null;
private boolean blockTextInput = true;
public Solver2() {
JPanel gridPanel = new JPanel(new GridLayout(gridEdgeSize, gridEdgeSize, 2, 2));
gridPanel.setBorder(BorderFactory.createLineBorder(Color.BLACK, 2));
gridPanel.setBackground(Color.BLACK);
FocusListener textFieldFocusListenr = new TextFieldFocusListener();
for (int i = 0; i < gridEdgeSize; i ) {
for (int j = 0; j < gridEdgeSize; j ) {
JTextField textField = new JTextField(2);
textField.setHorizontalAlignment(SwingConstants.CENTER);
textField.setFont(textField.getFont().deriveFont(Font.BOLD, FONT_SIZE));
textField.setBorder(null);
textField.addFocusListener(textFieldFocusListenr);
((PlainDocument) textField.getDocument()).setDocumentFilter(new MyDocFilter());
gridPanel.add(textField);
fieldList.add(textField);
}
}
JPanel numberPanel = new JPanel(new GridLayout(0, 3));
for (int i = 1; i < 10; i ) {
String text = String.valueOf(i);
JButton button = new JButton(text);
button.setFont(button.getFont().deriveFont(Font.BOLD, FONT_SIZE));
button.addActionListener(e -> buttonListener(e));
numberPanel.add(button);
}
setLayout(new GridLayout(1, 2));
add(gridPanel);
add(numberPanel);
}
private void buttonListener(ActionEvent e) {
String text = e.getActionCommand();
if (selectedField != null) {
blockTextInput = false;
selectedField.setText(text);
blockTextInput = true;
}
private class TextFieldFocusListener extends FocusAdapter {
@Override
public void focusGained(FocusEvent e) {
selectedField = (JTextField) e.getComponent();
}
}
private class MyDocFilter extends DocumentFilter {
@Override
public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr)
throws BadLocationException {
if (blockTextInput) {
return;
}
super.insertString(fb, offset, string, attr);
}
@Override
public void remove(FilterBypass fb, int offset, int length) throws BadLocationException {
if (blockTextInput) {
return;
}
super.remove(fb, offset, length);
}
@Override
public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs)
throws BadLocationException {
if (blockTextInput) {
return;
}
super.replace(fb, offset, length, text, attrs);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
Solver2 mainPanel = new Solver2();
JFrame frame = new JFrame("GUI");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}