为数独求解器构建GUI界面(附带ASCII示例)

16

.

概述、示例

大家好,

我创建了一个基本的数独求解器,可以相对快速地解决大多数问题。我还有很多工作要做,以使其能够解决最难的问题,但我想先尝试实现一个基本的JFrame GUI。

我以前使用过互联网小程序,但从未使用过JFrames。

我想创建类似下面图片的东西(首先):

-------------------------------------------------------------------------------------------------
! Sudoku Solver 1.0                                                                      - [] X !
-------------------------------------------------------------------------------------------------
!  _____________ _____________ _____________         _____________ _____________ _____________  !
! |  _   _   _  |  _   _   _  |  _   _   _  |       |  _   _   _  |  _   _   _  |  _   _   _  | !
! | !5! !_! !_! | !_! !_! !_! | !6! !_! !1! |       | !5! !7! !2! | !4! !9! !3! | !6! !8! !1! | !
! |  _   _   _  |  _   _   _  |  _   _   _  |       |  _   _   _  |  _   _   _  |  _   _   _  | !
! | !6! !_! !_! | !_! !_! !2! | !4! !_! !_! |       | !6! !1! !3! | !8! !5! !2! | !4! !7! !9! | !
! |  _   _   _  |  _   _   _  |  _   _   _  |       |  _   _   _  |  _   _   _  |  _   _   _  | !
! | !_! !_! !_! | !7! !_! !1! | !_! !_! !2! |       | !8! !4! !9! | !7! !6! !1! | !3! !5! !2! | !
! -_____________-_____________-_____________-       -_____________-_____________-_____________- !
! |  _   _   _  |  _   _   _  |  _   _   _  |       |  _   _   _  |  _   _   _  |  _   _   _  | !
! | !_! !_! !4! | !_! !2! !_! | !_! !3! !_! |       | !1! !6! !4! | !9! !2! !7! | !5! !3! !8! | !
! |  _   _   _  |  _   _   _  |  _   _   _  | .---. |  _   _   _  |  _   _   _  |  _   _   _  | !
! | !_! !3! !_! | !_! !_! !_! | !_! !9! !_! | | > | | !2! !3! !8! | !5! !1! !6! | !7! !9! !4! | !
! |  _   _   _  |  _   _   _  |  _   _   _  | '---' |  _   _   _  |  _   _   _  |  _   _   _  | !
! | !_! !_! !_! | !_! !4! !_! | !_! !_! !_! |       | !7! !9! !5! | !3! !4! !8! | !1! !2! !6! | !
! -_____________-_____________-_____________-       -_____________-_____________-_____________- !
! |  _   _   _  |  _   _   _  |  _   _   _  |       |  _   _   _  |  _   _   _  |  _   _   _  | !
! | !_! !2! !_! | !1! !_! !5! | !9! !_! !_! |       | !4! !2! !7! | !1! !8! !5! | !9! !6! !3! | !
! |  _   _   _  |  _   _   _  |  _   _   _  |       |  _   _   _  |  _   _   _  |  _   _   _  | !
! | !_! !_! !_! | !6! !_! !_! | !_! !_! !5! |       | !3! !8! !1! | !6! !7! !9! | !2! !4! !5! | !
! |  _   _   _  |  _   _   _  |  _   _   _  |       |  _   _   _  |  _   _   _  |  _   _   _  | !
! | !_! !_! !6! | !_! !3! !_! | !_! !_! !7! |       | !9! !5! !6! | !2! !3! !4! | !8! !1! !7! | !
! -_____________-_____________-_____________-       -_____________-_____________-_____________- !
!                                                                                               !
! .-------------------------------------------------------------------------------------------. !
! |                                                                                           | !
! |               Solved Puzzle in 9.096ms      |      Completely Solved: True                | !
! |                                                                                           | !
! '-------------------------------------------------------------------------------------------' !
!                                                                                               !
-------------------------------------------------------------------------------------------------

.

具体要求

