Working with DateTime as StringProperty - javafx-2

I have defined a DateTime field as StringProperty in my model to display date. I have few rows where the date column is empty in database and have defined a cellfactory to display the date in a desired format & blank for empty rows. My problem starts when i try to update one of those empty columns. The new date doesnt appear. It works for the rows where there is already a date value present.
Part of cellfactory:
txtfld.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent t) {
if (t.getCode() == KeyCode.ENTER) {
if(txtfld.getText().length() == 0) {
commitEdit(null);
} else {
commitEdit((new DateTime(txtfld.getText(),"dd/MM/yyyy")).toString());
}
} else if (t.getCode() == KeyCode.ESCAPE) {
cancelEdit();
}
}
});
And the part where I am updating the model:
col_Purchase_DT.setOnEditCommit(new EventHandler<CellEditEvent<Purchase, String>>() {
#Override
public void handle(CellEditEvent<Purchase, String> tbl) {
(tbl.getTableView().getItems().get(tbl.getTablePosition().getRow())).setDOB(tbl.getNewValue());
}
});
I have figured it out that after updating the empty cell with a date col_Purchase_DT.setOnEditCommit() is not called. But is works for non-empty cell. I am using JodaTime for datetime.
I cannot update the second row. But it works perfectly for first & third row.
Any pointers will be helpful.

