如何在递归方法中同步 Swing

How to synchronize Swing inside a recursive method

我尝试编写一个数独求解器,它通过回溯法求解数独。这工作得很好,所以我尝试编写一个可以工作的 GUI,但是创建边框很困难而且真的很长,也许你对如何做到这一点有更好的想法。现在我希望您可以看到回溯算法在工作,但我不知道如何同步 Swing。

我认为 SwingWorker 不能在这里工作,因为它必须调用自己。同步块不起作用。

我的代码:

package games.sudoku;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;

public class MyFrame {
    private JFrame f = new JFrame("Sudoku Solver");
    private JPanel mainPanel = new JPanel();
    private JPanel sudokuPanel = new JPanel();
    private JPanel buttonPanel = new JPanel();
    private JTextField [][] cells = new JTextField[9][9];
    private JTextField progressBar = new JTextField();
    private JButton solve = new JButton("Solve!");
    private JButton reset = new JButton("Reset");
    private int[][] intCells = new int[9][9];
    private int x = 0;
    private int y = 0;
    private Font sudokuFont = new Font("SansSerief", Font.BOLD, 20);
    private ActionListener al;
    private PropertyChangeSupport pcs = new PropertyChangeSupport(intCells);

    public MyFrame() {
        f.setSize(400, 400);
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.add(init());
        f.setResizable(false);
        f.setVisible(true);
    }

    /**
     * create and initialized mainPanel
     * 
     * @return completly initialized mainPanel
     */
    private JPanel init(){
        listeners();
        solve.addActionListener(al);
        progressBar.setEditable(false);

        // Init the panel where the sudoku is displayed
        sudokuPanel.setLayout(new GridLayout(9, 9));
        for(x = 0; x < cells.length; x++){
            for(y = 0; y < cells[x].length; y++){
                cells[x][y] = new JTextField();     
                cells[x][y].setFont(sudokuFont);
                cells[x][y].setHorizontalAlignment(JTextField.CENTER);
                //TODO find a better way
                //Create borders to simulate a sudoku's layout
                if(bordered(x, 0)){ // Top total
                    if(bordered(y, 0)){ //Left
                        cells[x][y].setBorder(BorderFactory.createMatteBorder(4, 4, 1, 1, Color.BLACK));
                    }else if(bordered(y, 1, 4, 7)){ //Mid
                        cells[x][y].setBorder(BorderFactory.createMatteBorder(4, 1, 1, 1, Color.BLACK));
                    }else if(bordered(y, 2, 5)){ //Right-Half
                        cells[x][y].setBorder(BorderFactory.createMatteBorder(4, 1, 1, 2, Color.BLACK));
                    }else if(bordered(y, 3, 6)){ //Left-Half
                        cells[x][y].setBorder(BorderFactory.createMatteBorder(4, 2, 1, 1, Color.BLACK));
                    }else{ //Right
                        cells[x][y].setBorder(BorderFactory.createMatteBorder(4, 1, 1, 4, Color.BLACK));
                    }                   
                }else if(bordered(x, 3, 6)){ //top of box
                    if(bordered(y, 0)){ //Left
                        cells[x][y].setBorder(BorderFactory.createMatteBorder(2, 4, 1, 1, Color.BLACK));
                    }else if(bordered(y, 1, 4, 7)){ //Mid
                        cells[x][y].setBorder(BorderFactory.createMatteBorder(2, 1, 1, 1, Color.BLACK));
                    }else if(bordered(y, 2, 5)){ //Right-Half
                        cells[x][y].setBorder(BorderFactory.createMatteBorder(2, 1, 1, 2, Color.BLACK));
                    }else if(bordered(y, 3, 6)){ //Left-Half
                        cells[x][y].setBorder(BorderFactory.createMatteBorder(2, 2, 1, 1, Color.BLACK));
                    }else{ //Right
                        cells[x][y].setBorder(BorderFactory.createMatteBorder(2, 1, 1, 4, Color.BLACK));
                    }   
                }else if(bordered(x, 1, 4, 7)){ //Mid
                    if(bordered(y, 0)){ //Left
                        cells[x][y].setBorder(BorderFactory.createMatteBorder(1, 4, 1, 1, Color.BLACK));
                    }else if(bordered(y, 1, 4, 7)){ //Mid
                        cells[x][y].setBorder(BorderFactory.createMatteBorder(1, 1, 1, 1, Color.BLACK));
                    }else if(bordered(y, 2, 5)){ //Right-Half
                        cells[x][y].setBorder(BorderFactory.createMatteBorder(1, 1, 1, 2, Color.BLACK));
                    }else if(bordered(y, 3, 6)){ //Left-Half
                        cells[x][y].setBorder(BorderFactory.createMatteBorder(1, 2, 1, 1, Color.BLACK));
                    }else{ //Right
                        cells[x][y].setBorder(BorderFactory.createMatteBorder(1, 1, 1, 4, Color.BLACK));
                    }   
                }else if(bordered(x, 2, 5)){ // Bottom of box
                    if(bordered(y, 0)){ //Left
                        cells[x][y].setBorder(BorderFactory.createMatteBorder(1, 4, 2, 1, Color.BLACK));
                    }else if(bordered(y, 1, 4, 7)){ //Mid
                        cells[x][y].setBorder(BorderFactory.createMatteBorder(1, 1, 2, 1, Color.BLACK));
                    }else if(bordered(y, 2, 5)){ //Right-Half
                        cells[x][y].setBorder(BorderFactory.createMatteBorder(1, 1, 2, 2, Color.BLACK));
                    }else if(bordered(y, 3, 6)){ //Left-Half
                        cells[x][y].setBorder(BorderFactory.createMatteBorder(1, 2, 2, 1, Color.BLACK));
                    }else{ //Right
                        cells[x][y].setBorder(BorderFactory.createMatteBorder(1, 1, 2, 4, Color.BLACK));
                    }   
                }else if(bordered(x, 8)){ // Bottom overall
                    if(bordered(y, 0)){ //Left
                        cells[x][y].setBorder(BorderFactory.createMatteBorder(1, 4, 4, 1, Color.BLACK));
                    }else if(bordered(y, 1, 4, 7)){ //Mid
                        cells[x][y].setBorder(BorderFactory.createMatteBorder(1, 1, 4, 1, Color.BLACK));
                    }else if(bordered(y, 2, 5)){ //Right-Half
                        cells[x][y].setBorder(BorderFactory.createMatteBorder(1, 1, 4, 2, Color.BLACK));
                    }else if(bordered(y, 3, 6)){ //Left-Half
                        cells[x][y].setBorder(BorderFactory.createMatteBorder(1, 2, 4, 1, Color.BLACK));
                    }else{ //Right
                        cells[x][y].setBorder(BorderFactory.createMatteBorder(1, 1, 4, 4, Color.BLACK));
                    }
                }                   
                sudokuPanel.add(cells[x][y]);
            }
        }

        buttonPanel.setLayout(new FlowLayout());
        buttonPanel.add(solve);
        buttonPanel.add(reset);

        mainPanel.setLayout(new BorderLayout());
        mainPanel.add(sudokuPanel, BorderLayout.CENTER);
        mainPanel.add(buttonPanel, BorderLayout.SOUTH);
        mainPanel.add(progressBar, BorderLayout.NORTH);
        return mainPanel;
    }

