I'm new to javafx programming, and i dont understand why my javafx Text isn't getting updated, when it is changed.
I want to make a timer, that counts from 60 to 0. I'm trying to change the timeCounter Text, for every second that has passed.
Help would be appreciated!
Here's my controller code:
public class Controller {
TimerUtil timerUtil;
#FXML
private Button startButton;
#FXML
private Text timeCounter;
#FXML
private Text pointCounter;
#FXML
private Circle circle;
#FXML
private void handleStartButtonClick(ActionEvent event) {
timerUtil = new TimerUtil();
}
private class TimerUtil extends Pane {
private int tmp = 60;
private Timeline animation;
public TimerUtil(){
getChildren().add(timeCounter);
animation = new Timeline(new KeyFrame(Duration.seconds(1), e -> timeLabel()));
animation.setCycleCount(Timeline.INDEFINITE);
animation.play();
}
private void timeLabel(){
if(tmp > 0){
tmp--;
}
timeCounter.setText(String.valueOf(tmp));
System.out.println(tmp);
}
}
}
Your error occurs because the label has been silently removed from it's displayed parent node:
You have your TimerUtil class extend Pane (I have no idea why).
You add the timeCounter text to the TimeUtil pane (again, I have no idea why).
Adding the timeCounter text to the TimeUtil pane will silently remove it from the parent which the FXML loader injected it into.
You are probably only displaying the parent which the FXML loader injected.
You are never displaying the TimerUtil pane.
Therefore, even though the text is getting updated by your timeline, you never see it.
To better understand your error, read:
JavaFX - Why does adding a node to a pane multiple times or to different panes result in an error?
From the Node javadoc:
If a program adds a child node to a Parent (including Group, Region, etc) and that node is already a child of a different Parent or the root of a Scene, the node is automatically (and silently) removed from its former parent.
Once you fix your error, the basic concept works for me. Here is the runnable example I created from your code:
import javafx.animation.*;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Timer extends Application {
private int tmp = 60;
private Text counter = new Text();
private Timeline animation = new Timeline(
new KeyFrame(Duration.seconds(1), e -> updateCounter())
);
#Override
public void start(Stage stage) {
animation.setCycleCount(Timeline.INDEFINITE);
animation.play();
StackPane layout = new StackPane(counter);
layout.setPadding(new Insets(20));
stage.setScene(new Scene(layout));
stage.show();
}
private void updateCounter() {
if (tmp > 0){
tmp--;
} else {
animation.stop();
}
counter.setText(String.valueOf(tmp));
System.out.println(tmp);
}
public static void main(String[] args) {
launch(args);
}
}
Related
On javafx, I have a GridPane with buttons. Each button has a number on them that increases when a leftclick on the button happens and it decreases when a rightclick happens.
I want to know how can I make it so that when the number of one button reaches a certain value (5 for example), then the mouseEvent activates on the buttons that surround the button.
Also if one of those buttons reaches that certain value too, then the mouseEvent activates on its surrounding buttons as well and so on and so forth.
For increasing, decreasing and showing the number of the buttons, I wrap the Button class in an inner class called NumberButton.
Here is my code:
package sample;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
public class Main extends Application {
BorderPane root= new BorderPane();
GridPane grid= new GridPane();
class NumberButton extends Button{
private int innerNumber;
public NumberButton(){
this.innerNumber=0;
showNumber();
}
public void showNumber(){
this.setText(String.valueOf(innerNumber));
}
public void increaseInnerNumber(){
this.innerNumber++;
showNumber();
}
public void decreaseInnerNumber(){
this.innerNumber--;
showNumber();
}
public int getInnerNumber() {
return this.innerNumber;
}
}
#Override
public void start(Stage primaryStage) throws Exception{
NumberButton[][] buttons= new NumberButton[10][10];
for (int i=0; i<buttons.length;i++){
for (int j=0; j<buttons[i].length;j++){
buttons[i][j]= new NumberButton();
NumberButton button= buttons[i][j];
button.setOnMouseClicked(mouseEvent -> {
if (mouseEvent.getButton()== MouseButton.PRIMARY){
button.increaseInnerNumber();
if (button.getInnerNumber()==5){
/*all surrounding buttons activate the mouseEvent
* and mouseEvent.getButton()== MouseButton.PRIMARY is true */
}
}else if (mouseEvent.getButton()==MouseButton.SECONDARY){
button.decreaseInnerNumber();
}
});
button.setMinSize(30,30);
button.setMaxSize(30,30);
grid.add(button,i,j);
}
}
root.setCenter(grid);
primaryStage.setTitle("Number Buttons!");
primaryStage.setScene(new Scene(root, 300, 275));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I have tried using button.setOnAction() but then I don't know how to get if the event was triggered by a leftclick or a rightclick and when I tried using both button.setOnAction() and button.setOnMouseClicked() then the event triggered twice and I don't want that.
I was trying to make a program where you can add names as CheckBoxes. By checking them and pressing the randomize button all the names would be placed in 2 different ListViews (each name can be placed only once and each ListView has to have the same number of names or 1 more name inside). I have no idea how it should be written in the "onRandom" section.
package sample;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import java.util.Objects;
public class Controller {
#FXML
private Button add;
#FXML
private Button delete;
#FXML
private VBox vbox;
#FXML
private TextField text;
#FXML
private Button random;
#FXML
private ListView listview1;
#FXML
private ListView listview2;
#FXML
private CheckBox cb;
#FXML
void initialize() {
}
#FXML
public void onEnter(ActionEvent e){
CheckBox cb = new CheckBox(text.getText());
vbox.getChildren().add(cb);
if (text.getText().matches("")) {
vbox.getChildren().remove(cb);
}
}
#FXML
public void onAdd(ActionEvent e) {
CheckBox cb = new CheckBox(text.getText());
vbox.getChildren().add(cb);
if (text.getText().matches("")) {
vbox.getChildren().remove(cb);
}
}
#FXML
public void onDelete(ActionEvent e) {
vbox.getChildren().removeIf(child -> ((CheckBox) child).isSelected());
}
#FXML
public void onRandom(ActionEvent e) {
vbox.getChildren()
.stream()
.map(item -> (CheckBox) item)
.filter(item -> item.isSelected())
.filter(value -> Objects.nonNull(value))
.forEach(value -> {
listview1.getItems().add(value.getText());
listview2.getItems().add(value.getText());
});
}
}
This one below is just for testing.
.forEach(value -> {
listview1.getItems().add(value.getText());
listview2.getItems().add(value.getText());
});
Here is my FXML file https://pastebin.com/9v8e0c0Y
Use Collections.shuffle to create a random permutation of the list, then add the first half to one ListView and the rest to the other.
// do not use raw types
#FXML
private ListView<String> listview1;
#FXML
private ListView<String> listview2;
...
private final Random randomNumberGenerator = new Random();
List<String> items = new ArrayList<>(); // copy children to new list
// the following loop imho is easier to comprehend than the Stream implementation
for (Node child : vbox.getChildren()) {
CheckBox cb = (CheckBox) child;
if (cb.isSelected) {
items.add(cb.getText());
}
}
Collections.shuffle(items, randomNumberGenerator);
final int size = items.size();
final int half = size / 2;
// add first half to first ListView and second half to second ListView
listview1.getItems().setAll(items.sublist(0, half));
listview2.getItems().setAll(items.sublist(half, size));
Note that some of the method calls on Stream are actually unnecessary in your case:
.filter(value -> Objects.nonNull(value))
Checking for null is never necessary for the children list of a Parent. The list implementation prevents null from being inserted to that list. The previous filter whould have thrown a NPE in cases where that predicate yields false anyways. In cases where you do need a predicate like this, you can use a method reference to shorten the code:
.filter(Objects::nonNull)
I have a JavaFX application with several Stages open to provide floating windows. I want to iterate through these Stages from front to back. I have a list of all the Stages which I'd like to sort and am looking for a method that will let me compare two of them and determine which is in front. Is there a way to do this?
This is just one possibility.
Store each open Stage in a list that can be observed for changes.
Add a listener on each Stage's focusedProperty. When it changes to true,
Remove the stage from our List and readd it at index 0
Now, create a listener on the List and your "focused" Stage will always be at index 0.
You now have an ArrayList that stores the open stages, in order.
Here is a simple MCVE to demonstrate. There are certainly areas to be improved upon and I welcome suggestions, but this does provide some basic functionality.
OpenStages.java:
import javafx.beans.property.SimpleListProperty;
import javafx.collections.FXCollections;
/**
* Implementation of a SimpleListProperty that will store our ObservableArrayList
*/
public class OpenStages<Stage> extends SimpleListProperty<Stage> {
/**
* Constructor that creates an ObservableArrayList
*/
public OpenStages() {
super(FXCollections.observableArrayList());
}
/**
* Removes this Stage from the list and re-adds it at index 0
*/
public void focusStage(Stage stage) {
this.remove(stage);
this.add(0, stage);
}
}
Main.java:
import javafx.application.Application;
import javafx.collections.ListChangeListener;
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.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
// Custom SimpleListProperty that holds our ObservableArrayList of open stages
OpenStages<Stage> openStages = new OpenStages<>();
// Simple interface
VBox root = new VBox(5);
root.setPadding(new Insets(10));
root.setAlignment(Pos.CENTER);
Button btnCreateStage = new Button("Create New Stage");
btnCreateStage.setOnAction(event -> {
// Create a new Stage
Stage newStage = new Stage();
// Add a listener to the focusedProperty of the Stage. When focus changes to true, we
// need to update our openStages list
newStage.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (newValue) {
openStages.focusStage(newStage);
}
});
// Add the stage to our OpenStages list.
openStages.add(newStage);
// Simple layout for the new Stage
VBox stageRoot = new VBox();
stageRoot.setPrefSize(300, 100);
stageRoot.setPadding(new Insets(10));
stageRoot.setAlignment(Pos.CENTER);
// Let's add a label and title to indicate which Stage this is
stageRoot.getChildren().add(new Label("Stage #" + openStages.size()));
newStage.setTitle("Stage #" + openStages.size());
newStage.setScene(new Scene(stageRoot));
// Finally, let's show the stage
newStage.show();
});
// Now, let's create a simple listener for our openStages list to print out the focused Stage
openStages.addListener(new ListChangeListener<Stage>() {
#Override
public void onChanged(Change<? extends Stage> c) {
// Only interested in a stage being added
if (c.next() && c.wasAdded()) {
System.out.println("Focused stage: " + openStages.get(0).getTitle());
}
}
});
// Add the Button to our main layout
root.getChildren().add(btnCreateStage);
// Show the Stage
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}
Does anyone know how to imitate the functionality from the UI components shown below? I want to replicate adding form fields when text is entered into the TextField box. I don't need the dropdown button, just the dynamic adding of the forms.
You could modify the children of a GridPane adding a new TextField & Button every time one of the buttons is activated. Listen to the text properties to enable/disable the Button and save the results.
private static void insertRow(GridPane grid, List<String> values, int index) {
// increment index of children with rowIndex >= index
for (Node n : grid.getChildren()) {
int row = GridPane.getRowIndex(n);
if (row >= index) {
GridPane.setRowIndex(n, row + 1);
}
}
TextField text = new TextField();
Button add = new Button("+");
add.setDisable(true);
add.setOnAction(evt -> {
insertRow(grid, values, GridPane.getRowIndex(add) + 1);
});
values.add(index, "");
text.textProperty().addListener((a, oldValue, newValue) -> {
add.setDisable(newValue.isEmpty());
values.set(GridPane.getRowIndex(add), newValue);
});
grid.addRow(index, text, add);
}
#Override
public void start(Stage primaryStage) throws Exception {
GridPane grid = new GridPane();
List<String> list = new ArrayList<>();
insertRow(grid, list, 0);
Button print = new Button("print");
print.setOnAction(evt -> {
System.out.println(list);
});
grid.add(print, 0, 1);
Scene scene = new Scene(grid, 300, 500);
primaryStage.setScene(scene);
primaryStage.show();
}
This may not be exactly what you're looking for and may not be the best way to do this, but should be easy to adapt it to your needs.
Basically, you will need a list of HBox objects to be added to a VBox in your application. You could create the list yourself and bind it to the children of your VBox, or just add/remove the HBoxes to/from the VBox using the getChildren().add() and getChildren().remove() methods.
Here is a complete little application to demonstrate the concept. I created an internal class to handle the HBox with the fields you need. This could be adapted to be more felixable:
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.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
private static VBox mainPane;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
mainPane = new VBox(5);
mainPane.setPadding(new Insets(10));
mainPane.setAlignment(Pos.TOP_CENTER);
mainPane.getChildren().add(new UIForms());
primaryStage.setScene(new Scene(mainPane));
primaryStage.show();
}
static void addField() {
mainPane.getChildren().add(new UIForms());
}
static void removeField(UIForms field) {
if (mainPane.getChildren().size() > 1) {
mainPane.getChildren().remove(field);
}
}
}
class UIForms extends HBox {
private TextField textField1;
private TextField textField2;
private Button btnAddField;
private Button btnRemoveField;
public UIForms() {
// Setup the HBox layout
setAlignment(Pos.CENTER_LEFT);
setSpacing(5);
// Create the UI controls
textField1 = new TextField();
textField2 = new TextField();
btnAddField = new Button("+");
btnRemoveField = new Button("-");
// Setup button actions
btnAddField.setOnAction(e -> Main.addField());
btnRemoveField.setOnAction(e -> Main.removeField(this));
// Add the UI controls
getChildren().addAll(
textField1, textField2, btnAddField, btnRemoveField
);
}
}
I want to set up a model for my project so my controllers can communicate with each other. I want it to have a setter and getter, to allow easy access to styling certain nodes from either class.
My question: is it possible to bind a style property (ex. "-fx-background-color: blue") to a node?
From my research, I see that this is definitely possible with text values for labels (explained by James_D here: JavaFX - How to use a method in a controller from another controller?), but I am having a hard time figuring out what the syntax for doing a similar thing with "setStyle" would be.
The model I have so far:
public class Model {
private final StringProperty shadow = new SimpleStringProperty("-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.24), 10,0,0,0)");
public StringProperty shadowProperty() {
return shadow;
}
public final String getShadow() {
return shadowProperty().get();
}
public final void setShadow(String shadow) {
shadowProperty().set(shadow);
}
}
I understand how I would set the "shadow" value from a controller, but what I don't understand is how I can bind a node from another controller to listen to that change.
Let's say the node is something like:
#FXML AnchorPane appBar
I want "appBar" to take on any changes made to "shadow" in the model. What would that look like?
You need to add a listener to the shadowProperty to listen to its changes.
something.shadowProperty() .addListener( (observable, oldValue, newValue) -> {
//do something with appBar
}) ;
I'm not entirely sure what you want to achieve, but this should answer your question about how to listen to property changes.
PS: im on mobile, so no guarantees regarding typos
Edit: you can also bind the property of one object to the property of another. Use bind() for that.
EDIT: Here is an example:
import javafx.application.Application;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Scene;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class Main extends Application {
Property<Background> backgroundProperty;
StringProperty styleProperty;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
VBox root = new VBox(10);
backgroundProperty = new SimpleObjectProperty<>();
styleProperty = new SimpleStringProperty();
// Pane that changes background by listener
Pane pane1 = new Pane();
pane1.setMinHeight(40);
backgroundProperty.addListener( (observable, oldValue, newValue) -> {
pane1.setBackground(backgroundProperty.getValue());
});
// Pane that changes background by property binding
Pane pane2 = new Pane();
pane2.setMinHeight(40);
pane2.backgroundProperty().bind(backgroundProperty);
// Pane that binds the style property
Pane pane3 = new Pane();
pane3.setMinHeight(40);
pane3.styleProperty().bind(styleProperty);
backgroundProperty.setValue(new Background(new BackgroundFill(Color.RED, null, null)));
styleProperty.setValue("-fx-background-color: black");
root.getChildren().add(pane1);
root.getChildren().add(pane2);
root.getChildren().add(pane3);
Scene scene = new Scene(root, 200, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
}