: 左边的谜题

  • 9x9个区域应该清晰地定义(中间有线条;分开的框)
  • 文本框只能接受数字/只允许输入一个数字(如果可能的话)

: 右边的谜题

  • 9x9个区域应该清晰地定义(中间有线条;分开的框)
  • 无论框是否可以编辑,只要能显示结果即可

: 中心按钮

  • 应该运行[SudokuPuzzle].solve();

: 底部文本框

  • 不应该是可编辑的

.

我所需要的

从过去的经验来看,这一切都可以在JFrame中完成,但由于我从未建立过自己的JFrame,我不太确定我需要使用哪些组件 (内容项、面板、设置等) 来满足我的要求。我还没有找到一种方法来限制我的文本框只能输入数字,并防止用户一次插入多个值。文本框真的是最好的选择吗,还是我错过了可以更具体地满足我的需求的东西?

我不仅需要知道我需要哪些类,还要知道如何组织它们,使得按钮舒适地位于两个谜题之间,文本框位于下方。从我读到的信息MigLayout似乎是简化这个过程的一个选项。

.

结束语

非常感谢任何帮助的人。如果这个问题的任何部分看起来有点粗鲁或突兀,我道歉。我倾向于在晚上发布我的大多数问题,所以社区有几个小时来考虑所有的回答(还有我大部分时间都在外面做事)。

我还会清醒1-2个小时来回答任何问题。

再次感谢,

Justian


1
令人印象深刻的 ASCII 原型 :-) - Chris
3
赞成这个问题的提问方式...很抱歉我无法为这个主题提供任何帮助,但是您对自己想要实现目标的详细解释仍然受到赞赏! - kander
我从未见过这样的东西... :D 你让我的一天变得更美好了! - Rekin
2
首先,因为它是如此详细和完整的描述。其次,这种讽刺 - 在我的计算机科学学习过程中,我从未遇到过如此好的UI定义。我的大学里的一些博士生可以从中学到很多东西。 :) - Rekin
1
@rekin:我不太确定我是如何/定义了什么,但是非常感谢你的赞美 :) - Justian Meyer
6个回答

6

旧源:http://i38.tinypic.com/5mieqa.png 这应该足够让你开始了。只需添加获取逻辑以提取他们在文本字段中输入的值即可。

主要:

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package sudoku;

import javax.swing.JFrame;
import javax.swing.JPanel;

/**
 *
 * @author nicholasdunn
 */
public class Main {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        // TODO code application logic here
        JFrame frame = new JFrame("");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JPanel panel = new JPanel();

        panel.add(new Board());
        panel.add(new JButton(">"));
        panel.add(new Board());
        frame.add(panel);
        frame.pack();
        frame.setVisible(true);
    }
}

九宫格:
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package sudoku;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Toolkit;
import javax.swing.BorderFactory;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.PlainDocument;

/**
 *
 * @author nicholasdunn
 */
public class NineSquare extends JPanel {

    // What direction in relation to the center square
    private JTextField nw,n,ne,e,se,s,sw,w,c;
    private JTextField[] fields = new JTextField[]{
        nw,n,ne,e,se,s,sw,w,c
    };
    private static final int BORDER_WIDTH = 5;

    public NineSquare(Color bgColor) {
        setLayout(new GridLayout(3,3));
        initGui();
        setBackground(bgColor);
    }

    private void initGui() {
        for (int i = 0; i < fields.length; i++) {
            fields[i] = new JTextField(1);
            fields[i].setDocument(new NumericalDocument());
            add(fields[i]);
        }
        setBorder(BorderFactory.createMatteBorder(BORDER_WIDTH,BORDER_WIDTH,BORDER_WIDTH,BORDER_WIDTH, Color.BLACK));
    }

    public Dimension getPreferredDimension() {
        return new Dimension(100,100);
    }

