JavaFX SimpleStringProperty Prism错误

时间:2017-10-27 11:52:54

标签: javafx

我有一个更复杂的应用程序,我在其中渲染文本,以单个字符显示在屏幕上,就像它正在被写入一样。在我的真实应用程序中,事情更复杂,所以我已经简化了这个测试。

我的问题是:我有一个包含SimpleStringProperty的模型。我绑定了这个Text元素。然后通过更改后台线程中的SimpleStringProperty,我期待我的Text元素发生变化。

在较低速度下工作正常。但是在更高的速度下,我得到一个棱镜错误(粘贴在帖子的末尾)。我可以通过将我的SimpleStringProperty的更新移动到Platform.RunLater来解决问题,但这似乎违背了任何类型的MVC架构。我的SimpleStringProperty是我的模型的一部分,而不是我的视图类,它存在于JavaFX线程中。

通过添加Platform.Runlater,事情变得不同步,一些角色被遗漏,因为我可能创建了太多的RunLaters。我已经尝试添加一个信号量和一个changelistener来释放信号量,所以我不会更新,直到RunLater完成。没有快乐。

感谢任何帮助,谢谢。

gg <- ggplot(iris, aes(x=ID, y=Sepal.Length, fill=iris_limits,
                   text=paste("Sepal Length:", Sepal.Length, "cm"))) + 
  geom_bar(stat="identity", position="dodge") +
  facet_wrap(~iris_limits, scales="free_x") +
  theme_minimal() + xlab("") + ylab("Sepal Length (cm)") +
  theme(axis.text.x=element_blank(), legend.title=element_text(size=10)) +
  labs(fill="Sepal \nLength (cm)")
ggplotly(gg, tooltip=c("x", "text"))

当前错误:

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.scene.Scene;
import javafx.scene.layout.GridPane;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class TestCase extends Application {

String s = "The Hobbit is a tale of high adventure, undertaken by a company of dwarves, in search of dragon- \n" +
        "guarded gold. A reluctant partner in this perilous quest is Bilbo Baggins, a comfort-loving, \n" +
        "unambitious hobbit, who surprises even himself by his resourcefulness and his skill as a burglar. \n" +
        "\n" +
        "Encounters with trolls, goblins, dwarves, elves and giant spiders, conversations with the dragon, \n" +
        "Smaug the Magnificent, and a rather unwilling presence at the Battle of the Five Armies are some of \n" +
        "the adventures that befall Bilbo. But there are lighter moments as well: good fellowship, welcome \n" +
        "meals, laughter and song.";

SimpleStringProperty stringProperty = new SimpleStringProperty("");

private void run() {
    new Thread(() -> {
        try {
            for (int i = 0; i < s.length(); i++) {
                Thread.sleep(10); // works at 15ms
                stringProperty.setValue(stringProperty.getValue().concat(s.substring(i, i + 1))); //this line into Platform.runLater -- but out of sync
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }).start();
}

@Override
public void start(Stage primaryStage) throws Exception {
    Text text = new Text();
    text.setWrappingWidth(500);
    text.textProperty().bind(stringProperty);
    GridPane root = new GridPane();
    root.setPrefSize(500,400);
    Scene theScene = new Scene(root);
    primaryStage.setScene(theScene);
    root.getChildren().add(text);
    primaryStage.show();
    run();
}
}

以下是使用RunLater的信号版本:

Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
at com.sun.javafx.text.PrismTextLayout.layout(PrismTextLayout.java:1267)
at com.sun.javafx.text.PrismTextLayout.ensureLayout(PrismTextLayout.java:223)
at com.sun.javafx.text.PrismTextLayout.getBounds(PrismTextLayout.java:246)
at javafx.scene.text.Text.getLogicalBounds(Text.java:358)
at javafx.scene.text.Text.impl_computeLayoutBounds(Text.java:1115)
at javafx.scene.Node$12.computeBounds(Node.java:3225)
at javafx.scene.Node$LazyBoundsProperty.get(Node.java:9308)
at javafx.scene.Node$LazyBoundsProperty.get(Node.java:9278)
at javafx.scene.Node.getLayoutBounds(Node.java:3240)
at javafx.scene.Node.prefHeight(Node.java:2770)
at javafx.scene.Node.minHeight(Node.java:2712)
at javafx.scene.layout.Region.computeChildPrefAreaHeight(Region.java:1762)
at javafx.scene.layout.GridPane.computePrefHeights(GridPane.java:1424)
at javafx.scene.layout.GridPane.layoutChildren(GridPane.java:1690)
at javafx.scene.Parent.layout(Parent.java:1087)
at javafx.scene.Scene.doLayoutPass(Scene.java:552)
at javafx.scene.Scene$ScenePulseListener.pulse(Scene.java:2397)
at com.sun.javafx.tk.Toolkit.lambda$runPulse$30(Toolkit.java:355)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:354)
at com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:381)
at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:510)
at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:490)
at com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$404(QuantumToolkit.java:319)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$148(WinApplication.java:191)
at java.lang.Thread.run(Thread.java:748)

