views:

225

answers:

6

.

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

+2  A: 

I can't understand how you could possibly want to abandon that awesome ASCII printout.

You should really take a look at the tutorials given @ http://download.oracle.com/javase/tutorial/uiswing/ and take a look on how the layout managers work.

For the text boxes I would recommend using JTextField. Here is some code you can use to have them only accept one digit at the time:

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()));
    }
}
getekha
@getekha: Thanks for the response. That certainly helps with a part of it.
Justian Meyer
+1  A: 

Heya, cool project.

So you need to

  1. Limit textboxes to #s 1-9
  2. Prevent insertion of more than 1 value at a time.

(1) To limit your textboxes to only number, I think what you could use it a JComboBox that is populated with ints(from 1-9) wrapped in Integer boxes.

(2) This way the user has to select a JComboBox @ each (x,y) grid point on the sudoku board. Then must select the desired number from the dropdown, preventing multiple input at the same time.

Hope this helps,
Goodluck!

EDIT: clarity+1

cschar
@cschar: Ooh! Never thought about that. Dropdowns are a nice option.
Justian Meyer
+2  A: 

To make this really useful, you are going to have to customize a lot of stuff.

I suggest you use buttons instead of text fields. When someone clicks on the button it becomes selected, when someone types a number it goes to the selected text field (replacing the number there, if any). This gives you a little more control over how keys are input.

The layout may be tough. It would be nice to take advantage of Swing's ability to scale sizes with the size of the screen, but be sure to scale your fonts as well.

Nearly every hand-done swing GUI should start with a BorderLayout. This gives you "Sides", even if you only use north or south (your example only used south), BorderLayout is great at allocating all unused space to the center.

In the center you probably want to place another container, possibly a grid.

There is one--I think it is "Box" that has an even spacing of a single row. So if you set up a row of 3 horizontally, then added 3 vertically to each box, you could then INSIDE each of those create a box (so that you could differentiate each group of 9) and then inside that box add another 3 horiz, each filled with 3 vertical.

Swing layouts generally come down to a few standard tricks (like always staring with BorderLayout) followed by some guessing, experimenting and some level of cursing.

Bill K
Great advice. Much more specific than my answer.
Chris Nava
@Bill K: Loved the run-down. It's specific and yet vague enough to keep me guessing. "...followed by some guessing, experimenting and some level of cursing." made me laugh - it's just all too true. I've assisted people in designing web pages with css. It's just so frustrating sometimes.
Justian Meyer
+1  A: 

Netbeans IDE has a great GUI for generating GUIs. However, it helps (a lot) to have a basic understanding of Java GUIs before attempting to generate one with the tool. Especially important is playing with each of the layout managers and getting a feel for which one would help in a given situation. Also note that you can nest panels with different layout managers to get more control over the layout.

Chris Nava
NetBeans is one of the best GUI builders out there but in my personal experience every time I've started down the gui-builder road I've always reached some point where I just couldn't get it to do what I wanted it to do without editing the generated code--once you've done that you're screwed. I suggest just skipping the GUI generator--it might work for a while, it might work for the whole thing, but if it doesn't--gakk.
Bill K
@Chris: I've read the same thing as what Bill mentioned. It's better for me to practice generating the code myself. It's more flexible and I'm constantly aware of what's "under the hood" of my program.
Justian Meyer
I have never had big problems with editing generated code. I guess that depends on the way the builder works. But if you're learning Swing, you should definitely write the code yourself. Learning to use the layouts (or some of them) effectively is important. Especially if you want to keep the application window resizable.
COME FROM
@COME FROM it's not so much editing it by hand as going back and forth between editing and generating. If you have to hand-modify something that changes the way the GUI looks, you probably won't get the editor to read that change back in and make it part of it's model. Also, codegen is pretty much always problematic at some point unless you completely hide the generated code and don't allow it to be modififed (like C does with the C post-processed files--you never see them)
Bill K
I agree with all the above comments. I find that the GUI builder is useful to generate a quick straw-man that I then customize by hand. It's also good for trying out a few different layout options and getting instant feedback. Advice: You'll invariably find yourself editing the code by hand so just use the builder to get a quick start on the rough design and then dive in.
Chris Nava
+4  A: 

alt text

This should give you enough to get started. Just add the getter logic to pull out the values they entered in the text fields.

Main:

