JavaFX Observable List only one property with value true - javafx

I would like to ask you, there is the simple way to add constraint to Observable list that are only one value true of property.
For example:
I Have an observable list of Persons. The class person has boolean Property "isKing" and string property "guild". It can be only one king for each guild.
Sure, I can ( and I know how) add an listener to change value of this property for others persons if new king will be added. But there is other way ??
( there is no database in local aplication to manage data integrations...)

It doesn't seem like such a property should be part of the elements of the list, but instead should be just an external value. In the example you suggest, you could create a separate class that encapsulated your list and the king:
public class Guild {
private final ReadOnlyObjectWrapper<Person> king = new ReadOnlyObjectWrapper<>();
private final ObservableList<Person> members = FXCollections.observableArrayList();
public ObservableList<Person> getMembers() {
return members ;
}
public ReadOnlyObjectProperty<Person> kingProperty() {
return king.getReadOnlyProperty();
}
public Person getKing() {
return kingProperty().get();
}
public void setKing(Person king) {
if (! population.contains(king)) {
throw new IllegalArgumentException("king must be a member of the guild");
}
this.king.set(king);
}
}
If the situation is more complex (e.g. a single population and multiple guilds, each with a king) the same basic idea should be feasible: define a model class that encapsulates whatever data structures you need, for example an ObservableList<Person> population and an ObservableMap<String, Person> kings, the latter keeping a single king for each guild.
Here is a more complex example:
Person.java:
package model;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Person {
private final StringProperty name = new SimpleStringProperty();
private final StringProperty guild = new SimpleStringProperty();
public Person(String name, String guild) {
setName(name);
setGuild(guild);
}
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);
}
public final StringProperty guildProperty() {
return this.guild;
}
public final String getGuild() {
return this.guildProperty().get();
}
public final void setGuild(final String guild) {
this.guildProperty().set(guild);
}
}
DataModel.java:
package model;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.MapChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.collections.ObservableMap;
public class DataModel {
private final ObservableMap<String, Person> kings = FXCollections.observableHashMap() ;
private final ObservableList<Person> population = FXCollections.observableArrayList() ;
public DataModel() {
// if a current king switches guild, remove them as king:
ChangeListener<String> guildListener = (obs, oldGuild, newGuild) ->
kings.remove(oldGuild);
kings.addListener((Change<? extends String, ? extends Person> c) -> {
if (c.wasAdded()) {
c.getValueAdded().guildProperty().addListener(guildListener);
}
if (c.wasRemoved()) {
c.getValueRemoved().guildProperty().removeListener(guildListener);
}
});
}
public ObservableList<Person> getPopulation() {
return population ;
}
public ObservableMap<String, Person> getKings() {
return FXCollections.unmodifiableObservableMap(kings);
}
public void makeKing(Person person) {
kings.put(person.getGuild(), person);
}
}
ConstrainedModelDemo.java:
package ui;
import java.util.List;
import java.util.Random;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.ComboBoxTableCell;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import model.DataModel;
import model.Person;
public class ConstrainedModelDemo extends Application {
#Override
public void start(Stage primaryStage) {
DataModel model = new DataModel();
List<String> guilds = IntStream.rangeClosed(1, 5).mapToObj(i -> "Guild "+i).collect(Collectors.toList());
Random rng = new Random();
List<Person> population = IntStream.rangeClosed(1, 100).mapToObj(i -> new Person("Person "+i, guilds.get(rng.nextInt(guilds.size())))).collect(Collectors.toList());
model.getPopulation().setAll(population);
TableView<Person> table = new TableView<>();
table.setEditable(true);
table.getItems().addAll(population);
table.getColumns().add(column("Name", Person::nameProperty));
TableColumn<Person, String> guildColumn = column("Guild", Person::guildProperty);
guildColumn.setCellFactory(ComboBoxTableCell.forTableColumn(guilds.toArray(new String[guilds.size()])));
table.getColumns().add(guildColumn);
TableColumn<Person, Boolean> kingCol = new TableColumn<>("King");
kingCol.setCellValueFactory(cellData -> {
Person p = cellData.getValue();
return Bindings.createBooleanBinding(() ->
p == model.getKings().get(p.getGuild()), p.guildProperty(), model.getKings());
});
kingCol.setCellFactory(tc -> new TableCell<Person, Boolean>() {
private final CheckBox checkBox = new CheckBox();
{
checkBox.setOnAction(e -> {
if (checkBox.isSelected()) {
model.makeKing(table.getItems().get(getIndex()));
} else {
model.getKings().remove(table.getItems().get(getIndex()).getGuild());
}
});
}
#Override
protected void updateItem(Boolean item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
} else {
checkBox.setSelected(item);
setGraphic(checkBox);
}
}
});
table.getColumns().add(kingCol);
TableView<String> kingTable = new TableView<>();
kingTable.getItems().addAll(guilds);
kingTable.getColumns().add(column("Guild", guild -> new SimpleStringProperty(guild)));
kingTable.getColumns().add(column("King", guild -> Bindings.createStringBinding(() -> {
if (guild == null) return null ;
Person king = model.getKings().get(guild);
if (king == null) return null ;
return king.getName() ;
}, model.getKings())));
BorderPane root = new BorderPane(table);
root.setRight(kingTable);
Scene scene = new Scene(root, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
private <S,T> TableColumn<S,T> column(String title, Function<S, ObservableValue<T>> prop) {
TableColumn<S,T> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> prop.apply(cellData.getValue()));
return col ;
}
public static void main(String[] args) {
launch(args);
}
}

