Ответ 1
GUI Sudoku
Хорошо, я не мог с собой поделать... Вот моя попытка. Все это в одном пакете:
- GUI со всеми элементами, соответствующими спецификации (вопрос)
- гибкий макет
- нет внешних зависимостей - используются стандартные макеты Swing
- подтверждение ввода (только цифры 0-9)
- Архитектура контроллера модели Model
- бегун фонового задания (ваш графический интерфейс никогда не зависает)
- встроены некоторые методы отладки (вывод Sudoku в виде текста)
- фиктивная реализация - имитирует длительное вычисление, показывающее отзывчивость GUI.
Я изо всех сил старался сделать код максимально читаемым. Могут быть довольно неясные детали. Вероятно, резьбовая часть не ясна, но если кто-нибудь найдет это для использования, я был бы рад описать ее лучше.
Итак, моя цель была самым простым использованием. Если вы посмотрите на интерфейсы, очень сложно сломать этот материал (заморозить UI, получить Null Pointer Exc и т.д.) В качестве упражнения при написании публичных API. Это может быть не самая лучшая реализация, но это одно из лучших, что я написал.:)
Надеюсь, что это поможет.
Вот как это выглядит: Пример выполнения http://a.imageshack.us/img256/754/sudokusolvergui.png
(примечание: значения являются случайными)
Использование
Все, что вам нужно сделать, это реализовать интерфейс:
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);
И это все!
код
Все классы находятся в пакете по умолчанию - рефакторинг по вашему желанию. Вот список из них:
- SudokuView - основной графический интерфейс
- SudokuRun - пример бегуна
- SudokuController - позволяет безопасно управлять просмотром.
- SudokuImplementation - интерфейс для реализации судоку
- 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 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. 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;
}
}
Описание
Я не утверждаю, что то, что я сделал, является лучшим. Мне бы хотелось увидеть другой ответ, допустим, все, что было сделано с MigLayout. Это было бы очень поучительно. Я изучал Swing GUI, когда реализация Sun была единственной, так что она преобладала в моем стиле. Тем не менее, я рекомендую обратиться к короткому курсу GUI Sun Swing. Он также включает простой пример исследования. После прочтения почти вся часть SudokuView должна быть четкой.
Я разделил код, чтобы сделать его более читаемым. Вот почему контроллер - это еще один класс, а не часть представления. Представление предназначено только для построения виджетов и макета, но чтобы сделать его простым (не создавать несколько классов), я также инициализирую его в контроллере.
Реальная работа находится в контроллере. Он содержит самые жёсткие детали... Threading также идет туда, поэтому это не так очевидно, что это на самом деле. Я реализовал класс Thread с нуля. Существует альтернатива: использование SwingWorker. Это может быть клише, но ясно: я использую threading, чтобы сделать GUI отзывчивым в любое время. Без правильной резьбы весь GUI замерзнет, когда будет проведено вычисление. Я решил сделать это как можно проще с точки зрения реализации Sudoku, например, неблокирующих инкрементных обновлений.
Что касается потоковой передачи, важно знать, какой код работает в этом потоке. Каждое действие, выполняемое компонентом GUI, выполняется по EDT (потоку отправки событий). Если вы выполняете какую-либо долгосрочную задачу, графический интерфейс не будет реагировать. Поэтому я просто делаю еще один поток (см. Реализацию goButtonPressed()
) и запускаю его. После этого EDT может обрабатывать любые другие события без блокировки.
Итак, ваш Sudoku работает в специальном фоновом потоке. Он может делать все, что захочет, если только он не должен обновлять GUI. Это почти наверняка, потому что это происходит, когда происходят частичные обновления. Вот уловка: если вы вызываете какой-либо компонент GUI напрямую (устанавливаете некоторые значения), то графический интерфейс будет зависеть. Это состояние, вызванное нарушением диспетчеризации EDT. Все взаимодействие с Swing должно выполняться на EDT, чтобы избежать зависания. Как это сделать? Для этого у EDT есть специальная очередь событий. Вы отправляете событие обновления в очередь. В коде EDT постоянно отслеживаются входящие события и соответственно обновляется графический интерфейс. Таким образом, в основном это связь между фоновым потоком и EDT. Чтобы отправить событие в очередь, вы можете использовать специальный метод утилиты, разработанный именно для этого: EventQueue.invokeLater(new Runnable() { /* here goes your GUI interaction */ });
. Взгляните на методы SudokuController
:
- setSudokuResult()
- public void setSudokuTime()
- setSudokuCompleted()
Это были события обновления графического интерфейса пользователя.