    public static class NumericalDocument extends PlainDocument {
        String numbers = "0123456789";
        @Override
        public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
            if (getLength() == 0 && str.length() == 1 && numbers.contains(str)) {
                super.insertString(offs, str, a);
            }
            else {
                Toolkit.getDefaultToolkit().beep();
            }
        }
    }
}

板卡:

package sudoku;

import java.awt.Color;
import java.awt.GridLayout;
import javax.swing.JPanel;

/**
 *
 * @author nicholasdunn
 */
public class Board extends JPanel {
    private NineSquare[] gridSquares = new NineSquare[9];
    private Color[] bgs = {Color.blue.brighter(), Color.gray};
    public Board() {
        setLayout(new GridLayout(3,3));
        for (int i = 0; i < gridSquares.length; i++) {
            gridSquares[i] = new NineSquare(bgs[i%2]);
            add(gridSquares[i]);
        }
    }
}

@I82Much:感谢您提供的优秀示例代码。我是否有办法防止在调整窗口大小时网格变形?(我真的希望你是nicholasdunn - 我不支持未经明确提及的搭便车行为)。 - Justian Meyer
2
@I82Much:哇,哇!慢点,不要直接给我答案 :P - 我需要吃点苦头才能学到东西。 - Justian Meyer
@drachenstern:+1 对于互联网跟踪和确认。我一看到他在中间添加了按钮,就知道他是这样做的。看起来他正在使用一个图形用户界面编辑器。 - Justian Meyer
关于变形...你能截个屏吗? - I82Much
如果您从我的源代码中仅学到一点,那就是GridLayout和使用自定义文档,禁止输入非数字字符串到文本字段中。 - I82Much
显示剩余4条评论

4

数独GUI

好的,我忍不住了……这是我的尝试。它全部在一个包中:

  • 符合规范的所有元素的GUI(问题)
  • 灵活的布局
  • 没有外部依赖-使用标准Swing布局
  • 输入验证(仅限数字0-9)
  • 模型视图控制器架构
  • 后台任务运行程序(您的GUI永远不会冻结)
  • 内置一些调试方法(将数独输出为文本)
  • 虚拟实现-模拟长时间运行的计算,显示GUI的响应能力

我尽力让代码尽可能易读。可能有一些不太清晰的部分。可能线程部分不太清晰,但如果有人发现它有用,我很乐意更好地描述它。

所以我的目标是尽可能简单的使用。如果您查看接口,很难破坏这些东西(冻结UI,获取空指针等),作为编写公共API的练习。这可能不是最佳实现,但这是我编写的最好之一。 :)

希望对您有所帮助。

这是它的外观: Running example

(注意:值是随机的)

用法

您所需要做的就是实现接口:

public interface SudokuImplementation {

    void goButtonPressed(Integer[][] leftSudokuValues, SudokuController resultAcceptor);
}

在这个方法中进行所有计算,并使用resultAcceptor.setSudokuResult()存储结果。

以下是如何实际显示GUI:

    SudokuImplementation sudokuImplementation =
        new YourSuperSudoku(); // <- your implementation

    SudokuView sudokuView = new SudokuView();
    sudokuView.setSudokuImplementation(sudokuImplementation);
    sudokuView.setVisible(true);

And that's all!

代码

所有类都在默认包中 - 您可以根据需要进行重构。以下是它们的列表:

  1. SudokuView - 主要GUI
  2. SudokuRun - 示例运行器
  3. SudokuController - 允许以安全方式控制视图
  4. SudokuImplementation - 数独实现的接口
  5. DummySudokuImplementation - 示例实现

1. SudokuView:

import java.awt.*;
import javax.swing.*;
import javax.swing.text.*;
import javax.swing.border.*;
/**
 * View which constructs every component and creates it's own controller.
 */
public class SudokuView extends JFrame {

    SudokuController controller;

    public void setSudokuImplementation(SudokuImplementation listener) {
        controller.setListener(listener);
    }