Related

TableView row selection on Mouse Middle Button click

Currently in TableView, the row selection is not happening when we click mouse middle button. The row is selected if we do right or left click. I am trying to have the feature of selecting the row on middle button click.
I am already aware that including an event handler in table row factory can fix this. But I have a custom table view with lot of custom features for my application. And this custom TableView is widely used across my application. My main problem is, I cannot ask each and every table row factory to include this fix.
I am looking for a way to do this on higher level (may be on TableView level) so that the row factory does not need to care of that.
Any ideas/help is highly appreciated.
Below is a quick example of what I am trying to achieve.
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;
public class TableRowSelectionOnMiddleButtonDemo extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
ObservableList<Person> persons = FXCollections.observableArrayList();
for (int i = 0; i < 4; i++) {
persons.add(new Person("First name" + i, "Last Name" + i));
}
CustomTableView<Person> tableView = new CustomTableView<>();
TableColumn<Person, String> fnCol = new TableColumn<>("First Name");
fnCol.setCellValueFactory(param -> param.getValue().firstNameProperty());
TableColumn<Person, String> lnCol = new TableColumn<>("Last Name");
lnCol.setCellValueFactory(param -> param.getValue().lastNameProperty());
tableView.getColumns().addAll(fnCol, lnCol);
tableView.getItems().addAll(persons);
tableView.setRowFactory(new Callback<TableView<Person>, TableRow<Person>>() {
#Override
public TableRow<Person> call(TableView<Person> param) {
return new TableRow<Person>(){
{
/* This will fix my issue, but I dont want each tableView rowfactory to set this behavior.*/
// addEventHandler(MouseEvent.MOUSE_PRESSED,e->{
// getTableView().getSelectionModel().select(getItem());
// });
}
#Override
protected void updateItem(Person item, boolean empty) {
super.updateItem(item, empty);
}
};
}
});
VBox sp = new VBox();
sp.setAlignment(Pos.TOP_LEFT);
sp.getChildren().addAll(tableView);
Scene sc = new Scene(sp);
primaryStage.setScene(sc);
primaryStage.show();
}
public static void main(String... a) {
Application.launch(a);
}
/**
* My custom tableView.
* #param <S>
*/
class CustomTableView<S> extends TableView<S> {
public CustomTableView() {
// A lot of custom behavior is included to this TableView.
}
}
class Person {
private StringProperty firstName = new SimpleStringProperty();
private StringProperty lastName = new SimpleStringProperty();
public Person(String fn, String ln) {
setFirstName(fn);
setLastName(ln);
}
public String getFirstName() {
return firstName.get();
}
public StringProperty firstNameProperty() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName.set(firstName);
}
public String getLastName() {
return lastName.get();
}
public StringProperty lastNameProperty() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName.set(lastName);
}
}
}
The entry point for any global custom (per-control) functionality/behavior is the control's skin. More specifically: user interaction is controlled by the skin's behavior - which unfortunately didn't make it into public scope, such that its access/modification requires to go dirty (as in accessing internal classes/methods, partly via reflection).
Assuming that such access is allowed, the steps to tweak the mouse interaction into reacting the same way for the middle as for the primary button for a TableCell are
implement a custom TableCellSkin
reflectively access its behavior
find the mousePressed handler in the behavior's inputMap
replace the original handler with a custom handler that replaces the mouseEvent coming from the middle button by a mouseEvent coming from the primary button
make the custom TableCellSkin the default by css
Note: the TableRowSkin which is responsible for handling mouseEvents in the free space at the right of the table doesn't separate out the middle button, so currently nothing to do. If that changes in future, simple apply the same trick as for the table cells.
Example:
public class TableRowCustomMouse extends Application {
public static class CustomMouseTableCellSkin<T, S> extends TableCellSkin<T, S> {
EventHandler<MouseEvent> original;
public CustomMouseTableCellSkin(TableCell<T, S> control) {
super(control);
adjustMouseBehavior();
}
private void adjustMouseBehavior() {
// dirty: reflective access to behavior, use your custom reflective accessor
TableCellBehavior<T, S> behavior =
(TableCellBehavior<T, S>) FXUtils.invokeGetFieldValue(TableCellSkin.class, this, "behavior");
InputMap<TableCell<T, S>> inputMap = behavior.getInputMap();
ObservableList<Mapping<?>> mappings = inputMap.getMappings();
List<Mapping<?>> pressedMapping = mappings.stream()
.filter(mapping -> mapping.getEventType() == MouseEvent.MOUSE_PRESSED)
.collect(Collectors.toList());
if (pressedMapping.size() == 1) {
Mapping<?> originalMapping = pressedMapping.get(0);
original = (EventHandler<MouseEvent>) pressedMapping.get(0).getEventHandler();
if (original != null) {
EventHandler<MouseEvent> replaced = this::replaceMouseEvent;
mappings.remove(originalMapping);
mappings.add(new MouseMapping(MouseEvent.MOUSE_PRESSED, replaced));
}
}
}
private void replaceMouseEvent(MouseEvent e) {
MouseEvent replaced = e;
if (e.isMiddleButtonDown()) {
replaced = new MouseEvent(e.getSource(), e.getTarget(), e.getEventType(),
e.getX(), e.getY(),
e.getScreenX(), e.getScreenY(),
MouseButton.PRIMARY,
e.getClickCount(),
e.isShiftDown(), e.isControlDown(), e.isAltDown(), e.isMetaDown(),
true, false, false,
e.isSynthesized(), e.isPopupTrigger(), e.isStillSincePress(),
null
);
}
original.handle(replaced);
}
}
private Parent createContent() {
TableView<Person> table = new TableView<>(Person.persons());
TableColumn<Person, String> first = new TableColumn("First Name");
first.setCellValueFactory(cc -> cc.getValue().firstNameProperty());
TableColumn<Person, String> last = new TableColumn<>("Last Name");
last.setCellValueFactory(cc -> cc.getValue().lastNameProperty());
table.getColumns().addAll(first, last);
BorderPane content = new BorderPane(table);
return content;
}
#Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
// load the default css
stage.getScene().getStylesheets()
.add(getClass().getResource("customtablecellskin.css").toExternalForm());
stage.setTitle(FXUtils.version());
stage.show();
}
public static void main(String[] args) {
launch(args);
}
#SuppressWarnings("unused")
private static final Logger LOG = Logger
.getLogger(TableRowCustomMouse.class.getName());
}
The css with custom skin for TableCell:
.table-cell {
-fx-skin: "<yourpackage>.TableRowCustomMouse$CustomMouseTableCellSkin";
}
Accepted #kleopatra's approach as an answer. However the solution to my question is a bit different to #kleopatra's answer. But the core idea is still the same.
I used the approach to override the doSelect method of TableCellBehavior
#Override
protected void doSelect(double x, double y, MouseButton button, int clickCount, boolean shiftDown, boolean shortcutDown) {
MouseButton btn = button;
if (button == MouseButton.MIDDLE) {
btn = MouseButton.PRIMARY;
}
super.doSelect(x, y, btn, clickCount, shiftDown, shortcutDown);
}
Below is the working demo which solved my issue:
DemoClass:
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;
public class TableRowSelectionOnMiddleButtonDemo extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
ObservableList<Person> persons = FXCollections.observableArrayList();
for (int i = 0; i < 4; i++) {
persons.add(new Person("First name" + i, "Last Name" + i));
}
CustomTableView<Person> tableView = new CustomTableView<>();
TableColumn<Person, String> fnCol = new TableColumn<>("First Name");
fnCol.setCellValueFactory(param -> param.getValue().firstNameProperty());
TableColumn<Person, String> lnCol = new TableColumn<>("Last Name");
lnCol.setCellValueFactory(param -> param.getValue().lastNameProperty());
tableView.getColumns().addAll(fnCol, lnCol);
tableView.getItems().addAll(persons);
tableView.setRowFactory(new Callback<TableView<Person>, TableRow<Person>>() {
#Override
public TableRow<Person> call(TableView<Person> param) {
return new TableRow<Person>() {
{
/* This will fix my issue, but I dont want each tableView rowfactory to set this behavior.*/
// addEventHandler(MouseEvent.MOUSE_PRESSED,e->{
// getTableView().getSelectionModel().select(getItem());
// });
}
#Override
protected void updateItem(Person item, boolean empty) {
super.updateItem(item, empty);
}
};
}
});
VBox sp = new VBox();
sp.setAlignment(Pos.TOP_LEFT);
sp.getChildren().addAll(tableView);
Scene sc = new Scene(sp);
sc.getStylesheets().add(this.getClass().getResource("tableRowSelectionOnMiddleButton.css").toExternalForm());
primaryStage.setScene(sc);
primaryStage.show();
}
public static void main(String... a) {
Application.launch(a);
}
/**
* My custom tableView.
*
* #param <S>
*/
class CustomTableView<S> extends TableView<S> {
public CustomTableView() {
// A lot of custom behavior is included to this TableView.
}
}
class Person {
private StringProperty firstName = new SimpleStringProperty();
private StringProperty lastName = new SimpleStringProperty();
public Person(String fn, String ln) {
setFirstName(fn);
setLastName(ln);
}
public String getFirstName() {
return firstName.get();
}
public StringProperty firstNameProperty() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName.set(firstName);
}
public String getLastName() {
return lastName.get();
}
public StringProperty lastNameProperty() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName.set(lastName);
}
}
}
CustomTableCellSkin class:
import com.sun.javafx.scene.control.behavior.TableCellBehavior;
import com.sun.javafx.scene.control.skin.TableCellSkinBase;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.input.MouseButton;
public class CustomTableCellSkin<S, T> extends TableCellSkinBase<TableCell<S, T>, TableCellBehavior<S, T>> {
private final TableColumn<S, T> tableColumn;
public CustomTableCellSkin(TableCell<S, T> tableCell) {
super(tableCell, new CustomTableCellBehavior<S, T>(tableCell));
this.tableColumn = tableCell.getTableColumn();
super.init(tableCell);
}
#Override
protected BooleanProperty columnVisibleProperty() {
return tableColumn.visibleProperty();
}
#Override
protected ReadOnlyDoubleProperty columnWidthProperty() {
return tableColumn.widthProperty();
}
}
class CustomTableCellBehavior<S, T> extends TableCellBehavior<S, T> {
public CustomTableCellBehavior(TableCell<S, T> control) {
super(control);
}
#Override
protected void doSelect(double x, double y, MouseButton button, int clickCount, boolean shiftDown, boolean shortcutDown) {
MouseButton btn = button;
if (button == MouseButton.MIDDLE) {
btn = MouseButton.PRIMARY;
}
super.doSelect(x, y, btn, clickCount, shiftDown, shortcutDown);
}
}
tableRowSelectionOnMiddleButton.css
.table-cell {
-fx-skin: "<package>.CustomTableCellSkin";
}