    private void listeners(){
        al = new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                if(e.getSource().equals(solve)){
                    for(int i = 0; i < cells.length; i++){
                        for(int j = 0; j < cells[i].length; j++){
                            if(isInt(cells[i][j].getText())){
                                intCells[i][j] = new Integer(cells[i][j].getText());
                            }else if(cells[i][j].getText().equals("")){
                                intCells[i][j] = 0;
                                cells[i][j].setForeground(Color.ORANGE);
                            }else{
                                setProgressBarText(true, "Error: Input has to be a digit between 1 and 9!");
                            }
                        }
                    }
                    pcs.firePropertyChange("input", null, null);                    
                }else{
                    //TODO al for reset-button
                }

            }
        };
    }

    /**
     * method to stint y == 0 || y == 1 ...
     * 
     * @param var x or y
     * @param args specified numbers to check
     * @return true or false if var == args
     */
    private boolean bordered(int var, int... args){
        for(int i = 0; i < args.length; i++){
            if(var == args[i]){
                return true;
            }
        }
        return false;
    }

    /**
     * check wether a string can be parsed to int
     * 
     * @param s input String which should be checked
     * @return false if s cannot be parsed to int or 0 < x < 10
     *         to check if the int can be part of the sudoku (1-9 only)
     */
    private boolean isInt(String s){
        try{
            int i = Integer.parseInt(s);
            if(i < 0 || i > 10){
                return false;
            }
        }catch(Exception e){
            return false;
        }
        return true;
    }

    /**
     * set text of the progrss bar in top of the frame
     * 
     * @param error if error --> text displayed in red, otherwise in black
     * @param text text to be displayed
     */
    //TODO synchronize
    public void setProgressBarText(boolean error, String text){
        progressBar.setText(text);
        if(error){
            progressBar.setForeground(Color.RED);
        }else{
            progressBar.setForeground(Color.BLACK);
        }
    }

    /**
     * Adds a PropertyChangeLsitener to our PropertyChangeSupport
     * 
     * @param listener PropertyChangeListener to be added to PropertyChangeSupport
     */
    public void addPropertyChangeListener(PropertyChangeListener listener) {
        pcs.addPropertyChangeListener(listener);
    }

    /**
     * display the sudoku cells
     * 
     * @param cells two-dim int-array containing the number to be displayed
     *        if "0", nothing ("") is displayed
     */
    //TODO synchronize
    public void displaySudoku(int[][] cells){
        for(int i = 0; i < cells.length; i++){
            for(int j = 0; j < cells[i].length; j++){
                if(cells[i][j] != 0){
                    this.cells[i][j].setText(Integer.toString(cells[i][j]));
                }else{
                    this.cells[i][j].setText("");
                }
            }
        }
    }

    /**
     * number of tries, will be displayed in progress bar
     * 
     * @param n ammount of tries
     */
    public void setAmmountOfTries(int n){
        setProgressBarText(false, Integer.toString(n) + " tries");
    }


    public int[][] getModel(){
        return intCells;
    }
}

