Javafx bidirectional binding SimpleDoubleProperty SimpleStringProperty Array out of bounds

1.5k Views Asked by At

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);
    }

}
1

There are 1 best solutions below

2
On BEST ANSWER

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

import java.net.URL;
import java.util.Arrays;
import java.util.List;
import java.util.ResourceBundle;
import javafx.concurrent.Task;
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.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.scene.text.TextBoundsType;

public class FXMLDocumentController implements Initializable {

  @FXML
  private TreeView treeView;

  private final List<JobProgress> jobList = 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"));

  @Override
  public void initialize(URL url, ResourceBundle rb) {
    TreeItem<String> rootNode = new TreeItem<>("Job Details");

    rootNode.setExpanded(true);

    for (JobProgress jb : jobList) {
      Text text = new Text();
      ProgressBar bar = new ProgressBar(0.0);
      bar.progressProperty().bind(jb.jobProgressProperty());

      text.setBoundsType(TextBoundsType.VISUAL);
      text.setFill(Color.BLACK);
      text.textProperty().bind(jb.jobProgressStringProperty());

      StackPane stack = new StackPane();
      stack.getChildren().addAll(bar, text);
      Node icon = stack;

      TreeItem<String> empLeaf = new TreeItem<>(jb.getJobName(), icon);
      boolean found = false;
      for (TreeItem<String> depNode : rootNode.getChildren()) {
        if (depNode.getValue().contentEquals(jb.getJobStatus())) {
          depNode.getChildren().add(empLeaf);
          found = true;
          break;
        }
      }
      if (!found) {
        TreeItem<String> depNode = new TreeItem<>(jb.getJobStatus());
        rootNode.getChildren().add(depNode);
        depNode.getChildren().add(empLeaf);
      }
    }
    treeView.setRoot(rootNode);
    new Thread(simulateProgress()).start();

  }

  private Task<Void> simulateProgress() {
    Task<Void> task = new Task<Void>() {

      @Override
      protected Void call() throws Exception {
        for (double i = 0.0; i <= 1.0; i += 0.1) {
          for (JobProgress job : jobList) {
            job.setJobProgress(i);
          }
          Thread.sleep(1000);
        }
        return null;
      }
    };
    return task;
  }
}

JavaFXApplication2.java

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();
    Scene scene = new Scene(root);
    stage.setScene(scene);
    stage.show();
  }

  public static void main(String[] args) {
    launch(args);
  }
}

JobProgress.java

import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.util.converter.NumberStringConverter;

public class JobProgress {

  private final SimpleDoubleProperty jobProgress;

  public double getJobProgress() {
    return jobProgress.get();
  }

  public void setJobProgress(double value) {
    jobProgress.set(value);
  }

  public SimpleDoubleProperty jobProgressProperty() {
    return jobProgress;
  }

  private final SimpleStringProperty jobProgressString;

  public void setJobProgressString(String value) {
    jobProgressString.set(value);
  }

  public String getJobProgressString() {
    return jobProgressString.get();
  }

  public SimpleStringProperty jobProgressStringProperty() {
    return jobProgressString;
  }

  private final SimpleStringProperty jobName;

  public String getJobStatus() {
    return jobStatus.get();
  }

  public void setJobStatus(String value) {
    jobStatus.set(value);
  }

  private final SimpleStringProperty jobStatus;

  public String getJobName() {
    return jobName.get();
  }

  public void setJobName(String value) {
    jobName.set(value);
  }

  public JobProgress(String jobName, String jobStatus) {
    this.jobName = new SimpleStringProperty(jobName);
    this.jobStatus = new SimpleStringProperty(jobStatus);
    this.jobProgress = new SimpleDoubleProperty(0.0);
    this.jobProgressString = new SimpleStringProperty();
    Bindings.bindBidirectional(jobProgressString, jobProgress, new NumberStringConverter());
  }
}

Solution looks like this:

enter image description here