    /** Creates new form NewJFrame */
    public SudokuView() {
        controller = new SudokuController();
        setTitle("Sudoku Solver 1.0");
        getContentPane().add(createCenterPanel(), BorderLayout.CENTER);
        getContentPane().add(createBottomPanel(), BorderLayout.SOUTH);
        setMinimumSize(new Dimension(600, 300));
        pack();
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    private JPanel createBottomPanel() {
        JPanel bottomPanel = new JPanel(new GridBagLayout());
        JLabel leftLabel = createLabel("left");
        JLabel rightLabel = createLabel("right");

        controller.bindLeftLabel(leftLabel);
        controller.bindRightLabel(rightLabel);

        bottomPanel.add(leftLabel, getWholeCellConstraints());
        bottomPanel.add(new JSeparator(JSeparator.VERTICAL));
        bottomPanel.add(rightLabel, getWholeCellConstraints());

        bottomPanel.setBorder(new BevelBorder(BevelBorder.LOWERED));
        return bottomPanel;
    }

    private JLabel createLabel(String text) {
        JLabel label = new JLabel(text);
        label.setHorizontalAlignment(JLabel.CENTER);
        return label;
    }

    private JPanel createCenterPanel() {
        JPanel centerPanel = new JPanel(new GridBagLayout());
        centerPanel.add(createLeftPanel(), getWholeCellConstraints());
        centerPanel.add(createCenterButton(), getPreferredSizeConstraint());
        centerPanel.add(createRightPanel(), getWholeCellConstraints());
        return centerPanel;
    }

    private GridBagConstraints getPreferredSizeConstraint() {
        // default will do
        return new GridBagConstraints();
    }

    private JButton createCenterButton() {
        JButton goButton = new JButton(">");
        controller.bindCenterButton(goButton);
        return goButton;
    }
    private static final Insets sixPixelInset = new Insets(6, 6, 6, 6);

    private JPanel createRightPanel() {
        JPanel rightPanel = create3x3Panel(6);
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                JPanel panel2 = create3x3Panel(2);
                fillPanelWithNonEditable(panel2, i, j);
                rightPanel.add(panel2);

            }
        }
        rightPanel.setBorder(new EmptyBorder(sixPixelInset));
        return rightPanel;
    }

    private JPanel createLeftPanel() {
        JPanel leftPanel = create3x3Panel(6);
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                JPanel panel2 = create3x3Panel(2);
                fillPanelWithEditable(panel2, i, j);
                leftPanel.add(panel2);

            }
        }
        leftPanel.setBorder(new EmptyBorder(sixPixelInset));
        return leftPanel;
    }

    private GridBagConstraints getWholeCellConstraints() {
        GridBagConstraints wholePanelCnstr = getPreferredSizeConstraint();
        wholePanelCnstr.fill = java.awt.GridBagConstraints.BOTH;
        wholePanelCnstr.weightx = 1.0;
        wholePanelCnstr.weighty = 1.0;
        return wholePanelCnstr;
    }

    private void fillPanelWithEditable(JPanel panel, int majorRow, int majorColumn) {
        for (int minorRow = 0; minorRow < 3; minorRow++) {
            for (int minorColumn = 0; minorColumn < 3; minorColumn++) {
                final JFormattedTextField editableField = createEditableField();
                int column = majorColumn * 3 + minorColumn;
                int row = majorRow * 3 + minorRow;
                controller.bindLeftSudokuCell(row, column, editableField);
                panel.add(editableField);
            }
        }
    }

    private void fillPanelWithNonEditable(JPanel panel, int majorRow, int majorColumn) {
        for (int minorRow = 0; minorRow < 3; minorRow++) {
            for (int minorColumn = 0; minorColumn < 3; minorColumn++) {
                final JFormattedTextField editableField = createNonEditableField();
                int column = majorColumn * 3 + minorColumn;
                int row = majorRow * 3 + minorRow;
                controller.bindRightSudokuCell(row, column, editableField);
                panel.add(editableField);
            }
        }
    }

    private JPanel create3x3Panel(int gap) {
        final GridLayout gridLayout = new GridLayout(3, 3, 1, 1);
        gridLayout.setHgap(gap);
        gridLayout.setVgap(gap);
        JPanel panel = new JPanel(gridLayout);
        return panel;
    }

    private JFormattedTextField createNonEditableField() {
        JFormattedTextField field = createEditableField();
        field.setEditable(false);
        field.setBackground(Color.WHITE); // otherwise non-editable gets gray
        return field;
    }

    private JFormattedTextField createEditableField() {
        JFormattedTextField field = new JFormattedTextField();
        // accept only one digit and nothing else
        try {
            field.setFormatterFactory(new DefaultFormatterFactory(new MaskFormatter("#")));
        } catch (java.text.ParseException ex) {
        }
        field.setPreferredSize(new Dimension(16, 30));
        field.setHorizontalAlignment(javax.swing.JTextField.CENTER);
        field.setText(" ");
        field.setBorder(null);
        return field;
    }
}

