JavaFX randomly fill ListViews - javafx

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)

Related

JavaFX Button which adds and removes Checkboxes

I was trying to create a button which deletes checked CheckBox but don't know how to make "cb" the same as in the Adding button.
package sample;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
public class Controller {
#FXML
private Button add;
#FXML
private Button delete;
#FXML
private VBox vbox;
#FXML
private TextField text;
#FXML
private CheckBox cb;
#FXML
void initialize() {
}
#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) {
if(cb.isSelected()){
vbox.getChildren().remove(cb);
}
}
}
The "cb" in onAdd event isn't the same as in onDelete event. How can i solve it ?
Note that it's possible to add multiple CheckBoxes with the code you've posted. It's unclear which one should be removed. Of course you can access the children the way you access any other list elements. You deed to add a cast of course, since children is a List<Node>. If all children of vbox are CheckBoxes, you could simply iterate through all the children and remove all those CheckBoxes that are selected (assuming this is the functionality you're trying to achieve).
vbox.getChildren().removeIf(child -> ((CheckBox) child).isSelected());
If this is not the case, you could store the elements in a List<CheckBox> field:
private final List<CheckBox> checkBoxes = new ArrayList<>();
#FXML
public void onAdd(ActionEvent e) {
if (text.getText().isEmpty()) { // alternative: text.getText().equals(""); matches is for regular expressions
CheckBox cb = new CheckBox(text.getText());
vbox.getChildren().add(cb);
checkBoxes.add(cb);
}
}
#FXML
public void onDelete(ActionEvent e) {
Iterator<CheckBox> iter = checkBoxes.iterator();
while (iter.hasNext()) {
CheckBox cb = iter.next();
if (cb.isSelected()) {
// remove from both vbox and checkBoxes
vbox.getChildren().remove(cb);
iter.remove();
}
}
}

Why is my javafx label not showing after it being changed?

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

How to concatenate event handler with another method call?

I have two buttons in two separate classes, and I want to change the onAction of the first button when the second button has been pressed to be the original action plus one additional method call. Once the first button has been pressed I want its onAction to revert to the original.
What I currently have working is essientially
Button b1 = new Button("b1");
b1.setOnAction((event)-> {
oldActionMethod();
});
public void oldActionMethod(){
//actual code
}
b2.setOnAction((event)-> {
//some stuff
Button b1 = getB1();
EventHandler<ActionEvent> temp = b1.getOnAction();
b1.setOnAction((event) -> {
b1class.oldActionMethod();
additionalMethod();
b1.setOnAction(temp);
});
});
In order to make this work I had to move the block of code that was originally in the setOnAction lambda expression to a helper function. Is there a cleaner way to do this? Something like this which would eliminate the need for the helper function?
b1.setOnAction((event)-> {
//actual code
});
b2.setOnAction((event) -> {
//stuff
Button b1 = getB1();
EventHandler<ActionEvent> temp = b1.getOnAction();
b1.setOnAction(b1.getOnAction() + methodCall());
b1.setOnAction(temp);
//stuff
});
The way I have it currently does work but it feels really hack-y so I am just interested to know if there is a better option where you could essentially concatenate an actionEvent with another method. Also if there is a way to not require storing the original event in a temp object and resetting it at the end. A possible solution would be if I could tell b2 to listen for the next time b1 is pressed, but I don't know if there is any way to do that when they are in two separate classes.
One solution is to have a shared model class between the two classes containing the buttos.
See the following mcve. For conviniense the entire code can be copy-pasted into one file (FaMain.java) and run:
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class FxMain extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
Model model = new Model();
AClass aClass = new AClass(model);
BClass bClass = new BClass(model);
Label show = new Label();
show.textProperty().bind(model.getTextProperty());
VBox root = new VBox(10);
root.getChildren().addAll(aClass.getButton(),show, bClass.getButton());
primaryStage.setScene(new Scene(root, 400,100));
primaryStage.sizeToScene();
primaryStage.show();
}
public static void main(final String[] args) {
launch(args);
}
}
class Model {
private final BooleanProperty aButtonSelected;
private final SimpleStringProperty textProperty;
Model(){
aButtonSelected = new SimpleBooleanProperty();
textProperty= new SimpleStringProperty();
}
ObservableValue<? extends String> getTextProperty() {
return textProperty;
}
BooleanProperty aButtonSelectedProperty(){
return aButtonSelected;
}
void bButtonClicked() {
textProperty.set(aButtonSelected.get() ? "Button clicked. Toggle IS selected" :
"Button clicked. Toggle is NOT selected");
}
}
class AClass{
private final ToggleButton aButton;
AClass(Model model) {
aButton = new ToggleButton("Toogle");
model.aButtonSelectedProperty().bind(aButton.selectedProperty());
}
ToggleButton getButton(){
return aButton;
}
}
class BClass{
private final Button bButton;
BClass(Model model) {
bButton = new Button("Click");
bButton.setOnAction(e->model.bButtonClicked());
}
Button getButton(){
return bButton;
}
}

