2

EDIT: A EASIEST, SIMPLE AND CHECKABLE PROBLEM BELOW

RESUME

I'm doing a Latin Square application, which, sets a square of size s, and you need to colour it with some restrictions such as, not the same color in the same row, or in the same column.

But my trouble is not the problem itself, but Swing.

I'm trying to do it with Swing for some graphics and better appearence.

The problem it is, than when I have found a solution, I want to stop a few seconds for watch it, and then continue looking others (I will do this with a Thread.sleep()).

But I'm watching that the square is not getting coloured. Only when finish the method, changes itself.

MAIN PROBLEM

enter image description here

Only show the last solution that it founds and shows when finish the Backtracking method.

enter image description here

When I click on the resolve button, there is a class which implements ActionListener interface which has the actionPerformed method, that calls a method of the main frame class, which resolve the square. So the problem is, that if I stop the execution when it find a solution, the GUI doesn't changes, but internaly, when I check the properties (debugging) the cell has the color updated, but not in the GUI.

And I don't know why :(

MORE DETAILED

My idea, was, to make a frame with two pannels, one on the left, and one on the middle (maybe in the future, put something in the right).

For that I use a BorderLayout.

So the first pannel on the left, it's something like basic configuration menu, where the user can set the size of the square and run it for get the solution.

For that I have two buttons, one for modify the size, and the other for resolve it.

So I need events for the buttons. Size is not giving me problems, but resolve yes.

So I checked out, than if I colour some square and pause the execution (Scanner.nextLine() or a Thread.sleep()) is not making changes in the GUI, but when I debug, the square is coloured in the properties, so I don't understand very well why is faling.

WHERE I THINK IS THE PROBLEM

So I have a button, which resolve the square when clicking on. And I think, I don't really know, but I suspect, that doing like this, creates a new Thread or something, so, can't updates the GUI; and only when finish, do it.

There is anyway to change this?.

class ResolveListener implements  ActionListener
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                int size = Integer.parseInt(textField.getText());
                latinSquareFrame.resolve(size);
            }
        }

A SIMPLE PROBLEM

I have read in the comments to search a minimim and easy checkable problem similar to this.

So I did. This code is similar to my problem, there is a square, I and I want to colour it when click the button.

The problem is that, if I pause it, it's not coloured, only is coloured when the method finishes, and I don't know why.

I think it's similar to the problem I have faced before.

This is the minimum code I'm able to use for the problem I have.

    package LatinSquare;


import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Scanner;

public class Test
{
    public static void main(String[] args)
    {
        TestFrame testFrame = new TestFrame();
    }
}

class TestFrame extends JFrame
{

    public TestFrame()
    {
        this.setVisible(true);
        this.setBounds(400,300,400,300);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setLayout(new BorderLayout());
        TestJPanel testJPanel = new TestJPanel();
        this.add(testJPanel,BorderLayout.CENTER);

        TestContainerButtonJPanel testContainerButtonJPanel = new TestContainerButtonJPanel(testJPanel);
        this.add(testContainerButtonJPanel, BorderLayout.SOUTH);

        this.revalidate();
        this.repaint();
    }

}

class TestContainerButtonJPanel extends JPanel
{
    private JButton resolve;
    public TestJPanel testJPanel;

    public TestContainerButtonJPanel(TestJPanel testJPanel)
    {
        this.testJPanel = testJPanel;
        this.setVisible(true);
        resolve = new JButton("RESOLVE");
        ActionListener resolveListener = new ResolveListener();
        resolve.addActionListener(resolveListener);
        this.add(resolve);

    }

    class ResolveListener implements ActionListener
    {
        @Override
        public void actionPerformed(ActionEvent e)
        {
            try {
                TestContainerButtonJPanel.this.testJPanel.colourCells();
            } catch (InterruptedException e1) {
                e1.printStackTrace();
            }
        }


    }
}


class TestJPanel extends JPanel
{
    private JButton[][] board;
    public TestJPanel()
    {
        this.board = new JButton[4][4];
        this.setVisible(true);
        this.setLayout(new GridLayout(4,4));
        for(int i=0; i<4;i++)
        {
            for(int j=0; j<4;j++)
            {
                JButton cell = new JButton();
                board[i][j] = cell;
                board[i][j].setBackground(Color.WHITE);
                this.add(cell);
            }
        }

    }

    public void colourCells() throws InterruptedException {
        for(int i=0; i<4;i++)
        {
            for(int j=0;j<4;j++)
            {
                this.board[i][j].setBackground(Color.RED);
                Thread.sleep(300);

            }
        }

    }


}
Frakcool
  • 10,915
  • 9
  • 50
  • 89
