JavaFx: can data binding (textfield) be used in production mode? - java

I use java 8.0.45. I have implemented my first javafx application (very simple) with data binding. However, biding from user input-> pojo seems to work with bugs. I've checked about 200 times. I entered new values in text fields and after that I checked model values. The same code, the same my behaviour. Sometimes everything works fine (in most cases - about 80-90%) sometimes model value!=textfield value. I've noticed the following. Data binding for some certain text field works,works and then at some point of time that binding stops working and all new values for this certain textfield are not passed to model. Nor exceptions. Nor any warnings. Nothing. Just binding doesn't work.
I have 4 textfiled which are created via fxml. Two for string model type. One for integer. One for bigdecimal. The problem happens to all these fields(sometimes to one, sometimes to several). As my number fields can have null values, I use for example PropertyObject but not IntegerProperty (people from openjfx advised so).
So is this JavaFx bug or what? P.S. I use felix osgi, weld cdi, and pax - I don't know if it matters...
My code is the following:
DTO - POJO Model
public class Task {
private String name;
private Integer order;
private BigDecimal weight;
private String comment;
private final PropertyChangeSupport propertyChangeSupport;
public Task() {
this.propertyChangeSupport = new PropertyChangeSupport(this);
}
public String getName() {
return name;
}
public void setName(String name) {
String pv = this.name ;
this.name = name;
propertyChangeSupport.firePropertyChange("name", pv, name);
}
public Integer getOrder() {
return order;
}
public void setOrder(Integer order) {
Integer pv = this.order;
this.order = order;
propertyChangeSupport.firePropertyChange("order", pv, this.order);
}
public BigDecimal getWeight() {
return weight;
}
public void setWeight(BigDecimal weight) {
BigDecimal pv = this.weight;
this.weight = weight;
propertyChangeSupport.firePropertyChange("weight", pv, weight);
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
String pv = this.comment;
this.comment = comment;
propertyChangeSupport.firePropertyChange("comment", pv, this.comment);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
propertyChangeSupport.addPropertyChangeListener(listener);
}
}
Adapter
public class TaskAdapter {
private StringProperty nameProperty;
private ObjectProperty<Integer> orderProperty;
private ObjectProperty<BigDecimal> weightProperty;
private StringProperty commentProperty;
public TaskAdapter(Task task) {
try {
nameProperty=new JavaBeanStringPropertyBuilder().bean(task).name("name").build();
orderProperty=new JavaBeanObjectPropertyBuilder<Integer>().bean(task).name("order").build();
weightProperty=new JavaBeanObjectPropertyBuilder<BigDecimal>().bean(task).name("weight").build();
commentProperty=new JavaBeanStringPropertyBuilder().bean(task).name("comment").build();
} catch (NoSuchMethodException ex) {
Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
}
}
public StringProperty getNameProperty() {
return nameProperty;
}
public ObjectProperty<Integer> getOrderProperty() {
return orderProperty;
}
public ObjectProperty<BigDecimal> getWeightProperty() {
return weightProperty;
}
public StringProperty getCommentProperty() {
return commentProperty;
}
}
BigDecimal Converter
public class SimpleBigDecimalStringConverter extends StringConverter<BigDecimal>{
#Override
public String toString(BigDecimal i) {
if (i == null) {
return "" ;
} else {
return i.toString();
}
}
#Override
public BigDecimal fromString(String string) {
if (string.trim().length() == 0) {
return null ;
} else {
try {
return new BigDecimal(string);
} catch (NumberFormatException nfe) {
return null ;
}
}
}
}
IntegerConverter
public class SimpleIntegerStringConverter extends StringConverter<Integer>{
#Override
public String toString(Integer i) {
if (i == null) {
return "" ;
} else {
return i.toString();
}
}
#Override
public Integer fromString(String string) {
if (string.trim().length() == 0) {
return null ;
} else {
try {
return Integer.valueOf(string);
} catch (NumberFormatException nfe) {
return null ;
}
}
}
}
Initializing code
Task task=new Task();
TaskAdapter adapter=new TaskAdapter(task);
nameTextField.textProperty().bindBidirectional(adapter.getNameProperty());
orderTextField.textProperty().bindBidirectional(adapter.getOrderProperty(),new SimpleIntegerStringConverter());
weightTextField.textProperty().bindBidirectional(adapter.getWeightProperty(),new BigDecimalStringConverter());
commentTextField.textProperty().bindBidirectional(adapter.getCommentProperty());

