为数独求解器构建 GUI(完整的 ASCII 示例)
.
概览,示例
大家好,
我创建了一个基本的数独求解器,可以相当快地解决大多数问题.我还有很多工作要做,才能让它解决最困难的问题,但我想先尝试实现一个基本的 JFrame GUI.
我过去曾使用过互联网小程序,但以前从未使用过 JFrames.
我想创建类似于下图的东西(对于初学者):
-----------------------------------------------------------------------------------------------!数独求解器 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!|!!-_____________-_____________-______________- -_____________-_____________-______________- !!!!.----------------------------------------------------------------------------------------------.!!||!!|在 9.096 毫秒内解决难题 |完全解决:真|!!||!!'----------------------------------------------------------------------------------------------' !!!-------------------------------------------------------------------------------------------------------------
.
具体说明
:左拼图
- 应明确定义 9x9 部分(中间的线;单独的框)
- 文本框应该只接受数字/只允许输入一个数字(如果可能的话)
:正确的谜题
- 应明确定义 9x9 部分(中间的线;单独的框)
- 框是否可以编辑无关紧要,只要它们可以显示结果即可
:按钮在中心
- 应该运行 [SudokuPuzzle].solve();
:底部文本框
- 不应编辑
.
我在寻找什么
我从过去的经验中知道这都可以在 JFrame 中完成,但是因为我自己从未构建过一个,所以我不太确定是哪个
(注意:数值是随机的)
用法
你所要做的就是实现接口:
公共接口 SudokuImplementation {void goButtonPressed(Integer[][] leftSudokuValues, SudokuController resultAcceptor);}
只需在此方法中进行所有计算并使用 resultAcceptor.setSudokuResult()
这是实际显示 GUI 的方法:
SudokuImplementation sudokuImplementation =新的 YourSuperSudoku();//<- 你的实现数独视图数独视图=新数独视图();sudokuView.setSudokuImplementation(sudokuImplementation);sudokuView.setVisible(true);
仅此而已!
代码
所有类都在默认包中 - 随心所欲地重构.以下是它们的列表:
- SudokuView - 主界面
- SudokuRun - 跑步者示例
- SudokuController - 允许以安全的方式控制视图
- SudokuImplementation - 数独实现的接口
- DummySudokuImplementation - 示例实现
1.数独视图:
import java.awt.*;导入 javax.swing.*;导入 javax.swing.text.*;导入 javax.swing.border.*;/*** 构造每个组件并创建它自己的控制器的视图.*/公共类 SudokuView 扩展 JFrame {SudokuController 控制器;公共无效 setSudokuImplementation(SudokuImplementation 侦听器){控制器.setListener(监听器);}/** 创建新表单 NewJFrame */公共数独视图(){控制器 = 新数独控制器();setTitle("数独解算器 1.0");getContentPane().add(createCenterPanel(), BorderLayout.CENTER);getContentPane().add(createBottomPanel(), BorderLayout.SOUTH);setMinimumSize(新维度(600, 300));盒();设置默认关闭操作(JFrame.EXIT_ON_CLOSE);}私人JPanel createBottomPanel(){JPanel 底部面板 = 新 JPanel(新 GridBagLayout());JLabel leftLabel = createLabel("left");JLabel rightLabel = createLabel("right");controller.bindLeftLabel(leftLabel);控制器.bindRightLabel(rightLabel);bottomPanel.add(leftLabel, getWholeCellConstraints());bottomPanel.add(new JSeparator(JSeparator.VERTICAL));bottomPanel.add(rightLabel, getWholeCellConstraints());bottomPanel.setBorder(new BevelBorder(BevelBorder.LOWERED));返回底部面板;}私人 JLabel createLabel(字符串文本){JLabel 标签 = 新的 JLabel(文本);label.setHorizontalAlignment(JLabel.CENTER);返回标签;}私人JPanel createCenterPanel(){JPanel centerPanel = new JPanel(new GridBagLayout());centerPanel.add(createLeftPanel(), getWholeCellConstraints());centerPanel.add(createCenterButton(), getPreferredSizeConstraint());centerPanel.add(createRightPanel(), getWholeCellConstraints());返回中心面板;}私有 GridBagConstraints getPreferredSizeConstraint() {//默认会做返回新的 GridBagConstraints();}私人 JButton createCenterButton() {JButton goButton = new JButton(">");controller.bindCenterButton(goButton);返回按钮;}私有静态最终插图 SixPixelInset = new Insets(6, 6, 6, 6);私人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));返回右面板;}私人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));返回左面板;}私有 GridBagConstraints getWholeCellConstraints() {GridBagConstraints wholePanelCnstr = getPreferredSizeConstraint();WholePanelCnstr.fill = java.awt.GridBagConstraints.BOTH;WholePanelCnstr.weightx = 1.0;WholePanelCnstr.weighty = 1.0;返回整个PanelCnstr;}私人无效fillPanelWithEditable(JPanel面板,intmajorRow,intmajorColumn){for (int minorRow = 0; minorRow < 3; minorRow++) {for (int minorColumn = 0; minorColumn < 3; minorColumn++) {final JFormattedTextField editableField = createEditableField();int column = majorColumn * 3 + minorColumn;整数行 = 主要行 * 3 + 次要行;controller.bindLeftSudokuCell(行,列,可编辑字段);panel.add(editableField);}}}私人无效fillPanelWithNonEditable(JPanel面板,intmajorRow,intmajorColumn){for (int minorRow = 0; minorRow < 3; minorRow++) {for (int minorColumn = 0; minorColumn < 3; minorColumn++) {final JFormattedTextField editableField = createNonEditableField();int column = majorColumn * 3 + minorColumn;整数行 = 主要行 * 3 + 次要行;controller.bindRightSudokuCell(row, column, editableField);panel.add(editableField);}}}私人JPanel create3x3Panel(int gap){最终的 GridLayout gridLayout = new GridLayout(3, 3, 1, 1);gridLayout.setHgap(gap);gridLayout.setVgap(gap);JPanel 面板 = 新 JPanel(gridLayout);返回面板;}私人 JFormattedTextField createNonEditableField() {JFormattedTextField field = createEditableField();field.setEditable(false);field.setBackground(Color.WHITE);//否则不可编辑变为灰色返回字段;}私有 JFormattedTextField createEditableField() {JFormattedTextField 字段 = 新 JFormattedTextField();//只接受一位数字,不接受其他数字尝试 {field.setFormatterFactory(new DefaultFormatterFactory(new MaskFormatter("#")));} 捕捉(java.text.ParseException ex){}field.setPreferredSize(new Dimension(16, 30));field.setHorizontalAlignment(javax.swing.JTextField.CENTER);field.setText(" ");field.setBorder(null);返回字段;}}
2.数独:
import java.awt.EventQueue;导入 javax.swing.UIManager;公共类 SudokuRun 实现 Runnable {公共无效运行(){//******************** 这里可以交换你的真实实现SudokuImplementation sudokuImplementation = new DummySudokuImplementation();//***************************** *************** ********* **** * *数独视图数独视图=新数独视图();sudokuView.setSudokuImplementation(sudokuImplementation);sudokuView.setVisible(true);}公共静态无效主要(字符串参数[]){tryToSetSystemLookAndFeel();EventQueue.invokeLater(new SudokuRun());}私有静态无效 tryToSetSystemLookAndFeel() {尝试 {UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());} 捕捉(异常前){System.out.println("无法设置 LAF");}}}
3.数独控制器:
import java.awt.EventQueue;导入 java.awt.event.ActionEvent;导入 java.awt.event.ActionListener;导入 java.beans.PropertyChangeEvent;导入 java.beans.PropertyChangeListener;导入 javax.swing.JButton;导入 javax.swing.JFormattedTextField;导入 javax.swing.JLabel;公共类数独控制器 {JLabel 左标签,右标签;JFormattedTextField[][] 左数独,右数独;JButton 按钮;公共数独控制器(){leftSudoku = new JFormattedTextField[9][9];//标准数独大小rightSudoku = new JFormattedTextField[9][9];}无效 bindLeftLabel(JLabel 标签){左标签 = 标签;}无效 bindRightLabel(JLabel 标签){右标签 = 标签;}void bindLeftSudokuCell(final int row, final int column, JFormattedTextField field) {field.addPropertyChangeListener("值", new PropertyChangeListener() {//如果用户编辑字段比你可以在这里做点什么公共无效propertyChange(PropertyChangeEvent evt){if (evt.getNewValue() != null) {String newValue = (String) evt.getNewValue();userEditedValueAt(row, column, Integer.valueOf(newValue));}}});左数独[行][列] = 字段;}void userEditedValueAt(int row, int column, int value) {System.out.println("行的值改变:" + row + ",列:" + column + " 到 " + value);}void bindRightSudokuCell(int row, int column, JFormattedTextField field) {对数独[行][列] = 字段;}无效吐出数独(){System.out.println("左:");System.out.println(getPrettyPrinted(leftSudoku));System.out.println("右:");System.out.println(getPrettyPrinted(rightSudoku));}私人字符串 getPrettyPrinted(JFormattedTextField[][] 数独) {StringBuilder sb = new StringBuilder();for (int i = 0; i < 9; i++) {sb.append("|");对于 (int j = 0; j < 9; j++) {如果(数独[i][j]!= null){sb.append(数独[i][j].getText());} 别的 {sb.append("-");}sb.append("");}sb.append("|
");}返回 sb.toString();}无效绑定中心按钮(JButton goButton){this.goButton = goButton;goButton.addActionListener(new ActionListener() {公共无效actionPerformed(ActionEvent e){goButtonPressed();}});}数独实现监听器;public void setListener(SudokuImplementation listener) {this.listener = 监听器;}线程背景GroundThread;私人无效goButtonPressed(){如果(听众!= null){if (backGroundThread == null || (backGroundThread != null && !backGroundThread.isAlive())) {后台线程 = 新线程() {@覆盖公共无效运行(){listener.goButtonPressed(getLeftValues(), SudokuController.this);}};backGroundThread.start();}}}私有整数[][] getLeftValues() {整数[][] 值 = 新整数[9][9];for (int i = 0; i < 9; i++) {对于 (int j = 0; j < 9; j++) {if (!leftSudoku[i][j].getText().equals(" ")) {values[i][j] = Integer.valueOf(leftSudoku[i][j].getText());}}}返回值;}public void setSudokuResult(final Integer[][] 结果) {//任何 GUI 交互都必须在 EDT 上完成//我们不想阻塞计算所以我们选择invokeLater//与 invokeAndWait 不同.EventQueue.invokeLater(new Runnable() {公共无效运行(){for (int i = 0; i < 9; i++) {对于 (int j = 0; j < 9; j++) {对数独[i][j].setValue(String.valueOf(result[i][j]));}}}});}公共无效 setSudokuTime(最终字符串时间){EventQueue.invokeLater(new Runnable() {公共无效运行(){leftLabel.setText("<html>运行时间:<b>" + time);}});}公共无效 setSudokuCompleted(最终布尔完成){EventQueue.invokeLater(new Runnable() {公共无效运行(){rightLabel.setText("<html>完全解决:<b>" + 完成);如果(完成){吐出数独();}}});}}
4.数独实现:
公共接口 SudokuImplementation {void goButtonPressed(Integer[][] leftSudokuValues, SudokuController resultAcceptor);}
5.虚拟数独实现:
import java.util.concurrent.TimeUnit;/*** 模拟数独求解器.演示如何更新 GUI.整体* 构建实现,因此 GUI 永远不会冻结.*/类 DummySudokuImplementation 实现 SudokuImplementation {公共 DummySudokuImplementation() {}public void goButtonPressed(Integer[][] leftSudokuValues, SudokuController resultAcceptor) {System.out.println("长时间运行的计算模拟...");对于 (int i = 0; i < 50; i++) {resultAcceptor.setSudokuCompleted(false);resultAcceptor.setSudokuTime(String.valueOf(i * 50) + "ms");resultAcceptor.setSudokuResult(getRandomResult());等待时间();}resultAcceptor.setSudokuResult(leftSudokuValues);resultAcceptor.setSudokuCompleted(true);等待时间();System.out.println("完成!");}私人无效waitSomeTime(){尝试 {TimeUnit.MILLISECONDS.sleep(50);} 捕捉(InterruptedException ex){}}私有整数[][] getRandomResult() {整数[][] 随机结果 = 新整数[9][9];for (int i = 0; i < 9; i++) {对于 (int j = 0; j < 9; j++) {randomResult[i][j] = (int) (Math.random() * 9);}}返回随机结果;}}
说明
我并没有声称我的做法是最好的.我很想看到其他答案,比如说,所有视图都是用 MigLayout 完成的.这将是非常有启发性的.当 Sun 的实现只有一个时,我正在学习 Swing GUI,所以它在我的风格中占了上风.也就是说,我建议参考 Sun 的 Swing GUI 短期课程.它还包括一个简单的研究案例.读完之后,SudokuView 几乎整个部分都应该清楚了.
我确实将代码分开以使其更具可读性.这就是为什么控制器是另一个类,而不是视图的一部分.该视图仅用于构建小部件和布局,但为了使其简单(而不是创建更多类),我还在其中初始化控制器.
真正的工作是在控制器中.它包含最毛茸茸的细节......线程也在那里,所以它实际上做了什么并不那么明显.我从头开始实现了一个 Thread 类.还有另一种选择:使用 SwingWorker.这可能是陈词滥调,但要明确一点:我使用线程来使 GUI 随时响应.如果没有适当的线程,整个 GUI 将在计算发生时冻结.我决定从 Sudoku 的实现角度使其尽可能简单,例如非阻塞增量更新.
至于线程,了解哪些代码在哪个线程中运行至关重要.GUI 组件触发的每个操作都在 EDT(事件调度线程)上运行.如果您对其执行任何长时间运行的任务,GUI 将不会响应.所以我只是创建另一个线程(参见 goButtonPressed()
的实现)并启动它.之后,EDT 可以在不阻塞的情况下处理任何其他事件.
所以你的数独在一个特殊的后台线程中运行.它可以为所欲为,除非它必须更新 GUI.几乎可以肯定它会,因为那是部分更新的地方.这里有一个问题:如果您直接调用任何 GUI 组件(设置一些值),那么 GUI 将冻结.这是一种称为 EDT 调度违规的情况.与 Swing 的所有交互都应在 EDT 上完成,以避免任何冻结.怎么做?EDT 有专门的事件队列.您在队列中发布了一个更新事件.在 EDT 上,代码一直在监视传入的事件并相应地更新 GUI.所以基本上,这是后台线程和 EDT 之间的通信.在队列上发布事件您可以使用专门为此设计的特殊实用程序方法:EventQueue.invokeLater(new Runnable() {/* 这是您的 GUI 交互 */});
.看看 SudokuController
方法:
- setSudokuResult()
- public void setSudokuTime()
- setSudokuCompleted()
这是发布了 GUI 更新事件.
.
OVERVIEW, SAMPLE
Hello everyone,
I have created a basic Sudoku solver that can solve most problems fairly quickly. I still have a lot of work ahead of me to make it solve even the hardest problems, but I'd like to try to implement a basic JFrame GUI first.
I have worked with internet applets in the past, but never before with JFrames.
I want to create something similar to the image below (for starters):
-------------------------------------------------------------------------------------------------
! 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 | !
! | | !
! '-------------------------------------------------------------------------------------------' !
! !
-------------------------------------------------------------------------------------------------
.
SPECIFICS
: Left Puzzle
- 9x9 Sections should be clearly defined (lines in between; seperate boxes)
- Text boxes should only accept numbers/only allow for one number to be entered (if possible)
: Right Puzzle
- 9x9 Sections should be clearly defined (lines in between; seperate boxes)
- Doesn't matter if boxes can/cannot be edited so long as they can display the result
: Button In Center
- Should run [SudokuPuzzle].solve();
: Bottom Text Box
- Should not be editable
.
WHAT I'M LOOKING FOR
I know from past experiences that this can all be done in a JFrame, but because I have never built one myself, I'm not quite sure which components (content items, panels, settings, etc) I need to use to meet my specifications. I have yet to find a way to limit my text boxes to numbers and prevent the user from inserting more than one value at a time. Are text boxes really the best option, or am I missing something that can more specifically suit my needs?
I not only need to know which classes I need, but also how to organize these so that the button stays comfortably between the two puzzles and the text box sits underneath. From what I've read, MigLayout seems like an option to simplify this process.
.
END NOTES
Many, many thanks to anyone who helps. If any part of this question appears a little rude or abrupt, I apologize. I tend to post most of my questions at night, so the community has a few hours to mull it over before I try all the responses (that and the fact that I'm out doing stuff most days).
I will be awake for 1-2 more hours to answer any questions.
Again thanks,
Justian
解决方案The Sudoku GUI
Ok, I couldn't help myself... Here is my try. It's all in one package:
- GUI with all elements conforming specification (question)
- flexible layout
- no external dependencies - standard Swing layouts used
- input validation (only digits 0-9)
- Model View Controller architecture
- background task runner (Your GUI never freezes)
- some debugging methods built-in (output of Sudoku as text)
- dummy implementation - simulates long running computation showing GUI responsiveness
I tried my best to make the code as readable as I could. There might be rather unclear parts. Probably the threading part isn't lucid, but if anyone finds this of any use I'd be glad to describe it better.
So my aim was the simplest possible usage. If You look at the interfaces it's really hard to break this stuff (froze UI, get Null Pointer Exc etc.) as an exercise in writing public API's. This may not the best implementation, but It's one of the best I wrote. :)
Hope it helps.
Here's how it looks like:
(note: values are random)
Usage
All You have to do is implement the interface:
public interface SudokuImplementation {
void goButtonPressed(Integer[][] leftSudokuValues, SudokuController resultAcceptor);
}
Just do all computation in this method and store results with resultAcceptor.setSudokuResult()
Here is how to actually show GUI:
SudokuImplementation sudokuImplementation =
new YourSuperSudoku(); // <- your implementation
SudokuView sudokuView = new SudokuView();
sudokuView.setSudokuImplementation(sudokuImplementation);
sudokuView.setVisible(true);
And that's all!
Code
All classes are in default package - refactor as You wish. Here is list of them:
- SudokuView - main GUI
- SudokuRun - example runner
- SudokuController - allows to control the view in a safe manner
- SudokuImplementation - interface to sudoku implementation
- DummySudokuImplementation - example implementation
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("|
");
}
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. SudokuImplementation:
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;
}
}
Explanation
I don't claim that the way I did is the best. I'd love to see other answer with, let's say, all view done with MigLayout. It would be very instructive. I was learning Swing GUI when Sun's implementation were only one so it prevailed in my style. That said, I recommend consulting Sun's Swing GUI short course. It also includes a simple study case. After reading it almost whole part of SudokuView should be clear.
I did separate the code to make it more readable. That's why controller is another class, not part of view. The view is only for construction of widgets and layout, but to make it simple (not to create few more classes) I also initialize controller in it.
The real work is in the controller. It contains the hairiest details... Threading also goes there so it's not so obvious what it actually does. I implemented a Thread class from scratch. There is alternative: using SwingWorker. It might be a cliche, but make it clear: I use threading to make GUI responsive at any time. Without proper threading whole GUI would freeze when the computation would take place. I decided to make it as easy as possible from Sudoku's implementation point of view, like non-blocking incremental updates.
As for threading it's crucial to know which code runs in which thread. Every action fired by GUI component runs on EDT (event dispatch thread). If you do any long-running task on it, the GUI won't be responsive. So I just make another thread (see implementation of goButtonPressed()
) and start it. After that EDT can process any other events without blocking.
So Your Sudoku runs in a special, background thread. It can do whatever it wants, unless it has to update the GUI. It almost certain it will, since that's where partial updates go. Here's a catch: if You call any GUI component directly (set some values) than the GUI will freeze. This is a condition called EDT dispatch violation. All interaction with Swing should be done on EDT to avoid any freezes. How to do it? The EDT has special event queue just for that. You post an update event on the queue. On EDT code is constantly watching for incoming events and updates GUI accordingly. So basically, it's a communication between background thread and EDT. To post an event on the queue You could use special utility method designed just for this: EventQueue.invokeLater(new Runnable() { /* here goes your GUI interaction */ });
. Take a look at SudokuController
methods:
- setSudokuResult()
- public void setSudokuTime()
- setSudokuCompleted()
That's were the GUI update events are posted.
相关文章