2. SudokuRun:

import java.awt.EventQueue;
import javax.swing.UIManager;

public class SudokuRun implements Runnable {

    public void run() {
        // ******************** here You can swap Your true implementation
        SudokuImplementation sudokuImplementation = new DummySudokuImplementation();
        // ***************************** *************** ********* **** ** *


        SudokuView sudokuView = new SudokuView();
        sudokuView.setSudokuImplementation(sudokuImplementation);
        sudokuView.setVisible(true);
    }

    public static void main(String args[]) {
        tryToSetSystemLookAndFeel();
        EventQueue.invokeLater(new SudokuRun());
    }

    private static void tryToSetSystemLookAndFeel() {
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (Exception ex) {
            System.out.println("Couldn't set LAF");
        }
    }
}

3. SudokuController:

import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JButton;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;

public class SudokuController {

    JLabel leftLabel, rightLabel;
    JFormattedTextField[][] leftSudoku, rightSudoku;
    JButton goButton;

    public SudokuController() {
        leftSudoku = new JFormattedTextField[9][9]; // standard sudoku size
        rightSudoku = new JFormattedTextField[9][9];
    }

    void bindLeftLabel(JLabel label) {
        leftLabel = label;
    }

    void bindRightLabel(JLabel label) {
        rightLabel = label;
    }

    void bindLeftSudokuCell(final int row, final int column, JFormattedTextField field) {
        field.addPropertyChangeListener("value", new PropertyChangeListener() {

            // if user edits field than You could do something about it here
            public void propertyChange(PropertyChangeEvent evt) {
                if (evt.getNewValue() != null) {
                    String newValue = (String) evt.getNewValue();
                    userEditedValueAt(row, column, Integer.valueOf(newValue));
                }
            }
        });
        leftSudoku[row][column] = field;
    }

    void userEditedValueAt(int row, int column, int value) {
        System.out.println("Value changed at row:" + row + ", column:" + column + " to " + value);
    }

    void bindRightSudokuCell(int row, int column, JFormattedTextField field) {
        rightSudoku[row][column] = field;
    }

    void spitOutSudokus() {
        System.out.println("Left:");
        System.out.println(getPrettyPrinted(leftSudoku));
        System.out.println("Right:");
        System.out.println(getPrettyPrinted(rightSudoku));
    }

