GUI更新有线程错误 [英] GUI update with threads error

查看:69
本文介绍了GUI更新有线程错误的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我用javafx做棋盘游戏,偶然发现一个问题.我的应用程序具有客户端/服务器连接.每当服务器发送有关玩家将棋子移动到何处的数据时,我都会调用一个函数pawnPawn()来移动棋子,并调用另一个函数refresh()来重新绘制棋盘和棋子.不幸的是,我收到错误消息,说该函数是从不是FX的线程调用的,因此无法继续执行.我尝试了一些任务和Platform.runLater,但要么做错了,要么不起作用.有人可以帮忙吗?

I make board game in javafx and I stumbled upon a problem. My application has Client/Server connection. Whenever server sends data about where player moved his pawn I call a function movePawn() that moves pawn and call another function refresh() that repaints board and pawns. unfortunatelly I get error saying that function is called from thread that is not FX and therefore cannot proceed. I tried tasks and Platform.runLater but either I'm doing it wrong or it doesnt work. Can anyone help?

如果您需要其他任何类代码,也可以在此处发布控制器代码:

Here is code of controller if you want any of other class codes I can post them as well:

package Client;

import javafx.application.Platform;
import javafx.concurrent.*;
import javafx.event.ActionEvent;
import javafx.fxml.*;
import javafx.geometry.Pos;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.effect.Lighting;
import javafx.scene.image.Image;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Polygon;
import javafx.stage.Modality;
import javafx.stage.Stage;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;

/*
 *  TODO: Finish implementing movement
 *  TODO: Write startTurn()
 */
public class Controller implements Initializable
{
    private Game game;
    private Client client;
    private List<Circle> pawnsGUI = new ArrayList();
    private int playerNum = -1;
    private int currentX;
    private int currentY;

    @FXML MenuItem twoPlayers;
    @FXML MenuItem threePlayers;
    @FXML MenuItem fourPlayers;
    @FXML MenuItem sixPlayers;
    @FXML MenuItem startGame;

    @FXML AnchorPane mainPane;
    @FXML GridPane boardGrid;
    @FXML MenuItem exitMI;
    @FXML Button endTurnB;
    @FXML Label redPoints, bluePoints, greenPoints, yellowPoints, blackPoints, whitePoints;



    @Override // Initializer for our GUI Controller
    public void initialize(URL location, ResourceBundle resources)
    {
        connectToServer();
        boardGrid.setAlignment(Pos.CENTER);
    }

    public void connectToServer()
    {
        client = new Client(this);
        client.start();
    }

    // Refreshing board
    private void refresh()
    {
        boardGrid.getChildren().clear();

        for(int i = 0; i <= 18; i++)
        {
            for(int j = 0; j <= 14; j++)
            {
                if(game.getBoard().getField(i, j).getClass() == AccessibleField.class)
                {
                    if(i % 2 == 1)
                    {
                        boardFill(i, j, false);
                    }
                    else
                    {
                        boardFill(i, j, true);
                    }
                }
                else if(game.getBoard().getField(i, j).getClass() == WinningField.class)
                {
                    if(i % 2 == 1)
                    {
                        boardFill(i, j, false);
                    }
                    else
                    {
                        boardFill(i, j, true);
                    }
                }
            }
        }

        fillPawns();
    }

