Errors when sorting a tableview - java

Simply put, I have a tableview with a single column which I want to be sorted and stay sorted whenever I add a new item to the observable list. I've looked at several examples and similar questions online but nothing is working for me. If I try calling TableView.sort() from the list's change listener, nothing really seems to happen.
When I add the column to the table's sort order that's when things get problematic. Attempting to sort the table after that results in a hard crash of the application. The only error is "Exception in Application start method". I then moved the sort() call to Platform.runLater() and that prevents the app from crashing. But then a new problem arose. When calling sort I'll get an array index out of bounds error, which seems to happen at random though I think mostly when the new items exceeds the table's view and needs a scrollbar.
While writing this, I moved the sort out of the change listener and down below into the Task where I'm adding the actual data. No more errors. So I guess my new question is, why am I having so many issues when attempting to do things from the listener?
public class Temp extends Application{
private ObservableList<String> libraryList = FXCollections.observableArrayList();
public void start(Stage stage) {
Label statusLabel = new Label("stuff goes here");
TableView<String> table = new TableView<String>(libraryList);
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
TableColumn<String, String> col = new TableColumn<String, String>("Stuff");
col.setCellValueFactory(cellData -> new ReadOnlyStringWrapper(cellData.getValue()));
table.getColumns().add(col);
table.getSortOrder().add(col);
libraryList.addListener(new ListChangeListener<String>() {
public void onChanged(Change change) {
Platform.runLater(()->{
table.sort();
statusLabel.setText(libraryList.size()+" entries");
});
}
});
// dummy stuff
libraryList.add("foo");
libraryList.add("bar");
Button b = new Button("Press Me");
b.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent e) {
FileTask task = new FileTask();
new Thread(task).start();
}
});
BorderPane mainBody = new BorderPane();
mainBody.setTop(statusLabel);
mainBody.setCenter(table);
mainBody.setBottom(b);
Scene scene = new Scene(mainBody);
stage.setScene(scene);
stage.show();
}
class FileTask extends Task<Boolean>{
public FileTask(){
}
protected Boolean call() throws Exception{
Random rand = new Random();
for(int i = 0; i < 3; i++) {
String s = ""+rand.nextInt(Integer.MAX_VALUE);
libraryList.add(s);
}
return true;
}
}
public static void main(String[] args) {
Application.launch(args);
}
}

I believe I understand the problem now. When I add an item to the list it calls onChanged(). When I sort the the table, it's also triggering the onChanged thus creating an infinite loop. I didn't realize the table sorts the data by actually changing the list itself.

Related

Timer in JavaFX updating Label (Bindings)