    private String getPrettyPrinted(JFormattedTextField[][] sudoku) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 9; i++) {
            sb.append("|");
            for (int j = 0; j < 9; j++) {
                if (sudoku[i][j] != null) {
                    sb.append(sudoku[i][j].getText());
                } else {
                    sb.append("-");
                }
                sb.append(" ");
            }
            sb.append("|\n");
        }
        return sb.toString();
    }

    void bindCenterButton(JButton goButton) {
        this.goButton = goButton;
        goButton.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                goButtonPressed();
            }
        });
    }
    SudokuImplementation listener;

    public void setListener(SudokuImplementation listener) {
        this.listener = listener;
    }
    Thread backGroundThread;

    private void goButtonPressed() {
        if (listener != null) {
            if (backGroundThread == null || (backGroundThread != null && !backGroundThread.isAlive())) {
                backGroundThread = new Thread() {

                    @Override
                    public void run() {
                        listener.goButtonPressed(getLeftValues(), SudokuController.this);
                    }
                };
                backGroundThread.start();
            }
        }
    }

    private Integer[][] getLeftValues() {
        Integer[][] values = new Integer[9][9];
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                if (!leftSudoku[i][j].getText().equals(" ")) {
                    values[i][j] = Integer.valueOf(leftSudoku[i][j].getText());
                }
            }
        }
        return values;
    }

    public void setSudokuResult(final Integer[][] result) {
        // Any GUI interaction must be done on EDT
        // We don't want to block computation so we choose invokeLater
        // as opposed to invokeAndWait.
        EventQueue.invokeLater(new Runnable() {

            public void run() {
                for (int i = 0; i < 9; i++) {
                    for (int j = 0; j < 9; j++) {
                        rightSudoku[i][j].setValue(String.valueOf(result[i][j]));
                    }
                }
            }
        });
    }

    public void setSudokuTime(final String time) {
        EventQueue.invokeLater(new Runnable() {

            public void run() {
                leftLabel.setText("<html>Running time: <b>" + time);
            }
        });
    }

    public void setSudokuCompleted(final boolean completed) {
        EventQueue.invokeLater(new Runnable() {

            public void run() {

                rightLabel.setText("<html>Completely Solved: <b>" + completed);
                if (completed) {
                    spitOutSudokus();
                }

            }
        });
    }
}

4. 数独实现:

public interface SudokuImplementation {

    void goButtonPressed(Integer[][] leftSudokuValues, SudokuController resultAcceptor);
}

5. DummySudokuImplementation:

import java.util.concurrent.TimeUnit;

/**
 * Simulates Sudoku solver. Demonstrates how to update GUI. The whole
 * implementation is constructed so GUI never freezes.
 */
class DummySudokuImplementation implements SudokuImplementation {

    public DummySudokuImplementation() {
    }

    public void goButtonPressed(Integer[][] leftSudokuValues, SudokuController resultAcceptor) {
        System.out.println("Long running computation simulation...");
        for (int i = 0; i < 50; i++) {
            resultAcceptor.setSudokuCompleted(false);
            resultAcceptor.setSudokuTime(String.valueOf(i * 50) + "ms");
            resultAcceptor.setSudokuResult(getRandomResult());
            waitSomeTime();
        }
        resultAcceptor.setSudokuResult(leftSudokuValues);
        resultAcceptor.setSudokuCompleted(true);
        waitSomeTime();
        System.out.println("Done!");
    }

    private void waitSomeTime() {
        try {
            TimeUnit.MILLISECONDS.sleep(50);
        } catch (InterruptedException ex) {
        }
    }

    private Integer[][] getRandomResult() {
        Integer[][] randomResult = new Integer[9][9];
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                randomResult[i][j] = (int) (Math.random() * 9);
            }
        }
        return randomResult;
    }
}

解释

我并不认为我的做法是最好的。我很想看到其他答案,比如所有视图都使用MigLayout完成。这将非常有教育意义。当Sun的实现只有一个时,我正在学习Swing GUI,所以它在我的风格中占主导地位。话虽如此,我建议咨询Sun的Swing GUI短期课程。它还包括一个简单的案例研究。阅读完后,几乎整个SudokuView的大部分都应该清晰明了。

我确实将代码分离以使其更易读。这就是为什么控制器是另一个类而不是视图的一部分。视图仅用于构建小部件和布局,但为了简单起见(不创建更多类),我也在其中初始化控制器。

