Calling the getDocument() method on the WebEngine object for me only returns the source retrieved from the server, without the JavaScript being executed (there are still elements). This is the kind of source you would see if you used "View Source" in Chrome. How do I retrieve the interpreted source with the JavaScript already run?
public Browser() {
WebView browser = new WebView();
final WebEngine webEngine = browser.getEngine();
webEngine.load("*******************************");
webEngine.getLoadWorker().stateProperty().addListener(new ChangeListener<State>() {
#Override
public void changed(ObservableValue<? extends State> ov, State oldState, State newState) {
if (newState == State.SUCCEEDED) {
Document doc = webEngine.getDocument();
printDocument(doc);
}
}
});
}
This works as expected for me. In this example, the div contains a text node with the text that is set by the Javascript function:
import javafx.application.Application;
import javafx.concurrent.Worker;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class WebViewOnLoadExample extends Application {
#Override
public void start(Stage primaryStage) {
WebView webView = new WebView();
webView.getEngine()
.getLoadWorker()
.stateProperty()
.addListener((obs, oldState, newState) -> {
if (newState == Worker.State.SUCCEEDED) {
Document doc = webView.getEngine().getDocument();
showNodeContent(doc, 0);
}
});
BorderPane root = new BorderPane(webView);
primaryStage.setScene(new Scene(root, 600, 400));
primaryStage.show();
webView.getEngine().loadContent("<html>"
+"<head><script>"
+"function setText() {"
+" document.getElementById(\"target\").appendChild(document.createTextNode(\"Hello World\"));"
+"}"
+"</script></head>"
+"<body onload='setText()'>"
+"<div id='target'></div></body></html>");
}
private void showNodeContent(Node n, int depth) {
for (int i=0; i<depth; i++) {
System.out.print(" ");
}
System.out.println(n.getNodeName()+":"+n.getNodeValue());
NodeList children = n.getChildNodes() ;
for (int i=0; i<children.getLength(); i++) {
showNodeContent(children.item(i), depth+1);
}
}
public static void main(String[] args) {
launch(args);
}
}
The problem you are facing is the following: the LoadWorker's state is set to SUCCEEDED before JavaScript is done running. JavaScript does in fact run (as shown in #James_D's reply) but there is no callback to signal when it finishes. AFAIK, there is no reliable way to detect when the WebEngine is done executing JS.
What you could do as a workaround is play a PauseTransition after the state changes to SUCCEEDED, which can be abused to act like a sleep on the JavaFX thread (JS is executed in the background thread that also loads the Document, so JS will not pause). However, sleeping (to wait for JS to finish) is inherently a violation of JavaFX's core principle never to block the UI thread. On top of that, waiting for a period of time does not guarantee that JS is done executing before that period passes.
I've faced the same problem and I have not found a decent solution. Let me know if you do!
I'm not sure if I'm getting your question right, but if you are looking for a way go print the visible content of the web you are loading, getting the DocumentElement from Document will allow you to dive into its structure and filter what you need.
This method will print the content of the desired tags:
private void printElement(Element el, int level){
NodeList childNodes = el.getChildNodes();
for(int j=0; j<level; j++) System.out.print("-");
System.out.print("tag: "+el.getNodeName());
if(el.getNodeName().equals("A")){
System.out.print(", content: "+el.getTextContent());
}
System.out.println("");
for(int i=0; i<childNodes.getLength(); i++){
Node item = childNodes.item(i);
if(item instanceof Element){
printElement((Element)item, level++);
}
}
}
so once you've loaded the URL, just call it:
if(newState==State.SUCCEEDED){
Document doc = webEngine.getDocument();
Element el = doc.getDocumentElement();
printElement(el,0);
}
This will print all the DOM tags with their level of indentation, and for the tag specified, it will print also the content. In this case, with the tag "A" it will print the content of all the links.
I'm not sure if this will help. Please clarify your question otherwise.
Related
I have a JavaFX application that gets CPU and Memory problems after the computer is going to sleep or hibernate.
In the application, I use a Canvas that is painted two times a second. This may cause issues. I am wondering if it's possible to detect when the computer is sleeping, and not repaint it. Maybe the canvas.isVisible() is already checking this?
You could only paint if your app has focus.
This can be achieved by pausing the animation when your application's main window no longer has focus.
You can monitor the stage's focusedProperty() to find out when the stage has focus.
I ran some tests on a Mac (OS X 12.3) with JavaFX 18 and found that when the computer is put to sleep (click the apple icon in the menu bar and select Sleep), the focus is removed from the application, which allows the animation for the application to be paused while it does not have focus.
Example application
import javafx.animation.*;
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
public class SleepyApp extends Application {
private long start;
private Counter continuousCounter = new Counter();
private Counter focusedCounter = new Counter();
#Override
public void start(Stage stage) throws Exception {
VBox layout = new VBox(10,
focusedCounter.getCounterLabel(),
continuousCounter.getCounterLabel()
);
layout.setPrefSize(80, 80);
layout.setAlignment(Pos.CENTER);
stage.setScene(new Scene(layout));
stage.show();
stage.focusedProperty().addListener((observable, wasFocused, isFocused) -> {
if (isFocused) {
focusedCounter.play();
} else {
focusedCounter.pause();
}
});
continuousCounter.play();
focusedCounter.play();
start = System.currentTimeMillis();
}
#Override
public void stop() throws Exception {
long stop = System.currentTimeMillis();
continuousCounter.stop();
focusedCounter.stop();
long elapsed = (stop - start) / 1_000;
System.out.println("Elapsed: " + elapsed + " seconds.");
System.out.println("Continuous Counter: " + continuousCounter.getCount() + " seconds.");
System.out.println("Focused Counter: " + focusedCounter.getCount() + " seconds.");
}
public static void main(String[] args) {
launch(args);
}
class Counter {
private final Timeline timeline;
private final IntegerProperty count;
private final Label counterLabel;
public Counter() {
count = new SimpleIntegerProperty(0);
counterLabel = new Label(count.asString().get());
counterLabel.textProperty().bind(
count.asString()
);
counterLabel.setStyle("-fx-font-size: 20px");
timeline = new Timeline(
new KeyFrame(
Duration.seconds(1),
e -> count.set(count.get() + 1)
)
);
timeline.setCycleCount(Timeline.INDEFINITE);
}
public int getCount() {
return count.get();
}
public Label getCounterLabel() {
return counterLabel;
}
public void play() {
timeline.play();
}
public void pause() {
timeline.pause();
}
public void stop() {
timeline.stop();
}
}
}
Output
In this case, the computer was put to sleep for 16 seconds.
The first number is the number of one per second animation frames rendered only when the application has focus.
The second number is the number of one per second animation frames rendered regardless of whether the application has focus (and sleep state).
Elapsed: 30 seconds.
Continuous Counter: 30 seconds.
Focused Counter: 14 seconds.
On isVisible()
node.isVisible() is not applicable for this case.
This is a description of the isVisible method:
Specifies whether this Node and any subnodes should be rendered as part of the scene graph. A node may be visible and yet not be shown in the rendered scene if, for instance, it is off the screen or obscured by another Node. Invisible nodes never receive mouse events or keyboard focus and never maintain keyboard focus when they become invisible.
As the documentation indicates, the node may be visible but not shown in the rendered scene, which will be the case when the computer is sleeping.
Problem
I want to add custom made panels, built via javafx scene builder, to a gridpane at runtime. My custom made panel exsits of buttons, labels and so on.
My Attempt
I tried to extend from pane...
public class Celli extends Pane{
public Celli() throws IOException{
Parent root = FXMLLoader.load(getClass().getResource("Cell.fxml"));
this.getChildren().add(root);
}
}
... and then use this panel in the adding method of the conroller
#FXML
private void textChange(KeyEvent event) {
GridPane g = new GridPane();
for (int i=0 : i<100; i++){
g.getChildren().add(new Celli());
}
}
}
It works, but it performs very very poor.
What I am looking for
Is there a way to design panels via javafx scene builder (and as a result having this panels in fxml) and then add it to a gridpane at runtime without make use of this fxmlloader for each instance. I think it performs poor because of the fxml loader. When I add a standard button e.g. whitout fxml it is very much faster.
Short answer: No, it is not (as of JavaFX 2.x and 8.0). It may be in a future version (JFX >8)
Long answer:
The FXMLLoader is currently not designed to perform as a template provider that instantiates the same item over and over again. Rather it is meant to be a one-time-loader for large GUIs (or to serialize them).
The performance is poor because depending on the FXML file, on each call to load(), the FXMLLoader has to look up the classes and its properties via reflection. That means:
For each import statement, try to load each class until the class could successfully be loaded.
For each class, create a BeanAdapter that looks up all properties this class has and tries to apply the given parameters to the property.
The application of the parameters to the properties is done via reflection again.
There is also currently no improvement for subsequent calls to load() to the same FXML file done in the code. This means: no caching of found classes, no caching of BeanAdapters and so on.
There is a workaround for the performance of step 1, though, by setting a custom classloader to the FXMLLoader instance:
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
public class MyClassLoader extends ClassLoader{
private final Map<String, Class> classes = new HashMap<String, Class>();
private final ClassLoader parent;
public MyClassLoader(ClassLoader parent) {
this.parent = parent;
}
#Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
Class<?> c = findClass(name);
if ( c == null ) {
throw new ClassNotFoundException( name );
}
return c;
}
#Override
protected Class<?> findClass( String className ) throws ClassNotFoundException {
// System.out.print("try to load " + className);
if (classes.containsKey(className)) {
Class<?> result = classes.get(className);
return result;
} else {
try {
Class<?> result = parent.loadClass(className);
// System.out.println(" -> success!");
classes.put(className, result);
return result;
} catch (ClassNotFoundException ignore) {
// System.out.println();
classes.put(className, null);
return null;
}
}
}
// ========= delegating methods =============
#Override
public URL getResource( String name ) {
return parent.getResource(name);
}
#Override
public Enumeration<URL> getResources( String name ) throws IOException {
return parent.getResources(name);
}
#Override
public String toString() {
return parent.toString();
}
#Override
public void setDefaultAssertionStatus(boolean enabled) {
parent.setDefaultAssertionStatus(enabled);
}
#Override
public void setPackageAssertionStatus(String packageName, boolean enabled) {
parent.setPackageAssertionStatus(packageName, enabled);
}
#Override
public void setClassAssertionStatus(String className, boolean enabled) {
parent.setClassAssertionStatus(className, enabled);
}
#Override
public void clearAssertionStatus() {
parent.clearAssertionStatus();
}
}
Usage:
public static ClassLoader cachingClassLoader = new MyClassLoader(FXMLLoader.getDefaultClassLoader());
FXMLLoader loader = new FXMLLoader(resource);
loader.setClassLoader(cachingClassLoader);
This significantly speeds up the performance. However, there is no workaround for step 2, so this might still be a problem.
However, there are already feature requests in the official JavaFX jira for this. It would be nice of you to support this requests.
Links:
FXMLLoader should be able to cache imports and properties between to load() calls:
https://bugs.openjdk.java.net/browse/JDK-8090848
add setAdapterFactory() to the FXMLLoader:
https://bugs.openjdk.java.net/browse/JDK-8102624
I have had a similar issue. I also had to load a custom fxml-based component several times, dynamically, and it was taking too long. The FXMLLoader.load method call was expensive, in my case.
My approach was to parallelize the component instantiation and it solved the problem.
Considering the example posted on the question, the controller method with multithread approach would be:
private void textChange(KeyEvent event) {
GridPane g = new GridPane();
// creates a thread pool with 10 threads
ExecutorService threadPool = Executors.newFixedThreadPool(10);
final List<Celli> listOfComponents = Collections.synchronizedList(new ArrayList<Celli>(100));
for (int i = 0; i < 100; i++) {
// parallelizes component loading
threadPool.execute(new Runnable() {
#Override
public void run() {
listOfComponents.add(new Celli());
}
});
}
// waits until all threads completion
try {
threadPool.shutdown();
threadPool.awaitTermination(3, TimeUnit.SECONDS);
} catch (InterruptedException e) {
// seems to be a improbable exception, but we have to deal with it
e.printStackTrace();
}
g.getChildren().addAll(listOfComponents);
}
Just adding code for "caching of already loaded classes" in #Sebastian sir given code. It is working for me. Please suggest changes in it for better performance.
#Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
System.out.println("In Class loader");
Class result;
System.out.println(" >>>>>> Load class : "+name);
result = (Class)classes.get(name);
if(result != null){
System.out.println(" >>>>>> returning cached class.");
return result;
}else{
Class<?> c = findClass(name);
if ( c == null ) {
throw new ClassNotFoundException( name );
}
System.out.println(" >>>>>> loading new class for first time only");
return c;
}
}
Here is my code, can someone explain why it works every time?
package dingding;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class Dingding extends Application {
TextField tfAuto = new TextField("0");
AutoRunThread runner = new AutoRunThread();
boolean shouldStop = false;
private class AutoRunThread extends Thread {
#Override
public void run() {
while (true) {
int i = Integer.parseInt(tfAuto.getText());
++i;
tfAuto.setText(String.valueOf(i));
try {
Thread.sleep(1000);
} catch (Throwable t) {
}
if (shouldStop) {
runner = null;
shouldStop = false;
return;
}
}
}
}
#Override
public void start(Stage primaryStage) {
Button btnStart = new Button("Increment Automatically");
Button btnStop = new Button("Stop Autotask");
btnStart.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
if (runner == null) {
runner = new AutoRunThread();
runner.setDaemon(true);
}
if (runner != null && !(runner.isAlive())) {
runner.start();
}
}
});
btnStop.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
shouldStop = true;
}
});
VBox rootBox = new VBox();
HBox autoBox = new HBox();
autoBox.getChildren().addAll(tfAuto, btnStart, btnStop);
rootBox.getChildren().addAll(autoBox);
Scene scene = new Scene(rootBox, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
As I said in my comment, improperly synchronized code doesn't guarantee errors per se. However, that doesn't mean said code, when used in a multi-threaded context, is actually working—you're merely getting lucky. Eventually you'll run into undefined behavior such as corrupted state, stale values, and unexpected exceptions. This is because, without synchronization, actions performed by one thread are not guaranteed to be visible to any other thread. You need a happens-before relationship, better described in the package documentation of java.util.concurrent and this SO question.
JavaFX, like most UI frameworks/toolkits, is single threaded. This means there's a special thread—in this case, the JavaFX Application Thread— that is responsible for all UI related actions1. It is this thread, and this thread only, that must be used to access and/or modify state related to a "live" scene graph (i.e. nodes that are in a scene that's in a window that's showing2). Using any other thread can lead to the undefined behavior described above.
Some UI related functions actually ensure they're being called on the JavaFX Application Thread, usually throwing an IllegalStateException if not. However, the remaining functions will silently let you call them from any thread—but that doesn't mean it's safe to do so. This is done this way, I believe, because checking the thread in every UI related function is a maintenance nightmare and would incur a not-insignificant performance cost.
1. It's slightly more complicated that this; JavaFX also has a "prism render thread" and a "media thread". See Understanding JavaFX Architecture for more information. But note that, from an application developer's point of view, the only thread that matters is the JavaFX Application Thread.
2. This is documented by Node. Note that some nodes, such as WebView, are more restrictive when it comes to threading; this will be documented in the appropriate places.
I encounter a problem in developing javafx, I find latch has no effect in JavaFx, for example, in the following code:
public class JavafxLatchDemo1 extends Application {
#Override
public void start(Stage primaryStage) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
TextArea txtOut = new TextArea();
StackPane root = new StackPane();
root.getChildren().add(txtOut);
Scene scene = new Scene(root, 300, 250);
//invoke rpc function
Callable<Integer> fibCall = new fibCallable(latch, txtOut);
FutureTask<Integer> fibTask = new FutureTask<Integer>(fibCall);
Thread fibThread = new Thread(fibTask);
fibThread.start();
latch.await(); //阻塞等待计数为0
txtOut.appendText("\n Say 'Hello World'");
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
class fibCallable implements Callable<Integer>{
CountDownLatch latch;
TextArea txtInput;
fibCallable(CountDownLatch mylatch, TextArea txtIn){
latch = mylatch;
txtInput = txtIn;
}
#Override
public Integer call() throws Exception {
int temp1=1,temp2=0;
System.out.println("Client will pay money for eshop");
for(int i=0; i<10; i++){
temp1 = temp1 + temp2;
temp2 = temp1;
}
System.out.println("Client already decide to pay money for eshop");
Platform.runLater(()->{
txtInput.appendText("\nWhy, am I first?");
});
latch.countDown(); //计数减1
return (new Integer(temp1));
}
}
Since I set a latch to stop JavaFx main thread in latch.await();, and want the callable thread fibCallable output the content first: so I expect:
Why, am I first?
Say 'Hello World'
but the real output is opposite:
Say 'Hello World'
Why, am I first?
why? and a solution?
Platform.runLater() submits a runnable to be executed on the FX Application Thread. The start() method is also executed on the FX Application Thread. So the Runnable you submitted with Platform.runLater() cannot be executed until anything already executing on that thread completes.
So you start your fibThread in the background and then immediately wait for the latch: this blocks the FX Application Thread. The fibThread does a little bit of work and then submits a call to the (blocked) FX Application Thread. Then the fibThread releases the latch: the currently-blocked FX Application thread unblocks and finishes the current method call (appending the text "Say Hello World" to the text area and displaying the stage), and at some point after that the runnable submitted to Platform.runLater() executes on the same thread.
A "quick and dirty" fix is simply to wrap the second call to txtOut.appendText(...) in another Platform.runLater():
Platform.runLater(() -> txtOut.appendText("\n Say 'Hello World'"));
This is guaranteed to work, because runnables passed to Platform.runLater() are guaranteed to be executed in the order in which they are passed, and the countdown latch establishes a "happens-before" relationship between the two calls to Platform.runLater().
Note however that you are blocking the FX Application Thread with the call to latch.await(), which is bad practice (and will delay the display of the stage until the background thread completes). You should really put the call to latch.await(), along with the second Platform.runLater() in another background thread. Also note that you don't really need the latch at all, as you already have a FutureTask, and you can just wait for its result (this will be equivalent to waiting for the latch). So you can do
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class JavafxLatchDemo1 extends Application {
#Override
public void start(Stage primaryStage) throws InterruptedException {
// CountDownLatch latch = new CountDownLatch(1);
TextArea txtOut = new TextArea();
StackPane root = new StackPane();
root.getChildren().add(txtOut);
Scene scene = new Scene(root, 300, 250);
//invoke rpc function
// Callable<Integer> fibCall = new fibCallable(latch, txtOut);
Callable<Integer> fibCall = new fibCallable(txtOut);
FutureTask<Integer> fibTask = new FutureTask<Integer>(fibCall);
Thread fibThread = new Thread(fibTask);
fibThread.start();
// latch.await(); //阻塞等待计数为0
new Thread(() -> {
try {
// wait for fibTask to complete:
fibTask.get();
// and now append text to text area,
// but this now must be done back on the FX Application Thread
Platform.runLater(() -> txtOut.appendText("\n Say 'Hello World'"));
} catch (Exception ignored) {
// ignore interruption: thread is exiting anyway....
}
}).start();
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
class fibCallable implements Callable<Integer>{
// CountDownLatch latch;
TextArea txtInput;
fibCallable(TextArea txtIn){
txtInput = txtIn;
}
#Override
public Integer call() throws Exception {
int temp1=1,temp2=0;
System.out.println("Client will pay money for eshop");
for(int i=0; i<10; i++){
temp1 = temp1 + temp2;
temp2 = temp1;
}
System.out.println("Client already decide to pay money for eshop");
Platform.runLater(()->{
txtInput.appendText("\nWhy, am I first?");
});
// latch.countDown(); //计数减1
return (new Integer(temp1));
}
}
}
Finally, note that JavaFX has a concurrency API of its own, that supports various callbacks on the FX Application Thread directly. This API usually means you can avoid getting your hands dirty with latches and locks, etc.
I have a JavaFX TreeView with an invisible root and a handful of 'folder' TreeItems that have many 'file' TreeItems as children. The 'folder' TreeItems typically fit inside the TreeView without there being any scrollbars.
invisible-root/
folder/
folder/
folder/
file
file
file
...
file
Sometimes, when I expand a 'folder' TreeItem, the scrollbars appear but the scroll position remains the same. (This is what I want!) However, sometimes, expanding a TreeItem causes the scrollbars appear and the TableView scrolls to the last child of the expanded TreeItem!
This is very unexpected and surprising, especially since I have difficulty predicting which of the two behaviors I will see: (1) stay put, or (2) scroll to last item. Personally, I think behavior (1) is less surprising and preferable.
Any thoughts on how to deal with this?
I see this behavior on Java8u31.
The problem is in VirtualFlow. In layoutChildren() there is this section:
if (lastCellCount != cellCount) {
// The cell count has changed. We want to keep the viewport
// stable if possible. If position was 0 or 1, we want to keep
// the position in the same place. If the new cell count is >=
// the currentIndex, then we will adjust the position to be 1.
// Otherwise, our goal is to leave the index of the cell at the
// top consistent, with the same translation etc.
if (position == 0 || position == 1) {
// Update the item count
// setItemCount(cellCount);
} else if (currentIndex >= cellCount) {
setPosition(1.0f);
// setItemCount(cellCount);
} else if (firstCell != null) {
double firstCellOffset = getCellPosition(firstCell);
int firstCellIndex = getCellIndex(firstCell);
// setItemCount(cellCount);
adjustPositionToIndex(firstCellIndex);
double viewportTopToCellTop = -computeOffsetForCell(firstCellIndex);
adjustByPixelAmount(viewportTopToCellTop - firstCellOffset);
}
The problem arises if position is 1.0 (== scrolled to bottom), because in that case there is no recalculation. A workaround would be to override the TreeViewSkin to provide your own VirtualFlow and fix the behavior there.
The code below is meant to illustrate the problem, it's not a real solution, just a starting point if you really want to fix it:
import com.sun.javafx.scene.control.skin.TreeViewSkin;
import com.sun.javafx.scene.control.skin.VirtualFlow;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.IndexedCell;
import javafx.scene.control.Skin;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class TreeViewScrollBehaviour extends Application {
#Override
public void start(Stage primaryStage) {
TreeView treeView = new TreeView() {
#Override
protected Skin createDefaultSkin() {
return new TTreeViewSkin(this); //To change body of generated methods, choose Tools | Templates.
}
};
TreeItem<String> treeItem = new TreeItem<String>("Root");
for (int i = 0; i < 20; i++) {
TreeItem<String> treeItem1 = new TreeItem<>("second layer " + i);
treeItem.getChildren().add(treeItem1);
for (int j = 0; j < 20; j++) {
treeItem1.getChildren().add(new TreeItem<>("Third Layer " + j));
}
}
treeView.setRoot(treeItem);
StackPane root = new StackPane();
root.getChildren().addAll(treeView);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
class TTreeViewSkin<T extends IndexedCell> extends TreeViewSkin<T> {
public TTreeViewSkin(TreeView treeView) {
super(treeView);
}
#Override
protected VirtualFlow createVirtualFlow() {
return new TVirtualFlow<T>(); //To change body of generated methods, choose Tools | Templates.
}
}
class TVirtualFlow<T extends IndexedCell> extends VirtualFlow<T> {
#Override
public double getPosition() {
double position = super.getPosition();
if (position == 1.0d) {
return 0.99999999999;
}
return super.getPosition(); //To change body of generated methods, choose Tools | Templates.
}
#Override
public void setPosition(double newPosition) {
if (newPosition == 1.0d) {
newPosition = 0.99999999999;
}
super.setPosition(newPosition); //To change body of generated methods, choose Tools | Templates.
}
}
}