I want to add a clock to my application that tells how long you have been doing the task. To simplify it, I have included a counter that increments every second in a new thread and update the label 'setTimer' with the counter number. For this I have a label fx:id="setTimer" in my .fxml file and imported it into my class.
#FXML
private Label setTimer;
And created another class in my class that extends the thread TimerTask and increments the counter by one on each call. Created a new Object 'text', which should always be updated with the current value of the counter.
SimpleStringProperty text = new SimpleStringProperty("undefined");
public class MyTask extends TimerTask {
#Override
public void run() {
counter++;
text.set(Integer.toString(counter));
}
}
To have this class called every second I created a timer in the initialize method and set it to one second.
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
MyTask myTask = new MyTask();
Timer timer = new Timer(true);
timer.scheduleAtFixedRate(myTask, 0 , 1000);
setTimer.textProperty().bind(text);
}
At the moment I get the exception 'Not on FX application thread; currentThread = Timer-0'.
I've tried many ways to solve my problem, but I haven't gotten to the right point.
My idea of ​​what I want to do should be clear, and I would be happy if someone could help me.
My Problem is to update the changes of the counter in the GUI.
It doesn't have to be solved the way I thought it would, just need a tip on how to best implement it.
Thank you
Ok, my comments are too long. This is how I would try to do it.
Start the stopwatch on the application being loaded
Create a new thread that launches itself every so often.
Inside there, get the time from the Stopwatch in seconds (sw.getTime(TimeUntis.seconds)). Convert that to hours and minutes if you want like shown in this SO post
Then, write the time to the UI using Platform.runLater(new Runnable(){ /* access ui element and write time here */ });
Using Platform.runLater() in a background thread is kind of a messy kludge that should probably be avoided. JavaFX has mechanisms to handle this kind of thing which you should use. Specifically, Task<> is designed to allow background threads to update data which is connected to JavaFX screen elements which need to be updated on the FXAT.
You CAN do what you're trying to do with a JavaFX Task, but using the Java Timer inside of it seems impossible, since there doesn't seem to be any way for a Java thread to wait on a Timer to complete. So, instead I've used a "for" loop with a sleep to do the same thing. It's clumsy, but it does demonstrate how to connect partial results from a Task to screen display:
public class Sample1 extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Scene scene = new Scene(new Timer1(), 300, 200);
primaryStage.setScene(scene);
primaryStage.show();
}
}
public class Timer1 extends VBox {
public Timer1() {
Text time = new Text();
Button startButton = new Button("Start");
Button stopButton = new Button("Stop");
getChildren().addAll(time, startButton, stopButton);
startButton.setOnAction(startEvt -> {
Task<Integer> timerFxTask = new Task<>() {
{
updateValue(0);
}
#Override
protected Integer call() throws Exception {
for (int counter = 0; counter <= 1000; counter++) {
sleep(1000);
updateValue(counter);
}
return 1000;
}
};
stopButton.setOnAction(stopEvt -> timerFxTask.cancel());
time.textProperty().bind(Bindings.createStringBinding(() -> timerFxTask.getValue().toString(),
timerFxTask.valueProperty()));
Thread timerThread = new Thread(timerFxTask);
timerThread.start();
});
}
}
But there is a better way to do what you're trying to do, which is essentially an animation - and JavaFX has a facility to do exactly this. Usually, people use animations to morph the appearance of JavaFX screen elements, but you can also use it to animate the contents of a Text over time as well. What I've done here is create an IntegerProperty which can be transitioned from a start value to an end value interpolated linearly over time and then bound that value to the TextProperty of a Text on the screen. So you see it update once per second.
public class Sample1 extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Scene scene = new Scene(new Timer2(), 300, 200);
primaryStage.setScene(scene);
primaryStage.show();
}
}
public class Timer2 extends VBox {
public Timer2() {
Text time = new Text();
Button startButton = new Button("Start");
Button stopButton = new Button("Stop");
getChildren().addAll(time, startButton, stopButton);
startButton.setOnAction(startEvt -> {
IntegerProperty counter = new SimpleIntegerProperty(0);
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(1000), new KeyValue(counter, 1000)));
stopButton.setOnAction(stopEvt -> timeline.stop());
time.textProperty().bind(Bindings.createStringBinding(() -> Integer.toString(counter.get()), counter));
timeline.play();
});
}
}

How to make something use the previous value of an integer even after the integer changes

I am new to programming, using JavaFX at the moment for a personal organization tool. I have showed here an arraylist of buttons(called books) and stages(called bookStages), a VBox called addBook, and an Int called bookButtonCount set to 0.
addBook.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent t) {
addBooks.getChildren().add(books.get(bookButtonCount));
books.get(bookButtonCount).setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent t) {
bookStages.get(bookButtonCount).show();
System.out.println(bookButtonCount);
}
});
bookButtonCount++;
}
});
The first button adds a button from the "books" arraylist to the VBox. The button from the vbox is supposed to open a stage from the stage arraylist. You should be able to fire the button multiple times, each time adding a new button to the vbox and setting that button to open its own stage. Though it seems that using bookButtonCount as a reference will not work because each time you press a button from the books arraylist in the vbox, it checks for the current value of bookButtonCount.(Which changes as more buttons are added) and opens the wrong stage.
Is there any way to have the action for the button be saved with the value of bookButtonCount at the time it is set only?
If not, how should I set this up?
Here is some more bits of code that may be useful:
ArrayList<Stage> bookStages = new ArrayList();
ArrayList<Button> books = new ArrayList();
for (int i=0;i<10;i++){
books.add(new Button("Book " + (i+1)));
bookStages.add(new Stage());
bookStages.get(i).setTitle("Book " + (i+1));
}
Just register the handler when you create the button and stage:
ArrayList<Stage> bookStages = new ArrayList();
ArrayList<Button> books = new ArrayList();
for (int i=0;i<10;i++){
Button button = new Button("Book " + (i+1));
books.add(button);
Stage stage = new Stage();
bookStages.add(stage);
stage.setTitle("Book " + (i+1));
button.setOnAction(e -> stage.show());
}
and
addBook.setOnAction(e -> {
addBooks.getChildren().add(books.get(bookButtonCount));
bookButtonCount++ ;
});