Accessing the OK button from inside the dialog controller [javaFX]

I am trying to disable the OK button in a javaFX dialog untill all of the text fields have content.
Due to the ButtonType not having FXML support it has to be added to the Dialog in the Controller class of the main Window. due to this I'm unable to (cant find a way) to link the button to a variable inside the dialog controller.
I have tried handling the process in the main Controller class as follows:
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("addDialog.fxml"));
try {
dialog.getDialogPane().setContent(loader.load());
} catch(IOException e) {
e.getStackTrace();
}
dialog.getDialogPane().getButtonTypes().add(ButtonType.OK);
dialog.getDialogPane().getButtonTypes().add(ButtonType.CANCEL);
dialog.getDialogPane().lookupButton(ButtonType.OK).setDisable(true);
AddDialogController controller = loader.getController();
// below calls on a getter from the addDialogController.java file to check if the input fields are full
if (controller.getInputsFull()) {
dialog.getDialogPane().lookupButton(ButtonType.OK).setDisable(false);
}
unfortunately this didn't work, the above code can only be run once before or after the window is called and cant run during.
so is there a way to access the OK ButtonType that comes with javaFX inside the dialog controller if it has been declared outside?
Or is there another way to disable the button based of information from the dialog controller that is being updated by the user?
thanks for any help
Edit 1:
As requested the addDialogController, this is very bare bones and incomplete, hopefully it helps:
import data.Contact;
import data.ContactData;
import javafx.fxml.FXML;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
public class AddDialogController {
#FXML
private TextField firstNameField;
#FXML
private TextField lastNameField;
#FXML
private TextField numberField;
#FXML
private TextArea notesArea;
private boolean inputsFull;
public void processResults() {
String first = firstNameField.getText().trim();
String last = lastNameField.getText().trim();
String number = numberField.getText().trim();
String notes = notesArea.getText().trim();
Contact contact = new Contact(first, last, number, notes);
// ContactData.add(contact);
}
#FXML
public void handleKeyRelease() {
boolean firstEmpty = firstNameField.getText().trim().isEmpty() && firstNameField.getText().isEmpty();
boolean lastEmpty = lastNameField.getText().trim().isEmpty() && lastNameField.getText().isEmpty();
boolean numberEmpty = numberField.getText().trim().isEmpty() && numberField.getText().isEmpty();
boolean notesEmpty = notesArea.getText().trim().isEmpty() && notesArea.getText().isEmpty();
inputsFull = !firstEmpty && !lastEmpty && !numberEmpty && !notesEmpty;
System.out.println(firstEmpty);
System.out.println(lastEmpty);
System.out.println(numberEmpty);
System.out.println(notesEmpty);
System.out.println(inputsFull);
System.out.println();
}
public boolean isInputsFull() {
return this.inputsFull;
}
First, delete your handleKeyRelease method. Never use key event handlers on text input components: for one thing they will not work if the user copies and pastes text into the text field with the mouse. Just register listeners with the textProperty() instead, if you need. Also, isn't (for example)
firstNameField.getText().trim().isEmpty() && firstNameField.getText().isEmpty()
true if and only if
firstNameField.getText().isEmpty();
is true? It's not clear what logic you are trying to implement there.
You should simply expose inputsFull as a JavaFX property:
public class AddDialogController {
#FXML
private TextField firstNameField;
#FXML
private TextField lastNameField;
#FXML
private TextField numberField;
#FXML
private TextArea notesArea;
private BooleanBinding inputsFull ;
public BooleanBinding inputsFullBinding() {
return inputsFull ;
}
public final boolean getInputsFull() {
return inputsFull.get();
}
public void initialize() {
inputsFull = new BooleanBinding() {
{
bind(firstNameField.textProperty(),
lastNameField.textProperty(),
numberField.textProperty(),
notesArea.textProperty());
}
#Override
protected boolean computeValue() {
return ! (firstNameTextField.getText().trim().isEmpty()
|| lastNameTextField.getText().trim().isEmpty()
|| numberField.getText().trim().isEmpty()
|| notesArea.getText().trim().isEmpty());
}
};
}
public void processResults() {
String first = firstNameField.getText().trim();
String last = lastNameField.getText().trim();
String number = numberField.getText().trim();
String notes = notesArea.getText().trim();
Contact contact = new Contact(first, last, number, notes);
// ContactData.add(contact);
}
}
and then all you need is
dialog.getDialogPane().getButtonTypes().add(ButtonType.OK);
dialog.getDialogPane().getButtonTypes().add(ButtonType.CANCEL);
AddDialogController controller = loader.getController();
dialog.getDialogPane().lookupButton(ButtonType.OK)
.disableProperty()
.bind(controller.inputsFullBinding().not());

Binding an enum's toString() to a Label

I have set up my application to change its function based on an enum. The value of a variable linked to this enum will determine how the program interprets certain actions like mouse clicks and so on. I would like a Label (perhaps in the status area in the bottom left) to reflect what the current "mode" the application is in, and display a readable message for the user to see.
Here's my enum:
enum Mode {
defaultMode, // Example states that will determine
alternativeMode; // how the program interprets mouse clicks
// My attempt at making a property that a label could bind to
private SimpleStringProperty property = new SimpleStringProperty(this, "myEnumProp", "Initial Text");
public SimpleStringProperty getProperty() {return property;}
// Override of the toString() method to display prettier text
#Override
public String toString()
{
switch(this) {
case defaultMode:
return "Default mode";
default:
return "Alternative mode";
}
}
}
From what I've gathered, what I'm looking for is a way to bind an enum's toString() property (which I overrode into more digestable form) to this label. The binding would be so that whenever I set something like
applicationState = Mode.alternativeMode;
the label will display the toString() results automatically, without me needing to place a leftStatus.setText(applicationState.toString()) every time I do that.
Here's what I've tried: (in my main controller class):
leftStatus.textProperty().bind(applicationState.getProperty());
That sets the label to the initial text, but won't update when I update the applicationState enum.
What am I doing wrong?
Instead of adding a property to the enum class, why not use a ObjectProperty for the application state? Have a look at this MCVE:
import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
public class Example extends Application {
private ObjectProperty<Mode> appState = new SimpleObjectProperty<>(Mode.DEFAULT);
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
Button btn = new Button("Toggle mode");
btn.setOnMouseClicked((event) -> appState.setValue(appState.get() == Mode.DEFAULT ? Mode.ALTERNATIVE : Mode.DEFAULT));
Label lbl = new Label();
lbl.textProperty().bind(appState.asString());
FlowPane pane = new FlowPane();
pane.getChildren().addAll(btn, lbl);
primaryStage.setScene(new Scene(pane));
primaryStage.show();
}
public enum Mode {
DEFAULT("Default mode"),
ALTERNATIVE("Alternative mode");
private String description;
private Mode(String description) {
this.description = description;
}
#Override
public String toString() {
return description;
}
}
}
Use asString to get a StringBinding from a Property<Mode> containing the value of the property converted to String using the object's toString method.
Example:
#Override
public void start(Stage primaryStage) {
ComboBox<Mode> combo = new ComboBox<>();
combo.getItems().setAll(Mode.values());
Label label = new Label();
// use "state" property from combo box
// (you could replace combo.valueProperty() with your own property)
label.textProperty().bind(combo.valueProperty().asString());
Scene scene = new Scene(new VBox(combo, label), 200, 200);
primaryStage.setScene(scene);
primaryStage.show();
}
Otherwise, if you want the property value contained in the enum, you could use Bindings.selectString, provided you rename the getProperty() method to propertyProperty() to adhere the naming conventions:
enum Mode {
...
public StringProperty propertyProperty() {return property;}
...
}
private final Random random = new Random();
#Override
public void start(Stage primaryStage) {
ComboBox<Mode> combo = new ComboBox<>();
combo.getItems().setAll(Mode.values());
Label label = new Label();
// use "state" property from combo box
// (you could replace combo.valueProperty() with your own property)
label.textProperty().bind(Bindings.selectString(combo.valueProperty(), "property"));
Scene scene = new Scene(new VBox(combo, label), 200, 200);
scene.setOnMouseClicked(evt -> {
// change property values at random
Mode.defaultMode.propertyProperty().set(random.nextBoolean() ? "a" : "b");
Mode.alternativeMode.propertyProperty().set(random.nextBoolean() ? "c" : "d");
});
primaryStage.setScene(scene);
primaryStage.show();
}

Resources