Почему этот многопоточный код не работает должным образом с графическим интерфейсом? [Java Swing] [Threading] - PullRequest
0 голосов
/ 10 ноября 2018

Мой проект использует Java Swing в качестве графического интерфейса. Я делаю игру Towers of Hanoi. Я только что получил графический интерфейс, но моя команда решения не будет работать должным образом.

Без потоков вызовов, он немедленно решает башни, как и ожидалось. Я добавил пару Thread.waits, ожидая, что он решит ее шаг за шагом, чтобы пользователь мог видеть, как это происходит, но вместо этого он ждет некоторое время, а затем решает всю головоломку за один раз. Я думаю , возможно, это не перекраска , но я не знаю почему. Кто-нибудь знает, что происходит?

Вот код для решения:

public class Solver {

public Solver() {
    // nothing
}


public void solve(
    int numberBlocks,
    int startPin,
    int auxiliaryPin,
    int endPin) {
    if (numberBlocks == 1) {
        movePin(startPin, endPin);
        try {
           Thread.sleep(200);
       }
        catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    else {
        solve(numberBlocks - 1, startPin, endPin, auxiliaryPin);
        movePin(startPin, endPin);
        try {
            Thread.sleep(200);
        }
        catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        solve(numberBlocks - 1, auxiliaryPin, startPin, endPin);
    }
}


private void movePin(int startPin, int endPin) {
    TowersOfHanoiGame.moveTopBlock(startPin, endPin);
}

Вот код из графического интерфейса, который выполняет работу: Я знаю, что это ужасно написано, это мой первый раз, когда я пишу на Java Swing, я изучаю его на ходу. Если у кого-нибудь есть указания, как лучше это структурировать, я бы тоже хотел об этом услышать. Я вставляю весь класс, но важными методами являются initListeners, moveTopBlock и методы, которые они вызывают.

import java.awt.BorderLayout;
import java.awt.Button;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;

public class TowersOfHanoiGame {
private static JFrame mainWindow;
private static JPanel mainContentPanel;
private static JPanel content;

private static ArrayList<Block> pegOneBlocks = new ArrayList<Block>();
private static ArrayList<Block> pegTwoBlocks = new ArrayList<Block>();
private static ArrayList<Block> pegThreeBlocks = new ArrayList<Block>();
private Color[] randomColors = new Color[8];

private Dimension menuSize = new Dimension(100, 100);
private static final int DISCSTEXTSIZE = 20;
private static final int MOVESTEXTSIZE = 30;

private ActionListener downButtonListener;
private ActionListener upButtonListener;
private ActionListener solveButtonListener;

private JLabel discs;
private JLabel moves;

private int discsNumber = 3;
private int movesNumber = 0;

private Solver solver = new Solver();


public TowersOfHanoiGame() {
    // do nothing
    initRandomColors();
    initBlocks(3);
}


/**
 * Initialize and display the game
 */
public void display() {
    initListeners();
    initWindow();
    mainWindow.setVisible(true);
}


private void initListeners() {
    downButtonListener = new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent arg0) {
            if (discsNumber > 3) {
                discsNumber--;
                updateLabels();
                // clearContentFrame();
                clearBlockArrays();
                initBlocks(discsNumber);
                reDrawContentFrame();
            }

        }
    };
    upButtonListener = new ActionListener() {
        public void actionPerformed(ActionEvent arg0) {
            if (discsNumber < 8) {
                discsNumber++;
                updateLabels();
                // clearContentFrame();
                clearBlockArrays();
                initBlocks(discsNumber);
                reDrawContentFrame();
            }
        }
    };

    solveButtonListener = new ActionListener() {
        public void actionPerformed(ActionEvent arg0) {
            solver.solve(discsNumber, 0, 1, 2);
        }
    };
}


private void updateLabels() {
    discs.setText("DISCS: " + discsNumber);
    moves.setText("MOVES: " + movesNumber);
}


/**
 * Init the main window
 */
private void initWindow() {
    mainWindow = new JFrame("Towers Of Hanoi");
    initContentPanel();
    mainWindow.setContentPane(mainContentPanel);
    mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    mainWindow.setSize(1000, 1000);
    mainWindow.setResizable(false);
    mainWindow.getContentPane().setBackground(Color.WHITE);
}


/**
 * Init the main content panel
 */
private void initContentPanel() {
    mainContentPanel = new JPanel(new BorderLayout(50, 50));
    JPanel menu = initMenuFrame();
    content = initContentFrame();
    mainContentPanel.add(menu, BorderLayout.PAGE_START);
    mainContentPanel.add(content, BorderLayout.CENTER);
}


private static JPanel initContentFrame() {
    JPanel ret = new JPanel(new BorderLayout());
    JPanel pegs = new JPanel(new BorderLayout());
    pegs.setBackground(Color.WHITE);
    ret.setBackground(Color.WHITE);

    Peg peg1 = new Peg(25, 500, 1.2);
    Peg peg2 = new Peg(50, 500, 1.2);
    Peg peg3 = new Peg(0, 500, 1.2);

    peg1.addBlocks(pegOneBlocks);
    peg2.addBlocks(pegTwoBlocks);
    peg3.addBlocks(pegThreeBlocks);

    pegs.add(peg1, BorderLayout.LINE_START);
    pegs.add(peg2, BorderLayout.CENTER);
    pegs.add(peg3, BorderLayout.LINE_END);

    ret.add(pegs, BorderLayout.CENTER);
    return ret;
}


private Color randomColor() {
    int R = (int)(Math.random() * 256);
    int G = (int)(Math.random() * 256);
    int B = (int)(Math.random() * 256);
    Color color = new Color(R, G, B); // random color, but can be bright or
                                      // dull

    // to get rainbow, pastel colors
    Random random = new Random();
    final float hue = random.nextFloat();
    final float saturation = 0.9f;// 1.0 for brilliant, 0.0 for dull
    final float luminance = 1.0f; // 1.0 for brighter, 0.0 for black
    color = Color.getHSBColor(hue, saturation, luminance);
    return color;
}


private void initRandomColors() {
    for (int i = 0; i < 8; i++) {
        randomColors[i] = randomColor();
    }
}


private void initBlocks(int numBlocks) {
    int startWidth = Block.LONGESTWIDTH;
    for (int i = 0; i < numBlocks; i++) {
        Block b = new Block((startWidth - (i * 15)), randomColors[i]);
        pegOneBlocks.add(b);
    }
}


private static void clearContentFrame() {
    mainContentPanel.remove(content);
    mainContentPanel.repaint();
}


private void clearBlockArrays() {
    pegOneBlocks.clear();
    pegTwoBlocks.clear();
    pegThreeBlocks.clear();
}


public static void reDrawContentFrame() {
    content = initContentFrame();
    mainContentPanel.add(content, BorderLayout.CENTER);
    mainContentPanel.repaint();
}


public static void moveTopBlock(int startPin, int destinationPin) {
    Block b = null;
    if (startPin == 0) {
        b = pegOneBlocks.get(pegOneBlocks.size() - 1);
        pegOneBlocks.remove(pegOneBlocks.size() - 1);
    }
    else if (startPin == 1) {
        b = pegTwoBlocks.get(pegTwoBlocks.size() - 1);
        pegTwoBlocks.remove(pegTwoBlocks.size() - 1);
    }
    else if (startPin == 2) {
        b = pegThreeBlocks.get(pegThreeBlocks.size() - 1);
        pegThreeBlocks.remove(pegThreeBlocks.size() - 1);
    }

    if (destinationPin == 0) {
        pegOneBlocks.add(b);
    }
    else if (destinationPin == 1) {
        pegTwoBlocks.add(b);
    }
    else if (destinationPin == 2) {
        pegThreeBlocks.add(b);
    }

    reDrawContentFrame();
    content.validate();
    mainContentPanel.validate();
    mainWindow.validate();
}


/**
 * Build the menu panel
 * 
 * @return menu panel
 */
private JPanel initMenuFrame() {
    JPanel ret = new JPanel(new BorderLayout());
    ret.setPreferredSize(menuSize);

    // left
    JPanel left = new JPanel(new FlowLayout());
    left.setPreferredSize(menuSize);
    JLabel label = new JLabel("DISCS: 3");
    discs = label;
    label.setFont(new Font("Serif", Font.BOLD, DISCSTEXTSIZE));
    Button down = new Button("Decrease");
    down.addActionListener(downButtonListener);
    Button up = new Button("Increase");
    up.addActionListener(upButtonListener);
    left.add(label);
    left.add(up);
    left.add(down);

    // mid
    moves = new JLabel("MOVES: 0");
    moves.setHorizontalAlignment(JLabel.CENTER);
    moves.setFont(new Font("Serif", Font.BOLD, MOVESTEXTSIZE));

    // right
    JPanel right = new JPanel(new FlowLayout());
    Button solve = new Button("Solve");
    solve.addActionListener(solveButtonListener);
    Button reset = new Button("Reset");
    right.add(solve);
    right.add(reset);

    // sync
    JPanel menu = new JPanel(new BorderLayout());
    menu.add(left, BorderLayout.LINE_START);
    menu.add(moves, BorderLayout.CENTER);
    menu.add(right, BorderLayout.LINE_END);

    ret.add(menu, BorderLayout.CENTER);
    return ret;
}
}

1 Ответ

0 голосов
/ 10 ноября 2018
solveButtonListener = new ActionListener() {
    public void actionPerformed(ActionEvent arg0) {
        solver.solve(discsNumber, 0, 1, 2);
    }
};

Проблема в том, что код, вызываемый для любого слушателя, выполняется на Event Dispatch Thread (EDT). EDT отвечает за реагирование на событие и перекрашивание GUI. Метод Thread.sleep () приводит к тому, что EDT переходит в спящий режим, и в результате GUI не может перерисовать себя, пока весь код не завершится.

Что вам нужно сделать, это запустить отдельный Thread при вызове метода solver.solve(...).

Прочтите раздел из учебника по Swing на Параллелизм для получения дополнительной информации.

Обратите внимание, что приведенное выше предложение использовать отдельный поток все еще не является правильным решением. Swing был спроектирован как единый поток, что означает, что все обновления состояния вашего графического интерфейса и перерисовки графического интерфейса должны выполняться на EDT. Так что это будет означать, что вы также должны использовать SwingUtilities.invokeLater () для добавления кода в EDT для обработки. Я никогда не пытался делать это при использовании рекурсии, поэтому я не уверен, что это лучший способ сделать это.

...