How would I go about Streamlining this code?

I have been following an online tutorial on how to use JavaFX to create Java Gui programs.
This is one of the programs I made following using the tutorials. I decided to add a counter to the command line which would display the number of orders as well as the customers choices, although I was able to get this working the counter feature looks inefficient.
I feel adding another class to just add the orderNumber variable was wrong.
Is there any advice on streamlining this code?
Also what could I do to output the orderNumber after the Order?
public class Main extends Application {
private class Order {
public int orderNumber = 0;
}
Stage window;
Button button;
public static void main(String[] args) {
launch(args);
}
#Override
public void start (Stage primaryStage) throws Exception {
Order Number = new Order();
window = primaryStage;
window.setTitle("Luke's Sandwich's");
//Check Box's
CheckBox box1 = new CheckBox("cheese");
CheckBox box2 = new CheckBox("Bacon");
CheckBox box3 = new CheckBox("Tuna");
CheckBox box4 = new CheckBox("Tomatoes");
box1.setSelected(true);
//button
button = new Button("Order Now");
button.setOnAction(e -> {
Number.orderNumber++;
System.out.println("order: " + Number.orderNumber);
handleOptions(box1,box2,box3,box4);
});
VBox layout = new VBox(10);
layout.setPadding(new Insets(20));
layout.getChildren().addAll(box1,box2,box3,box4,button);
Scene scene = new Scene(layout,300,250);
window.setScene(scene);
window.show();
}
//Handle checkbox options
public void handleOptions (CheckBox box1,CheckBox box2,CheckBox box3,CheckBox box4){
String message = "user order:\n";
if(box1.isSelected())
message += "cheese\n";
if(box2.isSelected())
message += "bacon\n";
if(box3.isSelected())
message += "tuna\n";
if(box4.isSelected())
message += "tomatoes\n";
System.out.println(message);
}
}

Check all CheckBoxTreeCells in a TreeView JavaFX

I have created a TreeView that contains many CheckBoxTreeCells. I also have a Button that I would like to check all of the CheckBoxTreeCells. I've been reading this tutorial, and I am a bit lost. Here is what I have so far:
Button Code:
public void fooMethod() {
/*selectAll is an instance variable*/
selectAll = new Button("Select All");
selectAll.setOnMouseClicked(e -> handleSelectAllButtonAction(e));
}
private void handlSelectAllButtonAction(MouseEvent e) {
/*Code goes here*/
}
TreeView Code:
public void fooMethod2() {
/*myTreeView is also an insance variable*/
myTreeView = new TreeView<String>();
CheckBoxTreeItem<String> root = new CheckBoxTreeItem<>();
CheckBoxTreeItem<String> branch1 = new CheckBoxTreeItem<>("Branch 1");
CheckBoxTreeItem<String> branch2 = new CheckBoxTreeItem<>("Branch 2");
CheckBoxTreeItem<String> branch3 = new CheckBoxTreeItem<>("Branch 3");
root.getChildren.add(branch1);
root.getChildren.add(branch2);
root.getChildren.add(branch3);
myTreeView.setCellFactory(CheckBoxTreeCell.forTreeView());
myTreeView.setRoot(root);
myTreeView.setShowRoot(false);
myTreeView.setEditable(true);
}
The example provided in the link is a bit more complex than what I need, and I think it is confusing me. How do I edit a CheckBoxTreeItems in a TreeView?
Unless there are independent CheckBoxTreeItems, its enough to select the root:
root.setSelected(true);
since this automatically selects the children.
Best way I could find was to do this:
private void handlSelectAllButtonAction(MouseEvent e) {
CheckBoxTreeItem<String> root = (CheckBoxTreeItem<String>)myTreeView.getRoot();
int numBranches = root.getChildren().size();
for(int i = 0; i < numBranches; i++) {
if(((CheckBoxTreeItem<String>)root.getChildren().get(i)).isSelected()) {
((CheckBoxTreeItem<String>)root.getChildren().get(i)).setSelected(false);
}
}
}