真正的工作在控制器中。它包含最复杂的细节...线程也在那里,因此它实际上是做了些什么并不那么明显。我从头开始实现了一个Thread类。有一个替代方案:使用SwingWorker。这可能是一个陈词滥调,但要明确:我使用线程使GUI随时响应。如果没有适当的线程处理,整个GUI将在计算时冻结。我决定从Sudoku的实现角度尽可能地简化它,如非阻塞增量更新。

至于线程,了解哪些代码运行在哪个线程非常重要。由GUI组件触发的每个操作都在EDT(事件分派线程)上运行。如果您在其中执行任何长时间运行的任务,则GUI将不会响应。因此,我只需创建另一个线程(请参见goButtonPressed()的实现)并启动它。之后,EDT可以处理任何其他事件而不会阻塞。

因此,您的Sudoku在一个特殊的后台线程中运行。除非它必须更新GUI,否则它可以随意进行任何操作。它几乎肯定会这样做,因为这就是部分更新的位置。这里有一个问题:如果您直接调用任何GUI组件(设置某些值),那么GUI将被冻结。这是一种称为EDT分派违规的条件。为避免任何冻结,所有与Swing的交互都应在EDT上完成。如何做到这一点?EDT有一个特殊的事件队列,专门用于此。您可以在队列上发布一个更新事件。在EDT上,代码会不断地监视传入事件并相应地更新GUI。因此,基本上它是后台线程和EDT之间的通信。要在队列上发布事件,您可以使用专门为此设计的特殊实用程序方法:EventQueue.invokeLater(new Runnable() { /* 这里放置您的GUI交互 */ });。请查看SudokuController方法:

  • setSudokuResult()
  • public void setSudokuTime()
  • setSudokuCompleted()

这就是GUI更新事件发布的位置。


如果您有兴趣,我可以贴一些布局教程的链接,这些教程是我从中学到的。 - Rekin
@rekin:代码就像你所写的那样。直到涉及到线程时,它变得非常难以理解。我对这个概念有基本的了解,但还没有足够的实践经验。我真的很努力地不想使用你的源代码,但是它太诱人了。我试图强迫自己逐步理解并逐个部分地工作,但是这些类彼此之间如此依赖,当我试图隔离一个单一的概念时,就会出现一整波错误:P - Justian Meyer
@Justian Meyer:看一下答案。我更新了它以澄清线程。希望这有所帮助。 :) - Rekin
@rekin:这段代码真的很容易编辑。非常感谢 : )。我会调整它并且玩弄它以适应我的喜好。顺便说一下,我已经让JComboBox工作了:http://img337.imageshack.us/img337/5568/laughablecombobox.jpg。我需要做一些调整——它非常混乱。我可能会参考I82Much的GridLayout示例来进行格式化指导。此外,通过线程处理,有些解决方案比以前运行得快得多(0-1毫秒与20+毫秒相比)。看源代码,我不知道这是怎么发生的。有什么想法吗? - Justian Meyer
1
我会将两个按钮都放在另一个JPanel中。这个面板将占据最初“>”按钮的位置。然后,我会在上面设置网格布局,使用一列并将两个按钮添加到其中。 - Rekin
显示剩余6条评论

3
我不明白你怎么可能想要放弃那个很棒的ASCII打印输出。
你应该去看一下@http://download.oracle.com/javase/tutorial/uiswing/ 上提供的教程,了解布局管理器是如何工作的。
对于文本框,我建议使用JTextField。这里有一些代码可供使用,使它们每次只接受一个数字:
public class textBox extends JTextField implements KeyListener{
    public textBox() {
        addKeyListener(this);       
    }

    @Override
    public void keyPressed(KeyEvent e) {
    }

    @Override
    public void keyReleased(KeyEvent e) {
    }

    @Override
    public void keyTyped(KeyEvent ke) {

        //consume the event otherwise the default implementation will add it to the text
        ke.consume(); 

        if (Character.isDigit(ke.getKeyChar())) 
            this.setText(Character.toString(ke.getKeyChar()));
    }
}

谢谢回复。这确实有助于其中的一部分。 - Justian Meyer