OnlyDavies
  • 123
  • 2
  • 8
  • 3
    1) I don't see any `Thread.sleep()` calls in your code. 2) You're mixing 2 paradigms of programming, console apps and GUI apps, choose one. 3) To create a delay use a Swing Worker or a Swing Timer instead of `Thread.sleep`. 4) Don't post all your code but rather a [mcve] that we can copy-paste and see the same behavior, in this case you could only have a button that when you press it, it changes its color after 1 second or so... – Frakcool Apr 16 '19 at 19:23
  • Possible duplicate of [How could I add a simple delay in a Java Swing application?](https://stackoverflow.com/questions/12767367/how-could-i-add-a-simple-delay-in-a-java-swing-application) – Frakcool Apr 16 '19 at 19:23
  • Here are some examples: [example 1](https://stackoverflow.com/questions/34746795/creating-an-animated-4x4-grid-in-java/34748083#34748083) and [example 2](https://stackoverflow.com/questions/41877789/animated-sprites-with-java-swing/41879444#41879444) – Frakcool Apr 16 '19 at 19:26
  • 1
    Okey, thanks you, I will read all the links. Ok I get I don't need the Thread, I will keep reading the links – OnlyDavies Apr 16 '19 at 19:32
  • 1
    You can ask if you don't understand something from the links – Frakcool Apr 16 '19 at 19:49
  • I wrote a minimal complete and verifiable example in the post (at least the better I could). That is the problem I face, I want to be able to stop at any time I want, and get some cells coloured, but doesn't coloured any one, only after the method, when finish – OnlyDavies Apr 16 '19 at 21:32
  • I recommend you to remove the part of "ALL THE CODE" (after you do so, I'll upvote for the effort of doing the MCVE), I'll check it in ~1 hour, I don't have my compiler here right now, and yes, you're still using `Thread.sleep()`... if no-one answers before I'll do after I get home – Frakcool Apr 16 '19 at 21:40
  • Thanks for the upvote, I find the problem, it was concurrency. Because I was only using one Thread, so during the process can't update the GUI because internally is doing other things. If I use another Thread for the action event, then works fine – OnlyDavies Apr 17 '19 at 10:10
  • Sorry, I had some issues to attend yesterday. I'll post a better answer later – Frakcool Apr 17 '19 at 15:45

2 Answers2

2

Ok, first things first:

  1. Don't update your GUI using Thread.sleep() it will block the EDT
  2. You're not placing your program on the EDT, see point number 2 in this answer
  3. Don't extend JFrame, instead create an instance of it, see: Extends JFrame vs. creating it inside the program
  4. Don't make your program visible (i.e. call setVisible(...)) before adding all the components to it. It may cause your program to behave in a wrong manner.
  5. Try not creating your own threads, instead use a Swing Timer or a Swing Worker (links in the question's comments)

So, taking all of that into consideration, I decided to create a new program that follows all the above rules and makes the cells blue for 3 seconds or white after that time has passed, and also updates the text in the JButton as well as disabling to prevent multiple timers executing at once.

import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class Test {
    private JFrame frame;
    private JPanel pane;
    private JPanel cellsPane;
    private MyCell[][] cells;
    private JButton button;
    private Timer timer;

    private int counter = 3;
    private boolean isFinished = false;

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

    private void createAndShowGui() {
        frame = new JFrame(getClass().getSimpleName());

        pane = new JPanel();
        cellsPane = new JPanel();

        pane.setLayout(new BoxLayout(pane, BoxLayout.PAGE_AXIS));
        cellsPane.setLayout(new GridLayout(4, 4, 5, 5));

        cells = new MyCell[4][4];

        for (int i = 0; i < cells.length; i++) {
            for (int j = 0; j < cells[i].length; j++) {
                cells[i][j] = new MyCell(Color.WHITE);
                cellsPane.add(cells[i][j]);
            }
        }

        button = new JButton("Press me!");
        timer = new Timer(1000, listener);

        button.addActionListener(e -> {
            button.setEnabled(false);
            isFinished = false;
            updateCellsColors();
            timer.start();
        });

        pane.add(cellsPane);
        pane.add(button);

        frame.add(pane);
        frame.pack();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }

    private void updateCellsColors() {
        for (int i = 0; i < cells.length; i++) {
            for (int j = 0; j < cells[i].length; j++) {
                cells[i][j].setCellColor(isFinished ? Color.WHITE : Color.BLUE);
            }
        }
    }

    private ActionListener listener = new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            if (counter == 0) {
                timer.stop();
                counter = 3;
                isFinished = true;
                button.setEnabled(true);
                updateCellsColors();
            }
            if (isFinished) {
                button.setText("Press me!");
            } else {
                button.setText("You have " + counter + " seconds remaining");
            }
            counter--;
        }
    };
}

@SuppressWarnings("serial")
class MyCell extends JPanel {
    private Color cellColor;

    public Color getCellColor() {
        return cellColor;
    }

    public void setCellColor(Color cellColor) {
        this.cellColor = cellColor;
        this.setBackground(cellColor);
    }

    public MyCell(Color cellColor) {
        this.cellColor = cellColor;
        this.setOpaque(true);
        this.setBackground(cellColor);
    }

    @Override
    public Dimension getPreferredSize() {
        // TODO Auto-generated method stub
        return new Dimension(30, 30);
    }
}

You can copy-paste it and see the same result as me:

enter image description here enter image description here

Frakcool
  • 10,915
  • 9
  • 50
  • 89
0

Need another Thread for fix it.

Changing the listener class, works fine:

 class ResolveListener implements ActionListener
    {
        @Override
        public void actionPerformed(ActionEvent e)
        {
            new Thread(new Runnable()
            {
                @Override
                public void run()
                {
                    try {
                        TestContainerButtonJPanel.this.testJPanel.colourCells();
                    } catch (InterruptedException e1) {
                        e1.printStackTrace();
                    }
                }
            }).start();
        }


    }
}
OnlyDavies
  • 123
  • 2
  • 8