/*
 * 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);
    }
}

NineSquare:

/*
 * 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();
            }
        }
    }
}

Board:

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
@I82Much: Thanks for the great sample code. Is there any way I can prevent this grid from warping when I resize the window? (I really hope that you're nicholasdunn - I don't support piggybacking without explicitly mentioning it).
Justian Meyer
@I82Much: Woah, woah! Slow down. Don't just feed me the answer :P - I need to suffer and learn.
Justian Meyer
@Justian Meyer~ It would appear he is http://user.gdgt.com/I82Much/
drachenstern
@drachenstern: +1 for internet stalking and confirmation. I figured he was as soon as he added the button in between. Looks like he's using a GUI editor.
Justian Meyer
No I'm not using a GUI editor. I know Swing pretty well .. does anything in there look like it was machine generated?
I82Much
with respect to the warping... can you take a screenshot of what you're talking about?
I82Much
@I82Much: (1st comment): It wasn't accusatory. I just figured because of the "To change the template..." comment. Perhaps it's from an IDE, but I honestly have no idea. (2nd comment): Certainly. What's a good image hosting site?
Justian Meyer
@I82Much: Warped example: http://img697.imageshack.us/img697/3158/warpedz.jpg.
Justian Meyer
You have a few choices. 1) Disallow user to resize window. (JFrame.setResizable(false). 2) Ensure that the window remains square while they're dragging (much trickier, involves listening for Window resizing events and manually changing the size to ensure that the size is square)Now that the example has the default flow layout, this does not happen when you resize.
I82Much
@I82Much: It's likely that I will recreate the GUI from scratch after peeking in at your source for guidance, so `JFrame.setResizable(false)` is probably the simpler option for me at the moment. Many thanks :)
Justian Meyer
+1 from ascii-art to star trek
stacker
If you take away nothing else from my source, take away the GridLayout and the use of a custom Document that disallows nonnumerical strings from being entered into the textfields.
I82Much
@I82Much: Will do :)
Justian Meyer
+2  A: 

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: Running example

(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:

  1. SudokuView - main GUI
  2. SudokuRun - example runner
  3. SudokuController - allows to control the view in a safe manner
  4. SudokuImplementation - interface to sudoku implementation
  5. 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("|\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;
    }
}

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.

Rekin
If you're interested, I could paste some links to layout tutorials which I learned from.
Rekin
@rekin: Wow. You certainly enjoyed yourself ;). Links would be awesome! As always, I will use this source code as a guide only.
Justian Meyer
@rekin: The code is exactly as you put it. It's really easy to read until it comes down to the threading. It's a concept that I have a basic understanding of, but haven't put into practice enough. I'm really trying hard not to use your source, but it's so tempting. I'm trying to force myself to work through it and understand it piece-by-piece, but the classes are so dependent on one another, that I get a whole wave of errors when I try to isolate a single concept :P
Justian Meyer
@Justian Meyer: Take a look at answer. I updated it to clarify the threading. Hope this helps. :)
Rekin
@rekin: Because it was so easy to use my code with yours, I decided to just use your source. To make up for it, I'll add additional features such as file loading, dropdown menus, logs etc. I'm currently looking at a way to use cschar's suggestion of JComboBox's instead of TextFields. I'm not familiar with JComboBox (and most Swing, really), but I'm hoping (from what I've seen in the source) that it won't need major surgery.
Justian Meyer
Rekin
@rekin: This code is really easy to edit. Many Thanks :). I will tweak it and mess around with it to fit it to my liking. I got JComboBox's working by the way: http://img337.imageshack.us/img337/5568/laughablecombobox.jpg. I'll need to do some tweaking - it's horribly unorganized. I will probably refer to I82Much's example of a GridLayout for formatting guidance. Also, with the threading, some solutions run significantly faster than before (0-1 ms compared to 20+ ms). Looking at the source, I don't see how this exactly happened. Any ideas?
Justian Meyer
As for the combo boxes - try setting preferred size, similar to this: `jComboBox1.setPreferredSize(new Dimension(30, 30));` As for the GridLayout... It's exactly what it uses already. And the threading: that's strange, unless You did some GUI interaction in the solving code before. Painting the GUI is time consuming so I'd bet moving to other thread helped to unblock it. Without knowing the details of Your's previous implementation I can only guess that EDT rules were violated.
Rekin
@rekin: Yeah, I realized the setPrefferedSize and GridLayout after looking back at the code. As for the speed, it's really unusual. All I had was a text output similar to the one you had in the background for debugging. The time keeping code remained the same. `long start, end; double time; start = System.nanoTime(); puzzle.solve(); end = System.nanoTime(); time = (double) (end - start) / 1000000;`. It's just really odd, but I'm glad it just worked out that way :P
Justian Meyer
@rekin: I just added a "clear" button. How can I position it just below the ">" button? Right now it's to the right of the ">" button.
Justian Meyer
I would enclose both buttons in another JPanel. This panel would take the place where the initial ">" button was. I'd then set grid layout on it using one column and add both buttons in it.
Rekin