3
为了使其真正有用,您需要自定义很多内容。
我建议您使用按钮而不是文本字段。当某人单击按钮时,它变为选定状态;当某人键入数字时,它将进入选定的文本字段(如果有任何数字,则替换该数字)。这样可以更好地控制输入键。
布局可能会很棘手。利用Swing随屏幕大小缩放的能力会很好,但一定要缩放字体。
几乎所有手动制作的Swing GUI都应该从BorderLayout开始。即使只使用北部或南部(您的示例仅使用了南部),BorderLayout也非常适合将所有未使用的空间分配给中心。
在中心,您可能希望放置另一个容器,可能是一个网格。
有一个 - 我认为是“Box” - 其中具有单行均匀间距。因此,如果您水平设置了一排3个,然后在每个框中垂直添加3个,那么您可以在每个框内部创建一个框(以便您可以区分每组9个),然后在其中添加另外3个水平,每个填充3个垂直。
Swing布局通常归结为一些标准技巧(例如始终从BorderLayout开始),然后进行一些猜测、实验和某种程度的诅咒。

非常好的建议。比我的回答更具体。 - Chris Nava
喜欢这篇文章的描述。它具体而又模糊,让我猜测不停。“……接着是一些猜测、实验和某种程度的咒骂。”这让我笑了——因为这太真实了。我曾经帮助过人们设计带有 CSS 的网页,有时候真的很令人沮丧。 - Justian Meyer

2

所以你需要

  1. 限制文本框只能输入数字1-9
  2. 防止一次性插入多个值。

(1) 为了将文本框限制为仅可输入数字,我认为你可以使用一个JComboBox,其中填充了int(从1到9)包装在Integer盒子中。

(2) 这样用户必须选择一个位于数独板上每个(x,y)网格点处的JComboBox。然后必须从下拉列表中选择所需数字,防止同时输入多个。

希望这有所帮助,
祝你好运!

编辑:增加了清晰度。


哦!从来没有想过那个。下拉菜单是一个不错的选择。 - Justian Meyer

2

Netbeans IDE有一个很好的图形用户界面(GUI)生成工具。然而,在尝试使用该工具生成GUI之前,有一个基本的Java GUI理解会非常有帮助。特别重要的是尝试使用各种布局管理器并感受哪个在特定情况下会更有帮助。还要注意,您可以嵌套具有不同布局管理器的面板,以获得对布局更多的控制。


1
NetBeans是最好的GUI构建器之一,但根据我的个人经验,每次我开始使用GUI构建器时,总会遇到某些点,无法在不编辑生成的代码的情况下实现所需功能--一旦你这样做了,就会陷入困境。我建议跳过GUI生成器--它可能会有一段时间工作,甚至整个过程都能工作,但如果不行的话,那就糟糕了。 - Bill K
1
我读过和Bill提到的一样的东西。对我来说,最好是自己练习生成代码。这样更灵活,我可以时刻了解程序的“底层”。 - Justian Meyer
1
我从来没有遇到过编辑生成代码的大问题。我想这取决于构建器的工作方式。但是,如果你正在学习Swing,你应该自己编写代码。学会有效地使用布局(或其中一些)非常重要,特别是如果你想保持应用程序窗口可调整大小。 - COME FROM
1
@COME FROM 不是手动编辑,而是在编辑和生成之间来回切换。如果您必须手动修改某些内容以更改GUI的外观,则可能无法使编辑器读取该更改并将其作为其模型的一部分。此外,除非您完全隐藏生成的代码并且不允许其被修改(例如C使用C后处理文件隐藏生成的代码-您永远不会看到它们),否则codegen几乎总是存在问题的。 - Bill K
我同意以上所有评论。我发现GUI构建器很有用,可以快速生成一个草图,然后手动自定义。它也适用于尝试几种不同的布局选项并获得即时反馈。建议:您肯定会发现自己需要手动编辑代码,因此只需使用构建器快速启动粗略设计,然后深入了解。 - Chris Nava

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接