when I run the following code I get an array index out of bounds exception. My goal is to bind both a Text obj and a ProgressBar object so that they both update simultaneously. I tried to use bidirectional binding. I must be stepping on something somewhere. I've run it many times and have seen it successfully run once or twice. Normally after a few moments of watching the progress bars the exception occurs. Sometimes I have seen a Text obj or two quit updating on their respective progress bars but the progress bar continues to progress until the exception. JavaFX binding experts please help.
Exception in thread "JavaFX Application Thread" java.lang.ArrayIndexOutOfBoundsException: -1
at java.util.ArrayList.elementData(ArrayList.java:418)
at java.util.ArrayList.get(ArrayList.java:431)
at javafx.scene.Parent.updateCachedBounds(Parent.java:1580)
at javafx.scene.Parent.recomputeBounds(Parent.java:1524)
at javafx.scene.Parent.impl_computeGeomBounds(Parent.java:1377)
at javafx.scene.Node.updateGeomBounds(Node.java:3556)
at javafx.scene.Node.getGeomBounds(Node.java:3509)
at javafx.scene.Node.getLocalBounds(Node.java:3457)
at javafx.scene.Node.updateTxBounds(Node.java:3620)
at javafx.scene.Node.getTransformedBounds(Node.java:3403)
at javafx.scene.Node.updateBounds(Node.java:538)
at javafx.scene.Parent.updateBounds(Parent.java:1708)
at javafx.scene.Parent.updateBounds(Parent.java:1706)
at javafx.scene.Parent.updateBounds(Parent.java:1706)
at javafx.scene.Parent.updateBounds(Parent.java:1706)
at javafx.scene.Parent.updateBounds(Parent.java:1706)
at javafx.scene.Scene$ScenePulseListener.pulse(Scene.java:2404)
at com.sun.javafx.tk.Toolkit.lambda$runPulse$30(Toolkit.java:314)
at com.sun.javafx.tk.Toolkit$$Lambda$163/2039336538.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:313)
at com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:340)
at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:525)
at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:505)
at com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$400(QuantumToolkit.java:334)
at com.sun.javafx.tk.quantum.QuantumToolkit$$Lambda$40/1271950976.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$36/1963387170.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
And here is the complete source.
package javafxapplication2;
import java.io.BufferedReader;
import java.net.URL;
import java.security.Provider.Service;
import java.text.DecimalFormat;
import java.text.FieldPosition;
import java.text.Format;
import java.text.ParsePosition;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.ResourceBundle;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Task;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.Node;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.scene.text.TextBoundsType;
import javafx.util.StringConverter;
import javafx.util.converter.DoubleStringConverter;
import javafx.util.converter.NumberStringConverter;
import org.controlsfx.control.HyperlinkLabel;
public class FXMLDocumentController implements Initializable {
@FXML
private TreeView treeView;
List<JobProgress> jobProgressInfos = Arrays.<JobProgress>asList(
new JobProgress("JJ&P_POP_DAT", "Running Jobs"),
new JobProgress("Des_master_33334", "Running Jobs"),
new JobProgress("FS_BUSS", "Running Jobs"),
new JobProgress("CONER_DWN_MIX", "Running Jobs"),
new JobProgress("newjb", "Running Jobs"),
new JobProgress("jb name", "Running Jobs"),
new JobProgress("tst jb", "Pending Jobs"),
new JobProgress("ult--ob", "Pending Jobs"),
new JobProgress("inl jb", "Pending Jobs"),
new JobProgress("fst jb", "Completed Jobs"),
new JobProgress("VoCt_Nme_Jb", "Completed Jobs"));
TreeItem<String> rootNode = new TreeItem<>("Job Details");
@Override
public void initialize(URL url, ResourceBundle rb) {
}
void setup()
{
rootNode.setExpanded(true);
for (JobProgress jobProgressInfo : jobProgressInfos)
{
Text text = new Text();
text.setBoundsType(TextBoundsType.VISUAL);
text.setFill(Color.BLACK);
ProgressBar bar = new ProgressBar(0.0);
//Bind bar and text:
bar.progressProperty().bind(jobProgressInfo.getJobPercentDouble());
text.textProperty().bind(jobProgressInfo.getJobPercentString());
StackPane stack = new StackPane();
stack.getChildren().addAll(bar,text);
Node icon = stack;
text.textProperty().addListener(new ChangeListener<String>()
{
public void changed(ObservableValue ov, String t, String t1) {
System.out.println(ov);
System.out.println(t);
System.out.println(t1);
System.out.println("");
}
});
TreeItem<String> empLeaf = new TreeItem<>(jobProgressInfo.getJobName(),icon);
boolean found = false;
for (TreeItem<String> depNode : rootNode.getChildren())
{
if (depNode.getValue().contentEquals(jobProgressInfo.getJobStatus()))
{
depNode.getChildren().add(empLeaf);
found = true;
break;
}
}
if (!found) {
TreeItem<String> depNode = new TreeItem<>(
jobProgressInfo.getJobStatus()
);
rootNode.getChildren().add(depNode);
depNode.getChildren().add(empLeaf);
}
}
treeView.setRoot(rootNode);
treeView.addEventHandler(MouseEvent.MOUSE_CLICKED,new EventHandler<MouseEvent>()
{
@Override public void handle(MouseEvent mouseEvent)
{
if ( mouseEvent.getButton().equals(MouseButton.PRIMARY))
{
if (treeView.getSelectionModel().getSelectedItem() instanceof TreeItem)
{
TreeItem item = (TreeItem)treeView.getSelectionModel().getSelectedItem();
if (item.getParent() != null && item.getParent().getParent() != null)
{
System.out.println("You primary clicked a job!");
}
}
}
else if ( mouseEvent.getButton().equals(MouseButton.SECONDARY))
{
if (treeView.getSelectionModel().getSelectedItem() instanceof TreeItem)
{
TreeItem item = (TreeItem)treeView.getSelectionModel().getSelectedItem();
if (item.getParent() != null && item.getParent().getParent() != null)
{
System.out.println("You scondary clicked a job!");
}
}
}
}
});
Service svc = new Service();
svc.createTask().run();
}
public class Service extends javafx.concurrent.Service<Void>
{
@Override
protected Task<Void> createTask()
{
return new TestTask();
}
private class TestTask extends Task<Void>
{
public TestTask()
{
}
@Override
protected Void call() throws Exception
{
for (JobProgress jp : jobProgressInfos)
{
new Thread(){
@Override
public void run(){
try {
for(double i=0; i<=1; i+=0.01)
{
DecimalFormat df = new DecimalFormat("#.####");
jp.setJobPercentDouble(Double.parseDouble( df.format(i)));
Thread.sleep(500);
}
} catch (InterruptedException ex) {
System.err.println("Error on Thread Sleep");
}
}
}.start();
}
return null;
}
@Override
protected void succeeded()
{
}
@Override
protected void cancelled()
{
}
}
}
public static class JobProgress
{
private final SimpleDoubleProperty jobPercentDouble;
private final SimpleStringProperty jobPercentString;
private final SimpleStringProperty jobName;
private final SimpleStringProperty jobStatus;
private final HyperlinkLabel hyperLink;
private JobProgress(String aJobName, String aJobStatus) {
this.jobName = new SimpleStringProperty(aJobName);
this.jobStatus = new SimpleStringProperty(aJobStatus);
this.hyperLink = new HyperlinkLabel("test");
this.jobPercentDouble = new SimpleDoubleProperty();
this.jobPercentString = new SimpleStringProperty();
Bindings.bindBidirectional(jobPercentString,jobPercentDouble, new NumberStringConverter());
// StringConverter<? extends Number> converter = new NumberStringConverter();
// Bindings.bindBidirectional(stringProp, doubleProp, new Format()
// {
//
// @Override
// public StringBuffer format(Object obj, StringBuffer toAppendTo,FieldPosition pos)
// {
// System.out.println(obj.toString());
// return toAppendTo.append(obj.toString());
// }
//
// @Override
// public Object parseObject(String source, ParsePosition pos)
// {
// System.out.println("parseObj " + source);
// return Integer.parseInt(source);
// }
//
// });
}
public String getJobName() {
return jobName.get();
}
public void setJobName(String value) {
jobName.set(value);
}
public String getJobStatus() {
return jobStatus.get();
}
public void setJobStatus(String value) {
jobStatus.set(value);
}
public SimpleStringProperty getJobPercentString() {
return jobPercentString;
}
public void setJobPercentString(String value) {
jobPercentString.set(value);
}
public SimpleDoubleProperty getJobPercentDouble() {
return jobPercentDouble;
}
public void setJobPercentDouble(double value) {
jobPercentDouble.set(value);
}
}
}
and here is the FXML
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane id="AnchorPane" prefHeight="509.0" prefWidth="222.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.40" fx:controller="javafxapplication2.FXMLDocumentController">
<children>
<TreeView fx:id="treeView" layoutX="11.0" layoutY="5.0" prefHeight="496.0" prefWidth="200.0" AnchorPane.bottomAnchor="8.0" AnchorPane.leftAnchor="11.0" AnchorPane.rightAnchor="11.0" AnchorPane.topAnchor="5.0" />
</children>
</AnchorPane>
And last but not least the main:
package javafxapplication2;
import java.net.URL;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class JavaFXApplication2 extends Application {
@Override
public void start(Stage stage) throws Exception
{
URL resource = getClass().getResource("FXMLDocument.fxml");
FXMLLoader loader = new FXMLLoader(resource);
Parent root = (Parent) loader.load();
FXMLDocumentController controller = loader.getController();
controller.setup();
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
/**
* @param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
}
You mixed so much parts together, like Service, Task, Thread. And your naming conventions are not the one for JavaFX, like your properties.
After I cleaned the code a bit and made an own class file of JobProgress, you now should be ready to get it.
You missed to set an initial progress on your JobProgress Objects. So how should the bibinding can convert a number to string if no number is present?!
Also I've removed some listeners and action handlers. You should be able to simply add them again.
FXMLDocumentController.java
JavaFXApplication2.java
JobProgress.java
Solution looks like this: