CheckBoxTreeTableCell Select All Children Under Parent Event - java

I'm trying to select all child check boxes from a parent root. The action is invoked when the parent check box is selected.
Here's the pseudo/modified/shortened set up with scenebuilder:
#FXML
private TreeTableView<Info> testTable;
#FXML
private TreeTableColumn<Info, Boolean> checkBoxCol;
Model:
public class Info{
private final BooleanProperty onHold;
public Info(){
this.onHold = new SimpleBooleanProperty(false);
}
public Boolean getOnHold(){
return onHold.get();
}
public void setOnHold(Boolean onHold) {
this.onHold.set(onHold);
}
public BooleanProperty onHoldProperty(){
return onHold;
}
}
Controller:
checkBoxCol.setCellValueFactory(new TreeItemPropertyValueFactory("onHold"));
checkBoxCol.setCellFactory(CheckBoxTreeTableCell.forTreeTableColumn(checkBoxCol));
End Result would look like this (when parent nodes are clicked):
I tried onEditCommit/start/cancel, but those seem to only affect the cell and not the checkboxes. I am not exactly sure how to get a listener for only the parent nodes so that it can check all values underneath (if they have children). If it's too difficult to only allow just the parent nodes to have the listener, then all the checkbox can have listeners and I can simply check if there are children with:node.getChildren().size()

You should be able to manage this entirely in the model.
The TreeTableView consists of a TreeItem<Info> root with a bunch of descendent nodes. Just arrange that whenever you create the tree items, you add a listener to the properties:
private TreeItem<Info> createTreeItem(Info info) {
TreeItem<Info> item = new TreeItem<>(info);
info.onHoldProperty().addListener((obs, wasOnHold, isNowOnHold) -> {
if (isNowOnHold) {
item.getChildren().forEach(child -> child.getValue().setOnHold(true));
}
});
return item ;
}
Complete example:
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.Scene;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableView;
import javafx.scene.control.cell.CheckBoxTreeTableCell;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class TreeTableViewInheritableCheckBoxes extends Application {
#Override
public void start(Stage primaryStage) {
TreeTableView<Info> table = new TreeTableView<>();
table.setEditable(true);
TreeTableColumn<Info, Boolean> infoCol = new TreeTableColumn<>("Info");
infoCol.setPrefWidth(200);
infoCol.setCellValueFactory(cellData -> cellData.getValue().getValue().onHoldProperty());
infoCol.setCellFactory(CheckBoxTreeTableCell.forTreeTableColumn(infoCol));
table.getColumns().add(infoCol);
TreeItem<Info> root = createTreeItem(new Info());
buildTree(root, 0);
table.setRoot(root);
primaryStage.setScene(new Scene(new BorderPane(table), 250, 400));
primaryStage.show();
}
private void buildTree(TreeItem<Info> parent, int depth) {
if (depth > 2) return ;
for (int i = 0; i < 5; i++) {
TreeItem<Info> item = createTreeItem(new Info());
parent.getChildren().add(item);
buildTree(item, depth + 1);
}
}
private TreeItem<Info> createTreeItem(Info info) {
TreeItem<Info> item = new TreeItem<>(info);
info.onHoldProperty().addListener((obs, wasOnHold, isNowOnHold) -> {
if (isNowOnHold) {
item.getChildren().forEach(child -> child.getValue().setOnHold(true));
}
});
return item ;
}
public static class Info {
private final BooleanProperty onHold;
public Info(){
this.onHold = new SimpleBooleanProperty(false);
}
public Boolean getOnHold(){
return onHold.get();
}
public void setOnHold(Boolean onHold) {
this.onHold.set(onHold);
}
public BooleanProperty onHoldProperty(){
return onHold;
}
}
public static void main(String[] args) {
launch(args);
}
}

Related

how to generate button(picture) based on checkboxes selection in javafx

My question is how to generate buttons, set with car pictures, based on the checkboxes and/or radio buttons selected by a user in javafx?
I'm simulating a car dealership website with car pictures. The user should be able to filter the pictures displayed by clicking checkboxes and/or radio buttons selection.
I'm first creating all the picture buttons with a for each loop. I could use if and if/else statements to filter through the pictures but there would be duplicates. I've heard of observablelist but I haven't learned those yet.
Can someone help me out with this one please? Thank you!
ArrayList<Car> cars;
for (Car r : cars)
{
for (int i = 0; i < SIZE; i++)
{
// create buttons and set car pictures
carButton[i] = new Button();
carButton[i].setId(String.format("%d", i));
carButton[i].setGraphic(cars.get(i).getCarPicture());
}
}
Instead of using an ArrayList for your cars, I recommend using an ObservableList:
ObservableList<Car> carsList = FXCollections.observableArrayList<>();
An ObservableList allows you to listen for changes and respond accordingly. For example, when a new Car is added to the list, you could trigger an event that automatically adds a new Button to your scene.
Here is a short demo application that shows how this would work. I did comment the code below as well and many of the concepts being used may be beyond your level, but it's one method, at least.
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.ImageView;
import javafx.scene.layout.FlowPane;
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) {
VBox root = new VBox(5);
root.setPadding(new Insets(10));
root.setAlignment(Pos.CENTER);
// Create an ObservableList to hold our Cars
ObservableList<Car> carsList = FXCollections.observableArrayList();
// For our sample, let's use a FlowPane to display all of our buttons. We will add new buttons to this FlowPane
// automatically as new Cars are added to carsList
FlowPane flowPane = new FlowPane();
flowPane.setHgap(10);
flowPane.setVgap(5);
flowPane.setAlignment(Pos.TOP_CENTER);
// Create a ListChangeListener for our carsList. This allows us to perform some actions whenever an item is added
// to or removed from the list. For our example, we will only do something when a new Car is added.
carsList.addListener(new ListChangeListener<Car>() {
#Override
public void onChanged(Change<? extends Car> c) {
System.out.println(carsList.size());
// Get the first change
c.next();
// If an item was added to the list...
if (c.wasAdded()) {
// Create a new button and add it to the FlowPane
// The Change (c) provides access to a List of items that were added during this change. Since we
// are only going to add one Car at a time, we only need to get the first item from the AddedSubList
Button button = new Button(c.getAddedSubList().get(0).getName());
button.setGraphic(c.getAddedSubList().get(0).getIcon());
button.setOnAction(event -> {
// The code to be executed when this button is clicked goes here
});
// Add the button to our FlowPane
flowPane.getChildren().add(button);
}
}
});
// Now we need a Button that will add a new car to the List
Button button = new Button("Add Car");
button.setOnAction(event -> {
// We'll just add a random car to the carsList
carsList.add(new Car("Car #" + (carsList.size() + 1), new ImageView("icon.png")));
});
// Add our FlowPane and Button to the root layout
root.getChildren().addAll(button, flowPane);
primaryStage.setScene(new Scene(root, 550, 250));
primaryStage.show();
}
}
class Car {
private final String name;
private final ImageView icon;
public Car(String name, ImageView icon) {
this.name = name;
this.icon = icon;
}
public String getName() {
return name;
}
public ImageView getIcon() {
return icon;
}
}
The Results: (after clicking the "Add Car" button a few times)
This is a terrible implementation but It will give you some ideas on how to do things. You need to research FilteredList, ListView, and Predicate. This implementation does not handle more than one CheckBox at a time. It will only display the last CheckBox action.
CarList
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
*
* #author Sedrick
*/
public class CarList extends Application {
#Override
public void start(Stage primaryStage) {
List<Car> cars = new ArrayList();
cars.add(new Car("Honda", "2004"));
cars.add(new Car("Ford", "2005"));
cars.add(new Car("Ford", "2004"));
cars.add(new Car("Honda", "2005"));
cars.add(new Car("Toyota", "2004"));
cars.add(new Car("Cadillac", "2005"));
ListView<Car> view = new ListView();
view.setCellFactory((ListView<Car> param) -> {
ListCell<Car> cell = new ListCell<Car>() {
CarView carView = new CarView();
#Override
protected void updateItem(Car item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
setText("");
carView.setMake(item.getMake());
carView.setModel(item.getModel());
carView.setImageView(item.getUrl());
setGraphic(carView);
} else {
setText("");
setGraphic(null);
}
}
};
return cell;
});
ObservableList<Car> data = FXCollections.observableArrayList(cars);
FilteredList<Car> filteredList = new FilteredList(data);
view.setItems(filteredList);
HBox.setHgrow(view, Priority.ALWAYS);
CheckBox checkBox = new CheckBox("Honda");
checkBox.selectedProperty().addListener((observable, oldValue, newValue) -> {
if(newValue)
{
filteredList.setPredicate((item) -> {
return item.getMake().equals("Honda");
});
}
else{
filteredList.setPredicate((item) -> {
return true;
});
}
});
CheckBox checkBox2 = new CheckBox("Ford");
checkBox2.selectedProperty().addListener((observable, oldValue, newValue) -> {
if(newValue)
{
filteredList.setPredicate((item) -> {
return item.getMake().equals("Ford");
});
}
else{
filteredList.setPredicate((item) -> {
return true;
});
}
});
CheckBox checkBox3 = new CheckBox("2004");
checkBox3.selectedProperty().addListener((observable, oldValue, newValue) -> {
if(newValue)
{
filteredList.setPredicate((item) -> {
return item.getModel().equals("2004");
});
}
else{
filteredList.setPredicate((item) -> {
return true;
});
}
});
CheckBox checkBox4 = new CheckBox("2005");
checkBox4.selectedProperty().addListener((observable, oldValue, newValue) -> {
if(newValue)
{
filteredList.setPredicate((item) -> {
return item.getModel().equals("2005");
});
}
else{
filteredList.setPredicate((item) -> {
return true;
});
}
});
VBox leftPanel = new VBox(checkBox, checkBox2, checkBox3, checkBox4);
HBox root = new HBox(leftPanel, view);
Scene scene = new Scene(root, 625, 500);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
CarView
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
/**
*
* #author Sedrick
*/
final public class CarView extends HBox{
Label make = new Label();
Label model = new Label();
ImageView imageView = new ImageView();
public CarView(String make, String model, String url) {
this.make.setText(make);
this.model.setText(model);
HBox row1 = new HBox(new Label("Make: "), this.make);
HBox row2 = new HBox(new Label("Model: "), this.model);
VBox vbox = new VBox(row1, row2);
vbox.setMaxSize(USE_PREF_SIZE, USE_PREF_SIZE);
StackPane stackPane1 = new StackPane(vbox);
HBox.setHgrow(stackPane1, Priority.ALWAYS);
Image image = new Image(url);
this.imageView.setImage(image);
this.imageView.setFitHeight(100);
this.imageView.setFitWidth(200);
StackPane stackPane2 = new StackPane(this.imageView);
stackPane2.setStyle("-fx-background-color: yellow");
getChildren().addAll(stackPane1, stackPane2);
setPrefSize(500, 125);
}
public CarView()
{
HBox row1 = new HBox(new Label("Make: "), this.make);
HBox row2 = new HBox(new Label("Model: "), this.model);
VBox vbox = new VBox(row1, row2);
vbox.setMaxSize(USE_PREF_SIZE, USE_PREF_SIZE);
StackPane stackPane1 = new StackPane(vbox);
HBox.setHgrow(stackPane1, Priority.ALWAYS);
this.imageView.setFitHeight(100);
this.imageView.setFitWidth(200);
StackPane stackPane2 = new StackPane(this.imageView);
stackPane2.setStyle("-fx-background-color: yellow");
getChildren().addAll(stackPane1, stackPane2);
setPrefSize(500, 125);
}
public void setImageView(String url) {
Image image = new Image(url);
this.imageView.setImage(image);
}
public void setMake(String make) {
this.make.setText(make);
}
public void setModel(String model)
{
this.model.setText(model);
}
}
Car
/**
*
* #author Sedrick
*/
public class Car {
private String make;
private String model;
private String url = "https://cdn.pixabay.com/photo/2012/05/29/00/43/car-49278_960_720.jpg";
public Car(String make, String model) {
this.make = make;
this.model = model;
}
public String getMake() {
return make;
}
public String getModel() {
return model;
}
public String getUrl()
{
return url;
}
public void setMake(String make) {
this.make = make;
}
public void setModel(String model) {
this.model = model;
}
}

Also select parents up to the root when selecting child in TreeTableView

We try to achieve the following:
When a node gets selected in a JavaFX TreeTableView, also "the path to the root", i.e., the parent, the grandparent, and so on should get selected. Selected in this case means highlighted with a different background color, see the image (in the example, the node on Level 2 has been clicked by the user).
Is there a built-in function how to achieve this?
We tried using CSS but did not succeed.
There's no "built-in function" to do this. Use a row factory on the tree table view to create rows that observe the selected item, and set a pseudoclass on the row accordingly.
For example:
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.css.PseudoClass;
import javafx.scene.Scene;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableRow;
import javafx.scene.control.TreeTableView;
import javafx.stage.Stage;
public class TreeTableViewHighlightSelectionPath extends Application {
#Override
public void start(Stage primaryStage) {
TreeTableView<Item> table = new TreeTableView<Item>();
PseudoClass ancestorOfSelection = PseudoClass.getPseudoClass("ancestor-of-selection");
table.setRowFactory(ttv -> new TreeTableRow<Item>() {
{
table.getSelectionModel().selectedItemProperty().addListener(
(obs, oldSelection, newSelection) -> updateStyleClass());
}
#Override
protected void updateItem(Item item, boolean empty) {
super.updateItem(item, empty);
updateStyleClass();
}
private void updateStyleClass() {
pseudoClassStateChanged(ancestorOfSelection, false);
TreeItem<Item> treeItem = table.getSelectionModel().getSelectedItem();
if (treeItem != null) {
for (TreeItem<Item> parent = treeItem.getParent() ; parent != null ; parent = parent.getParent()) {
if (parent == getTreeItem()) {
pseudoClassStateChanged(ancestorOfSelection, true);
break ;
}
}
}
}
});
TreeTableColumn<Item, String> itemCol = new TreeTableColumn<>("Item");
itemCol.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().getValue().getName()));
table.getColumns().add(itemCol);
TreeTableColumn<Item, Number> valueCol = new TreeTableColumn<>("Value");
valueCol.setCellValueFactory(cellData -> cellData.getValue().getValue().valueProperty());
table.getColumns().add(valueCol);
table.setRoot(createRandomTree());
Scene scene = new Scene(table);
scene.getStylesheets().add("style.css");
primaryStage.setScene(scene);
primaryStage.show();
}
private TreeItem<Item> createRandomTree() {
TreeItem<Item> root = new TreeItem<>(new Item("Item 1", 0));
Random rng = new Random();
List<TreeItem<Item>> items = new ArrayList<>();
items.add(root);
for (int i = 2 ; i <= 20 ; i++) {
TreeItem<Item> item = new TreeItem<>(new Item("Item "+i, rng.nextInt(1000)));
items.get(rng.nextInt(items.size())).getChildren().add(item);
items.add(item);
}
return root ;
}
public static class Item {
private final String name ;
private final IntegerProperty value = new SimpleIntegerProperty();
public Item(String name, int value) {
this.name = name ;
setValue(value);
}
public String getName() {
return name ;
}
public IntegerProperty valueProperty() {
return value ;
}
public final int getValue() {
return valueProperty().get();
}
public final void setValue(int value) {
valueProperty().set(value);
}
}
public static void main(String[] args) {
launch(args);
}
}
Now you can just style the "ancestor of a selected node" in CSS:
File style.css:
.tree-table-row-cell:ancestor-of-selection {
-fx-background: -fx-selection-bar;
-fx-table-cell-border-color: derive(-fx-selection-bar, 20%);
}
(You may want to modify the CSS to get better control, e.g. set different colors for selected rows in a non-focused table, etc. See the default stylesheet for details on the default style.)
Here's a screenshot of the above test app:

Using JavaFX controller without FXML

Is there a possibility to use a controller with a JavaFX GUI without using FXML.
I noticed that the FXML file contains an fx-controller attribute to bind the controller but i don't find it an easy way to work with it.
Any ideas about have an MVC arch with JavaFX without using the FXML file or JavaFX Scene Builder ?
Your question isn't particularly clear to me: you just create the classes and basically tie everything together with listeners. I don't know if this helps, but here is a simple example that just has a couple of text fields and a label displaying their sum. This is what I regard as "classical MVC": the view observes the model and updates the UI elements if the model changes. It registers handlers with the UI elements and delegates to the controller if events happen: the controller in turn processes the input (if necessary) and updates the model.
Model:
package mvcexample;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ReadOnlyIntegerWrapper;
import javafx.beans.property.SimpleIntegerProperty;
public class AdditionModel {
private final IntegerProperty x = new SimpleIntegerProperty();
private final IntegerProperty y = new SimpleIntegerProperty();
private final ReadOnlyIntegerWrapper sum = new ReadOnlyIntegerWrapper();
public AdditionModel() {
sum.bind(x.add(y));
}
public final IntegerProperty xProperty() {
return this.x;
}
public final int getX() {
return this.xProperty().get();
}
public final void setX(final int x) {
this.xProperty().set(x);
}
public final IntegerProperty yProperty() {
return this.y;
}
public final int getY() {
return this.yProperty().get();
}
public final void setY(final int y) {
this.yProperty().set(y);
}
public final javafx.beans.property.ReadOnlyIntegerProperty sumProperty() {
return this.sum.getReadOnlyProperty();
}
public final int getSum() {
return this.sumProperty().get();
}
}
Controller:
package mvcexample;
public class AdditionController {
private final AdditionModel model ;
public AdditionController(AdditionModel model) {
this.model = model ;
}
public void updateX(String x) {
model.setX(convertStringToInt(x));
}
public void updateY(String y) {
model.setY(convertStringToInt(y));
}
private int convertStringToInt(String s) {
if (s == null || s.isEmpty()) {
return 0 ;
}
if ("-".equals(s)) {
return 0 ;
}
return Integer.parseInt(s);
}
}
View:
package mvcexample;
import javafx.geometry.HPos;
import javafx.geometry.Pos;
import javafx.scene.Parent;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.TextFormatter.Change;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
public class AdditionView {
private GridPane view ;
private TextField xField;
private TextField yField;
private Label sumLabel;
private AdditionController controller ;
private AdditionModel model ;
public AdditionView(AdditionController controller, AdditionModel model) {
this.controller = controller ;
this.model = model ;
createAndConfigurePane();
createAndLayoutControls();
updateControllerFromListeners();
observeModelAndUpdateControls();
}
public Parent asParent() {
return view ;
}
private void observeModelAndUpdateControls() {
model.xProperty().addListener((obs, oldX, newX) ->
updateIfNeeded(newX, xField));
model.yProperty().addListener((obs, oldY, newY) ->
updateIfNeeded(newY, yField));
sumLabel.textProperty().bind(model.sumProperty().asString());
}
private void updateIfNeeded(Number value, TextField field) {
String s = value.toString() ;
if (! field.getText().equals(s)) {
field.setText(s);
}
}
private void updateControllerFromListeners() {
xField.textProperty().addListener((obs, oldText, newText) -> controller.updateX(newText));
yField.textProperty().addListener((obs, oldText, newText) -> controller.updateY(newText));
}
private void createAndLayoutControls() {
xField = new TextField();
configTextFieldForInts(xField);
yField = new TextField();
configTextFieldForInts(yField);
sumLabel = new Label();
view.addRow(0, new Label("X:"), xField);
view.addRow(1, new Label("Y:"), yField);
view.addRow(2, new Label("Sum:"), sumLabel);
}
private void createAndConfigurePane() {
view = new GridPane();
ColumnConstraints leftCol = new ColumnConstraints();
leftCol.setHalignment(HPos.RIGHT);
leftCol.setHgrow(Priority.NEVER);
ColumnConstraints rightCol = new ColumnConstraints();
rightCol.setHgrow(Priority.SOMETIMES);
view.getColumnConstraints().addAll(leftCol, rightCol);
view.setAlignment(Pos.CENTER);
view.setHgap(5);
view.setVgap(10);
}
private void configTextFieldForInts(TextField field) {
field.setTextFormatter(new TextFormatter<Integer>((Change c) -> {
if (c.getControlNewText().matches("-?\\d*")) {
return c ;
}
return null ;
}));
}
}
Application class:
package mvcexample;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class MVCExample extends Application {
#Override
public void start(Stage primaryStage) {
AdditionModel model = new AdditionModel();
AdditionController controller = new AdditionController(model);
AdditionView view = new AdditionView(controller, model);
Scene scene = new Scene(view.asParent(), 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I use JavaFX extensively and do not use FXML or scenebuilder. So I can vouch that it can be done.
Below is the auto generated code made by my IDE to get an JavaFX main class. This will be the root of your application. You will then add to it to create your application.
public class NewFXMain extends Application {
#Override
public void start(Stage primaryStage) {
Button btn = new Button();
btn.setText("Say 'Hello World'");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("Hello World!");
}
});
StackPane root = new StackPane();
root.getChildren().add(btn);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
For the rest of us... Here is a VERY simple example showing how to create a JavaFX form without the use of any FXML files. This example can be used within an app that is already running, so I've skipped the Main class and all that ... it's just meant to show the simplicity of JavaFX.
In a nutshell, you simply create your scene based on a container such as an AnchorPane, then you create your Stage and assign the Scene to the stage ... add your controls then show the stage
package javafx;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
public class SimpleFX {
private AnchorPane anchorPane;
private TextArea textArea () {
TextArea textArea = new TextArea();
textArea.setLayoutX(20);
textArea.setLayoutY(20);
textArea.setMaxWidth(450);
textArea.setMinHeight(380);
return textArea;
}
private TextField textField () {
TextField textField = new TextField();
textField.setLayoutX(20);
textField.setLayoutY(410);
textField.setMinWidth(450);
textField.setMinHeight(25);
return textField;
}
private Button button() {
Button button = new Button("Button");
button.setLayoutX(240);
button.setLayoutY(450);
return button;
}
private void addControls () {
anchorPane.getChildren().add(0,textArea());
anchorPane.getChildren().add(1,textField());
anchorPane.getChildren().add(2,button());
}
public void startForm () {
anchorPane = new AnchorPane();
Scene scene = new Scene(anchorPane, 500, 500);
Stage stage = new Stage();
stage.setScene(scene);
addControls();
stage.show();
}
}

JavaFX TreeView of multiple object types? (and more)

I currently have the following object data structure:
Item
String name
ArrayList of information
Character
String name
Collection of Item
Account
String name
Collection of Character (up to 8 max)
I want to make a TreeView that looks like the following:
Root(invisible)
======Jake(Account)
============JakesChar(Character)
==================Amazing Sword(Item)
==================Broken Bow(Item)
==================Junk Metal(Item)
======Mark(Account)
============myChar(Character)
==================Godly Axe(Item)
======FreshAcc(Account)
======MarksAltAcc(Account)
============IllLvlThisIPromise(Character)
======Jeffrey(Account)
============Jeff(Character)
==================Super Gun(Item)
==================Better Super Gun(Item)
==================Super Gun Scope(Item)
I made all those names up and such, obviously the real implementation would be a lot more complex. How can this be done? The TreeItem requires each TreeItem to be the same type as its' parent.
The ONLY solution I have is to do the following:
public class ObjectPointer
{
Object pointer;
String name;
}
My TreeView would be of type ObjectPointer and on each row I would cast the ObjectPointer to Account, Character, or Item. This is AWFUL but I think it would work.
Sub Questions:
How do I get TreeItem(s) to detect setOnMouseHover events?
How do I get TreeItem(s) to not use the toString method of their type and instead a custom way of displaying the String property that they need?
How do I get the TreeItem(s) to display colored text in the GUI instead of plain text?
Thank you!
If you look at your model and think generically, all the classes have a degree of similarity, which you could factor out into a superclass:
package model;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
public abstract class GameObject<T extends GameObject<?>> {
public GameObject(String name) {
setName(name);
}
private final StringProperty name = new SimpleStringProperty();
public final StringProperty nameProperty() {
return this.name;
}
public final String getName() {
return this.nameProperty().get();
}
public final void setName(final String name) {
this.nameProperty().set(name);
}
private final ObservableList<T> items = FXCollections.observableArrayList();
public ObservableList<T> getItems() {
return items ;
}
public abstract void createAndAddChild(String name);
}
The type parameter T here represents the type of "child" objects. So your Account class (whose child type is GameCharacter - don't name classes the same as anything in java.lang, btw...) looks like
package model;
public class Account extends GameObject<GameCharacter> {
public Account(String name) {
super(name);
}
#Override
public void createAndAddChild(String name) {
getItems().add(new GameCharacter(name));
}
}
and similarly all the way down the hierarchy. I'd define an Information class (even though it just has a name) to make everything fit the structure, so:
package model;
public class Item extends GameObject<Information> {
public Item(String name) {
super(name);
}
#Override
public void createAndAddChild(String name) {
getItems().add(new Information(name));
}
}
and, since Information has no children, its child list is just an empty list:
package model;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
public class Information extends GameObject<GameObject<?>> {
public Information(String name) {
super(name);
}
#Override
public ObservableList<GameObject<?>> getItems() {
return FXCollections.emptyObservableList();
}
#Override
public void createAndAddChild(String name) {
throw new IllegalStateException("Information has no child items");
}
}
Now every item in your tree is a GameObject<?>, so you can basically build a TreeView<GameObject<?>>. The tricky part is that your tree items need to reflect the structure already built in the model. Since you have observable lists there, you can do this with listeners on the lists.
You can use a cell factory on the tree to customize the appearance of the cells displaying the TreeItems. If you want a different appearance for each type of item, I'd recommend defining the styles in an external CSS class, and setting a CSS PseudoClass on the cell corresponding to the type of item. If you use some naming convention (I have that the pseudo-class name is the lower case version of the class name), it can be quite slick to do that. Here's a fairly simple example:
package ui;
import static java.util.stream.Collectors.toList;
import java.util.Arrays;
import java.util.List;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.css.PseudoClass;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import model.Account;
import model.GameCharacter;
import model.GameObject;
import model.Information;
import model.Item;
public class Tree {
private final TreeView<GameObject<?>> treeView ;
private final List<Class<? extends GameObject<?>>> itemTypes = Arrays.asList(
Account.class, GameCharacter.class, Item.class, Information.class
);
public Tree(ObservableList<Account> accounts) {
treeView = new TreeView<>();
GameObject<?> root = new GameObject<Account>("") {
#Override
public ObservableList<Account> getItems() {
return accounts ;
}
#Override
public void createAndAddChild(String name) {
getItems().add(new Account(name));
}
};
TreeItem<GameObject<?>> treeRoot = createItem(root);
treeView.setRoot(treeRoot);
treeView.setShowRoot(false);
treeView.setCellFactory(tv -> {
TreeCell<GameObject<?>> cell = new TreeCell<GameObject<?>>() {
#Override
protected void updateItem(GameObject<?> item, boolean empty) {
super.updateItem(item, empty);
textProperty().unbind();
if (empty) {
setText(null);
itemTypes.stream().map(Tree.this::asPseudoClass)
.forEach(pc -> pseudoClassStateChanged(pc, false));
} else {
textProperty().bind(item.nameProperty());
PseudoClass itemPC = asPseudoClass(item.getClass());
itemTypes.stream().map(Tree.this::asPseudoClass)
.forEach(pc -> pseudoClassStateChanged(pc, itemPC.equals(pc)));
}
}
};
cell.hoverProperty().addListener((obs, wasHovered, isNowHovered) -> {
if (isNowHovered && (! cell.isEmpty())) {
System.out.println("Mouse hover on "+cell.getItem().getName());
}
});
return cell ;
}
}
public TreeView<GameObject<?>> getTreeView() {
return treeView ;
}
private TreeItem<GameObject<?>> createItem(GameObject<?> object) {
// create tree item with children from game object's list:
TreeItem<GameObject<?>> item = new TreeItem<>(object);
item.setExpanded(true);
item.getChildren().addAll(object.getItems().stream().map(this::createItem).collect(toList()));
// update tree item's children list if game object's list changes:
object.getItems().addListener((Change<? extends GameObject<?>> c) -> {
while (c.next()) {
if (c.wasAdded()) {
item.getChildren().addAll(c.getAddedSubList().stream().map(this::createItem).collect(toList()));
}
if (c.wasRemoved()) {
item.getChildren().removeIf(treeItem -> c.getRemoved().contains(treeItem.getValue()));
}
}
});
return item ;
}
private PseudoClass asPseudoClass(Class<?> clz) {
return PseudoClass.getPseudoClass(clz.getSimpleName().toLowerCase());
}
}
Quick test, which works, but note you probably need to test more of the functionality:
package application;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
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.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import model.Account;
import model.GameCharacter;
import model.GameObject;
import model.Information;
import model.Item;
import ui.Tree;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Tree tree = new Tree(createAccounts());
TreeView<GameObject<?>> treeView = tree.getTreeView();
TextField addField = new TextField();
Button addButton = new Button("Add");
EventHandler<ActionEvent> addHandler = e -> {
TreeItem<GameObject<?>> selected = treeView
.getSelectionModel()
.getSelectedItem();
if (selected != null) {
selected.getValue().createAndAddChild(addField.getText());
addField.clear();
}
};
addField.setOnAction(addHandler);
addButton.setOnAction(addHandler);
addButton.disableProperty().bind(Bindings.createBooleanBinding(() -> {
TreeItem<GameObject<?>> selected = treeView.getSelectionModel().getSelectedItem() ;
return selected == null || selected.getValue() instanceof Information ;
}, treeView.getSelectionModel().selectedItemProperty()));
Button deleteButton = new Button("Delete");
deleteButton.setOnAction(e -> {
TreeItem<GameObject<?>> selected = treeView.getSelectionModel().getSelectedItem() ;
TreeItem<GameObject<?>> parent = selected.getParent() ;
parent.getValue().getItems().remove(selected.getValue());
});
deleteButton.disableProperty().bind(treeView.getSelectionModel().selectedItemProperty().isNull());
HBox controls = new HBox(5, addField, addButton, deleteButton);
controls.setPadding(new Insets(5));
controls.setAlignment(Pos.CENTER);
BorderPane root = new BorderPane(treeView);
root.setBottom(controls);
Scene scene = new Scene(root, 600, 600);
scene.getStylesheets().add(getClass().getResource("/ui/style/style.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
private ObservableList<Account> createAccounts() {
Account jake = new Account("Jake");
Account mark = new Account("Mark");
Account freshAcc = new Account("Fresh Account");
Account marksAltAcc = new Account("Mark's alternative account");
Account jeffrey = new Account("Jeffrey");
GameCharacter jakesChar = new GameCharacter("Jakes character");
Item amazingSword = new Item("Amazing Sword");
Item brokenBow = new Item("Broken Bow");
Item junkMetal = new Item("Junk Metal");
GameCharacter myChar = new GameCharacter("Me");
Item godlyAxe = new Item("Godly Axe");
GameCharacter level = new GameCharacter("I'll level this I promise");
GameCharacter jeff = new GameCharacter("Jeff");
Item superGun = new Item("Super Gun");
Item superGunScope = new Item("Super Gun Scope");
jake.getItems().add(jakesChar);
mark.getItems().add(myChar);
marksAltAcc.getItems().add(level);
jeffrey.getItems().add(jeff);
jakesChar.getItems().addAll(amazingSword, brokenBow, junkMetal);
myChar.getItems().add(godlyAxe);
jeff.getItems().addAll(superGun, superGunScope);
return FXCollections.observableArrayList(jake, mark, freshAcc, marksAltAcc, jeffrey);
}
}
and the CSS as an example:
.tree-cell, .tree-cell:hover:empty {
-fx-background-color: -fx-background ;
-fx-background: -fx-control-inner-background ;
}
.tree-cell:hover {
-fx-background-color: crimson, -fx-background ;
-fx-background-insets: 0, 1;
}
.tree-cell:account {
-fx-background: lightsalmon ;
}
.tree-cell:gamecharacter {
-fx-background: bisque ;
}
.tree-cell:item {
-fx-background: antiquewhite ;
}
.tree-cell:selected {
-fx-background: crimson ;
}

How can I make some nodes bold in a javafx TreeView?

How can I display the names of nodes with children bold in a javax TreeView? (Leave nodes should be displayed non-bold)
Use a cell factory on the tree that sets the state of a CSS pseudoclass on the tree cell it creates, according to whether the tree item displayed is a leaf or not. Then you can use an external css file that styles the leaf nodes and non-leaf nodes any way you like.
Example:
import javafx.application.Application;
import javafx.css.PseudoClass;
import javafx.scene.Scene;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class BoldNonLeafNodes extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
final BorderPane uiRoot = new BorderPane();
TreeItem<Integer> root = createTreeItem(1);
final TreeView<Integer> tree = new TreeView<>(root);
PseudoClass leaf = PseudoClass.getPseudoClass("leaf");
tree.setCellFactory(tv -> {
TreeCell<Integer> cell = new TreeCell<>();
cell.itemProperty().addListener((obs, oldValue, newValue) -> {
if (newValue == null) {
cell.setText("");
} else {
cell.setText(newValue.toString());
}
});
cell.treeItemProperty().addListener((obs, oldTreeItem, newTreeItem) ->
cell.pseudoClassStateChanged(leaf, newTreeItem != null && newTreeItem.isLeaf()));
return cell ;
});
uiRoot.setCenter(tree);
final Scene scene = new Scene(uiRoot);
scene.getStylesheets().add("bold-non-leaf-nodes.css");
primaryStage.setScene(scene);
primaryStage.setTitle(getClass().getSimpleName());
primaryStage.show();
}
private TreeItem<Integer> createTreeItem(int value) {
TreeItem<Integer> item = new TreeItem<>(value);
if (value < 10000) {
for (int i=0; i<10; i++) {
item.getChildren().add(createTreeItem(10*value+i));
}
}
return item ;
}
public static void main(String[] args) {
launch(args);
}
}
bold-non-leaf-nodes.css:
.tree-cell {
-fx-font-weight: bold ;
}
.tree-cell:leaf {
-fx-font-weight: normal ;
}

Categories