JAVAFX 8 ComboBox and ObservableList

I need a combobox populated through observablelist which contains specific data retrieved from DB. This is my source.
Model
public ObservableList<Bank> listBank = FXCollections.observableArrayList();
public static class Bank {
private final StringProperty id;
private final StringProperty name;
private Bank(
String id,
String name
) {
this.id = new SimpleStringProperty(id);
this.name = new SimpleStringProperty(name);
}
public StringProperty idProperty() { return id; }
public StringProperty nameProperty() { return name; }
}
View
#FXML
private ComboBox comboBank<Bank>;
public final void getBankDataFields() {
comboBank.setItems(model.listBank);
}
comboBank.setButtonCell(new ListCell<Bank>() {
#Override
protected void updateItem(Bank t, boolean bln) {
super.updateItem(t, bln);
if (t != null) {
setText(t.nameProperty().getValue().toUpperCase());
} else {
setText(null);
}
}
});
comboBank.setCellFactory(new Callback<ListView<Bank>, ListCell<Bank>>() {
#Override
public ListCell<Bank> call(ListView<Bank> p) {
return new ListCell<Bank>() {
#Override
protected void updateItem(Bank t, boolean bln) {
super.updateItem(t, bln);
if(t != null){
setText(t.nomeProperty().getValue().toUpperCase());
} else {
setText(null);
}
}
};
}
});
comboBank.valueProperty().addListener((ObservableValue<? extends Bank> observable, Bank oldValue, Bank newValue) -> {
setIdBank(newValue.idProperty().getValue());
});
ComboBox is populated with NAME field and listener is used to get relative ID and pass it to a query for storing data on DB.
Ok, everything seems to work but i have two questions:
When user need to modify this record, i need to get the ID from DB and select the relative NAME in ComboBox. How can i do that?
comboBank.setValue(????);
Is there a better way to achieve this goal? An ObservableMap may substitute the ObservableList?
Thanks in advance.
There is an easier way to what you are trying to achieve. You should use a StringConverter on the ComboBox to display the names for the Bank instances.
comboBox.setConverter(new StringConverter<Bank>() {
#Override
public String toString(Bank object) {
return object.nameProperty().get();
}
#Override
public Bank fromString(String string) {
// Somehow pass id and return bank instance
// If not important, just return null
return null;
}
});
To get selected value i.e. instance of the selected bank, just use :
comboBox.getValue();
MCVE
import javafx.animation.PauseTransition;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
import javafx.util.StringConverter;
import java.util.stream.Collectors;
public class Main extends Application {
#Override
public void start(Stage stage) {
ComboBox<Bank> comboBox = new ComboBox<>();
ObservableList<Bank> items = FXCollections.observableArrayList(
new Bank("1", "A"), new Bank("2", "B"),
new Bank("3", "C"), new Bank("4", "D"));
comboBox.setItems(items);
StringConverter<Bank> converter = new StringConverter<Bank>() {
#Override
public String toString(Bank bank) {
return bank.nameProperty().get();
}
#Override
public Bank fromString(String id) {
return items.stream()
.filter(item -> item.idProperty().get().equals(id))
.collect(Collectors.toList()).get(0);
}
};
comboBox.setConverter(converter);
// Print the name of the Bank that is selected
comboBox.getSelectionModel().selectedItemProperty().addListener((o, ol, nw) -> {
System.out.println(comboBox.getValue().nameProperty().get());
});
// Wait for 3 seconds and select the item with id = 2
PauseTransition pauseTransition = new PauseTransition(Duration.seconds(3));
pauseTransition.setOnFinished(event -> comboBox.getSelectionModel().select(converter.fromString("2")));
pauseTransition.play();
VBox root = new VBox(comboBox);
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 200, 200);
stage.setScene(scene);
stage.show();
}
}

