I am a beginner messing around with JavaFX. In my program, when a checkbox is selected I want it to display a label along with a ChoiceBox. However, when it is unselected, I want both of these to disappear. I'm not quite sure how to do this however.
Here is my code:
String [] options = new String [] {"A", "B", "C", "D", "E", "F"};
CheckBox [] cbs = new CheckBox[options.length];
for (int i = 0; i < options.length; i++){
final CheckBox cb = cbs[i] = new CheckBox(options[i]);
cb.selectedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if(observable.getValue() == true){
ChoiceBox<Integer> choice = new ChoiceBox<>();
Label label = new Label("How many of the selected accounts do you have?");
choice.getItems().addAll(1, 2, 3, 4, 5);
choice.setValue(1);
selection.setAlignment(Pos.BOTTOM_LEFT);
selection.getChildren().addAll(label, choice);
gp.add(selection, 0, 8);
} else if (observable.getValue() == false){
// remove above block if getValue() == false;
}
}
});
}
You need to keep a reference to the nodes. You could do this by adding fields to the anonymus ChangeListener or by adding a (effectively) final local variable to the loop body.
I'm not sure what selection is, but using the same node for multiple checkboxes seems like a bad idea.
The following example simply adds/removes a VBox containing the Label and ChoiceBox. Since this changes the layout though, I recommend disabling the nodes instead or changing the visibility instead of adding/removing the nodes.
#Override
public void start(Stage primaryStage) throws Exception {
String[] options = new String[]{"A", "B", "C", "D", "E", "F"};
GridPane grid = new GridPane();
for (int i = 0; i < options.length; i++) {
final int row = i;
String option = options[i];
CheckBox checkBox = new CheckBox(option);
ChoiceBox<Integer> choice = new ChoiceBox<>();
Label label = new Label("How many of the selected accounts do you have?");
choice.getItems().addAll(1, 2, 3, 4, 5);
VBox choiceContainer = new VBox(label, choice);
checkBox.selectedProperty().addListener((o, oldValue, newValue) -> {
if (newValue) {
grid.add(choiceContainer, 1, row);
} else {
grid.getChildren().remove(choiceContainer);
}
});
grid.add(checkBox, 0, row);
}
Scene scene = new Scene(grid);
primaryStage.setScene(scene);
primaryStage.show();
}
Related
I have a program that displays animals and their names:
private int TOTAL_ANIMALS = 5;
private TextField[] animals;
private TextField[] names
public AnimalsApp {
animals = new TextField[TOTAL_ANIMALS];
names = new TextFields[TOTAL_ANIMALS];
for (int i = 0; i < TOTAL_ANIMALS; i++) {
animals[i] = new TextField();
names[i] = new TextField();
}
}
#Override
public void start(Stage primaryStage) {
GridPane grid = new GridPane();
...
for (int i = 0; i < TOTAL_ANIMALS; i++) {
grid.add(animals[i], 0, i);
grid.add(names[i], 1, i);
}
The method associated with Add animal button:
public void addAnimals() {
animals = Arrays.copyOf(animals, TOTAL_ANIMALS + 1);
names = Arrays.copyOf(names, TOTAL_ANIMALS + 1);
animals[TOTAL_ANIMALS] = new TextField();
names[TOTAL_ANIMALS] = new TextField();
TOTAL_ANIMALS++;
System.out.println("Animals updated");
}
The code inside addAnimals does execute, but the new text field doesn't appear. How should I fix it?
You forgot to add the text fields to grid, just creating a textfield doesn't show it you need to add it to your like you did in your start function. You would also need to make the GridPane a variable in the class.
public void addAnimals() {
animals = Arrays.copyOf(animals, TOTAL_ANIMALS + 1);
names = Arrays.copyOf(names, TOTAL_ANIMALS + 1);
animals[TOTAL_ANIMALS] = new TextField();
names[TOTAL_ANIMALS] = new TextField();
grid.add(animals[TOTAL_ANIMALS], 0, TOTAL_ANIMALS);
grid.add(names[TOTAL_ANIMALS], 1, TOTAL_ANIMALS);
TOTAL_ANIMALS++;
System.out.println("Animals updated");
}
I'm making a Movie Theater/Cinema booking system.
I have a List of Buttons set up to show the list of films playing in the Home Page. I am trying to make a function that when we click one of the Buttons in the Home Page, it'll change the Scene where you can see the full details of said movie.
The problem is it always only shows the first movie in my List of movies.
I've tried having some kind of pointer set up to identify each Button.
Here are the objects:
filmPointer starts at 1 because filmNumber starts at 1, also an error will pop up if I set it 0
private static List<User> userList = User.getList();
private static List<Film> filmList = Film.getList();
private static ArrayList<Button> allFilmLink = new ArrayList<>();
public static int filmPointer = 1, accountPointer;
The function to show the Button with Images on the Home Page:
public void filmListContent(){
mainPane.setContent(filmListPane);
for (int x = 0; x < filmList.size(); x++) {
Button listFilmButton = new Button();
listFilmButton.setPrefHeight(300);
listFilmButton.setPrefWidth(250);
int finalX = x;
listFilmButton.setOnAction(actionEvent -> {
mainWindow.setScene(currentFilmScene); //open film detail
filmPointer = filmList.get(finalX).getNumberFilm();
});
imageFilm = new Image(getClass().getResourceAsStream(filmList.get(x).pathPictureFilm), 250, 350, false, false);
listFilmButton.setGraphic(new ImageView(imageFilm));
allFilmLink.add(listFilmButton);
}
showFilmLink();
}
Here is the code I have on another class where I set up the Scene and it's functions:
private void createFilmScene(){
Text filmTitle = null;
filmPane = new GridPane();
HBox titleHB;
for (int x = 0; x < filmList.size(); x++) {
if (filmList.get(x).getNumberFilm() == Main.filmPointer) {
filmPictureStream = new Image(getClass().getResourceAsStream(filmList.get(x).getPathPictureFilm()));
filmPictureView = new Label("", new ImageView((filmPictureStream)));
filmTitle = new Text(filmList.get(x).getTitle());
}
}
filmTitle.setFont(Font.font("Times New Roman", FontWeight.BOLD, 30));
titleHB = new HBox(11);
titleHB.setAlignment(TOP_CENTER);
titleHB.getChildren().add(filmTitle);
filmPane.add(titleHB, 5, 19);
filmPane.add(filmPictureView, 2, 2, 2, 2); // placement not final
filmPane.setHgap(5);
filmPane.setVgap(15);
}
If I click on 47 Meters Down, I expect the Scene changes and then details the movie that I click. But it always shows IT Chapter 2.
My Home Page: Imgur Link
My Film Details Page when I click on 47 Meters Down: Imgur Link
The problem was createFilmScene() was called too early(in the start method). I coded it that way since I thought the currentFilmPane and currentFilmScene had to be initialized first before doing anything with it.
Code:
public void filmListContent() {
mainPane.setContent(filmListPane);
for (int x = 0; x < filmList.size(); x++) {
Button listFilmButton = new Button();
listFilmButton.setPrefHeight(300);
listFilmButton.setPrefWidth(250);
int finalX = x;
listFilmButton.setOnAction(actionEvent -> {
filmPointer = filmList.get(finalX).getNumberFilm();
currentFilmPane = new GridPane();
FilmPage setFilmPane = new FilmPage(currentFilmPane);
currentFilmScene = new Scene(setFilmPane.asParent(), 1920, 1080);
mainWindow.setScene(currentFilmScene);
});
imageFilm = new Image(getClass().getResourceAsStream(filmList.get(x).pathPictureFilm), 250, 350, false, false);
listFilmButton.setGraphic(new ImageView(imageFilm));
allFilmLink.add(listFilmButton);
}
showFilmLink();
}
I have two ComboBoxes: fruits and drinks.
fruits has the Strings: "apple", "orange", "banana"
drinks has the Strings: "water", "coffee", "juice"
How can I make a new ComboBox that has the values the user selects for the fruits ComboBox and the drinks ComboBox?
ex: if the user selects apple and water, the new ComboBox should include apple and water as options.
Use a listener to the value properties of the first 2 ComboBoxes and update the items of the third from it:
#Override
public void start(Stage primaryStage) {
ComboBox<String> c1 = new ComboBox<>();
c1.getItems().addAll("apple", "orange", "banana");
ComboBox<String> c2 = new ComboBox<>();
c2.getItems().addAll("water", "coffee", "juice");
ComboBox<String> c3 = new ComboBox<>();
ChangeListener<String> listener = (o, oldValue, newValue) -> {
final List<String> items = c3.getItems();
int index = items.indexOf(oldValue);
if (index >= 0) {
if (newValue == null) {
items.remove(index);
} else {
items.set(index, newValue);
}
} else if (newValue != null) {
items.add(newValue);
}
};
c1.valueProperty().addListener(listener);
c2.valueProperty().addListener(listener);
final VBox vBox = new VBox(c1, c2, c3);
primaryStage.setScene(new Scene(vBox));
primaryStage.show();
}
Note that this does not prevent the same string to be added from both ComboBoxes.
If you want to add only without removing items, change the listener to
ChangeListener<String> listener = (o, oldValue, newValue) -> {
final List<String> items = c3.getItems();
int index = items.indexOf(newValue);
if (index < 0) {
items.add(newValue);
}
};
This listener does prevent duplicate items.
I am creating multiple TextFields at run time using for-loop and adding them to inside Gridpane(which has 8 columns) like this:
public static GridPane table(int rows){
GridPane table = new GridPane();
for(int i=0; i<rows; i++){
JFXTextField textField1 = new JFXTextField();
textField1.setAlignment(Pos.CENTER);
JFXTextField textField2 = new JFXTextField();
textField1.setAlignment(Pos.CENTER);
JFXTextField textField3 = new JFXTextField();
textField1.setAlignment(Pos.CENTER);
JFXTextField textField4 = new JFXTextField();
textField1.setAlignment(Pos.CENTER);
JFXTextField textField5 = new JFXTextField();
textField1.setAlignment(Pos.CENTER);
JFXTextField textField6 = new JFXTextField();
textField1.setAlignment(Pos.CENTER);
JFXTextField textField7 = new JFXTextField();
textField1.setAlignment(Pos.CENTER);
JFXTextField textField8 = new JFXTextField();
textField1.setAlignment(Pos.CENTER);
//add them to the GridPane
table.add(textField1, 0, i+1);
table.add(textField2, 1, i+1);
table.add(textField3, 2, i+1);
table.add(textField4, 3, i+1);
table.add(textField5, 4, i+1);
table.add(textField6, 5, i+1);
table.add(textField7, 6, i+1);
table.add(textField8, 7, i+1);
}
return table;
}
Next I'm creating another method to return component from table at specific row and column like this:
public static Node getComponent (int row, int column, GridPane table) {
for (Node component : table.getChildren()) { // loop through every node in the table
if(GridPane.getRowIndex(component) == row &&
GridPane.getColumnIndex(component) == column) {
return component;
}
}
return null;
}
Problem is here: I want to validate each of the TextField, so if user forget to write in any of the TextField, I want to disable the Button, for this purpose I'm using binding like this:
private void validatingGrid() {
GridPane table = (GridPane) anchorPane().getChildren().get(0);
for(int i=1 ; i<=comboBox().getValue(); i++){
JFXTextField text0 = ((JFXTextField)getComponent (i, 0, table));
JFXTextField text1 = ((JFXTextField)getComponent (i, 1, table));
JFXTextField text2 = ((JFXTextField)getComponent (i, 2, table));
JFXTextField text3 = ((JFXTextField)getComponent (i, 3, table));
JFXTextField text4 = ((JFXTextField)getComponent (i, 4, table));
JFXTextField text5 = ((JFXTextField)getComponent (i, 5, table));
JFXTextField text6 = ((JFXTextField)getComponent (i, 6, table));
JFXTextField text7 = ((JFXTextField)getComponent (i, 7, table));
button.disableProperty().bind(
Bindings.isEmpty(text0.textProperty())
.or(Bindings.isEmpty(text1.textProperty()))
.or(Bindings.isEmpty(text2.textProperty()))
.or(Bindings.isEmpty(text3.textProperty()))
.or(Bindings.isEmpty(text4.textProperty()))
.or(Bindings.isEmpty(text5.textProperty()))
.or(Bindings.isEmpty(text6.textProperty()))
.or(Bindings.isEmpty(text7.textProperty()))
);
}
}
But what's happening is it's only validating last row, let say if I create 3 rows of textfeilds in the Gridpane, so it's only validating 3rd row not 1st and 2nd rows and on the basis of 3rd row entries it's enabling the button but I want after validating all of the rows it should enable button otherwise not. Please help me how can I achieve this.
Your binding logic is correct. However, the problem because of the for loop [for(int i=1 ; i<=comboBox().getValue(); i++)], which ruins your work. All TextFields are at column index 0 and the only thing changes is the row index. So you should use getComponent(i, 0, table); for all TextFields in your for loop without changing the column index to 1 , 2 .. and so on. But that also won't solve the problem because in every loop you're assigning ALL TextFields to the same index and then overwrites it in every loop until all of them points to the TextField at index comboBox().getValue() and column 0 (That's why it's working for the last row as you mentioned).
I would suggest different approach, something like this:
First You need a method to check if all other TextFields are filled/ not empty:
/**
* Check if all the TextFields are filled and not empty
* #param table
*/
private static boolean isAllFilled(GridPane table){
for(Node node : table.getChildren()){ // cycle through every component in the table (GridPane)
if(node instanceof TextField){ // if it's a TextField
// after removing the leading spaces, check if it's empty
if(((TextField)node).getText().trim().isEmpty()){
return false; // if so, return false
}
}
}
return true;
}
Secondly, Listen to the Text Changes for every TextField in the Table, and with every change, check if all other TextField are filled / not empty:
/**
* To Validate the Table (GridPane)
* This method should be added to the tabPane change listener
* #param table
* #param button
*/
private void validateTable(GridPane table, Button button) {
for(Node node : table.getChildren()){ // cycle through every component in the table (GridPane)
if(node instanceof TextField){ // if it's a TextField
((TextField)node).textProperty().addListener((obs, old, newV)->{ // add a change listener to every TextField
// then check if the new value is not empty AND all other TextFields are not empty
if(!newV.trim().isEmpty()&&isAllFilled(table)){
button.setDisable(false); // then make the button active again
}
else{
button.setDisable(true); // or else, make it disable until it achieves the required condition
}
});
}
}
Also, you need to set the button to disable once after its creation.
Button button = new Button("Test");
button.setDisable(true);
Finally, you need to add the method in the tabPane Change Listener Block:
tabPane.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Tab>(){
.........
.........
.........
validateTable((GridPane) anchorPane().getChildren().get(0), test);
}
Test
I'm trying to make a Sudoku Game in JavaFX. I made the 9x9 grid using GridPane and TextField.
Now I want to change the background color of the TextField when user clicks inside it. To check that everyting is fine I am prining the target od the MouseEvent.
My problem is that when I click in the center of TextField, the target is Pane and when i I click elsewhere the target is my GridPane and the background color is changing.
What should I do? I can't figure out how to do it!
public class SudokuGrid {
public static final int GRID_SIZE = 9;
private TextField[][] sudokuCells;
private GridPane sudokuGrid;
public SudokuGrid () {
sudokuCells = new TextField[GRID_SIZE][GRID_SIZE];
createSudokuGrid();
for (int row = 0; row < GRID_SIZE; row++) {
for(int col = 0; col < GRID_SIZE; col++) {
sudokuCells[row][col] = new TextField() {
#Override
public void replaceText(int start, int end, String text) {
// If the replaced text would end up being invalid, then simply
// ignore this call!
if (text.matches("[1-9]|\\s")) {
super.setText(text);
}
}
};
sudokuCells[row][col].setPrefSize(60, 60);
sudokuCells[row][col].setStyle("-fx-background-color: yellow;");
sudokuGrid.add(sudokuCells[row][col], col, row);
sudokuGrid.addEventFilter(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
Object source = e.getTarget();
System.out.println(source);
if(source instanceof TextField) {
((TextField) source).setStyle("-fx-background-color: green;");
}
}
});
}
}
sudokuGrid.setPrefSize(270, 270); // 30 * 9
sudokuGrid.setGridLinesVisible(true);
}
private void createSudokuGrid() {
sudokuGrid = new GridPane();
for (int i = 0; i < GRID_SIZE; i++) {
RowConstraints rc = new RowConstraints();
rc.setVgrow(Priority.ALWAYS) ; // allow row to grow
rc.setFillHeight(true); // ask nodes to fill height for row
// other settings as needed...
sudokuGrid.getRowConstraints().add(rc);
ColumnConstraints cc = new ColumnConstraints();
cc.setHgrow(Priority.ALWAYS) ; // allow column to grow
cc.setFillWidth(true); // ask nodes to fill space for column
// other settings as needed...
sudokuGrid.getColumnConstraints().add(cc);
}
}
The source of the event is the object on which you set the event filter; i.e. in this case it is sudokuGrid. So the condition
if (source instanceof TextField)
in your handler will never be true, since the only possible source is the sudokuGrid.
If you want to change the background color of the text field, you can add the event filter to the text field itself:
TextField sudokuCell = sudokuCells[row][col];
sudokuCell.addEventFilter(MouseEvent.MOUSE_PRESSED, e ->
sudokuCell.setStyle("-fx-background-color: green;"));
Better still would be to respond to changes in the text field's focused property (because using a mouse listener will not change the background if the user uses the Tab key to navigate to different text fields):
TextField sudokuCell = sudokuCells[row][col];
sudokuCell.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
if (isNowFocused) {
sudokuCell.setStyle("-fx-background-color: green;");
} else {
sudokuCell.setStyle("");
}
});
And even better would just be to use an external css file to do this:
sudoku-grid.css:
.text-field:focused {
-fx-background-color: green ;
}
and then in your Java code associate the CSS file with the grid:
sudokuGrid.getStyleSheets().add("sudoku-grid.css");
and remove the handlers entirely.