1 个答案:

答案 0 :(得分:1)

由于文本已绑定到StringProperty,因此必须仅更改FX应用程序主题上的字符串属性。否则违反了JavaFX的线程规则,这就是为什么你得到空指针异常(由于某些竞争条件在内部API的某处失败)。

您使用Platform.runLater()的尝试未正确实施。通过将索引变量i移动到实例变量,您将在一个线程(FX应用程序线程,Platform.runLater()内)访问它,但在后台的for循环中修改它线。很容易看出,在调用提交给FX Application Thread的两个连续runnable之间,索引变量可能会多次增加。您应该只在final调用中使用Platform.runLater(...)变量,或者只从FX应用程序线程访问过的变量。以下工作正常:

private void run() {
    new Thread(() -> {
        try {
            for (int i = 0; i < s.length(); i++) {
                Thread.sleep(10); // works at 15ms
                final String append = s.substring(i, i+1);
                Platform.runLater(() -> 
                    stringProperty.setValue(stringProperty.getValue().concat(append))); 
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }).start();
}

每当你像这样定期修改UI时,你应该考虑使用动画API而不是使用后台线程来实现“帧”之间的“暂停”。这里有许多优点:大多数情况下你根本不保存后台线程的创建(即使是引擎盖下的动画实现也没有使用额外的线程),所以你节省了资源并避免了你在你看到的竞争条件的任何可能性试图实施Platform.runLater()。一旦熟悉动画API,我认为代码也变得更容易阅读。

以下是使用Timeline代替线程重新实现示例:

import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.scene.Scene;
import javafx.scene.layout.GridPane;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Duration;

public class TestCase extends Application {

    String s = "The Hobbit is a tale of high adventure, undertaken by a company of dwarves, in search of dragon- \n"
            + "guarded gold. A reluctant partner in this perilous quest is Bilbo Baggins, a comfort-loving, \n"
            + "unambitious hobbit, who surprises even himself by his resourcefulness and his skill as a burglar. \n"
            + "\n"
            + "Encounters with trolls, goblins, dwarves, elves and giant spiders, conversations with the dragon, \n"
            + "Smaug the Magnificent, and a rather unwilling presence at the Battle of the Five Armies are some of \n"
            + "the adventures that befall Bilbo. But there are lighter moments as well: good fellowship, welcome \n"
            + "meals, laughter and song.";

    SimpleStringProperty stringProperty = new SimpleStringProperty("");



    @Override
    public void start(Stage primaryStage) throws Exception {
        Text text = new Text();
        text.setWrappingWidth(500);
        GridPane root = new GridPane();
        root.setPrefSize(500, 400);
        Scene theScene = new Scene(root);
        primaryStage.setScene(theScene);
        root.getChildren().add(text);
        primaryStage.show();

        // Number of characters displayed in text:
        IntegerProperty textLength = new SimpleIntegerProperty(0);

        // "Animate" number of characters from 0 to total length of text,
        // over a total of 10 seconds:
        Timeline animation = new Timeline(
            new KeyFrame(Duration.seconds(10), 
                new KeyValue(textLength, s.length())));

        // ensure text displays the appropriate substring of s:
        text.textProperty().bind(Bindings.createStringBinding(
            () -> s.substring(0, textLength.get()), 
            textLength));

        // start the animation:
        animation.play();
    }
    public static void main(String[] args ) {
        launch(args);
    }
}

另请参阅Javadocs for Transition中的示例,该示例非常相似。