    // Filling board with proper colored pawns and fields
    private void boardFill(int i, int j, boolean shifted)
    {
        Polygon poly = new Polygon(27*(1), 27*(0),
                27*(0.5), 27*(0.86602540378),
                27*(-0.5), 27*(0.86602540378),
                27*(-1), 27*(0),
                27*(-0.5), 27*(-0.86602540378),
                27*(0.5), 27*(-0.86602540378));
        if(shifted)
        {
            poly.translateYProperty().set(-23);
        }
        poly.setOnMouseClicked(e -> fieldClicked(i, j));
        if(game.getBoard().getField(i, j).getClass() == WinningField.class)
        {
            for(int var = 0; var < game.getPlayers().length; var++)
            {
                if(game.getBoard().getField(i, j).getOwner() != null){
                    if(game.getBoard().getField(i, j).getOwner().equals(game.getPlayers()[var]))
                {
                    switch (var) {
                        case 0:
                            poly.setFill(Paint.valueOf("RED"));
                            break;
                        case 1:
                            poly.setFill(Paint.valueOf("BLUE"));
                            break;
                        case 2:
                            poly.setFill(Paint.valueOf("GREEN"));
                            break;
                        case 3:
                            poly.setFill(Paint.valueOf("YELLOW"));
                            break;
                        case 4:
                            poly.setFill(Paint.valueOf("#4f4f4f"));
                            break;
                        case 5:
                            poly.setFill(Paint.valueOf("WHITE"));
                            break;

                        }
                    }
                }
            }
        }
        else
        {
            poly.setFill(Paint.valueOf("#d6d6d6"));
        }

        poly.setStroke(Paint.valueOf("BLACK"));
        boardGrid.add(poly, i, j);
    }

    private void fieldClicked(int x, int y)
    {
        for(int i = 0; i < pawnsGUI.size(); i++)
        {
            if (pawnsGUI.get(i).getEffect() != null)
            {
                movePawn(currentX, currentY, x, y);
                client.sendMessage("M "+currentX+" "+currentY+" "+x+" "+y+" "+playerNum);
                endTurn();
            }
        }
    }

    private void fillPawns()
    {
        for(int i = 0; i < game.getPlayers().length ; i++)
        {
            for(int j = 0; j < 10; j++)
            {
                int x = game.getPlayers()[i].getPawns().get(j).getCoordinateX();
                int y = game.getPlayers()[i].getPawns().get(j).getCoordinateY();

                Circle circle = new Circle(15);
                if(x % 2 != 1)
                {
                    circle.translateYProperty().set(-23);
                }
                circle.translateXProperty().set(14);
                pawnsGUI.add(circle);
                circle.setOnMouseClicked(event -> pawnClicked(circle, x, y));

                switch (i) {
                    case 0:
                        circle.setFill(Paint.valueOf("RED"));
                        break;
                    case 1:
                        circle.setFill(Paint.valueOf("BLUE"));
                        break;
                    case 2:
                        circle.setFill(Paint.valueOf("GREEN"));
                        break;
                    case 3:
                        circle.setFill(Paint.valueOf("YELLOW"));
                        break;
                    case 4:
                        circle.setFill(Paint.valueOf("BLACK"));
                        break;
                    case 5:
                        circle.setFill(Paint.valueOf("WHITE"));
                        break;
                }
                circle.setStroke(Paint.valueOf("BLACK"));
                boardGrid.add(circle, x, y);
            }
        }
    }

    private void pawnClicked(Circle circle, int x, int y)
    {
        if(game.getBoard().getField(x, y).getPawn().getOwner().equals(game.getPlayers()[playerNum]))
        {
            // Clear effects for other pawns
            for(int i = 0; i < pawnsGUI.size(); i++)
            {
                pawnsGUI.get(i).setEffect(null);
            }
            currentX = x;
            currentY = y;
            // Set effect for this pawn
            Lighting lighting = new Lighting();
            circle.setEffect(lighting);
        }
    }

    public void startTurn()
    {
        mainPane.setDisable(false);
    }

    public void setPlayerNum(int playerNum)
    {
        this.playerNum = playerNum;
    }

    public void movePawn(int x1, int y1, int x2, int y2)
    {
        Pawn pawnTemp = game.getBoard().getField(x1, y1).getPawn();
        game.getBoard().getField(x1, y1).setPawn(null);
        game.getBoard().getField(x2, y2).setPawn(pawnTemp);

        refresh();
    }