You seem to be doing way too much coding for this. There's a TextFieldTableCell class that you can use for creating editable cells, and it handles all the wiring for you. Here's an example, based on the usual example from the tutorial. I used the Java 8 java.time.LocalDate for the date column, but the same idea can be applied for JodaTime (I'm just not familiar with the API).
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
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.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.StringConverter;
public class TableWithEditableDateColumn extends Application {
#Override
public void start(Stage primaryStage) {
final TableView<Person> table = new TableView<>();
final TableColumn<Person, String> firstNameCol = createTableColumn("firstName", "First Name", String.class);
final TableColumn<Person, String> lastNameCol = createTableColumn("lastName", "Last Name", String.class);
final TableColumn<Person, LocalDate> birthdayCol = createTableColumn("birthday", "Birthday", LocalDate.class);
final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("dd/MM/yyyy");
firstNameCol.setCellFactory(TextFieldTableCell.forTableColumn());
lastNameCol.setCellFactory(TextFieldTableCell.forTableColumn());
birthdayCol.setCellFactory(TextFieldTableCell.forTableColumn(new StringConverter<LocalDate>() {
#Override
public String toString(LocalDate t) {
if (t==null) {
return "" ;
} else {
return dateFormat.format(t);
}
}
#Override
public LocalDate fromString(String string) {
try {
return LocalDate.parse(string, dateFormat);
} catch (DateTimeParseException exc) {
return null ;
}
}
}));
final ObservableList<Person> data =
FXCollections.observableArrayList(
new Person("Jacob", "Smith", LocalDate.parse("14/03/1975", dateFormat)),
new Person("Isabella", "Johnson", LocalDate.parse("27/09/1982", dateFormat)),
new Person("Ethan", "Williams", null),
new Person("Emma", "Jones", LocalDate.parse("12/07/1979", dateFormat)),
new Person("Michael", "Brown", LocalDate.parse("19/10/1984", dateFormat))
);
table.getColumns().addAll(firstNameCol, lastNameCol, birthdayCol);
table.setItems(data);
table.setEditable(true);
final BorderPane root = new BorderPane();
root.setCenter(table);
final Scene scene = new Scene(root, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
private <T> TableColumn<Person, T> createTableColumn(String property, String title, Class<T> type) {
TableColumn<Person, T> col = new TableColumn<>(title);
col.setCellValueFactory(new PropertyValueFactory<>(property));
col.setEditable(true);
col.setPrefWidth(100);
return col ;
}
public static class Person {
private final StringProperty firstName ;
private final StringProperty lastName ;
private final ObjectProperty<LocalDate> birthday ;
public Person(String firstName, String lastName, LocalDate birthday) {
this.firstName = new SimpleStringProperty(this, "firstName", firstName);
this.lastName = new SimpleStringProperty(this, "lastName", lastName);
this.birthday = new SimpleObjectProperty<>(this, "birthday", birthday);
}
public String getFirstName() {
return firstName.get();
}
public void setFirstName(String value) {
firstName.set(value);
}
public StringProperty firstNameProperty() {
return firstName;
}
public String getLastName() {
return lastName.get();
}
public void setLastName(String value) {
lastName.set(value);
}
public StringProperty lastNameProperty() {
return lastName;
}
public LocalDate getBirthday() {
return birthday.get();
}
public void setBirthday(LocalDate value) {
birthday.set(value);
}
public ObjectProperty birthdayProperty() {
return birthday;
}
}
public static void main(String[] args) {
launch(args);
}
}

onEditCommit is not fired if an update happens on a null value.
Here is the answer: http://www.wobblycogs.co.uk/index.php/computing/javafx/145-editing-null-data-values-in-a-cell-with-javafx-2

Related

Using RowEditing and CheckBoxSelectionModel in a Grid Fails

In a GXT Grid I am attempting to use RowEditing and the CheckBoxSelectionModel. The Sencha Explorer Demo has examples of these in the Row Editable Grid and CheckBox Grid samples, but they don't show an example that includes a combination of these features. When I use both features on the same grid I am not getting the behavior that I had expected. If I click on the "selection" checkbox the row is placed into edit mode, where I would have expected the checkbox to just change from checked to unchecked or vice versa. In addition, when the row is placed into edit mode there is corruption on the line. Here is an example of a row from the grid prior to clicking on any of the values in that row:
and here is that row after clicking on one of the values:
Does anyone have any experience with this?
Update
Here's a sample class which demonstrates the issue:
package org.greatlogic.gxtgrid.client;
import java.util.ArrayList;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.ui.RootLayoutPanel;
import com.sencha.gxt.core.client.IdentityValueProvider;
import com.sencha.gxt.core.client.ValueProvider;
import com.sencha.gxt.data.shared.ListStore;
import com.sencha.gxt.data.shared.ModelKeyProvider;
import com.sencha.gxt.widget.core.client.ContentPanel;
import com.sencha.gxt.widget.core.client.form.TextField;
import com.sencha.gxt.widget.core.client.grid.CheckBoxSelectionModel;
import com.sencha.gxt.widget.core.client.grid.ColumnConfig;
import com.sencha.gxt.widget.core.client.grid.ColumnModel;
import com.sencha.gxt.widget.core.client.grid.Grid;
import com.sencha.gxt.widget.core.client.grid.GridView;
import com.sencha.gxt.widget.core.client.grid.editing.GridRowEditing;
public class GXTGrid implements EntryPoint {
//-------------------------------------------------------------------------
#Override
public void onModuleLoad() {
ListStore<Pet> listStore = new ListStore<>(new ModelKeyProvider<Pet>() {
#Override
public String getKey(Pet pet) {
return Integer.toString(pet.getPetId());
}
});
IdentityValueProvider<Pet> ivp = new IdentityValueProvider<>();
CheckBoxSelectionModel<Pet> sm = new CheckBoxSelectionModel<>(ivp);
ArrayList<ColumnConfig<Pet, ?>> ccList = new ArrayList<>();
ccList.add(sm.getColumn());
ColumnConfig<Pet, String> cc1;
cc1 = new ColumnConfig<>(Pet.getPetNameValueProvider(), 100, "Name");
ccList.add(cc1);
ColumnModel<Pet> columnModel = new ColumnModel<>(ccList);
Grid<Pet> grid = new Grid<>(listStore, columnModel);
grid.setSelectionModel(sm);
grid.setView(new GridView<Pet>());
GridRowEditing<Pet> gre = new GridRowEditing<>(grid);
gre.addEditor(cc1, new TextField());
listStore.add(new Pet(1, "Lassie"));
listStore.add(new Pet(2, "Scooby"));
listStore.add(new Pet(3, "Snoopy"));
ContentPanel contentPanel = new ContentPanel();
contentPanel.add(grid);
RootLayoutPanel.get().add(contentPanel);
}
//-------------------------------------------------------------------------
private static class Pet {
private int _petId;
private String _petName;
public static ValueProvider<Pet, String> getPetNameValueProvider() {
return new ValueProvider<Pet, String>() {
#Override
public String getPath() {
return "Pet.PetName";
}
#Override
public String getValue(Pet pet) {
return pet._petName;
}
#Override
public void setValue(Pet pet, final String value) {
pet._petName = value;
}
};
}
public Pet(int petId, final String petName) {
_petId = petId;
_petName = petName;
}
public int getPetId() {
return _petId;
}
}
//-------------------------------------------------------------------------
}
This behavior of GridRowEditing with CheckBoxSelectionModel is completely normal.
I have used you code to try some things. I think the best way to use GridRowEditing and CheckBoxSelectionModel is, as I guessed, to start editing on double click, as nothing is provided to do it with just one click yet. To do so just add
gre.setClicksToEdit(ClicksToEdit.TWO);
Otherwise, if you really do not want to use two clicks to start editing, you can also use InlineRowEditing, which will enable you to use CheckBoxSelectionModel as you want.
Eventually, you might be able to override the whole behavior of GridRowEditing to handle CheckBoxSelectionModel properly on one click only, but it would be more complicated and require more specific knowledge of GXT framework that I don't have.
I haven't found a solution to this problem using the CheckBoxSelectionModel, and so I decided to try another approach, namely, adding a column to the grid that contains a checkbox, and handling the state of the selections manually. To do this I found that I needed to respond to a few events, which wasn't too bad. Here's a new version of the sample code, which should provide a starting point for a real implementation:
import java.util.ArrayList;
import java.util.TreeSet;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.user.client.ui.RootLayoutPanel;
import com.sencha.gxt.cell.core.client.form.CheckBoxCell;
import com.sencha.gxt.core.client.ValueProvider;
import com.sencha.gxt.core.client.dom.XElement;
import com.sencha.gxt.data.shared.ListStore;
import com.sencha.gxt.data.shared.ModelKeyProvider;
import com.sencha.gxt.widget.core.client.ContentPanel;
import com.sencha.gxt.widget.core.client.form.CheckBox;
import com.sencha.gxt.widget.core.client.form.Field;
import com.sencha.gxt.widget.core.client.form.TextField;
import com.sencha.gxt.widget.core.client.grid.CellSelectionModel;
import com.sencha.gxt.widget.core.client.grid.ColumnConfig;
import com.sencha.gxt.widget.core.client.grid.ColumnModel;
import com.sencha.gxt.widget.core.client.grid.Grid;
import com.sencha.gxt.widget.core.client.grid.GridView;
import com.sencha.gxt.widget.core.client.grid.editing.GridRowEditing;
public class GXTGrid implements EntryPoint {
//-------------------------------------------------------------------------
private ListStore<Pet> _listStore;
private TreeSet<Integer> _selectedPetIdSet;
//-------------------------------------------------------------------------
#Override
public void onModuleLoad() {
_selectedPetIdSet = new TreeSet<>();
_listStore = new ListStore<>(new ModelKeyProvider<Pet>() {
#Override
public String getKey(Pet pet) {
return Integer.toString(pet.getPetId());
}
});
final CellSelectionModel<Pet> sm = new CellSelectionModel<>();
ArrayList<ColumnConfig<Pet, ?>> ccList = new ArrayList<>();
ValueProvider<Pet, Boolean> selectValueProvider;
selectValueProvider = new ValueProvider<GXTGrid.Pet, Boolean>() {
#Override
public String getPath() {
return "SelectCheckBox";
}
#Override
public Boolean getValue(Pet pet) {
return _selectedPetIdSet.contains(pet.getPetId());
}
#Override
public void setValue(Pet pet, final Boolean selected) { //
}
};
ColumnConfig<Pet, Boolean> cc0 = new ColumnConfig<>(selectValueProvider, 23, "");
CheckBoxCell checkBoxCell = new CheckBoxCell() {
#Override
protected void onClick(XElement parent, final NativeEvent event) {
super.onClick(parent, event);
Pet pet = sm.getSelectedItem();
if (!_selectedPetIdSet.remove(pet.getPetId())) {
_selectedPetIdSet.add(pet.getPetId());
}
}
};
cc0.setCell(checkBoxCell);
cc0.setFixed(true);
cc0.setHideable(false);
cc0.setMenuDisabled(true);
cc0.setResizable(false);
cc0.setSortable(false);
ccList.add(cc0);
ColumnConfig<Pet, String> cc1;
cc1 = new ColumnConfig<>(Pet.getPetNameValueProvider(), 100, "Name");
ccList.add(cc1);
ColumnModel<Pet> columnModel = new ColumnModel<>(ccList);
Grid<Pet> grid = new Grid<>(_listStore, columnModel);
grid.setSelectionModel(sm);
grid.setView(new GridView<Pet>());
GridRowEditing<Pet> gre = new GridRowEditing<>(grid);
Field<Boolean> checkBox = new CheckBox();
checkBox.setEnabled(false);
gre.addEditor(cc0, checkBox);
gre.addEditor(cc1, new TextField());
_listStore.add(new Pet(1, "Lassie"));
_listStore.add(new Pet(2, "Scooby"));
_listStore.add(new Pet(3, "Snoopy"));
ContentPanel contentPanel = new ContentPanel();
contentPanel.add(grid);
RootLayoutPanel.get().add(contentPanel);
}
//-------------------------------------------------------------------------
private static class Pet {
private int _petId;
private String _petName;
public static ValueProvider<Pet, String> getPetNameValueProvider() {
return new ValueProvider<Pet, String>() {
#Override
public String getPath() {
return "Pet.PetName";
}
#Override
public String getValue(Pet pet) {
return pet._petName;
}
#Override
public void setValue(Pet pet, final String value) {
pet._petName = value;
}
};
}
public Pet(int petId, final String petName) {
_petId = petId;
_petName = petName;
}
public int getPetId() {
return _petId;
}
}
//-------------------------------------------------------------------------
}

JavaFX 2 TableView how to update cell when object is changed

I'm creating a TableView to show information regarding a list of custom objects (EntityEvents).
The table view must have 2 columns.
First column to show the corresponding EntityEvent's name.
The second column would display a button. The button text deppends on a property of the EntityEvent. If the property is ZERO, it would be "Create", otherwise "Edit".
I managed to do it all just fine, except that I can't find a way to update the TableView line when the corresponding EntityEvent object is changed.
Very Important: I can't change the EntityEvent class to use JavaFX properties, since they are not under my control. This class uses PropertyChangeSupport to notify listeners when the monitored property is changed.
Note:
I realize that adding new elements to the List would PROBABLY cause the TableView to repaint itself, but that is not what I need. I say PROBABLY because I've read about some bugs that affect this behavior.
I tried using this approach to force the repaint, by I couldn't make it work.
Does anyone knows how to do it?
Thanks very much.
Here is a reduced code example that illustrates the scenario:
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import javafx.application.Application;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.TableView;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Callback;
public class Main extends Application {
//=============================================================================================
public class EntityEvent {
private String m_Name;
private PropertyChangeSupport m_NamePCS = new PropertyChangeSupport(this);
private int m_ActionCounter;
private PropertyChangeSupport m_ActionCounterPCS = new PropertyChangeSupport(this);
public EntityEvent(String name, int actionCounter) {
m_Name = name;
m_ActionCounter = actionCounter;
}
public String getName() {
return m_Name;
}
public void setName(String name) {
String lastName = m_Name;
m_Name = name;
System.out.println("Name changed: " + lastName + " -> " + m_Name);
m_NamePCS.firePropertyChange("Name", lastName, m_Name);
}
public void addNameChangeListener(PropertyChangeListener listener) {
m_NamePCS.addPropertyChangeListener(listener);
}
public int getActionCounter() {
return m_ActionCounter;
}
public void setActionCounter(int actionCounter) {
int lastActionCounter = m_ActionCounter;
m_ActionCounter = actionCounter;
System.out.println(m_Name + ": ActionCounter changed: " + lastActionCounter + " -> " + m_ActionCounter);
m_ActionCounterPCS.firePropertyChange("ActionCounter", lastActionCounter, m_ActionCounter);
}
public void addActionCounterChangeListener(PropertyChangeListener listener) {
m_ActionCounterPCS.addPropertyChangeListener(listener);
}
}
//=============================================================================================
private class AddPersonCell extends TableCell<EntityEvent, String> {
Button m_Button = new Button("Undefined");
StackPane m_Padded = new StackPane();
AddPersonCell(final TableView<EntityEvent> table) {
m_Padded.setPadding(new Insets(3));
m_Padded.getChildren().add(m_Button);
m_Button.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent actionEvent) {
// Do something
}
});
}
#Override protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (!empty) {
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
setGraphic(m_Padded);
m_Button.setText(item);
}
}
}
//=============================================================================================
private ObservableList<EntityEvent> m_EventList;
//=============================================================================================
#SuppressWarnings("unchecked")
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Table View test.");
VBox container = new VBox();
m_EventList = FXCollections.observableArrayList(
new EntityEvent("Event 1", -1),
new EntityEvent("Event 2", 0),
new EntityEvent("Event 3", 1)
);
final TableView<EntityEvent> table = new TableView<EntityEvent>();
table.setItems(m_EventList);
TableColumn<EntityEvent, String> eventsColumn = new TableColumn<>("Events");
TableColumn<EntityEvent, String> actionCol = new TableColumn<>("Actions");
actionCol.setSortable(false);
eventsColumn.setCellValueFactory(new Callback<CellDataFeatures<EntityEvent, String>, ObservableValue<String>>() {
public ObservableValue<String> call(CellDataFeatures<EntityEvent, String> p) {
EntityEvent event = p.getValue();
event.addActionCounterChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent event) {
// TODO: I'd like to update the table cell information.
}
});
return new ReadOnlyStringWrapper(event.getName());
}
});
actionCol.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<EntityEvent, String>, ObservableValue<String>>() {
#Override
public ObservableValue<String> call(TableColumn.CellDataFeatures<EntityEvent, String> ev) {
String text = "NONE";
if(ev.getValue() != null) {
text = (ev.getValue().getActionCounter() != 0) ? "Edit" : "Create";
}
return new ReadOnlyStringWrapper(text);
}
});
// create a cell value factory with an add button for each row in the table.
actionCol.setCellFactory(new Callback<TableColumn<EntityEvent, String>, TableCell<EntityEvent, String>>() {
#Override
public TableCell<EntityEvent, String> call(TableColumn<EntityEvent, String> personBooleanTableColumn) {
return new AddPersonCell(table);
}
});
table.getColumns().setAll(eventsColumn, actionCol);
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
// Add Resources Button
Button btnInc = new Button("+");
btnInc.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent ev) {
System.out.println("+ clicked.");
EntityEvent entityEvent = table.getSelectionModel().getSelectedItem();
if (entityEvent == null) {
System.out.println("No Event selected.");
return;
}
entityEvent.setActionCounter(entityEvent.getActionCounter() + 1);
// TODO: I expected the TableView to be updated since I modified the object.
}
});
// Add Resources Button
Button btnDec = new Button("-");
btnDec.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent ev) {
System.out.println("- clicked.");
EntityEvent entityEvent = table.getSelectionModel().getSelectedItem();
if (entityEvent == null) {
System.out.println("No Event selected.");
return;
}
entityEvent.setActionCounter(entityEvent.getActionCounter() - 1);
// TODO: I expected the TableView to be updated since I modified the object.
}
});
container.getChildren().add(table);
container.getChildren().add(btnInc);
container.getChildren().add(btnDec);
Scene scene = new Scene(container, 300, 600, Color.WHITE);
primaryStage.setScene(scene);
primaryStage.show();
}
//=============================================================================================
public Main() {
}
//=============================================================================================
public static void main(String[] args) {
launch(Main.class, args);
}
}
Try the javafx.beans.property.adapter classes, particularly JavaBeanStringProperty and JavaBeanIntegerProperty. I haven't used these, but I think you can do something like
TableColumn<EntityEvent, Integer> actionCol = new TableColumn<>("Actions");
actionCol.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<EntityEvent, Integer> ev) {
return new JavaBeanIntegerPropertyBuilder().bean(ev.getValue()).name("actionCounter").build();
});
// ...
public class AddPersonCell extends TableCell<EntityEvent, Integer>() {
final Button button = new Button();
public AddPersonCell() {
setPadding(new Insets(3));
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
button.setOnAction(...);
}
#Override
public void updateItem(Integer actionCounter, boolean empty) {
if (empty) {
setGraphic(null);
} else {
if (actionCounter.intValue()==0) {
button.setText("Create");
} else {
button.setText("Add");
}
setGraphic(button);
}
}
}
As I said, I haven't used the Java bean property adapter classes, but the idea is that they "translate" property change events to JavaFX change events. I just typed this in here without testing, but it should at least give you something to start with.
UPDATE: After a little experimenting, I don't think this approach will work if your EntityEvent is really set up the way you showed it in your code example. The standard Java beans bound properties pattern (which the JavaFX property adapters rely on) has a single property change listener and an addPropertyChangeListener(...) method. (The listeners can query the event to see which property changed.)
I think if you do
public class EntityEvent {
private String m_Name;
private PropertyChangeSupport pcs = new PropertyChangeSupport(this);
private int m_ActionCounter;
public EntityEvent(String name, int actionCounter) {
m_Name = name;
m_ActionCounter = actionCounter;
}
public String getName() {
return m_Name;
}
public void setName(String name) {
String lastName = m_Name;
m_Name = name;
System.out.println("Name changed: " + lastName + " -> " + m_Name);
pcs.firePropertyChange("name", lastName, m_Name);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
pcs.removePropertyChangeListener(listener);
}
public int getActionCounter() {
return m_ActionCounter;
}
public void setActionCounter(int actionCounter) {
int lastActionCounter = m_ActionCounter;
m_ActionCounter = actionCounter;
System.out.println(m_Name + ": ActionCounter changed: " + lastActionCounter + " -> " + m_ActionCounter);
pcs.firePropertyChange("ActionCounter", lastActionCounter, m_ActionCounter);
}
}
it will work with the adapter classes above. Obviously, if you have existing code calling the addActionChangeListener and addNameChangeListener methods you would want to keep those existing methods and the existing property change listeners, but I see no reason you can't have both.