How to restart a JavaFX application when a button is clicked

I went through almost every post here regarding the matter but most of them doesn't explain what to do properly.
To the question:
I created a javaFX application, a dice game, human player vs. computer, but during any time while playing the game human player should be able to click button "new game" and what it should do is to restart the game from beginning.
I tried relaunching the stage again but in javafx we cannot call the launch method twice.
1)Is there a way i can implement this without restarting the whole application?
2)if not how can i restart the application completely using a button click?
Main class
public class Main {
public static void main(String[] args) {
GameUI gameUI = new GameUI();
gameUI.launch(GameUI.class, args);
}
GameUI
(i removed many codes from this class to make it short. codes that i think enough to give an idea is included. sorry if it is too long.)
public class GameUI extends Application {
//all btn and label declarations
//creating instances for necessary classes
private Scene scene;
#Override
public void start(Stage primaryStage) throws Exception {
//Displaying Dice for Player and Computer
setLabelsPlyr(diesP);
setLabels(diesC);
btnThrow = new Button("Throw");
btnThrow.setPrefSize(70, 40);
//Throw action is performed
btnThrow.setOnAction(e -> {
//setting and displaying dies
DieClass[] com = getNewDiceArrC();
lblDiceOneC.setGraphic(new ImageView(diesC[0].getDieImageC()));
//so on.....
DieClass[] playerAr = getNewDiceArrP();
lblDiceOnePlyr.setGraphic(new ImageView(diesP[0].getDieImageP()));
//so on...
});
btnNewGame = new Button("New Game");
btnNewGame.setOnAction(e -> {
**//WHAT TO DO HERE?????**
});
//setting layouts
GridPane gridPane = new GridPane();
gridPane.add(lblComputer, 0, 0);
//so on.....
Scene scene = new Scene(gridPane, 1100, 400);
primaryStage.setScene(scene);
primaryStage.setTitle("dice Game");
primaryStage.show();
}
//some other methods
public void setLabels(DieClass[] dies) {
for (int i=0; i < dies.length; i++) {
lblDiceOneC = new Label();
lblDiceOneC.setGraphic(new ImageView(dies[0].getDieImageC()));
++i;
//so on.....
break;
}
}
public void setLabelsPlyr(DieClass[] dies){
for (int i=0; i<dies.length; i++) {
lblDiceOnePlyr = new Label();
lblDiceOnePlyr.setGraphic(new ImageView(dies[0].getDieImageP()));
++i;
lblDiceTwoPlyr = new Label();
//so on......
break;
}
}
p.s I am very new to JavaFX and somewhat new to java programming.
You already noticed that you cannot do the launching process again. Therefore your best option is to rewrite the application class and move the initialisation logic to a new method:
void cleanup() {
// stop animations reset model ect.
}
void startGame(Stage stage) {
// initialisation from start method goes here
btnNewGame.setOnAction(e -> {
restart(stage);
});
stage.show();
}
void restart(Stage stage) {
cleanup();
startGame(stage);
}
#Override
public void start(Stage primaryStage) {
startGame(primaryStage);
}
Notes
Depending on the parts of the scene changed, it may be enough to change the state of some of the nodes (more efficient than creating a new scene). (Just take a look at the changes you made during the game and decide for yourself)
launch() is a static method and you should not create a instance of your application class yourself for that reason. Use Application.launch(GameUI.class, args); instead and let the method handle the creation of the GameUI instance.
It may be a better design to move the UI creation to a class different to the application class. This way reuse of the code is easier, since it does not require the creation of a instance of a subclass of Application.
In your Start() function, add the following line:
Platform.setImplicitExit(false);
This will make your application run in background without exiting.
Now, use a wrapper function to start the second time, say:
void displayApplication() {
Platform.runLater(new Runnable() {
#Override
public void run() {
try {
start(new Stage());
} catch (Exception e) {
e.printStackTrace();
}
}
});
}

Categories