I am using a TableView which is populated by my model.
I want to use a spinner control in one column.
I am able to create the spinner in the cells in the desired column,
but I am struggling to bind the spinners value to the models property.
This is the fxml
<ScrollPane>
<content>
<TableView prefHeight="525.0" prefWidth="814.0">
<columns>
<TableColumn prefWidth="75.0">
<cellValueFactory><PropertyValueFactory property="state"/></cellValueFactory>
</TableColumn>
<TableColumn prefWidth="75.0" text="Side">
<cellValueFactory><PropertyValueFactory property="side"/></cellValueFactory>
</TableColumn>
<TableColumn prefWidth="75.0" text="Source">
<cellValueFactory><PropertyValueFactory property="sourceContract"/></cellValueFactory>
</TableColumn>
<TableColumn prefWidth="75.0" text="Reference">
<cellValueFactory><PropertyValueFactory property="referenceContract"/></cellValueFactory>
</TableColumn>
<TableColumn prefWidth="75.0" text="Destination">
<cellValueFactory><PropertyValueFactory property="destinationContract"/></cellValueFactory>
</TableColumn>
<TableColumn prefWidth="75.0" text="Margin" editable="true">
<cellFactory><SpinnerTableCellFactory /></cellFactory>
<cellValueFactory><PropertyValueFactory property="margin"/></cellValueFactory>
</TableColumn>
<TableColumn prefWidth="75.0" text="Bot">
<cellValueFactory><PropertyValueFactory property="bot"/></cellValueFactory>
</TableColumn>
<TableColumn prefWidth="75.0" text="Price">
<cellValueFactory><PropertyValueFactory property="price"/></cellValueFactory>
</TableColumn>
<TableColumn prefWidth="75.0" text="Volume">
<cellValueFactory><PropertyValueFactory property="volume"/></cellValueFactory>
</TableColumn>
</columns>
<items>
<FXCollections fx:factory="observableArrayList">
<GridRowModel state="false" side="BID" sourceContract="s01" referenceContract="" destinationContract="d01" margin="0" bot="MinMax" price="15.125" volume="0" />
<GridRowModel state="false" side="ASK" sourceContract="s02" referenceContract="" destinationContract="d02" margin="0" bot="MinMax" price="15.125" volume="0" />
</FXCollections>
</items>
</TableView>
</content>
</ScrollPane>
And this is the SpinnerTableCellFactory
public class SpinnerTableCellFactory<S, T> implements Callback<TableColumn<S, Double>, TableCell<S, Double>> {
#Override
public TableCell<S, Double> call(TableColumn<S, Double> param) {
return new TableCell<S, Double>() {
Spinner<Double> spinner = new Spinner<>(0d, 1d, 0d, 0.025d);
protected void updateItem(Double item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
if (isEditing()) {
setText(null);
setGraphic(null);
} else {
spinner.getValueFactory().setValue(getItem());
setText(null);
setGraphic(spinner);
}
}
};
};
}
}
When the fxml is loaded the updateItem method is called with the default values from the fxml for the margin property. I can see and use the spinner in the cells. But how do I pass any new spinner value back to the margin property in the GridRowModel object?
Just register a listener with the spinner's valueProperty():
public class SpinnerTableCellFactory<S, T> implements Callback<TableColumn<S, Double>, TableCell<S, Double>> {
#Override
public TableCell<S, Double> call(TableColumn<S, Double> param) {
return new TableCell<S, Double>() {
Spinner<Double> spinner = new Spinner<>(0d, 1d, 0d, 0.025d);
{
spinner.valueProperty().addListener((obs, oldValue, newValue) -> {
ObservableValue<Double> value = getTableColumn().getCellObservableValue(getIndex());
if (value instanceof WritableValue) {
((WritableValue<Double>)value).setValue(newValue);
}
});
}
protected void updateItem(Double item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
if (isEditing()) {
setText(null);
setGraphic(null);
} else {
spinner.getValueFactory().setValue(getItem());
setText(null);
setGraphic(spinner);
}
}
};
};
}
}
I'm assuming here that your model class GridRowModel follows the JavaFX properties pattern.
If you don't use JavaFX Properties, or want to avoid the cast, you can give the cell factory a BiConsumer<GridRowModel, Double> to process updates:
public class SpinnerTableCellFactory<S, T> implements Callback<TableColumn<S, Double>, TableCell<S, Double>> {
private BiConsumer<S, Double> updater = null ;
public void setUpdater(BiConsumer<S, Double> updater) {
this.updater = updater ;
}
#Override
public TableCell<S, Double> call(TableColumn<S, Double> param) {
return new TableCell<S, Double>() {
Spinner<Double> spinner = new Spinner<>(0d, 1d, 0d, 0.025d);
{
spinner.valueProperty().addListener((obs, oldValue, newValue) -> {
if (updater != null) {
updater.accept(getTableView().getItems().get(getIndex()), newValue);
}
});
}
protected void updateItem(Double item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
if (isEditing()) {
setText(null);
setGraphic(null);
} else {
spinner.getValueFactory().setValue(getItem());
setText(null);
setGraphic(spinner);
}
}
};
};
}
}
Then you can add a fx:id to your factory:
<cellFactory><SpinnerTableCellFactory fx:id="marginCellFactory" /></cellFactory>
and in your controller do:
#FXML
private SpinnerTableCellFactory<GridRowModel, Double> marginCellFactory ;
public void initialize() {
marginCellFactory.setUpdater(GridRowModel::setMargin);
}
Related
In my project I have a TreeTableView with 5 columns.
I need a different context Menu for every columns.
I have created a ContextMenu in Source Builder for every columns (in the example you see only one for "value" column), but the menĂ¹ appears only with right click on column header, but I need the menĂ¹ appears only on right click on cell value
<TreeTableView fx:id="valueTable" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<columns>
<TreeTableColumn fx:id="context" prefWidth="483.20001524686813" sortable="false" text="Context" />
<TreeTableColumn fx:id="tag" minWidth="50.0" prefWidth="90.0" sortable="false" text="Tag" />
<TreeTableColumn fx:id="offset" minWidth="50.0" prefWidth="90.0" sortable="false" text="OffSet" />
<TreeTableColumn fx:id="lenght" minWidth="50.0" prefWidth="90.0" sortable="false" text="Lenght" />
<TreeTableColumn fx:id="value" prefWidth="367.99993896484375" sortable="false" text="Value">
<contextMenu>
<ContextMenu fx:id="contextMenuValue" >
<items>
<MenuItem mnemonicParsing="false" text="Action 1" />
<MenuItem mnemonicParsing="false" text="Action 2" />
<MenuItem mnemonicParsing="false" text="Action 3" />
<MenuItem mnemonicParsing="false" text="Action 4" />
</items>
</ContextMenu>
</contextMenu>
</TreeTableColumn>
</columns>
</TreeTableView>
I try to add this code for check the MouseClick event, but event is intercepted only on TreeTableView "valueTable" and not on TreeTableColumn "value".
value.addEventHandler(MouseEvent.MOUSE_CLICKED, (MouseEvent e) -> {
System.out.println("Work Cell");
});
valueTable.addEventHandler(MouseEvent.MOUSE_CLICKED, (MouseEvent e) -> {
System.out.println("Work Table");
});
Can you help me?
Regard.
You can do it by implementing a custom TreeTableCell then add the ContextMenu to the cell instead of the column like:
public class Controller implements Initializable {
#FXML
private TreeTableView<MyModel> table;
#FXML
private TreeTableColumn<MyModel, String> first;
#FXML
private TreeTableColumn<MyModel, Boolean> second;
#Override
public void initialize(URL location, ResourceBundle resources) {
first.setCellValueFactory(data -> data.getValue().getValue().nameProperty());
first.setCellFactory(cell -> new MyCell());
second.setCellValueFactory(data -> data.getValue().getValue().selectedProperty());
MyModel john = new MyModel("John");
MyModel andrew = new MyModel("Andrew");
table.setRoot(new TreeItem<>());
table.setShowRoot(false);
table.getRoot().getChildren().add(new TreeItem<>(john));
table.getRoot().getChildren().add(new TreeItem<>(andrew));
}
private class MyModel {
private StringProperty name;
private BooleanProperty selected;
MyModel(String name) {
this.name = new SimpleStringProperty(name);
this.selected = new SimpleBooleanProperty(false);
}
StringProperty nameProperty() {
return name;
}
BooleanProperty selectedProperty() {
return selected;
}
}
private class MyMenu extends ContextMenu {
MyMenu() {
getItems().add(new MenuItem("Test"));
getItems().add(new MenuItem("Item"));
}
}
private class MyCell extends TreeTableCell<MyModel, String> {
MyCell() {
// Here you can set the same menu for each cell. Then the column is having the same cell for every row
setContextMenu(new MyMenu());
}
// Overridden just to show the text of the cell.
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
} else {
contextMenuProperty().bind(Bindings
// here you can define your own condition
.when(getTreeTableRow().getTreeItem().getValue().selectedProperty())
.then(new MyMenu())
.otherwise((MyMenu) null));
setText(item);
}
}
}
}
Of course you can set a different menu for the second column same as it is set for the first one.
I have resolved with
value.setCellFactory(tc -> {
TreeTableCell<MyModel, String> cell = new TreeTableCell<MyModel, String>() {
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty) ;
setText(empty ? null : item);
}
};
cell.setOnMouseClicked(e -> {
if (! cell.isEmpty()) {
String userId = cell.getItem();
System.out.println("Work!");
}
});
return cell ;
});
This question already has answers here:
Javafx tableview not showing data in all columns
(3 answers)
Closed 5 years ago.
javaFX: TableView's cellvalue is not enough to display in columns i can't solved, The following code executes, but the column is showing anything.
and display is like picture pict
this is Ligne_Commande class :
package pfe;
public class Ligne_Commande {
private int n_liv1;
private String des_art1;
private float prix_vent1;
private int qte_com1;
public Ligne_Commande(){
super();
}
public Ligne_Commande(String des_art, int qte_com, float prix_vent){
super();
this.des_art1= des_art;
this.prix_vent1= prix_vent;
this.qte_com1= qte_com;
}
public void setN_liv1(int n_liv) {
this.n_liv1 = n_liv;
}
public void setN_art1(String des_art) {
this.des_art1 = des_art;
}
public void setPrix_vent1(float prix_vent) {
this.prix_vent1 = prix_vent;
}
public void setQte_com1(int qte_com) {
this.qte_com1 = qte_com;
}
public int getN_liv1() {
return n_liv1;
}
public String getN_art1() {
return des_art1;
}
public float getPrix_vent1() {
return prix_vent1;
}
public int getQte_com1() {
return qte_com1;
}
}
and FXML controller :
#FXML
private TableView<Ligne_Commande> tableview_art_qte;
#FXML
private TableColumn<Ligne_Commande, String> col_art_commande;
#FXML
private TableColumn<Ligne_Commande, Integer> col_qte_commande;
#FXML
private TableColumn<Ligne_Commande, Float> col_prix_vent;
#Override
public void initialize(URL url, ResourceBundle rb) {
ObservableList<Ligne_Commande> data = FXCollections.observableArrayList();
data.add(new Ligne_Commande("pommme", 100, 125));
col_art_commande.setCellValueFactory(new PropertyValueFactory<Ligne_Commande, String>("des_art1"));
col_qte_commande.setCellValueFactory(new PropertyValueFactory<Ligne_Commande, Integer>("qte_com1"));
col_prix_vent.setCellValueFactory(new PropertyValueFactory<Ligne_Commande, Float>("prix_vent1"));
tableview_art_qte.setItems(data);
}
FXML file :
<TableView fx:id="tableview_art_qte" editable="true" prefHeight="381.0" prefWidth="230.0" GridPane.columnIndex="2" GridPane.rowIndex="3">
<columns>
<TableColumn fx:id="col_art_commande" prefWidth="75.0" text="Article Commande" />
<TableColumn fx:id="col_qte_commande" maxWidth="2500.0" prefWidth="75.0" text="Qte Commande" />
<TableColumn fx:id="col_prix_vent" maxWidth="3000.0" prefWidth="75.0" text="Prix Vent" />
</columns>
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
</columnResizePolicy>
</TableView>
Your getter/setter methods getN_art1 and setN_art1 should be named getDes_art1 and setDes_art1, respectively. PropertyValueFactory<> searches for getters named getNameOfProperty.
See PropertyValueFactory<> documentation for details.
I want to make TableViewCell using only fxml. How can I do it.
Now I have a model class DuplicateFileInfo
class DuplicateFileInfo(var id: Long, var path: String, var editableField: String?) {}
And I have TableView
<TableView AnchorPane.bottomAnchor="50.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"
editable="true"
layoutX="121.0" layoutY="6.0" fx:id="duplicatesList">
<columns>
<TableColumn prefWidth="300.0" text="%file.filename" fx:id="fileNameColumn" editable="false">
<cellValueFactory>
<PropertyValueFactory property="path" />
</cellValueFactory>
</TableColumn>
<TableColumn prefWidth="150.0" text="%file.EditableField" fx:id="editableColumn">
<cellValueFactory>
<PropertyValueFactory property="editableField" />
</cellValueFactory>
<cellFactory>
<TextFieldTableCell fx:factory="forTableColumn" />
</cellFactory>
</TableColumn>
</columns>
</TableView>
In this case I have editable table view. But the value doesn't set to model after editing is finish.
Is it possible to make this work without codding?
Thanks for James_D
I could get the result.
The model class with kotlin should be like this
class DuplicateFileInfo(id: Long, path: String, shouldBeDeleted: Boolean) {
private val id: LongProperty
private val path: StringProperty
private val shouldBeDeleted: BooleanProperty
init {
this.id = SimpleLongProperty(id)
this.path = SimpleStringProperty(path)
this.shouldBeDeleted = SimpleBooleanProperty(shouldBeDeleted)
}
fun getId(): Long {
return id.get()
}
fun idProperty(): LongProperty {
return id
}
fun setId(id: Long) {
this.id.set(id)
}
fun getPath(): String {
return path.get()
}
fun pathProperty(): StringProperty {
return path
}
fun setPath(path: String) {
this.path.set(path)
}
var isShouldBeDeleted: Boolean
get() = shouldBeDeleted.get()
set(shouldBeDeleted) = this.shouldBeDeleted.set(shouldBeDeleted)
fun shouldBeDeletedProperty(): BooleanProperty {
return shouldBeDeleted
}
override fun toString(): String {
val sb = StringBuffer("DuplicateFileInfo{")
sb.append("id=").append(id.get())
sb.append(", path=").append(path.get())
sb.append(", shouldBeDeleted=").append(shouldBeDeleted.get())
sb.append('}')
return sb.toString()
}
}
I have this TableView
<TableView fx:id="tableView">
<columns>
<TableColumn prefWidth="220.0" text="Source">
<cellValueFactory>
<PropertyValueFactory property="sourceContract" />
</cellValueFactory>
</TableColumn>
</columns>
<items>
<FXCollections fx:factory="observableArrayList">
<GridRowModel sourceContract="some contract" />
</FXCollections>
</items>
</TableView>
and these classes
public class GridRowModel {
private ObjectProperty<ContractConfig> sourceContract = new SimpleObjectProperty<>();
public GridRowModel() {
}
public ObjectProperty<ContractConfig> sourceContractProperty() {
return sourceContract;
}
public ContractConfig getSourceContract() {
return sourceContract.get();
}
public void setSourceContract(ContractConfig sourceContract) {
this.sourceContract.set(sourceContract);
}
}
public class ContractConfig {
private String name;
private String userFriendlyName;
public ContractConfig() {
}
public ContractConfig(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public void setUserFriendlyName(String userFriendlyName) {
this.userFriendlyName = userFriendlyName;
}
public String getName() {
return name;
}
public String getUserFriendlyName() {
return userFriendlyName;
}
}
I get this obvious error:
Caused by: java.lang.IllegalArgumentException: Unable to coerce some contract to class com.ui.util.ContractConfig.
at com.sun.javafx.fxml.BeanAdapter.coerce(BeanAdapter.java:496)
at com.sun.javafx.fxml.BeanAdapter.put(BeanAdapter.java:258)
at com.sun.javafx.fxml.BeanAdapter.put(BeanAdapter.java:54)
I also tried this
public void setSourceContract(String sourceContract) {
ContractConfig cc = new ContractConfig();
cc.setUserFriendlyName(sourceContract);
this.sourceContract.set(cc);
}
But I get this error
Caused by: com.sun.javafx.fxml.PropertyNotFoundException: Property "sourceContract" does not exist or is read-only.
at com.sun.javafx.fxml.BeanAdapter.put(BeanAdapter.java:253)
at com.sun.javafx.fxml.BeanAdapter.put(BeanAdapter.java:54)
at javafx.fxml.FXMLLoader$Element.applyProperty(FXMLLoader.java:512)
Is it possible to use ObjectProperty with FXML values and if so, how can I use my ContractConfig object in the FXML?
You use the wrong fxml code for the class structure you've created. It should look like this instead:
<GridRowModel>
<sourceContract>
<ContractConfig name="some contract"/>
</sourceContract>
</GridRowModel>
You can also add a constructor with #NamedArg to GridRowModel and use
<GridRowModel sourceContract="some contract" />
private final ObjectProperty<ContractConfig> sourceContract;
private GridRowModel(ContractConfig sourceContract) {
this.sourceContract = new SimpleObjectProperty<>(sourceContract);
}
public GridRowModel() {
this((ContractConfig) null);
}
public GridRowModel(#NamedArg("sourceContract") String sourceContract) {
this(new ContractConfig(sourceContract));
}
I want to display data in tableview's column with custom rendering. I tried to adapt this tuto to my need.
Problem :
In the code example below, when I use the setCellFactory() method, the data in the corresponding column isn't displayed.
You can comment or uncomment the delimited section to see what happen in controller class.
Main class
public class CellFactory extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
AnchorPane root = FXMLLoader.load(CellFactory.class.getResource("CellFactory_Layout.fxml"));
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.setTitle("CellFactory EXAMPLE");
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Model class
public class Fruit {
private final SimpleStringProperty name;
private final SimpleIntegerProperty weight;
public Fruit(String name, int weight){
this.name = new SimpleStringProperty(name);
this.weight = new SimpleIntegerProperty(weight);
}
public String getName() {return this.name.get();}
public void setName(String v) {this.name.set(v);}
public SimpleStringProperty nameProperty() {return this.name;}
public int getWeight() {return this.weight.get();}
public void setWeight(int v) {this.weight.set(v);}
public SimpleIntegerProperty weightProperty() {return this.weight;}
}
Controller class
public class CellFactory_Controller implements Initializable {
#FXML private TableView<Fruit> fruit_tbl;
#FXML private TableColumn<Fruit, String> name_cln;
#FXML private TableColumn<Fruit, Integer> weight_cln;
// array with table data
final ObservableList<Fruit> data = FXCollections.observableArrayList();
public CellFactory_Controller() {
// some data
this.data.add(new Fruit("banana", 120));
this.data.add(new Fruit("apple", 150));
this.data.add(new Fruit("coconut", 500));
this.data.add(new Fruit("orange", 200));
}
#Override
public void initialize(URL location, ResourceBundle resources) {
this.name_cln.setCellValueFactory(new PropertyValueFactory<>("name"));
this.weight_cln.setCellValueFactory(new PropertyValueFactory<>("weight"));
this.weight_cln.setCellValueFactory(cellData -> cellData.getValue().weightProperty().asObject());
// comment or uncomment to see what happen
///////////////////////////////////////////////////////////////////////
this.weight_cln.setCellFactory(column -> new TableCell<Fruit, Integer>() {
#Override
protected void updateItem(Integer item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);
setStyle("");
} else {
if (item < 10) {
setTextFill(Color.CHOCOLATE);
} else {
setTextFill(Color.BLACK);
setStyle("");
}
}
}
});
///////////////////////////////////////////////////////////////////////
this.fruit_tbl.setItems(this.data);
}
}
FXML
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="CellFactory.CellFactory_Controller">
<children>
<TableView fx:id="fruit_tbl" layoutX="189.0" layoutY="93.0" prefHeight="400.0" prefWidth="600.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columns>
<TableColumn fx:id="name_cln" prefWidth="471.0" text="FRUIT" />
<TableColumn fx:id="weight_cln" prefWidth="75.0" text="WEIGHT" />
</columns>
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
</columnResizePolicy>
</TableView>
</children>
</AnchorPane>
Question :
From my code example, how can I use custom cell renderer properly (with int data type) to display my data ?
you forgot to add the following statement:
setText(String.valueOf(item));
So, your setCellFactory() method should look like the following:
this.weight_cln.setCellFactory(column -> new TableCell<Fruit, Integer>() {
#Override
protected void updateItem(Integer item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);
setStyle("");
} else {
setText(String.valueOf(item));
if (item < 200) {
setTextFill(Color.CHOCOLATE);
} else {
setTextFill(Color.BLACK);
}
}
}
});