Javafx, get the object referenced by a TableCell

I have the following Callback listening on the selected Cell of a TableView:
Callback<TableColumn<MyFTPFile,String>, TableCell<MyFTPFile,String>> cellFactory =
new Callback<TableColumn<MyFTPFile,String>, TableCell<MyFTPFile,String>>() {
public TableCell<MyFTPFile,String> call(TableColumn<MyFTPFile,String> p) {
TableCell<MyFTPFile,String> cell = new TableCell<MyFTPFile, String>() {
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
setText(empty ? null : getString());
setGraphic(null);
}
private String getString() {
return getItem() == null ? "" : getItem().toString();
}
};
cell.addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (event.getClickCount() > 1) {
TableCell<MyFTPFile,String> c = (TableCell<MyFTPFile,String>) event.getSource();
ftpObservablelist = MyFTPClient.getInstance().getFtpObservableList();
ftpTable.setItems(ftpObservablelist);
}
}
});
Now, I would like to get the MyFTPFile object which is referenced by the cell, which is doubleclicked, so that i can pass it to another class and do stuff... Any Idea how to do that???
Thanks in advance.
The MyFTPFile object is associated with the cell's row, so, as the asker pointed out in his comment, it is retrievable via cell.getTableRow().getItem().
At first I thought this should be cell.getItem(), which returns the data value associated with the cell. However, most of the time, the cell data value will be a property of the backing item rather than the object itself (for example a filename field of a MyFTPFile object).
Executable sample for the curious:
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
import javafx.util.Callback;
public class TableClickListener extends Application {
public static void main(String[] args) {
launch(args);
}
class FTPTableCell<S, T> extends TextFieldTableCell<S, T> {
FTPTableCell() {
super();
addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (event.getClickCount() > 1 && getItem() != null) {
System.out.println("Sending " + getTableRow().getItem() + " to the FTP client");
}
}
});
}
}
final Callback<TableColumn<MyFTPFile, String>, TableCell<MyFTPFile, String>> FTP_TABLE_CELL_FACTORY =
new Callback<TableColumn<MyFTPFile, String>, TableCell<MyFTPFile, String>>() {
public TableCell<MyFTPFile, String> call(TableColumn<MyFTPFile, String> p) {
return new FTPTableCell<>();
}
};
#Override
public void start(final Stage stage) {
final TableView<MyFTPFile> table = new TableView<>();
final TableColumn<MyFTPFile, String> filenameColumn = new TableColumn<>("Filename");
filenameColumn.setCellValueFactory(new PropertyValueFactory<MyFTPFile, String>("filename"));
filenameColumn.setCellFactory(FTP_TABLE_CELL_FACTORY);
filenameColumn.setMinWidth(150);
final TableColumn<MyFTPFile, String> ratingColumn = new TableColumn<>("Rating");
ratingColumn.setCellValueFactory(new PropertyValueFactory<MyFTPFile, String>("rating"));
ratingColumn.setCellFactory(FTP_TABLE_CELL_FACTORY);
ratingColumn.setMinWidth(20);
table.getColumns().setAll(filenameColumn, ratingColumn);
table.getItems().setAll(
new MyFTPFile("xyzzy.txt", 10),
new MyFTPFile("management_report.doc", 1),
new MyFTPFile("flower.png", 7)
);
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
stage.setScene(new Scene(new Group(table)));
stage.show();
}
public class MyFTPFile {
private final String filename;
private final int rating;
MyFTPFile(String filename, int rating) {
this.filename = filename;
this.rating = rating;
}
public String getFilename() {
return filename;
}
public int getRating() {
return rating;
}
#Override
public String toString() {
return "MyFTPFile{" +
"filename='" + filename + '\'' +
", rating=" + rating +
'}';
}
}
}