What is happening
JavaFX Bindings use WeakChangeListeners behind the scenes to implement the binding. This means that the binding itself can be garbage collected if no other references to it are in scope. In your code, the adapter is defined as a local variable, so it gets prematurely garbage collected at some arbitrary time when the gc runs.
Demo
Here's a demo using your code that shows the issue. It has the same text fields you define, plus two buttons. One button dumps the value of the task to the console, the other forces the garbage collector to run. You'll see that the binding stops working as soon as you run the gc.
import javafx.application.Application;
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.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.converter.BigDecimalStringConverter;
public class POJOBindingExample extends Application {
private TextField nameTextField = new TextField();
private TextField orderTextField = new TextField();
private TextField weightTextField = new TextField();
private TextField commentTextField = new TextField();
#Override
public void start(Stage primaryStage) {
Task task = new Task();
TaskAdapter adapter = new TaskAdapter(task);
nameTextField.textProperty().bindBidirectional(adapter.getNameProperty());
orderTextField.textProperty().bindBidirectional(adapter.getOrderProperty(),new SimpleIntegerStringConverter());
weightTextField.textProperty().bindBidirectional(adapter.getWeightProperty(),new BigDecimalStringConverter());
commentTextField.textProperty().bindBidirectional(adapter.getCommentProperty());
GridPane grid = new GridPane();
grid.addRow(0, new Label("Name:"), nameTextField);
grid.addRow(1, new Label("Order:"), orderTextField);
grid.addRow(2, new Label("Weight:"), weightTextField);
grid.addRow(3, new Label("Comment:"), commentTextField);
Button showButton = new Button("Show Task");
showButton.setOnAction(e -> {
System.out.println(task.getName());
System.out.println(task.getOrder());
System.out.println(task.getWeight());
System.out.println(task.getComment());
System.out.println();
});
Button gcButton = new Button("Run GC");
gcButton.setOnAction(e -> System.gc());
HBox buttons = new HBox(10, showButton, gcButton);
BorderPane.setAlignment(grid, Pos.CENTER);
BorderPane.setAlignment(buttons, Pos.CENTER);
BorderPane.setMargin(grid, new Insets(10));
BorderPane.setMargin(buttons, new Insets(10));
BorderPane root = new BorderPane(grid, null, null, buttons, null);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Fix
To fix the problem, you need to ensure a reference to the TaskAdapter persists as long as you need it. In the above code, if you move the reference to the TaskAdapter so that it is an instance field, everything will work as required:
public class POJOBindingExample extends Application {
private TextField nameTextField = new TextField();
private TextField orderTextField = new TextField();
private TextField weightTextField = new TextField();
private TextField commentTextField = new TextField();
private TaskAdapter adapter;
#Override
public void start(Stage primaryStage) {
Task task = new Task();
adapter = new TaskAdapter(task);
// ... etc
}
}
You might also be interested in reading Tomas Mikula's blog, though I don't think you can use his library directly to implement binding to a POJO.

Related

Populate control without flooding FXThread

I have a following situation:
Large file with a lot of lines (~100k, logs from server). Each line in this file should be parsed, filtered and disaplayed on UI.
To read data from file I use BufferedReader, wich read lines, parse it and prepare for disaplying. It runs on different thread (THREAD-1) and populates BlockingQueue. In another thread (THREAD-2) runned UIUpdater - it purpose to get line batch from queue and run something line this:
Platform.runLater(() -> logArea.append(batchedLine));
Obviously, FX Thread floods and UI is freezes.
So, question is: where I can get information about patterns/best practices to resolove this issue?
It really depends on the control that you want to populate.
Adding lots of nodes to the scene-graph is expensive therefore it will be slow (for example putting Text objects to any container).
I would suggest the usage of a control that was originally designed to display a huge amount of data, like ListView.
In the example, even during the update of the ListView the Button is responsive.
Main.java
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
HBox root = new HBox();
Scene scene = new Scene(root, 700, 400, Color.WHITE);
TableView<Person> personsTable = new TableView<Person>();
TableColumn<Person, String> nameCol = new TableColumn<Person, String>("Name");
nameCol.setCellValueFactory(new PropertyValueFactory<>("name"));
personsTable.getColumns().add(nameCol);
ObservableList<Person> persons = FXCollections.observableArrayList();
Thread th = new Thread(new Runnable() {
#Override
public void run() {
for (int i = 0; i < 100000; i++) {
Person person = new Person();
person.setName("Name" + i);
person.setAddress("Address" + i);
person.setCountry("Country" + i);
person.setCourse("Course" + i);
persons.add(person);
try {
Thread.sleep(5);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}) ;
th.start();
personsTable.setItems(persons);
Button b = new Button();
b.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("I am printing independently of Person update!");
}
});
root.getChildren().addAll(personsTable, b);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Person.java
public class Person {
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getCourse() {
return course;
}
public void setCourse(String course) {
this.course = course;
}
private String name;
private String address;
private String country;
private String course;
}
User jewelsea has made a really good example on lgging. With little tailoring it could solve your issue.

Create a null-safe BooleanBinding with JavaFX 8

I need help creating a null-safe BooleanBinding. It has to be null-safe since I can not provide default values for all attributes in the model (one reason: the model contains enumerations). My first approach has been as follows:
executeButtonDisabled.bind(missionProperty().isNotNull().and(missionProperty().get().statusProperty().isNotEqualTo(MissionStatus.CREATED)));
final BooleanBinding isNotExecutingBinding = missionProperty().isNotNull().and(missionProperty().get().statusProperty().isNotEqualTo(MissionStatus.EXECUTING));
completeButtonDisabled.bind(isNotExecutingBinding);
cancelButtonDisabled.bind(isNotExecutingBinding)
But that approach does not work because the complete expression is evaluated which results in a NullPointerException (but it correctly updates the buttons, when a property is provided). Now I am trying to use the Bindings class as suggested in JavaFX binding and null values, but I can't make it work. Here's my current approach:
final BooleanBinding isNotCreatedBinding = Bindings.createBooleanBinding(
() -> mission.isNull().getValue()
? true
: missionProperty().get().statusProperty().isNotEqualTo(MissionStatus.CREATED).getValue());
final BooleanBinding isNotExecutingBinding = Bindings.createBooleanBinding(
() -> mission.isNull().getValue()
? true
: missionProperty().get().statusProperty().isNotEqualTo(MissionStatus.EXECUTING).getValue());
executeButtonDisabled.bind(isNotCreatedBinding);
completeButtonDisabled.bind(isNotExecutingBinding);
cancelButtonDisabled.bind(isNotExecutingBinding);
But this does not work and I do not understand why. It seems that the property binding for modelProperty() does not work here! Can you explain to me how-to convert the first working solution (at least without null) to a proper null-safe solution?
Edit 2016-04-26: The suggested solution does not work, therefore I created a simple fully-working example:
Mission.java:
package de.florianwolters.example.javafx.bindings;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Mission {
enum Status {
CREATED,
EXECUTING,
COMPLETED,
CANCELED;
}
private final StringProperty shortName = new SimpleStringProperty();
private final ObjectProperty<Status> status = new SimpleObjectProperty<>();
public Mission(final String shortName) {
this.setShortName(shortName);
this.setStatus(Status.CREATED);
}
public String getShortName() {
return shortNameProperty().get();
}
public void setShortName(final String shortName) {
shortNameProperty().set(shortName);
}
public StringProperty shortNameProperty() {
return shortName;
}
public Status getStatus() {
return statusProperty().get();
}
public void setStatus(final Status status) {
statusProperty().set(status);
}
public ObjectProperty<Status> statusProperty() {
return status;
}
}
MissionDetailsViewModel.java:
package de.florianwolters.example.javafx.bindings;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.SimpleObjectProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class MissionDetailsViewModel {
/**
* The logger used for logging in the `MissionDetailsViewModel` class.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(
MissionDetailsViewModel.class);
private ObjectProperty<Mission> mission = new SimpleObjectProperty<>();
private final ReadOnlyBooleanWrapper executeButtonDisabled = new ReadOnlyBooleanWrapper(true);
private final ReadOnlyBooleanWrapper completeButtonDisabled = new ReadOnlyBooleanWrapper(true);
private final ReadOnlyBooleanWrapper cancelButtonDisabled = new ReadOnlyBooleanWrapper(true);
/**
* Constructs a `MissionDetailsViewModel`.
*/
public MissionDetailsViewModel(final ObjectProperty<Mission> mission) {
this.mission.bind(mission);
// partialWorkingBinding();
notWorkingBinding();
}
private void notWorkingBinding() {
final BooleanBinding isNotCreatedBinding = Bindings.createBooleanBinding(
() -> missionProperty().isNull().get()
? true
: missionProperty().get().statusProperty().isNotEqualTo(Mission.Status.CREATED).get(),
missionProperty());
final BooleanBinding isNotExecutingBinding = Bindings.createBooleanBinding(
() -> mission.isNull().get()
? true
: missionProperty().get().statusProperty().isNotEqualTo(Mission.Status.EXECUTING).get(),
missionProperty());
executeButtonDisabled.bind(isNotCreatedBinding);
completeButtonDisabled.bind(isNotExecutingBinding);
cancelButtonDisabled.bind(isNotExecutingBinding);
}
private void partialWorkingBinding() {
executeButtonDisabled.bind(missionProperty().isNotNull().and(missionProperty().get().statusProperty().isNotEqualTo(Mission.Status.CREATED)));
final BooleanBinding isNotExecutingBinding = missionProperty().isNotNull().and(missionProperty().get().statusProperty().isNotEqualTo(Mission.Status.EXECUTING));
completeButtonDisabled.bind(isNotExecutingBinding);
cancelButtonDisabled.bind(isNotExecutingBinding);
}
public boolean isExecuteButtonDisabled() {
return executeButtonDisabledProperty().get();
}
public ReadOnlyBooleanProperty executeButtonDisabledProperty() {
return executeButtonDisabled;
}
public boolean isCompleteButtonDisabled() {
return completeButtonDisabledProperty().get();
}
public ReadOnlyBooleanProperty completeButtonDisabledProperty() {
return completeButtonDisabled;
}
public boolean isCancelButtonDisabled() {
return cancelButtonDisabledProperty().get();
}
public ReadOnlyBooleanProperty cancelButtonDisabledProperty() {
return cancelButtonDisabled;
}
public Mission getMission() {
return missionProperty().get();
}
public void setMission(final Mission mission) {
missionProperty().set(mission);
}
public ObjectProperty<Mission> missionProperty() {
return mission;
}
}
MissionDetailsViewModelTest.java:
package de.florianwolters.example.javafx.bindings;
import static eu.lestard.assertj.javafx.api.Assertions.assertThat;
import javafx.beans.property.SimpleObjectProperty;
import org.junit.Before;
import org.junit.Test;
public final class MissionDetailsViewModelTest {
private Mission mission;
private MissionDetailsViewModel viewModel;
#Before
public void setUp() {
mission = new Mission("My Short Name");
viewModel = new MissionDetailsViewModel(new SimpleObjectProperty<Mission>(mission));
}
#Test
public void testInitialValues() {
assertThat(viewModel.executeButtonDisabledProperty()).isFalse();
assertThat(viewModel.completeButtonDisabledProperty()).isTrue();
assertThat(viewModel.cancelButtonDisabledProperty()).isTrue();
}
#Test
public void testMissionStatusSetToExecuting() {
mission.setStatus(Mission.Status.EXECUTING);
assertThat(viewModel.executeButtonDisabledProperty()).isTrue();
assertThat(viewModel.completeButtonDisabledProperty()).isFalse();
assertThat(viewModel.cancelButtonDisabledProperty()).isFalse();
}
#Test
public void testMissionStatusSetToCompleted() {
mission.setStatus(Mission.Status.COMPLETED);
assertThat(viewModel.executeButtonDisabledProperty()).isTrue();
assertThat(viewModel.completeButtonDisabledProperty()).isTrue();
assertThat(viewModel.cancelButtonDisabledProperty()).isTrue();
}
#Test
public void testMissionStatusSetToCanceled() {
mission.setStatus(Mission.Status.CANCELED);
assertThat(viewModel.executeButtonDisabledProperty()).isTrue();
assertThat(viewModel.completeButtonDisabledProperty()).isTrue();
assertThat(viewModel.cancelButtonDisabledProperty()).isTrue();
}
}
The unit test fails with the code above (the method notWorkingBinding() is used) but works with the method partialWorkingBinding(). What am I doing wrong?
You set up the calculation function for isNotCreatedBinding, but you didn't set the dependencies for the binding. You need to add mision as dependency:
Bindings.createBooleanBinding(
() -> mission.isNull().getValue()
? true
: missionProperty().get().statusProperty().isNotEqualTo(MissionStatus.CREATED).getValue(), mission);
EDIT
You need to listen to the statusProperty instead of missionProperty, which will not work with createBooleanBinding when missionProperty().get() == null.
But you can use a When binding:
(is causing a NullPointerException as already mentioned in the question)
BooleanBinding isNotCreatedBinding = new When(mission.isNotNull()).then(mission.get().statusProperty().isNotEqualTo(Mission.Status.CREATED)).otherwise(false);
Or a more low-level solution:
missionProperty().addListener((ov, m, m1) -> {
if (m1 != null) {
executeButtonDisabled.bind(m1.statusProperty().isNotEqualTo(Mission.Status.CREATED));
}else {
executeButtonDisabled.unbind();
executeButtonDisabled.set(false);
}
});
Tomas Mikula's ReactFX framework (v 2.0) has this functionality built in:
import org.reactfx.value.Val;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
public class NestedBindingTest {
public static void main(String[] args) {
BooleanProperty disable = new SimpleBooleanProperty();
disable.addListener((obs, wasDisabled, isNowDisabled) ->
System.out.println("disable: "+wasDisabled+" -> "+isNowDisabled));
ObjectProperty<Item> item = new SimpleObjectProperty<>();
Val<Item.Status> status = Val.flatMap(item, Item::statusProperty);
disable.bind(status.map(s -> s == Item.Status.PENDING).orElseConst(true));
Item i = new Item();
System.out.println("Setting item");
item.set(i);
System.out.println("Setting item status to PENDING");
i.setStatus(Item.Status.PENDING);
System.out.println("Setting item status to READY");
i.setStatus(Item.Status.READY);
System.out.println("Setting item to null");
item.set(null);
}
public static class Item {
public enum Status {PENDING, READY}
private final ObjectProperty<Status> status = new SimpleObjectProperty<>();
public final ObjectProperty<Status> statusProperty() {
return this.status;
}
public final NestedBindingTest.Item.Status getStatus() {
return this.statusProperty().get();
}
public final void setStatus(final NestedBindingTest.Item.Status status) {
this.statusProperty().set(status);
}
}
}

NullPointerException while using constructor in controller class. JavaFX

This is my first post here, so I hope this question isn't a duplicate. I'm a hobbyist who is trying to use JavaFX to create an application which can manage a debating tournament.
I want to create a Singleton object in TournamentCreation, which TournamentEditor can also access and manipulate.
It seems that when my Singleton GlobalInstance object is created (which holds a tournament inside it that can be accessed and edited from elsewhere), a NullPointerException is thrown. Using debugging I've managed to figure out that it is occurring when GlobalInstance calls the default Tournament constructor.
Code time. Here is the TournamentCreationController where the error begins with the line GlobalInstance.getInstance().currentTournament().setRounds(roundsIn);
import java.net.URL;
import java.util.ResourceBundle;
import BusinessLogic.GlobalInstance;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.TextField;
public class TournamentCreationController implements Initializable , ControlledScreen {
ScreensController myController;
#FXML
TextField nameChooser;
#FXML
ChoiceBox roundNumberChooser;
#FXML
ChoiceBox breakNumberChooser;
#FXML
ChoiceBox noviceBreakNumberChooser;
#FXML
ChoiceBox eslBreakNumberChooser;
#FXML
ChoiceBox proAmBreakNumberChooser;
/**
* Initializes the controller class.
*/
#Override
public void initialize(URL url, ResourceBundle rb)
{
roundNumberChooser.setItems(FXCollections.observableArrayList(1,2,3,4,5,6,7,8,9));
roundNumberChooser.getSelectionModel().select(4);
breakNumberChooser.setItems(FXCollections.observableArrayList(4,8,16,32,64));
breakNumberChooser.getSelectionModel().selectFirst();
noviceBreakNumberChooser.setItems(FXCollections.observableArrayList(0,4,8,16,32,64));
noviceBreakNumberChooser.getSelectionModel().selectFirst();
eslBreakNumberChooser.setItems(FXCollections.observableArrayList(0,4,8,16,32,64));
eslBreakNumberChooser.getSelectionModel().selectFirst();
proAmBreakNumberChooser.setItems(FXCollections.observableArrayList(0,4,8,16,32,64));
proAmBreakNumberChooser.getSelectionModel().selectFirst();
}
public void setScreenParent(ScreensController screenParent)
{
myController = screenParent;
}
#FXML
private void goToEditor(ActionEvent event)
{
String nameIn = nameChooser.getText();
int roundsIn = (int)roundNumberChooser.getSelectionModel().getSelectedItem();
int openBreakTeamsIn = (int)breakNumberChooser.getSelectionModel().getSelectedItem();
int eslBreakTeamsIn = (int)eslBreakNumberChooser.getSelectionModel().getSelectedItem();
int noviceBreakTeamsIn = (int)noviceBreakNumberChooser.getSelectionModel().getSelectedItem();
int proAmBreakTeamsIn = (int)proAmBreakNumberChooser.getSelectionModel().getSelectedItem();
GlobalInstance.getInstance().currentTournament().setRounds(roundsIn);
GlobalInstance.getInstance().currentTournament().setOpenBreakTeams(openBreakTeamsIn);
GlobalInstance.getInstance().currentTournament().setESLBreakTeams(eslBreakTeamsIn);
GlobalInstance.getInstance().currentTournament().setNoviceBreakTeams(noviceBreakTeamsIn);
GlobalInstance.getInstance().currentTournament().setProAmBreakTeams(proAmBreakTeamsIn);
myController.setScreen(ScreensFramework.screen4ID);
}
}
And here is the Singleton class:
public class GlobalInstance
{
private final static GlobalInstance instance = new GlobalInstance();
private Tournament tournament = new Tournament();
public static GlobalInstance getInstance()
{
return instance;
}
public Tournament currentTournament()
{
return tournament;
}
}
And finally here is the Tournament class (ignore most of it, the exact error occurs at name.set("name") in the default constructor):
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.StringProperty;
public class Tournament
{
private StringProperty name;
private IntegerProperty rounds;
private BooleanProperty eslBreak;
private BooleanProperty noviceBreak;
private BooleanProperty proAmBreak;
private IntegerProperty openBreakTeams;
private IntegerProperty eslBreakTeams;
private IntegerProperty noviceBreakTeams;
private IntegerProperty proAmBreakTeams;
public Tournament()
{
name.set("name");
rounds.set(0);
eslBreak.set(false);
noviceBreak.set(false);
proAmBreak.set(false);
openBreakTeams.set(4);
eslBreakTeams.set(0);
noviceBreakTeams.set(0);
proAmBreakTeams.set(0);
}
public Tournament(String nameIn, int roundsIn, int openBreakTeamsIn, int eslBreakTeamsIn, int noviceBreakTeamsIn, int proAmBreakTeamsIn)
{
name.set(nameIn);
rounds.set(roundsIn);
openBreakTeams.set(openBreakTeamsIn);
if(eslBreakTeamsIn==0)
{
eslBreak.set(false);
eslBreakTeams.set(0);
}
else
{
eslBreak.set(true);
eslBreakTeams.set(eslBreakTeamsIn);
}
if(noviceBreakTeamsIn==0)
{
noviceBreak.set(false);
noviceBreakTeams.set(0);
}
else
{
noviceBreak.set(true);
noviceBreakTeams.set(noviceBreakTeamsIn);
}
if(proAmBreakTeamsIn==0)
{
proAmBreak.set(false);
proAmBreakTeams.set(0);
}
else
{
proAmBreak.set(true);
proAmBreakTeams.set(noviceBreakTeamsIn);
}
}
public String getName()
{
return name.get();
}
public void setName(String nameIn)
{
name.set(nameIn);
}
public int getRounds()
{
return rounds.get();
}
public void setRounds(int roundsIn)
{
rounds.set(roundsIn);
}
public int getOpenBreakTeams()
{
return openBreakTeams.get();
}
public void setOpenBreakTeams(int openBreakTeamsIn)
{
openBreakTeams.set(openBreakTeamsIn);
}
public boolean getESLBreak()
{
return eslBreak.get();
}
public int getESLBreakNumber()
{
return eslBreakTeams.get();
}
public void setESLBreakTeams(int eslBreakTeamsIn)
{
if (eslBreakTeamsIn==0)
{
eslBreak.set(false);
eslBreakTeams.set(0);
}
else
{
eslBreak.set(true);
eslBreakTeams.set(eslBreakTeamsIn);
}
}
public boolean getNoviceBreak()
{
return noviceBreak.get();
}
public int getNoviceBreakNumber()
{
return noviceBreakTeams.get();
}
public void setNoviceBreakTeams(int noviceBreakTeamsIn)
{
if (noviceBreakTeamsIn==0)
{
noviceBreak.set(false);
noviceBreakTeams.set(0);
}
else
{
noviceBreak.set(true);
noviceBreakTeams.set(noviceBreakTeamsIn);
}
}
public boolean getProAmBreak()
{
return proAmBreak.get();
}
public int getProAmBreakNumber()
{
return proAmBreakTeams.get();
}
public void setProAmBreakTeams(int proAmBreakTeamsIn)
{
if (proAmBreakTeamsIn==0)
{
proAmBreak.set(false);
proAmBreakTeams.set(0);
}
else
{
proAmBreak.set(true);
proAmBreakTeams.set(proAmBreakTeamsIn);
}
}
}
While programming, the error messages are the best helpers for resolving issues. In Java, the root cause of exception can tell the actual problem. So if we look at your use case,
Caused by: java.lang.ExceptionInInitializerError
at TabIt.TournamentCreationController.goToEditor(TournamentCreationController.java:84)
tells us, the error occurred in goToEditor() method at line 84 of TournamentCreationController class. Then if we continue to read the rest of exception tracktrace message, we see the actual root error (cause) of it,
Caused by: java.lang.NullPointerException
at BusinessLogic.Tournament.<init>(Tournament.java:24)
Here it is saying that the type of error is NullPointerException and is at line 24 of Tournament class.
These hints are enough to determine the problem.
UPDATE
Based on your updated question, the problem is that name variable is not initialized yet. Trying to use uninitialized object throws null pointer exception. the Tournament's constructor should be:
public Tournament()
{
name = new SimpleStringProperty("name");
rounds = new SimpleIntegerProperty(0);
eslBreak = new SimpleBooleanProperty(false);
// etc.
}
public Tournament(String nameIn, int roundsIn, int openBreakTeamsIn, int eslBreakTeamsIn, int noviceBreakTeamsIn, int proAmBreakTeamsIn)
{
name = new SimpleStringProperty(nameIn);
rounds = new SimpleIntegerProperty(roundsIn);
eslBreak = new SimpleBooleanProperty(openBreakTeamsIn);
// etc.
}

ListView is not reflecting changes

I created a TableView a while back and registered Properties to each of the TableColumns. Editing of the internal data reflected itself back in the TableView just fine.
With a ListView, however, it is a different story. The changes are not being shown right away unless I close the frame and open it again.
My ListView consists of ActionSteps. Note that I used the Javafx beans properties.
package application.objects;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.function.IntPredicate;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class ActionStep {
private StringProperty actionStepID;
private ObjectProperty<LocalDate> dateSet, dateFinished;
private StringProperty stepName;
private IntegerProperty completion;
private ArrayList<StepComment> comments;
public ActionStep(String name) {
actionStepID = new SimpleStringProperty();
stepName = new SimpleStringProperty();
dateSet = new SimpleObjectProperty<LocalDate>();
dateFinished = new SimpleObjectProperty<LocalDate>();
completion = new SimpleIntegerProperty();
stepName.setValue(name);
}
public void setName(String name) {
stepName.setValue(name);
}
public String getName() {
return stepName.getValue();
}
public StringProperty stepNameProperty() {
return actionStepID;
}
public void setID(String id) {
actionStepID.setValue(id);
}
public String getID() {
return actionStepID.get();
}
public StringProperty actionStepIDProperty() {
return actionStepID;
}
public void setCompletion(int percent) {
if (percent < 0 || percent > 100)
return;
completion.set(percent);
}
public int getCompletion() {
return completion.get();
}
public IntegerProperty completionProperty() {
return completion;
}
public void setDateSet(LocalDate date) {
dateSet.set(date);
}
public LocalDate getDateSet() {
return dateSet.get();
}
public ObjectProperty<LocalDate> dateSetProperty() {
return dateSet;
}
public void setDateFinished(LocalDate date) {
dateFinished.set(date);
}
public LocalDate getDateFinished() {
return dateFinished.get();
}
public ObjectProperty<LocalDate> dateFinishedProperty() {
return dateFinished;
}
public String toString() {
return stepNameProperty().get();
}
}
My ListView uses an ObservableList as well.
#FXML
private ListView<ActionStep> actionStepsListView;
private ObservableList<ActionStep> listOfSteps;
listOfSteps = FXCollections.observableArrayList();
actionStepsListView.setItems(listOfSteps);
if (plan != null) {
ArrayList<ActionStep> arrayOfSteps = plan.getStepsArrayList();
for (int i = 0; i < arrayOfSteps.size(); i++)
listOfSteps.add(arrayOfSteps.get(i));
} else
plan = new ActionPlan();
How come changes made to the ObservableList do not reflect themselves in the ListView? I noticed that the ListView called upon every object's toString() to display their values in the ListView, rather than binding it to their Properties.
What am I doing wrong? Am I supposed to override a cell factory or something?
Note that you're trying to do something more complex with the cells in your ListView than you were with the cells in the TableView. In the TableView, the objects displayed in the cells were changing, so it was easy for the cells to observe this. In the ListView, you want the cells to notice when properties that belong to the objects displayed in the cells change; this is one further step removed, so you have to do a bit of extra coding (though not much, as you'll see).
You could create a custom cell factory to bind to the stepNameProperty(), but it's tricky (you have to make sure to unbind/remove listeners from old items in the updateItem() method).
The easier way, though, which isn't well documented is to use an ObservableList with an extractor defined.
First, fix your method names: you have some weird mismatches in the code you posted. The getX/setX/xProperty method names should all match correctly. I.e. instead of
public void setName(String name) {
stepName.setValue(name);
}
public String getName() {
return stepName.getValue();
}
public StringProperty stepNameProperty() {
return actionStepID;
}
you should have
public final void setName(String name) {
stepName.setValue(name);
}
public final String getName() {
return stepName.getValue();
}
public StringProperty nameProperty() {
return stepName;
}
and similarly for the other property accessor methods. (Obviously, the names of the fields can be anything you like, as they're private.) Making the get/set methods final is good practice.
Then, create the list with an extractor. The extractor is a function that maps each element in the list to an array of Observables which the list will observe. If those values change, it will fire list updates to the list's observers. Since your ActionStep's toString() method references the nameProperty(), I assume you want the ListView to update if the nameProperty() changes. So you want to do
listOfSteps = FXCollections.observableArrayList(
actionStep -> new Observable[] { actionStep.nameProperty() } // the "extractor"
);
actionStepsListView.setItems(listOfSteps);
Note that in earlier versions of JavaFX 2.2 the ListView did not properly observe the list for update events; this was fixed (if I remember correctly) shortly prior to the release of Java 8. (Since you tagged the question JavaFX8, I assume you're using Java 8 and so you should be fine here.)
If you are not using Java 8, you can use the following (equivalent but more verbose) code:
listOfSteps = FXCollections.observableArrayList(
new Callback<ActionStep, Observable[]>() {
#Override
public Observable[] call(ActionStep actionStep) {
return new Observable[] { actionStep.nameProperty() } ;
}
});
actionStepListView.setItems(listOfSteps);
Here is sample how make listview with custom objects:
public class JavaFX_ListView extends Application {
class MyObject {
String day;
int number;
MyObject(String d, int n) {
day = d;
number = n;
}
String getDay() {
return day;
}
int getNumber() {
return number;
}
#Override
public String toString() {
return number + " " + day;
}
}
ObservableList<MyObject> myList;
// Create dummy list of MyObject
private void prepareMyList() {
myList = FXCollections.observableArrayList();
myList.add(new MyObject("Sunday", 50));
myList.add(new MyObject("Monday", 60));
myList.add(new MyObject("Tuesday", 20));
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("sample");
prepareMyList();
ListView<MyObject> listView = new ListView<>();
listView.setItems(myList);
Pane root = new Pane();
root.getChildren().add(listView);
primaryStage.setScene(new Scene(root, 300, 250));
primaryStage.show();
// testing
Timer timer = new Timer();
timer.schedule(new UpdateListTask(), 1000, 1000);
}
public static void main(String[] args) {
launch(args);
}
// testing
public class UpdateListTask extends TimerTask {
#Override
public void run() {
Platform.runLater(new Runnable() {
#Override
public void run() {
myList.add(new MyObject("sample", Calendar.getInstance()
.getTime().getSeconds()));
}
});
}
}
}

javafx 2 CheckBoxTableCell not working in tableview

I need some help, even my case is simple.
But i can't understand why the check box in the tableview is not geting the value.
I have get the example from the javafx ensemble
I have a class
public class ReservationObj {
private BooleanProperty tcheck;
private StringProperty tname;
private StringProperty tstatus;
private int tser;
public ReservationObj(boolean tcheck, String lname , String lBStatus, int serialinVector) {
this.tcheck = new SimpleBooleanProperty(tcheck);
this.tname = new SimpleStringProperty(lname);
this.tstatus = new SimpleStringProperty(lBStatus);
this.tser = serialinVector;
this.tcheck.addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) {
System.out.println("The check Box is: " + t1);
}
});
}
public BooleanProperty getTcheck() {return tcheck;}
public String getTname() {return tname.getValue() ;}
public void setTname(String tname) {this.tname.set(tname);}
public String getTstatus() {return tstatus.getValue() ;}
public void setTstatus(String tstatus) {this.tstatus.set(tstatus);}
public int getTser() {return tser;}
public void setTser(int tser) { this.tser = tser;}
}
And also i have the.
public Parent createContent() {
final ObservableList<ReservationObj> ReservationList = FXCollections.observableArrayList(
new ReservationObj(true, "aaaaaaaa", "bbbbbbbbb", 1));
TableColumn RCheckCol = new TableColumn<ReservationObj, Boolean>();
RCheckCol.setCellValueFactory(new PropertyValueFactory("tcheck"));
RCheckCol.setText("aaa");
RCheckCol.setCellFactory(new Callback<TableColumn<ReservationObj, Boolean>, TableCell<ReservationObj, Boolean>>() {
public TableCell<ReservationObj, Boolean> call(TableColumn<ReservationObj, Boolean> p) {
return new CheckBoxTableCell<ReservationObj, Boolean>();
}
});
TableColumn RNameCol = new TableColumn();
RNameCol.setCellValueFactory(new PropertyValueFactory("tname"));
RNameCol.setText("bbbb");
TableColumn RAgeCol = new TableColumn();
RAgeCol.setCellValueFactory(new PropertyValueFactory("tstatus"));
RAgeCol.setText("cccc");
TableView AAView = new TableView();
AAView.setItems(ReservationList);
AAView.setEditable(true);
AAView.getColumns().addAll(RCheckCol,RNameCol,RAgeCol);
return AAView;
}
And when simple .
#Override
public void start(Stage primaryStage) {
primaryStage.setScene(new Scene(createContent()));
primaryStage.show();
}
The columns of the tableview are getting the values except from the first “Checkbox”.
Also the listener in the checkbox is not working.
I really don’t understand what I have done wrong. Because I get the example from the ensemble.
Thanks for every idea and any solution.
Elias
Your ReservationObj class doesn’t respect JavaFX Properties convention in naming methods.
If you want to bind the RCheckCol with the tcheck BooleanProperty in :
RCheckCol.setCellValueFactory(new PropertyValueFactory("tcheck"));
You have to provide a tcheckProperty method in your model class:
public BooleanProperty tcheckProperty() {
return tcheck;
}
As an example of a valid JavaFX Bean:
public class Person {
private StringProperty name = new SimpleStringProperty("");
public Person(String name) {
this.name.setValue(name);
}
public String getName() {
return name.getValue();
}
public void setName(String name) {
this.name.setValue(name);
}
public StringProperty nameProperty() {
return name;
}
}

Categories

Resources