我正在开发IDE,我只是遇到了以下问题:
我想创建TabPane,加载到程序中的某个地方(到VBox),然后通过code_workspace.fxml文件添加TAB使用一些addFile hanler和BUILD TAB内容(这一切现在都在工作)。但我只想同步两个存储在同一FXML文件中的元素的滚动条。具体来说,这是ScrollPane(code_line_counter)和TextArea(code_text_area)的滚动条。
我有一年的Java学习经历,所以我欢迎你给我的每一条建议。正如你所看到的,我的英语不是很好,所以我想你会从代码中读到很多信息:
FileManager.java
package sample;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import java.net.URL;
import java.util.ResourceBundle;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class FileManager extends TabPane implements Initializable {
@FXML private ScrollPane code_line_counter;
@FXML private TextArea code_text_area;
@FXML private Label code_line_counter_label;
private int created_tabs;
public FileManager()
{
super();
super.setTabClosingPolicy(TabPane.TabClosingPolicy.ALL_TABS);
this.created_tabs = 0;
}
@Override
public void initialize(URL location, ResourceBundle resources)
{
code_text_area.textProperty().addListener((observable, oldValue, newValue) -> {
Matcher matcher_old_value = Pattern.compile("\n").matcher(oldValue);
Matcher matcher_new_value = Pattern.compile("\n").matcher(newValue);
int old_line_count = 0;
int new_line_count = 0;
while (matcher_old_value.find()) {
old_line_count++;
}
while (matcher_new_value.find()) {
new_line_count++;
}
if (new_line_count != old_line_count) {
code_line_counter_label.setText("1");
for (int i = 2; i < new_line_count + 2; i++)
code_line_counter_label.setText(code_line_counter_label.getText() + "\n" + i);
}
});
Platform.runLater(() -> {
ScrollBar s1 = (ScrollBar) code_line_counter.lookup(".scroll-bar:vertical");
ScrollBar s2 = (ScrollBar) code_text_area.lookup(".scroll-bar:vertical");
s2.valueProperty().bindBidirectional(s1.valueProperty());
});
}
public boolean addFile (StackPane work_space, VBox welcome_screen, NotificationManager notification_manager)
{
Tab new_tab = new Tab("Untitled " + (this.created_tabs + 1));
try {
FXMLLoader fxml_loader = new FXMLLoader(getClass().getResource("elements/code_workspace.fxml"));
new_tab.setContent(fxml_loader.load());
} catch (Exception e) {
Notification fxml_load_error = new Notification("icons/notifications/default_warning.png");
notification_manager.addNotification(fxml_load_error);
return false;
}
new_tab.setOnClosed(event -> {
if (super.getTabs().isEmpty()) {
this.created_tabs = 0;
work_space.getChildren().clear();
work_space.getChildren().add(welcome_screen);
}
});
super.getTabs().add(new_tab);
super.getSelectionModel().select(new_tab);
this.created_tabs++;
return true;
}
}
code_workspace.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import java.net.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<VBox xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.FileManager">
<children>
<HBox minHeight="-Infinity" styleClass="search-bar" VBox.vgrow="NEVER" />
<HBox VBox.vgrow="ALWAYS">
<children>
<ScrollPane id="code_line_counter" fx:id="code_line_counter" fitToWidth="true" focusTraversable="false" hbarPolicy="NEVER" minWidth="-Infinity" vbarPolicy="NEVER" HBox.hgrow="NEVER">
<content>
<Label fx:id="code_line_counter_label" alignment="TOP_LEFT" focusTraversable="false" nodeOrientation="RIGHT_TO_LEFT" text="1" />
</content>
</ScrollPane>
<TextArea fx:id="code_text_area" focusTraversable="false" promptText="Let's code!" HBox.hgrow="ALWAYS" />
</children>
</HBox>
</children>
<stylesheets>
<URL value="@../styles/ezies_theme.css" />
<URL value="@../styles/sunis_styles.css" />
</stylesheets>
</VBox>
我尝试了很多代码部分:
ScrollBar s1 = (ScrollBar) code_line_counter.lookup(".scroll-bar:vertical");
ScrollBar s2 = (ScrollBar) code_text_area.lookup(".scroll-bar:vertical");
s2.valueProperty().bindBidirectional(s1.valueProperty());
但没有什么能帮助我。我也尝试删除Platform.runLater(() -> {});
,但它也没有帮助。
伙计们,请问这个问题的正确解决方案是什么?
* ADDITION: 有一个非常有趣的行为 - 当我添加第一个标签时,它运行正常,但是当我在程序集中添加第二个或更多标签时,大约70%的情况下我带来了以下异常:
Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
at sample.FileManager.lambda$initialize$1(FileManager.java:75)
at sample.FileManager$$Lambda$294/1491808102.run(Unknown Source)
at com.sun.javafx.application.PlatformImpl.lambda$null$170(PlatformImpl.java:295)
at com.sun.javafx.application.PlatformImpl$$Lambda$50/1472939626.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater$171(PlatformImpl.java:294)
at com.sun.javafx.application.PlatformImpl$$Lambda$49/1870453520.run(Unknown Source)
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$145(WinApplication.java:101)
at com.sun.glass.ui.win.WinApplication$$Lambda$38/123208387.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
答案 0 :(得分:2)
查找(实际上是访问文本区域中滚动条的唯一方法)仅在场景图中呈现该节点后才能在节点上工作(基本上,CSS样式需要首先应用于节点) 。因此在控制器的initialize()
方法中使用它们很棘手,因为该方法必须在将FXML根返回给FXMLLoader.load()
方法的调用者之前执行(因此在将其添加到场景图之前) 。
我认为正在发生的事情是Platform.runLater(...)
在启动时成功,因为场景尚未完全渲染,因此Platform.runLater(...)
在挂起的UI处理后调度runnable(包括渲染第一个场景)。在以后的调用中,它可能只取决于何时在当前帧渲染周期内执行此操作(因此它看起来有点随机)。
对于您的特定用例(显示行号),我建议您查看使用Tomas Mikula的第三方库RichTextFX。
要做到这一点,更普遍是非常棘手的。确保在执行代码之前渲染帧(或两个)的最安全方法是使用AnimationTimer
。这是一个类,handle()
方法在每次渲染帧时执行一次,同时它正在运行。所以基本的想法是计算帧数,一旦1(或2,如果你想要真正安全)帧已被渲染,然后进行查找(并停止动画计时器,因为它不再需要)。这有点像黑客攻击(但是,使用查找通常是一种黑客攻击)。
以下是一个单独的类示例(您可以使用与FXML和控制器相同的想法,我只是为了简单起见这样做):
import static java.util.stream.Collectors.toList;
import java.util.Random;
import java.util.stream.IntStream;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;
public class SynchronizedTextAreaScrolling extends Application {
private static final Random RNG = new Random();
@Override
public void start(Stage primaryStage) {
TabPane tabs = new TabPane();
Tab tab = new Tab("Tab 1");
tab.setContent(createTextAreas());
tabs.getTabs().add(tab);
Button addTab = new Button("New");
addTab.setOnAction(e -> {
Tab newTab = new Tab("Tab "+(tabs.getTabs().size()+1));
newTab.setContent(createTextAreas());
tabs.getTabs().add(newTab);
tabs.getSelectionModel().select(newTab);
});
BorderPane root = new BorderPane(tabs, null, null, addTab, null);
primaryStage.setScene(new Scene(root, 600, 400));
primaryStage.show();
}
private HBox createTextAreas() {
TextArea lineNumbers = new TextArea();
int numLines = RNG.nextInt(20) + 20 ;
lineNumbers.setText(String.join("\n", IntStream.rangeClosed(1, numLines).mapToObj(Integer::toString).collect(toList())));
TextArea text = new TextArea();
text.setText(String.join("\n", IntStream.rangeClosed(1, numLines).mapToObj(i -> "Line "+i).collect(toList())));
lineNumbers.setMinWidth(40);
lineNumbers.setPrefWidth(60);
HBox.setHgrow(lineNumbers, Priority.NEVER);
HBox.setHgrow(text, Priority.ALWAYS);
// An AnimationTimer, whose handle(...) method will be invoked once
// on each frame pulse (i.e. each rendering of the scene graph)
AnimationTimer timer = new AnimationTimer() {
// count number of frames rendered since the timer was started:
private int frameCount = 0 ;
@Override
public void handle(long now) {
frameCount++ ;
// wait for the second frame. This should ensure that
// the text areas have been rendered to the scene and so
// they have had CSS applied. This allows us to use
// lookups on them
if (frameCount >= 2) {
ScrollBar sb1 = (ScrollBar) lineNumbers.lookup(".scroll-bar:vertical");
ScrollBar sb2 = (ScrollBar) text.lookup(".scroll-bar:vertical");
sb1.valueProperty().bindBidirectional(sb2.valueProperty());
// this animation timer is no longer needed, so stop it:
stop();
}
}
};
timer.start();
return new HBox(lineNumbers, text);
}
public static void main(String[] args) {
launch(args);
}
}
但是,对于这个特定用途,我会看一下RichTextFX。