TitledPane - Event

I have two distinct components: a TableView and a TitledPane next to the table.
What I´m trying to do is to redimension the tableview but only when the titledpane expands or collapse.
When the titledpane collapse the tableview gets bigger and when it expands the tableview gets smaller.
I don´t know what action should I take.
Anybody knows the solution?
Regards
Check out the sample code below:
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TitledPane;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class MyDemo extends Application {
private TableView<Person> tableview = new TableView<Person>();
// Suppose your preferred height values for those 2 component are as follows:
private double TABLE_MIN_HEIGHT = 30.0;
private double TABLE_MAX_HEIGHT = 500.0;
private double TITLED_PANE_HEIGHT; // will be determined
private final ObservableList<Person> data =
FXCollections.observableArrayList(
new Person("Jacob", "Smith", "jacob.smith#example.com"),
new Person("Isabella", "Johnson", "isabella.johnson#example.com"),
new Person("Ethan", "Williams", "ethan.williams#example.com"),
new Person("Emma", "Jones", "emma.jones#example.com"),
new Person("Michael", "Brown", "michael.brown#example.com"));
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage stage) {
TableColumn firstNameCol = new TableColumn("First Name");
firstNameCol.setMinWidth(100);
firstNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
TableColumn lastNameCol = new TableColumn("Last Name");
lastNameCol.setMinWidth(100);
lastNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("lastName"));
TableColumn emailCol = new TableColumn("Email");
emailCol.setMinWidth(200);
emailCol.setCellValueFactory(new PropertyValueFactory<Person, String>("email"));
tableview.setItems(data);
tableview.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
final TitledPane titledPane = new TitledPane("TitledPane", new Text("Content\n\n\n\n"));
titledPane.setAnimated(false); // we need to temporarily disable
// animation to get the titledpanes computed height correctly.
// Force to min height of table view
tableview.setMaxHeight(TABLE_MIN_HEIGHT);
tableview.setMinHeight(TABLE_MIN_HEIGHT);
// Here you have 2 options
int option = 2;
if (option == 1) {
// 1st simply force the table view height to its preferred max value
// when the titled pane's expanded property changed:
titledPane.expandedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
tableview.setMaxHeight(newValue ? TABLE_MIN_HEIGHT : TABLE_MAX_HEIGHT);
tableview.setMinHeight(newValue ? TABLE_MIN_HEIGHT : TABLE_MAX_HEIGHT);
}
});
} else if (option == 2) {
// 2nd. Similar to first but with "animation". Here observe height changes of titled pane:
titledPane.heightProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
tableview.setMaxHeight(TABLE_MAX_HEIGHT - (TABLE_MAX_HEIGHT * (newValue.doubleValue() / TITLED_PANE_HEIGHT)));
tableview.setMinHeight(TABLE_MAX_HEIGHT - (TABLE_MAX_HEIGHT * (newValue.doubleValue() / TITLED_PANE_HEIGHT)));
}
});
}
HBox hBox = new HBox(10);
hBox.getChildren().addAll(tableview, titledPane);
Scene scene = new Scene(hBox);
stage.setTitle("Table View Sample");
stage.setWidth(650);
stage.setHeight(700);
stage.setScene(scene);
TITLED_PANE_HEIGHT = titledPane.getHeight();
System.out.println("TITLED_PANE_HEIGHT = " + TITLED_PANE_HEIGHT);
stage.show();
// Determine the titledPane computed height value after stage has been shown.
TITLED_PANE_HEIGHT = titledPane.getHeight();
System.out.println("TITLED_PANE_HEIGHT = " + TITLED_PANE_HEIGHT);
// .. then enable animation
titledPane.setAnimated(true);
}
public static class Person {
private final SimpleStringProperty firstName;
private final SimpleStringProperty lastName;
private final SimpleStringProperty email;
private Person(String fName, String lName, String email) {
this.firstName = new SimpleStringProperty(fName);
this.lastName = new SimpleStringProperty(lName);
this.email = new SimpleStringProperty(email);
}
public String getFirstName() {
return firstName.get();
}
public void setFirstName(String fName) {
firstName.set(fName);
}
public String getLastName() {
return lastName.get();
}
public void setLastName(String fName) {
lastName.set(fName);
}
public String getEmail() {
return email.get();
}
public void setEmail(String fName) {
email.set(fName);
}
}
}
It is demo, improve it according to your needs. There maybe other approaches as well. HTH.

