I'm doing an interface to show the progress of my simulation (elevators simulating job on a building).
The thing is, I already did this on the console with System.out.println()s and I wanted to show the exact same thing on a javaFX window. Is there any way where I can set the text of a TextArea or a Label or something to match the output of the console? Just printing the same thing but instead of printing on the console I wanted to print on a window.
I was dumb enough to try and set the Text of a TextAreato the toString() of my simulator but ofc if it is System.out.println(), it shows on the console and not in the ThextArea.
EDIT: This is what I want to print:
#Override
public String toString() {
for (int y = 0; y < 50; y++) {
System.out.println("");
}
for (int i = pisos.size() - 1; i >= 0; i--) {
System.out.print(pisos.get(i).getPiso());
System.out.print(pisos.get(i).pQueue().toString());
System.out.print(" " + percorrerElevadores2(i));
System.out.print(" " + pisos.get(i).pessoasServidas() + "\n");
}
System.out.println("Numero total de passageiros à espera:" + " " + Predio.getPredio().getNPessoasEmEspera());
System.out.println("Numero total de pessageiros servidos:" + " " + Predio.getPredio().getNPessoasServidas());
for (int z = 0; z < getElevadores().size(); z++) {
System.out.println("Distancia percorrida pelo elevador" + " " + z + ":" + " " + Predio.getPredio().getElevadores().get(z).getDistanciaPercorrida() + " " + "Pisos");
System.out.println("Piso destino do elevador" + " " + z + ":" + " " + Predio.getPredio().getElevadores().get(z).getPisoDestino());
}
return "";
}
It is better to use the MessageDialogBox to print the message on window with reference of the panel on which you are working.
I don't really understand your question but it would be easier to rename the println calls than to redirect them. Use some code like this in your main class (the one that extends Application) or really in any class, but you need to add the textArea to the scene graph somewhere.
private static final TextArea textArea = new TextArea();
//add textArea to your scene somewhere in the start method
public static void println(String s){
Platform.runLater(new Runnable() {//in case you call from other thread
#Override
public void run() {
textArea.setText(textArea.getText()+s+"\n");
System.out.println(s);//for echo if you want
}
});
}
Then just use the IDE's search and replace to rename System.out.println to MainClassName.println.
From what I understand, you just want to print text in a location to investigate things, ie the results. Everything you want is just like you print a text on the console, you also want to print this text somewhere in a JavaFX application. Despite its formatting done in their toString method, you can catch the return of the method and print to a JavaFX application (within a control node, for example), right?
If this is the case ...
I created a simple application that works with two text areas. In the middle of application, you will find buttons that manipulate both areas. Basically, the buttons sends the contents of a text area to another. Note:
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class AreaTextual extends Application
{
// #########################################################################################################
// MAIN
// #########################################################################################################
public static void main(String[] args)
{
Application.launch(args);
}
// #########################################################################################################
// INSTÂNCIAS
// #########################################################################################################
// Controles.
private Label lab_receptor;
private Label lab_emissor;
private TextArea tArea_receptor;
private TextArea tArea_emissor;
private Button bot_enviar;
private Button bot_enviarLinha;
private Button bot_substituir;
private Button bot_apagar;
// Layout.
private HBox hbox_raiz;
private VBox vbox_oeste;
private VBox vbox_centro;
private VBox vbox_leste;
// #########################################################################################################
// INÍCIO FX
// #########################################################################################################
#Override public void start(Stage estagio) throws Exception
{
this.iniFX();
this.confFX();
this.adFX();
this.evFX();
Scene cenario = new Scene(this.hbox_raiz , 640 , 480);
estagio.setScene(cenario);
estagio.setTitle("Programa JavaFX");
estagio.show();
}
/** Inicia nós FX.*/
protected void iniFX()
{
// Controles.
this.lab_receptor = new Label();
this.lab_emissor = new Label();
this.tArea_receptor = new TextArea();
this.tArea_emissor = new TextArea();
this.bot_enviar = new Button();
this.bot_enviarLinha = new Button();
this.bot_substituir = new Button();
this.bot_apagar = new Button();
// Layout.
this.hbox_raiz = new HBox();
this.vbox_oeste = new VBox();
this.vbox_centro = new VBox();
this.vbox_leste = new VBox();
}
/** Configura nós FX.*/
protected void confFX()
{
// Controles.
this.lab_receptor.setText("RECEPTOR");
this.lab_receptor.setFont(new Font(32));
this.lab_emissor.setText("EMISSOR");
this.lab_emissor.setFont(new Font(32));
this.bot_enviar.setText("<- ENVIAR");
this.bot_enviar.setPrefSize(150 , 60);
this.bot_enviarLinha.setText("<- ENVIAR+");
this.bot_enviarLinha.setPrefSize(150 , 60);
this.bot_substituir.setText("<- SUBSTITUIR");
this.bot_substituir.setPrefSize(150 , 60);
this.bot_apagar.setText("<- APAGAR TUDO ->");
this.bot_apagar.setPrefSize(150 , 60);
// Layout.
this.hbox_raiz.setSpacing(20);
this.hbox_raiz.setPadding(new Insets(30 , 30 , 30 , 30));
this.hbox_raiz.setAlignment(Pos.CENTER);
this.vbox_oeste.setSpacing(10);
this.vbox_oeste.setAlignment(Pos.CENTER);
this.vbox_centro.setSpacing(10);
this.vbox_centro.setAlignment(Pos.CENTER);
this.vbox_centro.setPrefSize(400 , 200);
this.vbox_leste.setSpacing(10);
this.vbox_leste.setAlignment(Pos.CENTER);
}
/** Adiciona e organiza em layout os nós FX.*/
protected void adFX()
{
this.vbox_leste.getChildren().addAll(this.lab_emissor , this.tArea_emissor);
this.vbox_centro.getChildren().addAll(this.bot_enviar , this.bot_enviarLinha , this.bot_substituir , this.bot_apagar);
this.vbox_oeste.getChildren().addAll(this.lab_receptor , this.tArea_receptor);
this.hbox_raiz.getChildren().addAll(this.vbox_oeste , this.vbox_centro , this.vbox_leste);
}
/** Configura eventos de nós FX.*/
protected void evFX()
{
this.bot_enviar.setOnAction(new EventHandler<ActionEvent>()
{
#Override public void handle(ActionEvent e)
{
tArea_receptor.appendText(tArea_emissor.getText());
}
});
this.bot_enviarLinha.setOnAction(new EventHandler<ActionEvent>()
{
#Override public void handle(ActionEvent e)
{
tArea_receptor.appendText(String.format("%n%s" , tArea_emissor.getText()));
}
});
this.bot_substituir.setOnAction(new EventHandler<ActionEvent>()
{
#Override public void handle(ActionEvent e)
{
tArea_receptor.replaceText(0 , tArea_receptor.getLength() , tArea_emissor.getText());
}
});
this.bot_apagar.setOnAction(new EventHandler<ActionEvent>()
{
#Override public void handle(ActionEvent e)
{
tArea_receptor.setText("");
tArea_emissor.setText("");
}
});
}
}
OBS (PT-BR): Eu notei que você fala português, portanto deixei o código na linguagem para que você entenda-o melhor.
This class has nothing exceptional. It just shows you how you can manipulate the text of a TextArea. You can find other types of handlers of a TextArea right here, and also here.
Regarding your problem seen where you call the JavaFX methods from another thread, this can be happening just because you're not using the JavaFX Application Thread. Like the Swing library has the Event Dispatch Thread (EDT), JavaFX also has its own thread responsible for handling the JavaFX elements. Whenever you need to manipulate any JavaFX element, be to setup something or to obtain some data, you need to do this using the JavaFX Application Thread, and not another.
For you to call methods of the JavaFX Application Thread, use the Platform runLater method. For more information about the JavaFX threads system, visit the following links:
http://docs.oracle.com/javafx/2/architecture/jfxpub-architecture.htm
http://docs.oracle.com/javafx/2/threads/jfxpub-threads.htm
In the first link, read the part that talks about threads. If you have any more questions, come back here and ask.
Good luck.
Related
I'm currently working on a password manager. Before making any changes to a certain service, the program will ask the user for a password for authorization and then proceed to show the appropriate dialog, if the password is correct.
The issue that I'm having is that if I go through the cycle of putting in my password to make the change, click "ok", and then proceeding to make changes on the shown dialog, on the next turn if instead of putting the password when prompted I close the prompt, then the program retrieves the password from the previous iteration although it has been explicitly cleared. Resulting in the concurrent dialog showing, which is only supposed to show if you put in the correct password.
private void handleEditButton(MouseEvent event) {
Optional<String> rslt = passwordConfirmDialog.showAndWait();
if (rslt.get().equals(""))
return; //Do not proceed
String userInput = rslt.get().trim();
// Complex expression, but use of && statement is necessary to avoid an
// unecessary call to db and have return statement on this IF
if (!(!userInput.isBlank() && isCorrectPassword(userInput))) {
// show dialog
AlertConfigs.invalidPasswordTransactionFailed.showAndWait();
return;
}
System.out.println("Edit Handler: Correct password. -> " + userInput);
//Proceed to show next dialog...
private void initializePasswordConfirmDialog() {
passwordConfirmDialog.setTitle("User Account Control");
passwordConfirmDialog.setHeaderText("Please enter your password to continue.");
// Set the button types.
ButtonType ok = new ButtonType("Ok", ButtonData.OK_DONE);
passwordConfirmDialog.getDialogPane().getButtonTypes().addAll(ok, ButtonType.CANCEL);
final PasswordField psField = new PasswordField();
GridPane grid = new GridPane();
grid.setHgap(10);
grid.setVgap(10);
grid.setPadding(new Insets(20, 150, 10, 10));
grid.add(new Label("Please Enter your password"), 0, 0);
grid.add(psField, 1, 0);
passwordConfirmDialog.getDialogPane().setContent(grid);
passwordConfirmDialog.setResultConverter(buttonType -> {
String rslt = "";
if (buttonType == ok) {
rslt = psField.getText();
}
psField.clear();
return rslt;
});
}
I've posted a video on YouTube to help visualize the problem. https://youtu.be/sgayh7Q7Ne8
The PasswordField in initializePasswordConfirmDialog() is cleared because whenever I run the the prompt the second time, the PasswordField is blank (visually). Nevertheless, for some reason it still grabs the result from the previous iteration.
The initializePasswordConfirmDialog() is called once inside the constructor and is responsible for set the passwordConfirmDialog variable with the adequate properties.
Some additional code:
HomeController.java
#FXML
private GridPane servicesGrid;
private Dialog<String> passwordConfirmDialog;
private Dialog<Service> editServiceDialog;
private final int NUM_COLUMNS = 7;
public HomeController() {
passwordConfirmDialog = new Dialog<>();
initializePasswordConfirmDialog();
editServiceDialog = new Dialog<>();
}
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
loadServicesGridpane();
}
private void loadServicesGridpane() {
ArrayList<Service> currS = acct.getServices();
// int currentRow = 1;
for (Service s : currS)
addRowToServiceGrid(s);
}
private void addRowToServiceGrid(Service s) {
int rowIdx = servicesGrid.getChildren().size() / 4;
Button editButton = new Button("Edit");
editButton.setOnMouseClicked(event -> {
handleEditButton(event);
});
Button deleteButton = new Button("Delete");
deleteButton.setOnMouseClicked(event -> {
handleDeleteButton(event);
});
deleteButton.setId(s.getServiceName());
Label currServiceName = new Label(s.getServiceName());
currServiceName.setId(s.getServiceName());
Label currUsername = new Label(s.getServiceUsername());
Label currPassword = new Label(s.getServicePassword());
Label dateCreated = new Label(s.getDateCreated());
Label lastPssdChange = new Label(s.getLastPasswordChange());
servicesGrid.addRow(rowIdx, currServiceName, currUsername, currPassword, dateCreated, lastPssdChange,
deleteButton, editButton);
}
To study the problem in isolation, I refactored this example to permit reusing the dialog. As shown below, reusing the dialog requires clearing the password field. Replace the parameter dialog with an invocation of createDialog() to see that creating the dialog each time does not require clearing the password field. Comparing the profile of each approach may help you decide which approach is acceptable; in my experiments, reuse added negligible memory overhead (~250 KB), and it protracted garbage collection slightly(~50 ms).
#!/bin/sh
java … DialogTest -reuse &
pid1=$!
java … DialogTest -no-reuse &
pid2=$!
echo $pid1 $pid2
jconsole $pid1 $pid2
Unfortunately, creating the dialog each time may only appear to solve the problem; it may have exposed a latent synchronization problem. In particular, verify that your result converter's callback executes on the JavaFX Application Thread. To illustrate, I've added a call to Platform.isFxApplicationThread() in resultsNotPresent() below.
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Dialog;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
* #see https://stackoverflow.com/q/73328282/230513
* #see https://stackoverflow.com/a/44172143/230513
*/
public class DialogTest extends Application {
private static boolean REUSE_DIALOG = true;
private record Results(String text, String pass) {
private static Results of(String text, String pass) {
return new Results(text, pass);
}
}
#Override
public void start(Stage stage) {
var label = new Label("Reuse: " + REUSE_DIALOG);
var button = new Button("Button");
if (REUSE_DIALOG) {
var dialog = createDialog();
button.setOnAction(e -> showDialog(dialog));
} else {
button.setOnAction(e -> showDialog(createDialog()));
}
stage.setScene(new Scene(new HBox(8, label, button)));
stage.show();
}
private Dialog<Results> createDialog() {
var dialog = new Dialog<Results>();
dialog.setTitle("Dialog Test");
dialog.setHeaderText("Please authenticate…");
var dialogPane = dialog.getDialogPane();
dialogPane.getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
var text = new TextField("Name");
var pass = new PasswordField();
dialogPane.setContent(new VBox(8, text, pass));
dialog.showingProperty().addListener((o, wasShowing, isShowing) -> {
if (isShowing) {
Platform.runLater(pass::requestFocus);
}
});
dialog.setResultConverter((ButtonType bt) -> {
if (ButtonType.OK == bt) {
var results = Results.of(text.getText(), pass.getText());
pass.clear();
return results;
}
pass.clear();
return null;
});
return dialog;
}
private void showDialog(Dialog<Results> dialog) {
var optionalResult = dialog.showAndWait();
optionalResult.ifPresentOrElse(
(var results) -> System.out.println(results),
(this::resultsNotPresent));
}
private void resultsNotPresent() {
System.out.println("Canceled on FX application thread: "
+ Platform.isFxApplicationThread());
}
public static void main(String[] args) {
if (args.length > 0) {
REUSE_DIALOG = args[0].startsWith("-r");
}
launch(args);
}
}
I'm trying to create a simple UI that is accessible for screenreaders. I've been mostly successful, but I can't manage to design the UI in a way that has the screenreader read new text output.
Currently, I have a TextArea displaying the output of an anonymous PrintStream created and set by System.setOut. Sometimes I open up a TextField for string inputs, but I've been working with just the TextArea to test the reading of text (for now it just listens for keystrokes to display more text for testing purposes).
The issue is this: when new text is added via System.out to the TextArea, the screenreader does not read it. I am still able to navigate upward with the arrow keys to read what was added but it is not read when first added. Is there any way to get the screenreader to treat my TextArea more like a standard console (in which it reads all new text automatically)? I'm using NVDA.
Things I have tried:
- Using TextArea.notifyAccessibleAttributeChanged(AccessibleAttribute.TEXT)
- Using TextArea.requestFocus() and TextArea.notifyAccessibleAttributeChanged(AccessibleAttribute.FOCUS_NODE)
- Disabling autoflush on the PrintStream while using TextArea.setAccessibleText(theNewText) during a flush
- Using a hidden Label set to the new text and focusing on it (I'm still fiddling with this one; Screenreaders can't read actual "hidden" text so I'm trying to find a way to draw it but also be "invisible", perhaps behind the TextArea somehow)
- Changing focus to another Node and back, which doesn't work as I like because it reads the other Nodes accessible stuff and then reads the entire body of the TextArea
- Various combinations of these
I just can't seem to get it to work. I feel like I'm missing something simple and obvious here, but the JavaFX Accessibility API is still relatively new and I can't find solutions to specific problems like this one.
Here's the relevant code of my Application, if it helps any:
#Override
public void start(Stage primaryStage) {
try {
primaryStage.setTitle("Test");
root = new BorderPane();
root.setFocusTraversable(false);
Scene scene = new Scene(root,800,600);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
//Create middle
output = new TextArea();
output.setEditable(false);
output.setFocusTraversable(false); //I've tried true also, just to test
output.setAccessibleRole(AccessibleRole.TEXT_AREA);
root.setCenter(output);
...
//Begin
primaryStage.show();
Thread th = new Thread(new AppMain());
th.start();
} catch(Exception e) {
e.printStackTrace();
}
}
#Override
public void init() {
//Set output to TextArea
System.setOut(new PrintStream(new OutputStream() {
#Override
public void write(int b) throws IOException {
appendTextArea(String.valueOf((char) b));
}
}, true)); //I've also overriden flush while this is false, see above
}
public void appendTextArea(String str) {
Platform.runLater(() -> {
output.appendText(str);
});
}
I seriously appreciate any help or suggestions you can provide. I've been messing with this small issue for way too long, and I'm still new to JavaFX. Thank you!
Here is a full working example based off of your code.
Disclosure: For the screen reader I'm using the "Voice Over Utility" on my Mac, but hopefully that doesn't differ too much from your environment.
The key was to utilize the Control#executeAccessibleAction method.
Example:
TextArea.executeAccessibleAction(AccessibleAction.SET_TEXT_SELECTION, start, finish);
The Application Class
package application;
import java.io.PrintStream;
import java.io.IOException;
import java.io.OutputStream;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.stage.Stage;
import javafx.scene.AccessibleAction;
import javafx.scene.AccessibleRole;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane;
public class Main extends Application
{
private TextArea output;
private BorderPane root;
private final StringBuilder STR_BUFFER = new StringBuilder();
private static final String NEW_LINE = System.lineSeparator();
#Override
public void start(Stage primaryStage)
{
try
{
primaryStage.setTitle("Test");
root = new BorderPane();
root.setFocusTraversable(false);
Scene scene = new Scene(root, 800, 600);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
// Create middle
output = new TextArea();
output.setEditable(false);
output.setFocusTraversable(true); // I've tried true also, just to test
// ----------------------------------------------
// Tell the Screen Reader what it needs to access
// ----------------------------------------------
output.setAccessibleRole(AccessibleRole.TEXT_AREA);
root.setCenter(output);
// ...
// Begin
primaryStage.show();
// start the thread
Thread th = new Thread(new AppMain());
th.start();
}
catch (Exception e)
{
e.printStackTrace();
}
}
#Override
public void init()
{
// Set output to TextArea when we have a full string
System.setOut(new PrintStream(new OutputStream()
{
#Override
public void write(int b) throws IOException
{
if (b == '\r')
{
return;
}
if (b == '\n')
{
final String text = STR_BUFFER.toString() + NEW_LINE;
appendTextArea(text);
STR_BUFFER.setLength(0);
}
else
{
STR_BUFFER.append((char) b);
}
}
}, true));
}
public void appendTextArea(String str)
{
Platform.runLater(() ->
{
int anchor = output.getText().length();
output.appendText(str);
// just to clear it
output.positionCaret(0);
// ----------------------------------------------
// Tell the Screen Reader what it needs to do
// ----------------------------------------------
output.executeAccessibleAction(AccessibleAction.SET_TEXT_SELECTION, anchor, anchor + str.length());
});
}
public static void main(String[] args)
{
launch(args);
}
}
The Thread Class
/*
* Just to simulate a feed to the console (textArea).
* This will die after 1 minute.
*/
package application;
public class AppMain implements Runnable
{
#Override
public void run()
{
int i = 0;
long start = System.currentTimeMillis();
while (System.currentTimeMillis() - start < 60000)
{
try
{
Thread.sleep(3000);
}
catch (InterruptedException e)
{}
System.out.println("This is line number " + ++i);
}
}
}
in the past I read lot's of hints, Tipps'n Tricks in this Forum.
Now I try to ask my first question here :-)
I'm a bit new to JavaFX, but not new to Java. I'm trying to port an application from Swing to JavaFX so it will be more fancy and customizable.
The main Task of this program is to show two trees and compare them.
To make it easier I implemented in the old application a listener and celleditor to visualize the differences and when you select one Item in the one tree, it tries to find the corresponding Item in the other one. If it is not visible, it will be visible.
One screenshot to show the current application:
After the start it Looks like this and
When selecting on the left, autoselect on the right and vice versa:
Old application
Now I try to implement the same Feature in JavaFX:
But using this code:
public void setOtherTree(TreeView<CompareNode> otherTree) {
if (log.isTraceEnabled()) {
log.trace("setOtherTree("+otherTree+")");
}
this.otherTree = otherTree;
tree.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
tree.getSelectionModel().selectedItemProperty().addListener((observable,oldValue,newValue) -> selectInOtherTree(newValue));
}
public void selectInOtherTree(TreeItem<CompareNode> newValue) {
if (log.isDebugEnabled()) {
log.debug(logPrefix + " " + String.format("selectInOtherTree:newValue".replaceAll(", ", "=%s, ") + "=%s", newValue));
}
otherTree.getSelectionModel().clearSelection();
otherTree.getSelectionModel().select(newValue);
}
It always selects the item on an index base:
Problem in JavaFX
Am I missing something or is it a bug in JDK 1.8u45 (which I'm currently using)?
The class CompareNode overwrites toString, equals and hash
As I see, I need 10 reputations to post Images :-(, so I hope my words will explain it detailed enough or you can follow the links. Sorry for that
Any help is well appreciated.
Kind regards
Andreas
2015-07-10 UPDATE:
The two screenshots showing the old application:
And the screenshot of the new application:
Thanks for reputations :-)
2015-09-22 UPDATE
Meanwhile I create a Little demo app to demonstrate it.
TreeItemString.java
package application;
import javafx.scene.control.TreeItem;
public class TreeItemString extends TreeItem<String> {
public TreeItemString(String value) {
this.setValue(value);
}
#Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
return this.getValue().equals(((TreeItemString) obj).getValu());
}
#Override
public String toString() {
return this.getValue();
}
#Override
public int hashCode() {
return this.getValue().hashCode();
}
}
MainApp.java
package application;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
public void selectInOtherTree(TreeItem<String> toSelect, TreeView otherTree, TreeView thisTree) {
System.out.println("Othertree : " + toSelect);
System.out.println("Me : " + thisTree.getRow(toSelect));
System.out.println("Other: " + otherTree.getRow(toSelect));
otherTree.getSelectionModel().select(toSelect);
}
public TreeItemString newRoot(TreeView<String> tree, Label label, String labelPrefix) {
//Create TreeItems for the Hierarchy of the TreeView
TreeItemString royalRoot = new TreeItemString("Queen Elizabeth - Prince Philip");
TreeItemString Charlie = new TreeItemString("Prince Charles - Princess Diana");
TreeItemString Annie = new TreeItemString("Princess Anne - Mark Phillips");
TreeItemString Andy = new TreeItemString("Prince Andrew - Sarah Ferguson");
TreeItemString Eddie = new TreeItemString("Prince Edward - Sophie");
//Populate the TreeItem to be used as the root with the other TreeItems
royalRoot.getChildren().addAll(Charlie, Annie, Andy, Eddie);
//Populate the other TreeItems with more TreeItems
//to build the family tree
Charlie.getChildren().addAll(
new TreeItemString("Prince William"),
new TreeItemString("Prince Henry"));
Annie.getChildren().addAll(
new TreeItemString("Peter Phillips"),
new TreeItemString("Zara Phillips"));
Andy.getChildren().addAll(
new TreeItemString("Princess Beatrice"),
new TreeItemString("Princess Eugenie"));
Eddie.getChildren().addAll(
new TreeItemString("Lady Louise"),
new TreeItemString("Viscount Severn"));
return royalRoot;
}
#Override
public void start(Stage primaryStage) {
final String leftTreeLabelPrefix= "Selected Tree Item From left Tree: \n";
final String rightTreeLabelPrefix = "Selected Tree Item From right Tree: \n";
//Use HBOX and VBOX layout panes to space out the controls
//in a single row
HBox treeBox = new HBox();
VBox labelBox = new VBox(30);
HBox controlBox = new HBox(10);
//Create labels to highlight the selected items from the TreeViews
final Label leftTreeLabel = new Label(leftTreeLabelPrefix);
final Label rightTreeLabel = new Label(rightTreeLabelPrefix);
//Create and empty TreeView
TreeView<String> leftTree = new TreeView<String>();
//Use the setRoot method to set the root TreeItem
// duckTree.setRoot(duckRoot);
leftTree.setRoot(newRoot(leftTree,leftTreeLabel, "Selected Tree Item From left Tree: \n"));
//Create a TreeView using the root TreeItem
TreeView<String> rightTree = new TreeView<String>();
rightTree.setRoot(newRoot(rightTree,rightTreeLabel, "Selected Tree Item From right Tree: \n"));
rightTree.getSelectionModel().selectedItemProperty().addListener((observable,oldValue,newValue) -> selectInOtherTree(newValue, leftTree, rightTree));
leftTree.getSelectionModel().selectedItemProperty().addListener((observable,oldValue,newValue) -> selectInOtherTree(newValue, rightTree,leftTree));
//Add the TreeViews to the HBox
treeBox.getChildren().add(leftTree);
treeBox.getChildren().add(rightTree);
//Add the labels to the VBox
labelBox.getChildren().add(leftTreeLabel);
labelBox.getChildren().add(rightTreeLabel);
//Add the HBox and VBox to another HBox to
//position the layout panes
controlBox.getChildren().add(treeBox);
controlBox.getChildren().add(labelBox);
//Add the main HBOX layout pane to the scene
Scene scene = new Scene(controlBox, 800, 250);
Platform.setImplicitExit(false);
Thread backGroundThread = new Thread(() -> {
try {
Thread.sleep(5000);
System.out.println("Select left Viscount Severn");
leftTree.getRoot().setExpanded(true);
leftTree.getRoot().getChildren().get(1).setExpanded(true);
leftTree.getRoot().getChildren().get(2).setExpanded(true);
leftTree.getRoot().getChildren().get(3).setExpanded(true);
TreeItemString toSearch = new TreeItemString("Prince Edward - Sophie");
System.out.println("true:false for : " + toSearch);
System.out.println("true:false : " + leftTree.getRoot().getChildren().contains(toSearch));
System.out.println("indexOf: " + leftTree.getRoot().getChildren().indexOf(toSearch));
System.out.println("get: " + leftTree.getRoot().getChildren().get(leftTree.getRoot().getChildren().indexOf(toSearch)));
System.out.println("getRow: " + leftTree.getRow(leftTree.getRoot().getChildren().get(leftTree.getRoot().getChildren().indexOf(toSearch))));
leftTree.getSelectionModel().select(new TreeItemString("Princess Anne - Mark Phillips"));
Thread.sleep(5000);
System.out.println("Select right Princess Beatrice");
rightTree.getSelectionModel().select(new TreeItemString("Princess Beatrice"));
} catch (InterruptedException e) {
e.printStackTrace();
}
});
backGroundThread.setDaemon(true);
backGroundThread.start();
//Show the form
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
I hope I'm not duplicating a question, but I couldn't find one specifically for my issue.
I'm developing a small math flash card application, using JavaFX to create the GUI. The program should runs as follow:
user selects settings, then presses start button.
gui displays question and textfield for user input.
user inputs answer within X amount of seconds or gui automatically move onto the next question - alternatively, user can move onto next question immediately by pressing next button.
GUI displays score and average.
The problems is getText() from user textfield is processed as soon as start button is pressed, without giving the user a chance to enter an answer. How do I make the program wait for X amount of seconds or for the next button to be clicked before processing the user's answer? Here's my code:
//start button changes view and then runs startTest()
start.setOnAction(e -> {
setLeft(null);
setRight(null);
setCenter(test_container);
running_program_title.setText(getDifficulty().name() + " Test");
buttons_container.getChildren().clear();
buttons_container.getChildren().addAll(next, quit, submit);
startTest();
});
Here is the problem code... at least how I see it.
//startTest method calls askAdd() to ask an addition question
void startTest() {
int asked = 0;
int correct = 0;
while (asked < numberOfQuestions) {
if(askAdd()){
correct++;
asked++;
}
}
boolean askAdd() {
int a = (int) (Math.random() * getMultiplier());
int b = (int) (Math.random() * getMultiplier());
//ask question
question.setText("What is " + a + " + " + b + "?");
//code needed to pause method and wait for user input for X seconds
//retrieve user answer and return if its correct
return answer.getText().equalsIgnoreCase(String.valueOf(a+b));
}
I've tried using Thread.sleep(X) but that freezes the gui for however long I specify and then goes through the addAsk() method and the loop before going to the test screen. (I know because I had the program set up to print the questions and answer input to the console). It shows the last question and that's all.
I didn't include the next button code because I can't get the gui to go to the test page anyway.
Any help on any of the code is appreciated.
This can be achieved by various methods.
PauseTransition is one of the many apt solution present. It waits for X time interval and then performs a Task. It can start, restart, stop at any moment.
Here is an example of how it can used to achieve a similar result.
Complete Code
import javafx.animation.PauseTransition;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.stream.IntStream;
public class Main extends Application {
int questionIndex = 0;
int noOfQuestions = 10;
#Override
public void start(Stage stage) {
VBox box = new VBox(10);
box.setPadding(new Insets(10));
Scene scene = new Scene(new ScrollPane(box), 500, 200);
ObservableList<String> questions =
FXCollections.observableArrayList("1) Whats your (full) name?",
"2) How old are you?",
"3) Whats your Birthday?",
"4) What starsign does that make it?",
"5) Whats your favourite colour?",
"6) Whats your lucky number?",
"7) Do you have any pets?",
"8) Where are you from?",
"9) How tall are you?",
"10) What shoe size are you?");
ObservableList<String> answers = FXCollections.observableArrayList();
final PauseTransition pt = new PauseTransition(Duration.millis(5000));
Label questionLabel = new Label(questions.get(questionIndex));
Label timerLabel = new Label("Time Remaining : ");
Label time = new Label();
time.setStyle("-fx-text-fill: RED");
TextField answerField = new TextField();
Button nextQuestion = new Button("Next");
pt.currentTimeProperty().addListener(new ChangeListener<Duration>() {
#Override
public void changed(ObservableValue<? extends Duration> observable, Duration oldValue, Duration newValue) {
time.setText(String.valueOf(5 - (int)newValue.toSeconds()));
}
});
box.getChildren().addAll(questionLabel, answerField, new HBox(timerLabel, time), nextQuestion);
nextQuestion.setOnAction( (ActionEvent event) -> {
answers.add(questionIndex, answerField.getText());
//Check if it is the last question
if(questionIndex == noOfQuestions-1) {
pt.stop();
box.getChildren().clear();
IntStream.range(0, noOfQuestions).forEach(i -> {
Label question = new Label("Question : " + questions.get(i));
question.setStyle("-fx-text-fill: RED");
Label answer = new Label("Answer : " + answers.get(i));
answer.setStyle("-fx-text-fill: GREEN");
box.getChildren().addAll(question, answer);
});
}
// All other time
else {
//Set new question
questionLabel.setText(questions.get(++questionIndex));
answerField.clear();
pt.playFromStart();
}
});
pt.setOnFinished( ( ActionEvent event ) -> {
nextQuestion.fire();
});
pt.play();
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
For the timer you should (IMO) use a Timeline. Here is an example:
public class MultiGame extends Application {
ProgressBar progressBar;
final int allowedTime = 5; //seconds
final DoubleProperty percentOfTimeUsed = new SimpleDoubleProperty(0);
final Timeline timer =
new Timeline(
new KeyFrame(
Duration.ZERO, new KeyValue(percentOfTimeUsed, 0)),
new KeyFrame(
Duration.seconds(allowedTime), new KeyValue(percentOfTimeUsed, 1))
);
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
BorderPane root = new BorderPane();
progressBar = new ProgressBar();
progressBar.progressProperty().bindBidirectional(percentOfTimeUsed);
root.setTop(progressBar);
Button answer = new Button("Answer");
answer.setOnAction(ae -> restart());// the on answer handler
Button skip = new Button("Skip");
skip.setOnAction(ae -> restart());// the skip question handler
HBox mainContent = new HBox(15,
new Label("Your Question"), new TextField("The answer"), answer, skip);
root.setCenter(mainContent);
timer.setOnFinished(ae -> restart());// the end of timer handler
primaryStage.setScene(new Scene(root));
primaryStage.show();
restart();
}
void restart() { timer.stop(); timer.playFromStart(); }
void pause() { timer.pause(); }
void resume() { timer.play(); }
}
You just need to capture the text from the input in between the starting of the timeline and the restart method.
RESUME
Good day StackOverflow community.
I've been trying for some time to develop a program that enables users to put objects in an area, allowing this area to be moved by the mouse. For this type of program, I decided to use a ScrollPane, because the user can add various contents in the area which I call the canvas. For some reason, something strange is happening in my program.
EXPLANATION OF PROGRAM
What I basically did was create a group of objects, and define this group as the ScrollPane content. Within the group, there is a Rectangle object that was added to serve as canvas boundaries. This object has larger dimensions (such as 1500 x 1000, for example), and is used in calculations that prevent nodes from moving beyond its limits. This is just the logical behind the existing large rectangle in my program, but in reality, there is no Node object with the mouse movement. What exists is the random distribution of Shape objects by the rectangle area.
For ScrollPane has its scrollbars moved, I use the setHvalue setVvalue methods. Unfortunately for my purposes, this method does not change the position of the ScrollPane's viewport with pixel values, but values that are in a range between 0f and 1f. So I can move the viewport with the mouse, I used a equation known as Rule of 3 (here in my Country, as I know), which we equate values and cross multiply.
For example, say I want to move the viewport of the ScrollPane with the mouse horizontally, and that my canvas area has a width of 2000 pixels. Finding how far (in pixels) the mouse was dragged from one point to another, I need to know how this value represents in a range 0f to 1f. Suppose I have dragged the mouse in 3 pixels, I could find the representation of the 0f to 1f with the following comparison:
2000 px ---- 1f
3 px ---- xf
Multiplying crossed, I'll get the following result:
xf = 3 / 2000
xf = 0.0015
Note: I believe you all know that. I'm not teaching math to anyone,
just want to explain the logic of my problem.
SOURCE CODE
Here is my program class:
import testes.util.TestesUtil;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.geometry.Bounds;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.StrokeType;
import javafx.stage.Stage;
public class ScrollTest4 extends Application
{
// #########################################################################################################
// MAIN
// #########################################################################################################
public static void main(String[] args)
{
Application.launch(args);
}
// #########################################################################################################
// INSTÂNCIAS
// #########################################################################################################
// OUTSIDE
private BorderPane root;
private Button but_moreH;
private Button but_lessH;
private Button but_moreV;
private Button but_lessV;
// LOG
private VBox vbox_south;
private Label lab_hValue;
private Label lab_vValue;
private Label lab_viewport;
// INSIDE
private Rectangle rec_canvas;
private ScrollPane scroll;
private Group grp_objects;
// MOUSE
private double mouse_x = 0;
private double mouse_y = 0;
// MISC
private AnimationTimer timer;
// EDITED - 08/02/2014
private boolean moreH = false;
private boolean moreV = false; // Purposely unused.
private boolean lessH = false;
private boolean lessV = false; // Purposely unused.
// #########################################################################################################
// INÍCIO FX
// #########################################################################################################
#Override public void start(Stage estagio) throws Exception
{
this.iniFX();
this.confFX();
this.adFX();
this.evFX();
Scene cenario = new Scene(this.root , 640 , 480);
estagio.setScene(cenario);
estagio.setTitle("Programa JavaFX");
estagio.show();
}
protected void iniFX()
{
// OUTSIDE
this.root = new BorderPane();
this.but_moreH = new Button();
this.but_lessH = new Button();
this.but_moreV = new Button();
this.but_lessV = new Button();
// LOG
this.vbox_south = new VBox();
this.lab_hValue = new Label();
this.lab_vValue = new Label();
this.lab_viewport = new Label();
// INSIDE
this.scroll = new ScrollPane();
this.grp_objects = new Group();
this.rec_canvas = new Rectangle();
// MISC
this.timer = new AnimationTimer()
{
#Override public void handle(long now)
{
// EDITED - 08/02/2014
if(but_moreH.isArmed() || moreH)
{
// scroll.hvalueProperty().set(scroll.hvalueProperty().get() + 0.003f);
scroll.setHvalue(scroll.getHvalue() + 0.003f);
}
// EDITED - 08/02/2014
if(but_lessH.isArmed() || lessH)
{
// scroll.hvalueProperty().set(scroll.hvalueProperty().get() - 0.003f);
scroll.setHvalue(scroll.getHvalue() - 0.003f);
}
if(but_moreV.isArmed())
{
scroll.setVvalue(scroll.getVvalue() + 0.003f);
}
if(but_lessV.isArmed())
{
scroll.setVvalue(scroll.getVvalue() - 0.003f);
}
}
};
this.timer.start();
}
protected void confFX()
{
// OUTSIDE
this.but_moreH.setText("More H");
this.but_moreH.setMaxHeight(Double.MAX_VALUE);
this.but_lessH.setText("Less H");
this.but_lessH.setMaxHeight(Double.MAX_VALUE);
this.but_moreV.setText("More V");
this.but_moreV.setMaxWidth(Double.MAX_VALUE);
this.but_lessV.setText("Less V");
this.but_lessV.setMaxWidth(Double.MAX_VALUE);
// LOG
this.updateHvalue();
this.updateVvalue();
this.updateViewport();
// INSIDE
this.rec_canvas.setWidth(1200);
this.rec_canvas.setHeight(1000);
this.rec_canvas.setFill(Color.INDIANRED);
this.rec_canvas.setStroke(Color.RED);
this.rec_canvas.setStrokeType(StrokeType.INSIDE);
this.rec_canvas.setStrokeWidth(1);
}
protected void adFX()
{
// LOG
this.vbox_south.getChildren().add(this.but_moreV);
this.vbox_south.getChildren().addAll(this.lab_hValue , this.lab_vValue , this.lab_viewport);
// OUTSIDE
this.root.setCenter(this.scroll);
this.root.setTop(this.but_lessV);
this.root.setBottom(this.vbox_south);
this.root.setRight(this.but_moreH);
this.root.setLeft(this.but_lessH);
// INSIDE
this.grp_objects.getChildren().add(this.rec_canvas);
this.scroll.setContent(this.grp_objects);
// MISC
StrokeType[] strokes = {StrokeType.CENTERED , StrokeType.INSIDE , StrokeType.OUTSIDE};
for(int cont = 0 ; cont < 20 ; cont++)
{
Rectangle node = new Rectangle(Math.random() * 100 + 50 , Math.random() * 100 + 50);
node.setFill(TestesUtil.getCorAleatoria(false));
node.setStroke(TestesUtil.getCorAleatoria(false));
node.setStrokeType(strokes[(int) (Math.random() * 2)]);
node.setStrokeWidth(Math.random() * 9 + 1);
node.setRotate(Math.random() * 360);
node.setMouseTransparent(true);
// EDITED - 08/02/2014
TestsUtil.putRandomlyIn(
node ,
rec_canvas.getBoundsInParent().getMinY() ,
rec_canvas.getBoundsInParent().getMinY() + rec_canvas.getBoundsInParent().getHeight() ,
rec_canvas.getBoundsInParent().getMinX() + rec_canvas.getBoundsInParent().getWidth() ,
rec_canvas.getBoundsInParent().getMinX() );
this.grp_objects.getChildren().add(node);
}
}
protected void evFX()
{
// ##########################
// SCROLL PROPERTIES
// ##########################
this.scroll.hvalueProperty().addListener(new ChangeListener<Number>()
{
#Override public void changed(ObservableValue<? extends Number> observable,Number oldValue, Number newValue)
{
updateHvalue();
updateViewport();
}
});
this.scroll.vvalueProperty().addListener(new ChangeListener<Number>()
{
#Override public void changed(ObservableValue<? extends Number> observable,Number oldValue, Number newValue)
{
updateVvalue();
updateViewport();
}
});
this.scroll.setOnKeyPressed(new EventHandler<KeyEvent>()
{
#Override public void handle(KeyEvent e)
{
if(e.getCode() == KeyCode.RIGHT)
{
moreH = true;
}
else if(e.getCode() == KeyCode.LEFT)
{
lessH = true;
}
}
});
this.scroll.setOnKeyReleased(new EventHandler<KeyEvent>()
{
#Override public void handle(KeyEvent e)
{
if(e.getCode() == KeyCode.RIGHT)
{
moreH = false;
}
else if(e.getCode() == KeyCode.LEFT)
{
lessH = false;
}
}
});
// ##########################
// CANVAS
// ##########################
this.rec_canvas.setOnMousePressed(new EventHandler<MouseEvent>()
{
#Override public void handle(MouseEvent e)
{
// The XY distance from the upper left corner of the canvas.
mouse_x = e.getX();
mouse_y = e.getY();
}
});
this.rec_canvas.setOnMouseDragged(new EventHandler<MouseEvent>()
{
#Override public void handle(MouseEvent e)
{
// ##########################
// PIXELS
// ##########################
// The distance between mouse movements (drag events).
double xPixelsMoved = e.getX() - mouse_x;
// double yPixelsMoved = e.getY() - mouse_y;
// ##########################
// TO 1F
// ##########################
double h_of_1f = xPixelsMoved / rec_canvas.getBoundsInParent().getWidth();
double h_of_1f_inverted = h_of_1f * -1;
double currentH = scroll.getHvalue();
scroll.setHvalue(currentH + h_of_1f);
// scroll.hvalueProperty().set(scroll.getHvalue() + h_de_x);
// scroll.vvalueProperty().set(scroll.getVvalue() + v_de_y);
// ##########################
// DEBUG
// ##########################
System.out.printf("xPixelsMoved: %f , h_of_1f: %f , h_of_1f_inverted: %f %n",
xPixelsMoved , h_of_1f , h_of_1f_inverted);
// ##########################
// UPDATE FROM
// EVENT TO EVENT
// ##########################
// Writes last mouse position to update on new motion event.
mouse_x = e.getX();
mouse_y = e.getY();
}
});
}
// #########################################################################################################
// MISC.
// #########################################################################################################
protected void updateViewport()
{
Bounds vport = this.scroll.getViewportBounds();
this.lab_viewport.setText(String.format("Viewport - [X: %f , Y: %f , W: %f , H: %f]",
vport.getMinX() , vport.getMinY() , vport.getWidth() , vport.getHeight() ));
}
protected void updateHvalue()
{
this.lab_hValue.setText("H value: " + this.scroll.getHvalue() );
}
protected void updateVvalue()
{
this.lab_vValue.setText("V value: " + this.scroll.getVvalue() );
}
}
THE PROBLEM
Clicking the mouse button on the canvas area and drag it, you can see that the program moves the ScrollPane viewport horizontally. The program seems to work perfectly (or not). However, something goes wrong at the time when the mouse is dragged sometimes abruptly (...or not!). At certain times the ScrollPane Viewport is not visually updated. This is a strange behavior, because even if viewport is not visually updated, the scrollbars are still updated.
I put other ways to move the ScrollPane viewport horizontally using the same method, and for some reason, only the approach using the mouse makes it happen. I thought this could be solved by making a request for layout using requestLayout, also causing a request to a pulse, but it does not work.
THE TEST OUTPUT
The odd thing is that everything returns to normal when the window of my application is resized. Here's a video that shows what happens to my program:
VIDEO & MIRROR 1
I no longer know what else to do. Can anyone help me with this please?
EDIT (08/02/2014 10:08 AM GMT - 3:00)
The original source code of my application is found written in Portuguese, so you may be seeing something unknown. Basically TestesUtil is a utility class with static methods that define shortcuts to other client classes. I changed the call from my source code shown here previously and am now putting some methods of my class TestesUtil, translated into English as TestsUtil:
public static void putRandomlyIn(Node node , double northPoint , double southPoint , double eastPoint , double westPoint)
{
node.setLayoutX(Math.random() * pontoLeste);
node.setLayoutY(Math.random() * pontoSul);
fixEasternBoundary(node , eastPoint);
fixNorthernBoundary(node , northPoint);
fixWesternBoundary(node , westPoint);
fixSouthernBoundary(node , southPoint);
}
There is no mystery here. This method simply calculates a value from an interval, and defines the LayoutXY properties for the Node argument. Methods "fix ..." just check the boundsInParent bounds of the node compared to the point in the argument, and then adjust the layoutXYproperties from the Node object. Even if I remove the random distribution of objects, the problem still happens. So I'm sure this problem is not being caused by this.
The source code of the original post was changed with the addition of the ability to move the scroll bars with the arrow keys. Even if it is already an existing function of ScrollPane, adding that could reproduce the error seen with the mouse (now with arrows). Some things were also translated into English for better understanding by the community.
Please, I ask for help. I'm getting dizzy not knowing what to do. This type of situation could be happening because of some bug in JavaFX? Ahhrr... Please somebody help me in this. :'(
Thank you for your attention anyway.
EDIT (09/02/2014 10:50 AM GMT - 3:00)
Forgot to mention... My program was initially written and tested using JDK 8 b123. Currently I installed the JDK 8 b128 version and am still getting the same problem. My operating system is Windows 7 64x.
I am almost certain that this is a bug. Are you guys getting the same result as me? Or am I the only one to find this kind of problem? If this is a bug, which procedure should be taken?
Thank you for your attention.
EDIT (10/02/2014 09:45 AM GMT - 3:00)
A bounty was started.
UPDATE
This bug has now been fixed for JavaFX 8u20.
Bug description
This is a bug, that can be easily verified by executing the following code with JavaFx JRE 8:
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
final ScrollPane sp = new ScrollPane();
final Image[] images = new Image[5];
final ImageView[] pics = new ImageView[5];
final VBox vb = new VBox();
final Label fileName = new Label();
final String [] imageNames = new String [] {"fw1.jpg", "fw2.jpg",
"fw3.jpg", "fw4.jpg", "fw5.jpg"};
#Override
public void start(Stage stage) {
VBox box = new VBox();
Scene scene = new Scene(box, 180, 180);
stage.setScene(scene);
stage.setTitle("Scroll Pane");
box.getChildren().addAll(sp, fileName);
VBox.setVgrow(sp, Priority.ALWAYS);
fileName.setLayoutX(30);
fileName.setLayoutY(160);
for (int i = 0; i < 5; i++) {
images[i] = new Image(getClass().getResourceAsStream(imageNames[i]));
pics[i] = new ImageView(images[i]);
pics[i].setFitWidth(100);
pics[i].setPreserveRatio(true);
vb.getChildren().add(pics[i]);
}
sp.setVmax(440);
sp.setPrefSize(115, 150);
sp.setContent(vb);
sp.vvalueProperty().addListener(new ChangeListener<Number>() {
public void changed(ObservableValue<? extends Number> ov,
Number old_val, Number new_val) {
fileName.setText(imageNames[(new_val.intValue() - 1)/100]);
}
});
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
This code comes directly from the JavaFX ScrollPane Tutorial.
If one randomly moves the vertical scroll bar with the mouse very rapidly, then at some time the screen will freeze and no longer get updated. Although one is still able to move the scroll bar around, the displayed images will stay fixed. Only if one resizes the frame, the display of the images will be updated and the ScrollPane reverts to its previous state. Note, that this bug will only happen in JRE 8, it is not reproducible in JRE 7.
The only workaround for the problem, that I could find, is adding
sp.snapshot(new SnapshotParameters(), new WritableImage(1, 1));
to the listener:
sp.vvalueProperty().addListener(new ChangeListener<Number>() {
public void changed(ObservableValue<? extends Number> ov,
Number old_val, Number new_val) {
fileName.setText(imageNames[(new_val.intValue() - 1)/100]);
sp.snapshot(new SnapshotParameters(), new WritableImage(1, 1));
}
});
Calling snapshot on the ScrollPane seems to force the update every time the vvalueProperty changes. This seems to be a known workaround for several update problems with JavaFX - see here.