OnlyDavies
OnlyDavies

Reputation: 123

Java Swing: GUI is not updating some properties

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);

            }
        }

    }


}

Upvotes: 2

Views: 1106

Answers (2)

Frakcool
Frakcool

Reputation: 11153

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

Upvotes: 2

OnlyDavies
OnlyDavies

Reputation: 123

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


    }
}

Upvotes: 0

Related Questions