Determine a JavaFX table row details when reusing tableview context menu

I have a requirement similar to this example.
In the EventHandler callback, how do I determine which row was clicked on?
#Override
public void handle(ActionEvent event) {
// how do I get the row details when reusing context menu and handler code?
}
I am sharing the context menu because I have to add a CheckMenuItem who's state is "global" to the table, i.e. if its selected on any row, I want to show it as checked when I click on any other row in the table.
Use a row factory and one context menu per row, as in the question you linked.
For the "global" CheckMenuItem, create a BooleanProperty and bidirectionally bind the CheckMenuItems' selected properties to it.
SSCCE:
import java.util.function.Function;
import java.util.stream.IntStream;
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.CheckMenuItem;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SeparatorMenuItem;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class TableWithContextMenu extends Application {
#Override
public void start(Stage primaryStage) {
TableView<Item> table = new TableView<>();
table.getColumns().add(column("Item", Item::nameProperty));
table.getColumns().add(column("Value", Item::valueProperty));
BooleanProperty globalSelection = new SimpleBooleanProperty();
table.setRowFactory(t -> {
TableRow<Item> row = new TableRow<>();
ContextMenu contextMenu = new ContextMenu();
MenuItem item1 = new MenuItem("Do something");
item1.setOnAction(e -> System.out.println("Do something with "+row.getItem().getName()));
MenuItem item2 = new MenuItem("Do something else");
item2.setOnAction(e -> System.out.println("Do something else with "+row.getItem().getName()));
CheckMenuItem item3 = new CheckMenuItem("Global selection");
item3.selectedProperty().bindBidirectional(globalSelection);
contextMenu.getItems().addAll(item1, item2, new SeparatorMenuItem(), item3);
row.emptyProperty().addListener((obs, wasEmpty, isEmpty) -> {
if (isEmpty) {
row.setContextMenu(null);
} else {
row.setContextMenu(contextMenu);
}
});
return row ;
});
IntStream.rangeClosed(1, 25).mapToObj(i -> new Item("Item "+i, i)).forEach(table.getItems()::add);
primaryStage.setScene(new Scene(new BorderPane(table), 800, 600));
primaryStage.show();
}
private <S,T> TableColumn<S,T> column(String title, Function<S, ObservableValue<T>> property) {
TableColumn<S,T> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
return col ;
}
public static class Item {
private final IntegerProperty value = new SimpleIntegerProperty();
private final StringProperty name = new SimpleStringProperty();
public Item(String name, int value) {
setName(name);
setValue(value);
}
public final IntegerProperty valueProperty() {
return this.value;
}
public final int getValue() {
return this.valueProperty().get();
}
public final void setValue(final int value) {
this.valueProperty().set(value);
}
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);
}
}
public static void main(String[] args) {
launch(args);
}
}