Dealing with null values in Cell

I'm having serious problems getting a JavaFX 2 TableView to deal with null values in my data. So much trouble in fact I've put together a simple demo of the problem which is shown below.
Essentially the problem is that some of my data may be null and coercing the null to a value, such as an empty string, is not valid, it must be null. In the real code I'm working on I have a null date value, to keep the example simple I've shown a null string below.
Rows 1 and 2 of the table have null values. The two columns are different, the first shows the behaviour of the TextFieldTableCell the second my implementation of an editable cell. Both show the same incorrect behaviour.
The current behaviour is this:
Click the cell to enter editing mode
Enter a value in the cell
Press enter to commit the edit
Nothing happens
At step 4 I would expect the onEditCommit handler for the column to get called but it isn't. Having a look at the source for javax.scene.control.TableCell the commit isn't happening because of the first line of commitEdit is:
if (! isEditing()) return;
It seems that because the cell is null the editing property never gets set to true although I admit I've not yet traced through all the code to see why it never get's switched to true.
Thanks as always for any help.
Example
Main Application
package example;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.stage.Stage;
import javafx.util.Callback;
public class NullCellEditingExample extends Application {
private TableView table = new TableView();
private final ObservableList<Person> data =
FXCollections.observableArrayList( new Person(null, "Smith"), new Person("Isabella", null),
new Person("Ethan", "Williams"), new Person("Emma", "Jones"), new Person("Michael", "Brown"));
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
Scene scene = new Scene(new Group());
TableColumn firstNameCol = createSimpleFirstNameColumn();
TableColumn lastNameCol = createLastNameColumn();
table.setItems(data);
table.getColumns().addAll(firstNameCol, lastNameCol);
table.setEditable(true);
((Group) scene.getRoot()).getChildren().addAll(table);
stage.setScene(scene);
stage.show();
}
private TableColumn createSimpleFirstNameColumn() {
TableColumn firstNameCol = new TableColumn("First Name");
firstNameCol.setMinWidth(100);
firstNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
firstNameCol.setCellFactory(TextFieldTableCell.forTableColumn());
firstNameCol.setOnEditCommit(new EventHandler<TableColumn.CellEditEvent<Person, String>>() {
#Override
public void handle(TableColumn.CellEditEvent<Person, String> t) {
((Person) t.getTableView().getItems().get(t.getTablePosition().getRow())).setFirstName(t.getNewValue());
}
});
return firstNameCol;
}
private TableColumn createLastNameColumn() {
Callback<TableColumn, TableCell> editableFactory = new Callback<TableColumn, TableCell>() {
#Override
public TableCell call(TableColumn p) {
return new EditingCell();
}
};
TableColumn lastNameCol = new TableColumn("Last Name");
lastNameCol.setMinWidth(100);
lastNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("lastName"));
lastNameCol.setCellFactory(editableFactory);
lastNameCol.setOnEditCommit(new EventHandler<TableColumn.CellEditEvent<Person, String>>() {
#Override
public void handle(TableColumn.CellEditEvent<Person, String> t) {
t.getRowValue().setLastName(t.getNewValue());
}
});
return lastNameCol;
}
}
Editing Cell
package example;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.scene.control.TableCell;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
public class EditingCell extends TableCell<Person, String> {
private TextField textField;
public EditingCell() {
}
#Override
public void startEdit() {
super.startEdit();
if( textField == null ) {
createTextField();
}
setText(null);
setGraphic(textField);
textField.selectAll();
}
#Override
public void cancelEdit() {
super.cancelEdit();
setText((String) getItem());
setGraphic(null);
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
if (isEditing()) {
if (textField != null) {
textField.setText(getString());
}
setText(null);
setGraphic(textField);
} else {
setText(getString());
setGraphic(null);
}
}
}
private void createTextField() {
textField = new TextField(getString());
textField.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);
textField.focusedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> arg0, Boolean arg1, Boolean arg2) {
if (!arg2) { commitEdit(textField.getText()); }
}
});
textField.setOnKeyReleased(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent t) {
if (t.getCode() == KeyCode.ENTER) {
String value = textField.getText();
if (value != null) { commitEdit(value); } else { commitEdit(null); }
} else if (t.getCode() == KeyCode.ESCAPE) {
cancelEdit();
}
}
});
}
private String getString() {
return getItem() == null ? "" : getItem().toString();
}
}
Person
package example;
import javafx.beans.property.SimpleStringProperty;
public class Person {
private final SimpleStringProperty firstName;
private final SimpleStringProperty lastName;
public Person(String firstName, String lastName) {
this.firstName = new SimpleStringProperty(firstName);
this.lastName = new SimpleStringProperty(lastName);
}
public String getFirstName() {
return firstName.get();
}
public void setFirstName(String firstName) {
this.firstName.set(firstName);
}
public SimpleStringProperty firstNameProperty() {
return firstName;
}
public String getLastName() {
return lastName.get();
}
public void setLastName(String lastName) {
this.lastName.set(lastName);
}
public SimpleStringProperty lastNameProperty() {
return lastName;
}
}
Since posting I found another question that is basically identical. They took the approach of always just avoiding null values which is fine for strings (e.g. use an empty string) but not acceptable for dates or other data types where the is no obvious "empty" value.
The solution is to pass a value of false into the super.updateItem call in the EditingCell.updateItem method. I've put together a full write up of this if anyone is interested in the complete analysis.

Resources