gpt4 book ai didi

JavaFX - 如何同步 FXML 文件中声明的两个元素的滚动条

转载 作者:行者123 更新时间:2023-11-30 03:25:21 26 4
gpt4 key购买 nike

我正在开发 IDE,但我遇到了以下问题:

我想创建TabPane,加载到程序中的某个位置(到VBox),然后使用一些addFile hanler添加TAB并通过code_workspace.fxml文件构建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(() -> {});,但它也没有帮助。

各位,请问这个问题的正确解决方案是什么?

*添加:有一个非常有趣的行为 - 当我添加第一个选项卡时,它正常运行,但是当我将第二个或更多选项卡添加到程序集合中时,大约 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)

最佳答案

查找(这实际上是访问文本区域中滚动条的唯一方法)仅在场景图中渲染该节点后才在该节点上工作(基本上,需要首先将 CSS 样式应用于该节点) 。因此,在 Controller 的 initialize() 方法中使用它们很棘手,因为该方法必须在 FXML 根返回给 FXMLLoader.load() 方法的调用者之前执行(因此在将其添加到场景图中之前)。

我认为发生的情况是 Platform.runLater(...) 在启动时成功,因为场景尚未渲染,因此 Platform .runLater(...) 在挂起的 UI 处理(包括渲染第一个场景)之后安排可运行的任务。在以后的调用中,它可能仅取决于当前帧渲染周期内何时执行此操作(因此它看起来有些随机)。

对于您的特定用例(显示行号),我建议您查看使用第三方库 RichTextFX托马斯·米库拉。

要更普遍地做到这一点是相当棘手的。确保在执行代码之前渲染一帧(或两帧)的最安全方法是使用 AnimationTimer 。这个类的 handle() 方法在每次渲染帧时都会执行一次,并且在运行时执行。所以基本的想法是计算帧数,一旦渲染了 1 个(或 2 个,如果你想真正安全的话)帧,然后进行查找(并停止动画计时器,因为不再需要它)。这有点像黑客(但是,一般来说使用查找也有点像黑客)。

这是一个作为单个类的示例(您可以对 FXML 和 Controller 使用相同的想法,为了简单起见,我这样做了):

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 .

关于JavaFX - 如何同步 FXML 文件中声明的两个元素的滚动条,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30357217/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com