How I can change the style of a specific celldata in javafx?

Good Evening,
I would like to know, how i can change the background color to red of all below 18 year, is possible ?
I'm trying solve this since Monday. Could someone give me some website than explain better than oracle documentation ?
I see a lot of people, still using swing, Should I keep learn about javafx or start study swing ?
obs: sorry for my bad english.
Controller
package tableview;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.AnchorPane;
public class LayoutController implements Initializable {
#Override
public void initialize(URL url, ResourceBundle rb) {
getPerson();
columnFName.setCellValueFactory(celldata -> celldata.getValue().getfName());
columnLName.setCellValueFactory(celldata -> celldata.getValue().getlName());
columnAge.setCellValueFactory(celldata -> celldata.getValue().getAge());
tableView.setItems(person);
}
#FXML
private AnchorPane layout;
//TABLE
#FXML
private TableView<Person> tableView;
#FXML
private TableColumn<Person, String> columnLName;
#FXML
private TableColumn<Person, String> columnFName;
#FXML
private TableColumn<Person, Number> columnAge;
//END
ObservableList person = FXCollections.observableArrayList();
ObservableList getPerson() {
person.add(new Person("John", "Smith", 15));
person.add(new Person("May", "Smith", 18));
person.add(new Person("Sam", "Lucca", 21));
person.add(new Person("Homer", "Simpson", 14));
return person;
}
}
Person class
package tableview;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
public class Person {
private SimpleStringProperty fName, lName;
private SimpleIntegerProperty age;
public Person() {
this("", "", 0);
}
public Person(String fName, String lName, int age) {
this.fName = new SimpleStringProperty(fName);
this.lName = new SimpleStringProperty(lName);
this.age = new SimpleIntegerProperty(age);
}
public SimpleStringProperty getfName() {
return fName;
}
public SimpleStringProperty getlName() {
return lName;
}
public SimpleIntegerProperty getAge() {
return age;
}
}
Set a row factory on your table. You want to observe the itemProperty of the row. The best way to manage the background color is using an external CSS file and setting a CSS pseudoclass if the person represented by the row has age < 18. (You can put this code in your controller's initialize() method.)
PseudoClass minorPseudoClass = PseudoClass.getPseudoClass("minor");
tableView.setRowFactory(tv -> {
TableRow<Person> row = new TableRow<>();
row.itemProperty().addListener((obs, oldPerson, newPerson) -> {
if (newPerson != null) {
row.pseudoClassStateChanged(minorPseudoClass, newPerson.getAge() < 18);
} else {
row.pseudoClassStateChanged(minorPseudoClass, false);
}
});
return row ;
});
Then define an external style sheet with the appropriate style for the pseudoclass you created:
.table-row-cell:minor {
-fx-control-inner-background: red ;
-fx-control-inner-background-alt: #cc0000 ;
}
Note that this assumes the age is fixed for each person in the table. If it has the possibility of changing while the person is displayed, you need to register and deregister listeners with the age property as the row content changes:
tableView.setRowFactory(tv -> {
TableRow<Person> row = new TableRow<>();
ChangeListener<Number> ageListener = (obs, oldValue, newValue) -> {
row.pseudoClassStateChanged(minorPseudoClass, newValue.intValue() < 18);
};
row.itemProperty().addListener((obs, oldPerson, newPerson) -> {
if (oldPerson != null) {
oldPerson.ageProperty().removeListener(ageListener);
}
if (newPerson != null) {
newPerson.ageProperty().addListener(ageListener);
row.pseudoClassStateChanged(minorPseudoClass, newPerson.getAge() < 18);
} else {
row.pseudoClassStateChanged(minorPseudoClass, false);
}
});
return row ;
});
Here is a SSCCE. This doesn't use FXML, but obviously you can do the same thing, creating the rowFactory in the initialize() method in the controller.
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.css.PseudoClass;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class HighlightYoungPeopleTableExample extends Application {
#Override
public void start(Stage primaryStage) {
TableView<Person> tableView = new TableView<>();
TableColumn<Person, String> firstNameColumn = column("First Name", Person::firstNameProperty, 150);
TableColumn<Person, String> lastNameColumn = column("Last Name", Person::lastNameProperty, 150);
TableColumn<Person, Integer> ageColumn = column("Age", person -> person.ageProperty().asObject(), 50);
PseudoClass minorPseudoClass = PseudoClass.getPseudoClass("minor");
tableView.setRowFactory(tv -> {
TableRow<Person> row = new TableRow<>();
ChangeListener<Number> ageListener = (obs, oldValue, newValue) -> {
row.pseudoClassStateChanged(minorPseudoClass, newValue.intValue() < 18);
};
row.itemProperty().addListener((obs, oldPerson, newPerson) -> {
if (oldPerson != null) {
oldPerson.ageProperty().removeListener(ageListener);
}
if (newPerson != null) {
newPerson.ageProperty().addListener(ageListener);
row.pseudoClassStateChanged(minorPseudoClass, newPerson.getAge() < 18);
} else {
row.pseudoClassStateChanged(minorPseudoClass, false);
}
});
return row ;
});
tableView.getColumns().add(firstNameColumn);
tableView.getColumns().add(lastNameColumn);
tableView.getColumns().add(ageColumn);
tableView.getItems().addAll(
new Person("John", "Smith", 15),
new Person("May", "Smith", 18),
new Person("Sam", "Lucca", 21),
new Person("Homer", "Simpson", 14)
);
Scene scene = new Scene(new BorderPane(tableView), 800, 600);
scene.getStylesheets().add("highlight-young-people-table.css");
primaryStage.setScene(scene);
primaryStage.show();
}
private <S,T> TableColumn<S,T> column(String title, Function<S, ObservableValue<T>> property, double width) {
TableColumn<S,T> col = new TableColumn<>(title);
col.setPrefWidth(width);
col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
return col ;
}
public static class Person {
private final StringProperty firstName = new SimpleStringProperty() ;
private final StringProperty lastName = new SimpleStringProperty() ;
private final IntegerProperty age = new SimpleIntegerProperty();
public Person(String firstName, String lastName, int age) {
setFirstName(firstName);
setLastName(lastName);
setAge(age);
}
public final StringProperty firstNameProperty() {
return this.firstName;
}
public final String getFirstName() {
return this.firstNameProperty().get();
}
public final void setFirstName(final String firstName) {
this.firstNameProperty().set(firstName);
}
public final StringProperty lastNameProperty() {
return this.lastName;
}
public final String getLastName() {
return this.lastNameProperty().get();
}
public final void setLastName(final String lastName) {
this.lastNameProperty().set(lastName);
}
public final IntegerProperty ageProperty() {
return this.age;
}
public final int getAge() {
return this.ageProperty().get();
}
public final void setAge(final int age) {
this.ageProperty().set(age);
}
}
public static void main(String[] args) {
launch(args);
}
}
highlight-young-people-table.css
.table-row-cell:minor {
-fx-control-inner-background: red ;
-fx-control-inner-background-alt: #cc0000 ;
}

How to fetch value of selected item in choice box from a table view coloumn in javafx

How can i fetch the value of the selected choice from the choce box in the following table.
column3 has 13 choice box nodes populated using following code.I want to fetch the selected item.
final ObservableList LogLevelList=FXCollections.observableArrayList("FATAL", "ERROR", "WARN", "INFO", "INOUT", "DEBUG");
column3.setCellFactory(new Callback<TableColumn<Feature,String>,TableCell<Feature,String>>(){
#Override
public TableCell<Feature,String> call(TableColumn<Feature,String> param) {
TableCell<Feature,String> cell = new TableCell<Feature,String>(){
#Override
public void updateItem(String item, boolean empty) {
System.out.println("Inside UpdateItem");
ChoiceBox choice = new ChoiceBox(LogLevelList);
choice.getSelectionModel().select(LogLevelList.indexOf(item));
//SETTING ALL THE GRAPHICS COMPONENT FOR CELL
setGraphic(choice);
}
};
return cell;
}
});
Does the predefined ChoiceBoxTableCell do what you need?
column3.setCellFactory(ChoiceBoxTableCell.forTableColumn(logLevelList));
See if this helps:
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.ChoiceBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class TableChoiceBoxTest extends Application {
#Override
public void start(Stage primaryStage) {
final TableView<Feature> table = new TableView<>();
table.setEditable(true);
final TableColumn<Feature, String> nameCol = new TableColumn<>("Name");
nameCol.setCellValueFactory(new PropertyValueFactory<>("name"));
final TableColumn<Feature, String> logLevelCol = new TableColumn<>("Log level");
logLevelCol.setCellValueFactory(new PropertyValueFactory<>("logLevel"));
logLevelCol.setPrefWidth(150);
final ObservableList<String> logLevelList = FXCollections.observableArrayList("FATAL", "ERROR", "WARN", "INFO", "INOUT", "DEBUG");
logLevelCol.setCellFactory(ChoiceBoxTableCell.forTableColumn(logLevelList));
table.getColumns().addAll(nameCol, logLevelCol);
table.getItems().setAll(
IntStream.rangeClosed(1, 20)
.mapToObj(i -> new Feature("Item "+i, "FATAL"))
.collect(Collectors.toList())
);
Button showDataButton = new Button("Dump data");
showDataButton.setOnAction(event -> table.getItems().forEach(System.out::println));
BorderPane root = new BorderPane();
root.setCenter(table);
root.setBottom(showDataButton);
Scene scene = new Scene(root, 400, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static class Feature {
private final StringProperty name ;
private final StringProperty logLevel ;
public Feature(String name, String logLevel) {
this.name = new SimpleStringProperty(this, "name", name);
this.logLevel = new SimpleStringProperty(this, "logLevel", logLevel);
}
public StringProperty nameProperty() {
return name ;
}
public final String getName() {
return name.get();
}
public final void setName(String name) {
this.name.set(name);
}
public StringProperty logLevelProperty() {
return logLevel ;
}
public final String getLogLevel() {
return logLevel.get();
}
public final void setLogLevel(String logLevel) {
this.logLevel.set(logLevel);
}
#Override
public String toString() {
return getName() + ": " + getLogLevel();
}
}
public static void main(String[] args) {
launch(args);
}
}
The provided ChoiceBoxTableCell updates the property of the associated item for you, so there's never any need to get the value from the ChoiceBox; you can just get the value from your model object.
I think there are mistakes in your code. You do not want to display your Choice box in each and every cell of that column (i.e Emptied Row's Cell) and Also you should call super class function.
Now for getting the selected value of ChoiceBox , instead of just displaying your choicebox with the values you will have to save them in some ArrayList or Map or best options is to save inside your Feature class. So that you can finally use
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item,empty);
if(item != null){
ChoiceBox choice = new ChoiceBox(LogLevelList);
choice.getSelectionModel().select(LogLevelList.indexOf(item));
choice.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> ov, String t, String t1) {
//either use : myMap.put(getIndex(),t1);
//or : item.setChoice(t1);
}
});
//SETTING ALL THE GRAPHICS COMPONENT FOR CELL
setGraphic(choice);
}
}
Also for demo of ChoiceBox in TableView there is one blog post for you :http://blog.ngopal.com.np/2011/10/01/tableview-cell-modifiy-in-javafx/

Resources