package games.sudoku;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

public class SudokuSolver{

//Sudoku model to be solved
private int[][] model;
private int tries = 1;
private final MyFrame f;

public SudokuSolver() {
    f = new MyFrame();

    f.addPropertyChangeListener(new PropertyChangeListener() {

        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            model = f.getModel();
            //TODO check if model is legally
            //Try to solve
            if(solve(0, 0)){

            }else{
                //Model is not solvable
                f.setProgressBarText(true, "Sudoku cannot be solved!");
            }

        }
    });

}
/**
 * recursive method to solve the sudoku via backtracking
 * 
 * @param row 
 * @param col
 * @return true if a possible solution was found, otherwise false
 */
public boolean solve(int row, int col){
    //Update GUI
    f.displaySudoku(model);
    f.setAmmountOfTries(tries);

    tries++;

    //Change column if all rows of it are filled
    if(row == 9){
        row = 0;            
        if(++col == 9){
            return true;
        }
    }

    //Skip non-empty cell
    if(model[row][col] != 0){
        return solve(row+1, col);
    }

    //Solve 
    for(int num = 1; num < 10; num++){
        if(isValid(row, col, num)){
            model[row][col] = num;
            if(solve(row+1, col)){
                return true;
            }
        }
    }

    //Reset 
    model[row][col] = 0;
    return false;

}

/**
 * check if a number can be filled in in position [row][col]
 * 
 * @param row
 * @param col
 * @param num number to be filled in
 * @return true if number is legal, otherwise false
 */

private boolean isValid(int row, int col, int num){
    //Check row and column
    for(int i = 0; i < 9; i++){
        if(num == model[row][i] || num == model[i][col]){
            return false;
        }
    }
    //Check box
    row = (row / 3) * 3 ;
    col = (col / 3) * 3 ;
    for(int r = 0; r < 3; r++){
        for(int c = 0; c < 3; c++){
            if(model[row+r][col+c] == num){
                return false;
            }
        }
    }
    return true;
}
}

您似乎正在对 Swing 事件线程进行长时间的 运行ning 递归调用,这将导致您的 GUI 冻结。在这种情况下的解决方案是做你似乎主动避免做的事情:使用 SwingWorker 创建一个后台线程和 运行 这个后台线程中的递归代码。同步无济于事,所以不要在那棵树上吠叫。您声明不能以递归方式调用 SwingWorker,但您不必这样做;相反,您可以从 SwingWorker 内部开始递归调用,然后通过 done 方法将其生成的数据输出到 GUI 以 return 最终结果或通过 publish/process 方法对显示临时输出而 SwingWorker 是 运行ning.

有关此的更多信息,请查看 Lesson: Concurrency in Swing

至于边框,我会在 3 x 3 GridLayout 中嵌套 9 个 JPanel,在小 JPanel 内部使用细线边框,在较小 JPanel 外部使用粗线边框。

例如(仅布局和边框):

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.GridLayout;
import javax.swing.*;

public class SimpleSudokuPanel extends JPanel {
    private static final int PANEL_THICKNESS = 2;
    private static final int TF_THICKNESS = 1;
    private static final int TF_COLS = 2;
    private static final float TF_PTS = 36f;
    private JTextField[][] grid = new JTextField[9][9];

    public SimpleSudokuPanel() {
        JPanel mainPanel = new JPanel(new GridLayout(3, 3));
        JPanel[] innerPanels = new JPanel[9];
        for (int i = 0; i < innerPanels.length; i++) {
            innerPanels[i] = new JPanel(new GridLayout(3, 3));
            innerPanels[i].setBorder(BorderFactory.createLineBorder(Color.black, PANEL_THICKNESS));
            mainPanel.add(innerPanels[i]);
        }

        for (int i = 0; i < grid.length; i++) {
            for (int j = 0; j < grid[i].length; j++) {
                grid[i][j] = new JTextField(TF_COLS);
                grid[i][j].setFont(grid[i][j].getFont().deriveFont(Font.BOLD, TF_PTS));
                grid[i][j].setBorder(BorderFactory.createLineBorder(Color.black, TF_THICKNESS));
                grid[i][j].setHorizontalAlignment(JTextField.CENTER);
                int panelIndex = 3 * (i / 3) + j / 3;
                innerPanels[panelIndex].add(grid[i][j]);
            }
        }

        setBorder(BorderFactory.createLineBorder(Color.black, PANEL_THICKNESS));
        setLayout(new BorderLayout());
        add(mainPanel, BorderLayout.CENTER);
    }

    private static void createAndShowGui() {
        JFrame frame = new JFrame("SimpleSudokuPanel");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(new SimpleSudokuPanel());
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> createAndShowGui());
    }
}