I have a TableView that uses a ColorPicker to (display/edit) colors in a cell.
The table display the ColorPicker in the desired field, but edits aren't working.
TableColumn<SeriesPreferences, Color> c2 = new TableColumn<SeriesPreferences, Color>("Color");
c2.setCellValueFactory(new PropertyValueFactory<SeriesPreferences, Color>("color"));
c2.setCellFactory(new Callback<TableColumn<SeriesPreferences, Color>,
TableCell<SeriesPreferences, Color>>()
{
#Override
public TableCell<SeriesPreferences, Color>
call(final TableColumn<SeriesPreferences, Color> param)
{
TableCell<SeriesPreferences, Color> cell =
new TableCell<SeriesPreferences, Color>()
{
#Override
public void updateItem(Color c, boolean empty)
{
if(c != null)
{
final ColorPicker cp = new ColorPicker();
cp.setValue(c);
setGraphic(cp);
cp.setOnAction(new EventHandler<javafx.event.ActionEvent>()
{
public void
handle(javafx.event.ActionEvent t)
{
getTableView().edit(getTableRow().getIndex(), param);
commitEdit(cp.getValue());
}
});
}
}
};
return cell;
}
});
c2.setOnEditCommit(new EventHandler<CellEditEvent<SeriesPreferences, Color>>()
{
#Override
public void handle(CellEditEvent<SeriesPreferences, Color> t)
{
((SeriesPreferences) t.getTableView().getItems().get(t.getTablePosition().
getRow())).setColor(t.getNewValue());
}
});
The edit event handler isn't being called when i change the color in the color picker, any ideas?
There's no need to access the JavaFX POJO (or JavaFX Bean) directly if its properties are correctly bound to the table and also it isn't necessary to call anything other than commitEdit.
The answer from Max Beikirch is misleading, because it causes the color picker (and with it the color) to disappear when the table is not in edit mode. It's a workaround to put the table into edit mode, but a bad one. So do this before showing the color picker popup when click on the button:
Write your cell with a color picker like this:
public class ColorTableCell<T> extends TableCell<T, Color> {
private final ColorPicker colorPicker;
public ColorTableCell(TableColumn<T, Color> column) {
this.colorPicker = new ColorPicker();
this.colorPicker.editableProperty().bind(column.editableProperty());
this.colorPicker.disableProperty().bind(column.editableProperty().not());
this.colorPicker.setOnShowing(event -> {
final TableView<T> tableView = getTableView();
tableView.getSelectionModel().select(getTableRow().getIndex());
tableView.edit(tableView.getSelectionModel().getSelectedIndex(), column);
});
this.colorPicker.valueProperty().addListener((observable, oldValue, newValue) -> {
if(isEditing()) {
commitEdit(newValue);
}
});
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
#Override
protected void updateItem(Color item, boolean empty) {
super.updateItem(item, empty);
setText(null);
if(empty) {
setGraphic(null);
} else {
this.colorPicker.setValue(item);
this.setGraphic(this.colorPicker);
}
}
}
If you're on Java 7, replace the lambdas with anonymous inner classes, but it should work as well. Full blog post is here.
I had the same problem for CheckBoxTableCell and DatePickerTableCell and ColorPickerTableCells :-(
I deal it like that: on the events of the controls I get back the POJO objects in use by the "((Inputs)getTableView().getItems().get(getTableRow().getIndex()" and I update similary like is it done in the OnEditCommit method...
So for me it's look like this (update the color):
((Inputs) getTableView().getItems().get(
getTableRow().getIndex())
).setColor(cp.getValue());
Here is example with ColorPickerCell
:
public class ColorPickerTableCell<Inputs> extends TableCell<Inputs, Color>{
private ColorPicker cp;
public ColorPickerTableCell(){
cp = new ColorPicker();
cp.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
commitEdit(cp.getValue());
updateItem(cp.getValue(), isEmpty());
((Inputs) getTableView().getItems().get(
getTableRow().getIndex())
).setColor(cp.getValue());
}
});
setGraphic(cp);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
setEditable(true);
}
#Override
protected void updateItem(Color item, boolean empty) {
super.updateItem(item, empty);
cp.setVisible(!empty);
this.setItem(item);
cp.setValue(item);
}
}
With this simple JavaFX's POJO:
public ObjectProperty<Color> color = new SimpleObjectProperty<Color>();
this.color = new SimpleObjectProperty(color);
public ObjectProperty<Color> colorProperty() {
return color;
}
public void setColor(Color color2) {
color.set(color2);
}
I do not know if it's a good way to achive that but it worked for me... Note that the JavaFX's POJO is only accessible within an "ActionEvent" request (combobox, datepicker, colorpicker, etc..)
Regards,
Well, I investigated that topic a bit as I have had the same problem. I am afraid to say that JavaFX is just unusable.
I took a look at how others implemented their cells and the key was that were all using something that is representable by a string.
Now, it's the way it always is with Java: Do it the Java-way or be left alone in the rain. The docs for JavaFX are extremely bad at the moment, so I had to try until it works.
So: To trigger the editCommit-event, you have to call setContentDisplay(ContentDisplay. TEXT_ONLY) in updateItem(). That works well if want to display your data as string, but fails completely in cases like these, where a colorpicker just does the job.
Alternatively, it might be possible to fire the event manually. But how do you get the table-position? I don't know.
It like Michael Simons said in the comment on the OP. You need to be in edit mode. When creating your own custom cells you can trigger edit mode manually by calling startEdit(); from inside the TableCell.
for example using the focusProperty of your control:
cp.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (newValue) {
startEdit();
}
});
Related
I have tried to research about this, but could not find any examples. The examples I found were regarding normal TableView only. I could only create a JFXTreeTableView with an object and list out String. Any help would be appreciated! Thanks!
I need to put a button or basically any other object than a string into a TreeTableView.
Updated to make it clear on what I wanted it to look like, and the solution is below.
With reference to this post, I was trying to add a Button(Specifically JFXButton) into a TreeTableView(Specifically JFXTreeTableView)
How to add button in JavaFX table view
However, the post only talks about TableView. After analyzing the codes I tried to modify the codes to work with TreeTableView and TreeTableCell instead.
Using the sample codes from JFoenix and modifying it as seen in the codes snippets below, I could load a JFXButton into a JFXTreeTableView. (Also works with normal Button. Just replace the JFXButton to Button)
JFXTreeTableColumn<User, String> settingsColumn = new JFXTreeTableColumn<>("Others");
settingsColumn.setPrefWidth(100);
Callback<TreeTableColumn<User, String>, TreeTableCell<User, String>> cellFactory
= //
new Callback<TreeTableColumn<User, String>, TreeTableCell<User, String>>() {
#Override
public TreeTableCell call(final TreeTableColumn<User, String> param) {
final TreeTableCell<User, String> cell = new TreeTableCell<User, String>() {
final JFXButton btn = new JFXButton("Just Do it");
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
setText(null);
} else {
btn.setButtonType(JFXButton.ButtonType.RAISED);
btn.setOnAction(event -> {
//Button Action here
});
setGraphic(btn);
setText(null);
}
}
};
return cell;
}
};
settingsColumn.setCellFactory(cellFactory);
//Also remember to add the new column in
treeView.getColumns().setAll(deptColumn, ageColumn, empColumn, settingsColumn);
This is the end result:
I am fairly new in JavaFX. I have a table having multiple columns and a refresh button in each row. I am trying to set the old value in the edited cells whenever I click Refresh button. One idea is Passing the old value through a global variable to refresh button and set it. I can get the old value But how can I set that old value? here is my code
String old=null;
public void initialize(URL arg0, ResourceBundle arg1) {
colName.setCellValueFactory(new PropertyValueFactory<ModelBrBuilding,String>("BranchName"));
colName.setCellFactory(TextFieldTableCell.forTableColumn());
colName.setOnEditCommit(
new EventHandler<CellEditEvent<ModelBrBuilding, String>>() {
#Override
public void handle(CellEditEvent<ModelBrBuilding, String> t) {
old= ((ModelBrBuilding) t.getTableView().getItems().get(
t.getTablePosition().getRow())
).getBranchName();
((ModelBrBuilding) t.getTableView().getItems().get(
t.getTablePosition().getRow())
).setBranchName(t.getNewValue());
}
}
);
colAction.setCellFactory(col -> {
Button RefreshButton = new Button("Refresh");
hbox.getChildren().add(RefreshButton);
TableCell<ModelBrBuilding, ModelBrBuilding> cell = new TableCell<ModelBrBuilding, ModelBrBuilding>() {
#Override
//Updating with the number of row
public void updateItem(ModelBrBuilding building, boolean empty) {
super.updateItem(building, empty);
if (empty) {
setGraphic(null);
} else {
setGraphic(RefreshButton);
}
}
};
RefreshButton.setOnAction(event->{
//here I need to set the old value
});
return cell ;
});
Can any one give me idea how can I do that?
Finally, I have found my answer which is working
RefreshButton.setOnAction(event -> {
ModelBrBuilding buildin = new ModelBrBuilding();
int i = tableBuilding.getSelectionModel().getSelectedIndex();
buildin.setBranchName(old);
tableBuilding.getItems().set(i, buildin);
});
I have a TableView with 2 columns “Date” (LocalDate) and “FX” (Double). I have enabled the cell editing and following an example I found here (http://physalix.com/javafx8-render-a-datepicker-cell-in-a-tableview/) I have created a custom CellFactory that displays a DatePicker for the cells of column “Date”. This solution though renders the DatePciker immediately, so I changed my code to show the DatePicker only when the user double clicks on any of the (non-empty) Date cells. So far so good…
How do I “go back” and remove the DatePicker rendering from the cell after the user has changed the date or cancelled the input? See the pictures as reference. Pic 1 is the initial state of the list. Pic 2 is after double click. How do I go back to Pic 1 status? Let me know if you need to see my specific code.
Reference pictures
This is the code that checks for the double click and then creates the CellFactory
fxTable.getSelectionModel().setCellSelectionEnabled(true);
fxTable.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (event.getClickCount() == 2) {
TablePosition pos = fxTable.getSelectionModel().getSelectedCells().get(0);
int col = pos.getColumn();
if (col == 0) {
//The code below creates the DatePicker in the cell using the DatePickerCell class that I created following the example in the code I found
tblDateFX.setCellFactory(new Callback<TableColumn<Map.Entry<LocalDate, Double>, String>, TableCell<Map.Entry<LocalDate, Double>, String>>() {
#Override
public TableCell<Map.Entry<LocalDate, Double>, String> call(TableColumn<Map.Entry<LocalDate, Double>, String> param) {
ObservableMap<LocalDate, Double> items = FXCollections.observableMap(myBasket.getEnrtriesCur(curName));
DatePickerCell datePick = new DatePickerCell(items);
return datePick;
}
});
}
}
}
});
This is the DatePickerCell Class
public class DatePickerCell<S, T> extends TableCell<Map.Entry<LocalDate,Double>, String> {
private DatePicker datePicker;
private ObservableMap<LocalDate,Double> curEntries;
public DatePickerCell(ObservableMap<LocalDate,Double> curEntries) {
super();
this.curEntries = curEntries;
if (datePicker == null) {
createDatePicker();
}
setGraphic(datePicker);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
Platform.runLater(new Runnable() {
#Override
public void run() {
datePicker.requestFocus();
}
});
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (null == this.datePicker) {
System.out.println("datePicker is NULL");
}
if (empty) {
setText(null);
setGraphic(null);
} else {
if (isEditing()) {
setContentDisplay(ContentDisplay.TEXT_ONLY);
} else {
datePicker.setValue(LocalDate.parse(item,df));
setGraphic(this.datePicker);
setText(item);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
}
}
#Override
public void startEdit() {
super.startEdit();
}
#Override
public void cancelEdit() {
super.cancelEdit();
setContentDisplay(ContentDisplay.TEXT_ONLY);
setGraphic(null);
}
private void createDatePicker() {
this.datePicker = new DatePicker();
datePicker.setEditable(true);
datePicker.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
setGraphic(datePicker);
setText(df.format(datePicker.getValue()));
}
});
datePicker.setOnAction(new EventHandler() {
public void handle(Event t) {
LocalDate date = datePicker.getValue();
int index = getIndex();
commitEdit(df.format(date));
if (null != getCurEntries()) {
System.out.println("Modify value");
}
}
});
setAlignment(Pos.CENTER);
}
Have you tried the function setOnEditCommit to do reverse of your code?
column.setOnEditCommit((TableColumn.CellEditEvent<MyObject, Date> t) -> {
//modify the rendering of you cell to normal
});
After some research I found out that the default rendering of a cell in a TableView is a label. So I tweaked the DatePickerCell class to render a label in the "updateItem" method and render the DatePicker only when the label is clicked (meaning that the user wants to edit the date in the cell).
In terms of "going back" I added a listener for "ESC keypressed" on the DatePicker so when that key is pressed (during the edit) a label is rendered and the edit is therefore cancelled. That works quite well!
I'm still trying to figure out how to do the same when the user tries to cancel the edit by clicking somewhere else on the screen.
--
So here's my stab at the DatePickerEdit class.
This is doing what I need. Renders the cells normally at first, only when the user clicks on the date cell the datepicker is rendered. If the user clicks away from the cell, the cell goes back to its initial rendering (same happens when "ESC" is pressed whilst editing or indeed a new date is picked).
Note that I am passing to the class the Observable list that contains the values shown in the TableView. In this way I can update the value in the list directly in the class. Not sure if this is a good practice or not, this was a "forced solution" though. Originally I used the "setOnEditCommit" method for the TableColumn but after some testing I noticed that this event is not always called after the cell is updated (i.e. the commitEdit() method is called for the cell). Not sure if this is a bug or there's something wrong in my code. For sure it does not always happen. On multiple runs, I would say that 1 out of 3 showed this bugged behaviour.
Here's the code, not sure if it's a "good" code or not. I would appreciate any advice in merit.
public class DatePickerCell<S, T> extends TableCell<FX, String> {
private DatePicker datePicker;
private Label lbl;
private ObservableList<FX> currencies;
public DatePickerCell(ObservableList<FX> list) {
super();
lbl=new Label();
this.currencies=list;
if (datePicker == null) {
createDatePicker();
}
}
#Override
public void updateItem(String item, boolean empty) {
// This section here manages the graphic rendering of each cell
// As I don't want to generate the datepicker graphics immediately I just render a label
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
setGraphic(null);
} else {
createLabel(item);
}
}
#Override
public void startEdit() {
super.startEdit();
}
#Override
public void cancelEdit() {
super.cancelEdit();
}
private void createDatePicker() {
this.datePicker = new DatePicker();
datePicker.setEditable(true);
// when the user clicks on the label the DatePicker graphics is generated
lbl.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
datePicker.setValue(LocalDate.parse(lbl.getText(),df));
setGraphic(datePicker);
setText(lbl.getText());
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
datePicker.requestFocus();
}
});
// This listener manages the "lost focus" on the picker
datePicker.focusedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
// This combination of OldValue NewValue is generated whenever there is a click outside the DatePicker "graphic area"
// i.e. the calendar (when open), the text filed, the calendar icon OR when a NEW date is selected in the calendar.
// This last case generates the "OnAction" event as well that is managed below.
if (oldValue && !newValue) {
createLabel(df.format(datePicker.getValue()));
}
}
});
// This is generated when a NEW date is picked
// it simply commits the new date and changes the graphics back to a label
datePicker.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
LocalDate date = datePicker.getValue();
int index=getIndex();
if (date!=null) {
commitEdit(df.format(date));
getCurrencies().get(index).setDate(date);
createLabel(df.format(date));
}
}
});
// added this listener in case the user wants to cancel pressing "ESC"
// when this happens the label graphics is rendered
datePicker.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
LocalDate date = datePicker.getValue();
if (event.getCode()== KeyCode.ESCAPE) {
createLabel(df.format(date));
}
}
});
setAlignment(Pos.CENTER_LEFT);
}
private void createLabel(String item) {
lbl.setMinWidth(getWidth());
setGraphic(lbl);
lbl.setText(item);
}
public ObservableList<FX> getCurrencies() {
return currencies;
}
}
I have to display 5000 nodes using ListView. Every node contains complex controls but only some text part is different in nodes. How can i reuse existing nodes controls to recreate my cells while scrolling
The answer of James_D points into the right direction. Normally, in JavaFX you shouldn't worry about reusing existing nodes - the JavaFX framework does exactly this, out-of-the-box. If you want to implement some custom cell rendering, you need to set a cell factory, and that usually looks like this:
listView.setCellFactory(new Callback() {
#Override
public Object call(Object param) {
return new ListCell<String>() {
// you may declare fields for some more nodes here
// and initialize them in an anonymous constructor
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty); // Default behaviour: zebra pattern etc.
if (empty || item == null) { // Default technique: take care of empty rows!!!
this.setText(null);
} else {
this.setText("this is the content: " + item);
// ... do your custom rendering!
}
}
};
}
});
Please note: this should work, but is merely illustrative - we Java Devs know that e.g., we would use a StringBuilder for String concatenation, especially in such cases where the code will execute very often.
If you want some complex rendering, you may build that graphic with additional nodes and set them as graphics property with setGraphic(). This works similar to the Label control:
// another illustrative cell renderer:
listView.setCellFactory(new Callback() {
#Override
public Object call(Object param) {
return new ListCell<Integer>() {
Label l = new Label("X");
#Override
protected void updateItem(Integer item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
this.setGraphic(null);
} else {
this.setGraphic(l);
l.setBackground(
new Background(
new BackgroundFill(
Color.rgb(3 * item, 2 * item, item),
CornerRadii.EMPTY,
Insets.EMPTY)));
l.setPrefHeight(item);
l.setMinHeight(item);
}
}
};
}
});
Let's say i have 2 columns in a TreeTableView and now i want to add a string/Label in the first column and a ProgressBar in the other one. How would i accomplish something like this?
Really appreciate any help!
As correctly pointed out by James_D, you can use ProgressBarTreeTableCell for a column with ProgressBars. There is internal supports for some other UI controls such as TextField, CheckBox etc.
For other UI controls you can create a Custom TreeTableCell
as shown:
private class ProgressCell extends TreeTableCell<Employee, String> {
final ProgressBar progress = new ProgressBar();
ProgressCell() {
}
#Override
protected void updateItem(String t, boolean empty) {
super.updateItem(t, empty);
if (!empty) {
setGraphic(progress);
}
}
}
and then assign a CellFactory to the second column
secondCol.setCellFactory(
new Callback<TreeTableColumn<Employee, String>, TreeTableCell<Employee, String>>() {
#Override
public TreeTableCell<Employee, String> call(
TreeTableColumn<Employee, String> param) {
return new ProgressCell();
}
});
where Employee is the POJO class on which the TreeTableView is built