    @FXML
    public void newGame(ActionEvent e)
    {
        GameDirector director = new GameDirector();
        GameBuilder builder;

        if(e.getSource().equals(twoPlayers))
        {
            builder = new CCBoard2P();
            client.sendMessage("I 2");
        }
        else if(e.getSource().equals(threePlayers))
        {
            builder = new CCBoard3P();
            client.sendMessage("I 3");
        }
        else if(e.getSource().equals(fourPlayers))
        {
            builder = new CCBoard4P();
            client.sendMessage("I 4");
        }
        else
        {
            builder = new CCBoard6P();
            client.sendMessage("I 6");
        }

        director.setBuilder(builder);
        director.createGame();
        game = builder.setupGame();

        startGame.setDisable(true);
        mainPane.setDisable(true);

        refresh();
    }

    @FXML // EXIT menu item handler (exits game)
    public void exitHandler()
    {
        if(client.isAlive())
        {
            client.sendMessage("END");
        }
        System.exit(0);
    }

    @FXML // END TURN button handler (increments all score values)
    public void endTurn()
    {
        System.out.println("Turn passed... \n");

        if(!boardGrid.getChildren().isEmpty())
        {
            refresh();
            mainPane.setDisable(true);
        }
    }

    @FXML // RULES manu item handler (creates dialog window with rules)
    public void rulesHandler()
    {
        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/FXML/Rules.fxml"));
        Parent root = null;
        try
        {
            root = fxmlLoader.load();
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }

        Stage rulesDialog = new Stage();
        rulesDialog.setTitle("Rules");
        rulesDialog.getIcons().add(new Image(getClass().getResourceAsStream("icon.jpg")));
        rulesDialog.initModality(Modality.APPLICATION_MODAL);
        rulesDialog.setScene(new Scene(root));
        rulesDialog.setResizable(false);
        rulesDialog.show();
    }

    @FXML // AUTHOR manu item handler (creates dialog window with authors)
    public void authorsHandler()
    {
        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/FXML/Authors.fxml"));
        Parent root = null;
        try
        {
            root = fxmlLoader.load();
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }

        Stage authorsDialog = new Stage();
        authorsDialog.setTitle("Authors");
        authorsDialog.getIcons().add(new Image(getClass().getResourceAsStream("icon.jpg")));
        authorsDialog.initModality(Modality.APPLICATION_MODAL);
        authorsDialog.setScene(new Scene(root));
        authorsDialog.setResizable(false);
        authorsDialog.show();
    }
}

这是错误:

Exception in thread "Thread-3" java.lang.IllegalStateException: Not on FX 
application thread; currentThread = Thread-3
at javafx.graphics/com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:291)
at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:423)
at javafx.graphics/javafx.scene.Parent$3.onProposedChange(Parent.java:493)
at javafx.base/com.sun.javafx.collections.VetoableListDecorator.clear(VetoableListDecorator.java:294)
at Client.Controller.refresh(Controller.java:72)
at Client.Controller.movePawn(Controller.java:251)
at Client.Client.getMessage(Client.java:80)
at Client.Client.run(Client.java:45)

推荐答案

很显然,Client.run()正在后台线程上执行,并调用Controller.movePawn(),后者(通过refresh())更新了UI.您不能在后台线程上更新UI.您需要在Platform.runLater()中包装用于更新UI的代码.因此,如果没有完整的示例很难说清,但是以该特定顺序的方法调用为例,看来您需要这样做

Clearly Client.run() is being executed on a background thread, and calls Controller.movePawn(), which (via refresh()) updates the UI. You cannot update the UI on a background thread. You need to wrap the code that updates the UI in Platform.runLater(). So, it's pretty hard to tell without a complete example, but using that particular sequence of method calls as an example, it looks like you need to do

public void movePawn(int x1, int y1, int x2, int y2) {
    Platform.runLater(() -> {
        Pawn pawnTemp = game.getBoard().getField(x1, y1).getPawn();
        game.getBoard().getField(x1, y1).setPawn(null);
        game.getBoard().getField(x2, y2).setPawn(pawnTemp);

        refresh();
    });
}

整个代码中可能存在类似的问题,但最重要的是您:

There are probably similar issues throughout the code, but the bottom line is that you:

  1. 必须 执行更新FX应用程序线程上的UI的代码
  2. 不得 执行在FX Application线程上阻塞(或花费相当长的时间来运行)的代码.

这篇关于GUI更新有线程错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