使用多线程暂停我的应用程序的行为与预期不符

时间:2018-08-06 18:07:48

标签: java javafx

我正在设计一个匹配的记忆游戏,我几乎完成了所有工作,一切都按预期进行,但是,当用户打开两张不同的卡时,程序不会暂停(等待)几秒钟,所以用户可以看到第二张卡是什么。 我尝试使用long for循环操作,但是遇到了相同的问题。我已经尝试过Thread.sleep,TimeUnit.SECONDS.sleep,Task和Platform.runLater。

程序将打开卡并立即将其关闭,然后等待指定的持续时间,请记住我在打开函数之后和关闭函数之前调用pauseThread。

我尝试了上述建议,但它们没有带我去什么地方,而且我似乎找不到我的代码在哪里或应该在哪里放置pauseThread。预先感谢。

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.TimeUnit;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.*;
import javafx.stage.Stage;

public class MemoryMatchingGame extends Application{
    private static Card selectedCard=null;                  // This is to save a reference for the first card to use in comparison
    private static int numOfCorrectPairs = 0;       // Keeping track of how many cards the user got correct

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {

        String[] images = {"C:\\Users\\userName\\Desktop\\Project#4\\1.png",            // This is a string array to store images locations
                "C:\\Users\\userName\\Desktop\\Project#4\\2.png",
                "C:\\Users\\userName\\Desktop\\Project#4\\3.jpg",
                "C:\\Users\\userName\\Desktop\\Project#4\\4.jpg",
                "C:\\Users\\userName\\Desktop\\Project#4\\5.jpg",
                "C:\\Users\\userName\\Desktop\\Project#4\\6.png",
                "C:\\Users\\userName\\Desktop\\Project#4\\7.jpg",
                "C:\\Users\\userName\\Desktop\\Project#4\\8.jpg"};
        ArrayList<Card> listOfCards = new ArrayList<Card>();

        for(int i=0; i<images.length; i++) {                                            // This for loop will add each image twice to the array list
            listOfCards.add(new Card(images[i]));
            listOfCards.add(new Card(images[i]));
        }
        Collections.shuffle(listOfCards);                                               // Shuffling the deck of cards


        primaryStage.setTitle("Memory Matching Game");
        HBox hb = new HBox();

        VBox firstColoumn = new VBox();
            for(int i=0; i<4; i++) 
                firstColoumn.getChildren().add(listOfCards.get(i));
        VBox secondColoumn = new VBox();
            for(int i=4; i<8; i++) 
                secondColoumn.getChildren().add(listOfCards.get(i));
        VBox thirdColoumn = new VBox();
            for(int i=8; i<12; i++) 
                thirdColoumn.getChildren().add(listOfCards.get(i));
        VBox fourthColoumn = new VBox();
            for(int i=12; i<16; i++) 
                fourthColoumn.getChildren().add(listOfCards.get(i));

        hb.getChildren().addAll(firstColoumn, secondColoumn, thirdColoumn, fourthColoumn);

        Scene scene = new Scene(hb, 460, 450);
        primaryStage.setScene(scene);

        primaryStage.show();
    }

    private class Card extends Button {
        private String imageLocation;       // To store the destination of the image
        private Image img;                  // To store a reference of the image to be used when setting graphic on a button

        public Card(String imageLocation) throws FileNotFoundException {
            this.imageLocation = imageLocation;
            FileInputStream fis = new FileInputStream(imageLocation);
            img = new Image(fis);

            setPrefSize(150, 150);

            setOnMouseClicked(e -> {
                if(isCardOpen()==true)
                    return;             // To ensure no action is made once an image is already opened and the user clicked on it again

                if(selectedCard==null) {// This will test if the user has a card open already for comparison or not, if not it will store a reference to the card to use to compare once another card is opened
                    selectedCard = this;
                    open();
                }
                else {                  // If we enter this statement, this means the user has a card open already and we are ready to perform comparison

                    open();             // First action taken is to reveal the second card then perform comparison

                        if(this.isEqual(selectedCard)) {
                            numOfCorrectPairs++;
                            System.out.println("Got one");
                        }
                        else {
                            //Get program to pause here

                            Hold pauseThread = new Hold();
                            pauseThread.run();

                            System.out.println("After pausing");
                            this.close();
                            selectedCard.close();
                        }

                        selectedCard=null;      // This will nullify the variable so that we are able to perform comparison again for two other cards
                }                       // End of else statement

            });                         // End of actionHandler

            close();                    // This will ensure whenever a card is created it is set face-down
        }


        private void close() {
            setGraphic(null);
        }
        public void open() {
            setGraphic(new ImageView(img));
            System.out.println("Open");
        }

        private boolean isCardOpen() {
            return this.getGraphic()!=null;
        }

        private boolean isEqual(Card selectedCard) {
            return this.imageLocation.equals(selectedCard.imageLocation);
        }
    }

    private class Hold extends Thread{
        public void run() {
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

2 个答案:

答案 0 :(得分:1)

您的代码中的所有内容都在JavaFX Application Thread中运行。您不想暂停此线程,因为它将锁定您的GUI。如前所述,您正在启动另一个线程并使它进入睡眠状态,但这不会增加在JavaFX线程中运行的GUI的延迟。

另一种方法是使用Platform.runLater()。 Hold线程可以在JavaFX线程中调用实现Platform.runLater()可运行的方法。 runnable是一个短lambda,其中包含用于关闭所选卡的代码。时序可能与3000毫秒略有不同,但是JavaFX线程中的操作并不多,对于此应用程序来说似乎并不重要。

这是要尝试的修改。

首先修改Hold类,使其包含要传递到Card对象中的构造函数。然后在卡上调用closeAfterPause()方法。

private class Hold extends Thread {
    private Card card;
    public Hold(Card card) {
        this.card = card;
    }
    public void run() {
        try {
            TimeUnit.SECONDS.sleep(3);
            card.closeAfterPause();

        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

然后在MemoryMatchingGame类中创建closeAfterPause()方法。

 private void closeAfterPause() {
        Platform.runLater(() -> {
            System.out.println("After Pausing");
            close();
            selectedCard.close();
        });
    }

然后按如下所示修改if-else语句的else部分

          else {
                    //Get program to pause here
                    Hold pauseThread = new Hold(this);
                    new Thread(pauseThread).start();
                }

答案 1 :(得分:0)

FX附带了丰富的动画/时间轴支持-无需退回到裸线程。获取wait-for-xx的最简单形式是使用配置有xx的时间轴和一个在准备好时会执行某些操作的actionHandler:

Timeline holdTimer = new Timeline(new KeyFrame(
        Duration.seconds(2),  e -> closeCards()));

同样,最好将控件的所有逻辑集中在外部。实际上,绝对不应出于包含视图无关逻辑的目的而扩展视图。因此,您的长期目标应该是

  • 将Card中的所有单卡逻辑提取到CardModel中,该Model公开f.i之类的属性。图片,id,打开,已处理
  • 使用普通按钮,根据需要配置其属性并将其绑定到模型属性
  • 集中所有游戏逻辑,例如计时,选择,何时允许开放,何时成功进入游戏控制器

因为我不想破坏这样做的乐趣-我将在最后一个项目符号的方向上张贴一些轮廓。迄今为止的职责

  • 提供api来打开一张卡
  • 提供api结束回合:匹配或关闭卡片
  • 内部人员跟踪打开的卡片和时间

这些片段只是稍微重新混合了您的代码,将游戏逻辑从按钮移到了控制器中(又名:这里只是外部类),并设置了按钮的动作处理程序来访问控制器。

private Card firstCard; 
private Card secondCard;

private Timeline holdTimer = new Timeline(new KeyFrame(
        Duration.millis(2000),  e -> closeCards()));

public void closeCards() {
    if (firstCard == null || secondCard == null) {
        System.out.println("error!!");
        return;
    }
    if (firstCard.isEqual(secondCard)) {
        System.out.println("success");
        firstCard.setDisable(true);
        secondCard.setDisable(true);
        firstCard = null;
        secondCard = null;
    } else {
        firstCard.close();
        secondCard.close();
        firstCard = null;
        secondCard = null;
    }
}

public void openCard(Card card) {
    if (card.isCardOpen()) return;
    if (holdTimer.getStatus() == Status.RUNNING) return;
    if (firstCard ==  null) {
        firstCard = card;
        firstCard.open();
    } else if (secondCard == null) {
        secondCard = card;
        secondCard.open();
        holdTimer.playFromStart();
    }
}

// Dont! dont, dont!!! ever extend a Control
//**TBD**: Move open/close state logic into a CardModel
// then configure a plain Button with the properies of that model
private class Card extends Button {
    private String imageLocation;       // To store the destination of the image
   // private Image img;                  // To store a reference of the image to be used when setting graphic on a button

    public Card(String imageLocation) throws FileNotFoundException {
        this.imageLocation = imageLocation;
        setPrefSize(150, 150);
        setOnAction(e -> openCard(this));
    }

    public void close() {
        setText("");
    }

    public void open() {
        setText(imageLocation);
        System.out.println("Open");
    }

    public boolean isCardOpen() {
        return getText() !=  null && getText().length() > 0;//this.getGraphic()!=null;
    }

    private boolean isEqual(Card selectedCard) {
        if (selectedCard == null) return false;
        return this.imageLocation.equals(selectedCard.imageLocation);
    }
}