Datepicker in JavaFX 8 - javafx-2

Is there any implementation of Date Picker and Time Picker into the default JavaFX 8 package that I can use without using third party solutions?

DatePicker
Yes, Java 8 has a DatePicker:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.DatePicker;
import javafx.stage.Stage;
import java.time.LocalDate;
public class PickerDemo extends Application {
#Override public void start(Stage stage) {
final DatePicker datePicker = new DatePicker(LocalDate.now());
datePicker.setOnAction(event -> {
LocalDate date = datePicker.getValue();
System.out.println("Selected date: " + date);
});
stage.setScene(
new Scene(datePicker)
);
stage.show();
}
public static void main(String[] args) { launch(args); }
}
TimePicker
No, Java 8 does not have a TimePicker.
There is a TimePicker in jfxtras (source here).
Given that Java 8 already has a DatePicker, the addition of a TimePicker might be an appropriate feature request, you could make.

If you do not want to display calendar in popup. Here is a solution which uses internal CalendarPickerContent class.
DatePickerSkin skin = new DatePickerSkin(new DatePicker());
Node calendarControl = skin.getPopupContent();

here is my try based on #javaLearner answer:
DateTimePicker.java:
package test;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.control.DatePicker;
import javafx.scene.control.Skin;
import javafx.util.StringConverter;
public class DateTimePicker extends DatePicker{
private ObjectProperty<LocalTime> timeValue = new SimpleObjectProperty<>();
private ObjectProperty<ZonedDateTime> dateTimeValue;
public DateTimePicker(){
super();
setValue(LocalDate.now());
setTimeValue(LocalTime.now());
setConverter(new StringConverter<LocalDate>() {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd'T'HH:mm:ssZ");
#Override
public String toString ( LocalDate object ) {
return dateTimeValue.get().format(formatter);
}
#Override
public LocalDate fromString ( String string ) {
return LocalDate.parse(string, formatter);
}
});
}
#Override
protected Skin<?> createDefaultSkin () {
return new DateTimePickerSkin(this);
}
public LocalTime getTimeValue(){
return timeValue.get();
}
void setTimeValue(LocalTime timeValue){
this.timeValue.set(timeValue);
}
public ObjectProperty<LocalTime> timeValueProperty(){
return timeValue;
}
public ZonedDateTime getDateTimeValue() {
return dateTimeValueProperty().get();
}
public void setDateTimeValue (ZonedDateTime dateTimeValue) {
dateTimeValueProperty().set(dateTimeValue);
}
public ObjectProperty<ZonedDateTime> dateTimeValueProperty(){
if (dateTimeValue == null){
dateTimeValue = new SimpleObjectProperty<>(ZonedDateTime.of(LocalDateTime.of(this.getValue(), timeValue.get()), ZoneId.systemDefault()));
timeValue.addListener(t -> {
dateTimeValue.set(ZonedDateTime.of(LocalDateTime.of(this.getValue(), timeValue.get()), ZoneId.systemDefault()));
});
valueProperty().addListener(t -> {
dateTimeValue.set(ZonedDateTime.of(LocalDateTime.of(this.getValue(), timeValue.get()), ZoneId.systemDefault()));
});
}
return dateTimeValue;
}
}
DateTimePickerSkin.java:
package test;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.layout.HBox;
import com.sun.javafx.scene.control.skin.DatePickerContent;
import com.sun.javafx.scene.control.skin.DatePickerSkin;
public class DateTimePickerSkin extends DatePickerSkin {
private DateTimePicker datePicker;
private DatePickerContent ret;
public DateTimePickerSkin(DateTimePicker datePicker){
super(datePicker);
this.datePicker = datePicker;
}
#Override
public Node getPopupContent() {
if (ret == null){
ret = (DatePickerContent) super.getPopupContent();
Slider hours = new Slider(0, 23, (datePicker.getTimeValue() != null ? datePicker.getTimeValue().getMinute() : 0));
Label hoursValue = new Label("Hours: " + (datePicker.getTimeValue() != null ? datePicker.getTimeValue().getHour() : "") + " ");
Slider minutes = new Slider(0, 59, (datePicker.getTimeValue() != null ? datePicker.getTimeValue().getMinute() : 0));
Label minutesValue = new Label("Minutes: " + (datePicker.getTimeValue() != null ? datePicker.getTimeValue().getMinute() : "") + " ");
Slider seconds = new Slider(0, 59, (datePicker.getTimeValue() != null ? datePicker.getTimeValue().getSecond() : 0));
Label secondsValue = new Label("Seconds: " + (datePicker.getTimeValue() != null ? datePicker.getTimeValue().getSecond() : "") + " ");
ret.getChildren().addAll(new HBox(hoursValue, hours), new HBox(minutesValue, minutes), new HBox(secondsValue, seconds));
hours.valueProperty().addListener((observable, oldValue, newValue) -> {
datePicker.setTimeValue(datePicker.getTimeValue().withHour(newValue.intValue()));
hoursValue.setText("Hours: " + String.format("%02d", datePicker.getTimeValue().getHour()) + " ");
});
minutes.valueProperty().addListener((observable, oldValue, newValue) -> {
datePicker.setTimeValue(datePicker.getTimeValue().withMinute(newValue.intValue()));
minutesValue.setText("Minutes: " + String.format("%02d", datePicker.getTimeValue().getMinute()) + " ");
});
seconds.valueProperty().addListener((observable, oldValue, newValue) -> {
datePicker.setTimeValue(datePicker.getTimeValue().withSecond(newValue.intValue()));
secondsValue.setText("Seconds: " + String.format("%02d", datePicker.getTimeValue().getSecond()) + " ");
});
}
return ret;
}
}
usage:
Main.java:
public class Main extends Application{
#Override
public void start ( Stage primaryStage ) {
VBox vBox = new VBox();
Scene s = new Scene(new ScrollPane(vBox), 600, 400);
DateTimePicker d = new DateTimePicker();
// Date only
d.valueProperty().addListener(t -> System.out.println(t));
// Time only
d.timeValueProperty().addListener(t -> System.out.println(t));
// DateAndTime
d.dateTimeValueProperty().addListener(t -> System.out.println(t));
vBox.getChildren().add(d);
primaryStage.setScene(s);
primaryStage.show();
}
public static void main ( String[] args ) {
launch(args);
}
}
there is still the StringConverter job to be done, but it's quite usable even like this. hope it helps someone.
PS: this was tested with jdk8u40, and it uses classes from the com.sun package (DatePickerContent/DatePickerSkin) which are not public API and might change in the future, but common, even if they do, how hard is it to adapt the above code !? :)
edit: added a StringConverter for iso8601 format and added a ZonedDateTime property for a cleaner usage (can swapped with a LocalDateTime if you don't need the Zone information)

Related

Implementing a pause and resume feature for JavaFX Tasks

I'm building a UI for a Simulator running in background. Since this Simulator may not hold for a long time, it of course has to be in a separate thread from the JavaFx Thread. I want to start, pause, resume and stop/terminate the Simulator when the corresponding button is clicked.
The service class that advances the simulator looks like this:
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public class SimulatorService extends Service<Void> {
private Simulator simulator;
private long cycleLengthMS = 1000;
private final AtomicBoolean simulatorStopped = new AtomicBoolean(false);
public SimulatorService(Simulator simulator){
this.simulator = simulator;
}
#Override
protected Task<Void> createTask() {
return new Task<>() {
#Override
protected Void call() {
System.out.println("Requested start of Simulator");
int state;
do {
// advance
state = simulator.nextStep();
try {
TimeUnit.MILLISECONDS.sleep(cycleLengthMS);
if(simulatorStopped.get()){
super.cancel();
}
} catch (InterruptedException e) {
return null;
}
}
while (state == 0 && !simulatorStopped.get());
return null;
}
};
}
#Override
public void start(){
if(getState().equals(State.READY)){
simulatorStopped.set(false);
super.start();
}
else if(getState().equals(State.CANCELLED)){
simulatorStopped.set(false);
super.restart();
}
}
#Override
public boolean cancel(){
if(simulatorStopped.get()){
simulatorStopped.set(true);
return false;
} else{
simulatorStopped.set(true);
return true; //if value changed
}
}
}
The Simulator starts the Service if a button on the GUI is pressed:
import javafx.application.Platform;
import java.util.concurrent.atomic.AtomicInteger;
public class Simulator {
Model model;
private final SimulatorService simulatorService;
public Simulator(Model model){
this.model = model;
simulatorService = new SimulatorService(this);
}
public int nextStep(){
final AtomicInteger res = new AtomicInteger(0);
Platform.runLater(new Thread(()-> {
res.set(model.nextStep());
}));
return res.get();
}
public boolean stopSimulationService() throws IllegalStateException{
return simulatorService.cancel();
}
public void startSimulationService() throws IllegalStateException{
simulatorService.start();
}
}
Parts of the window are redrawn if a observed value in the model changes:
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
public class Model {
private final IntegerProperty observedValue = new SimpleIntegerProperty(0);
public int nextStep() {
observedValue.set(observedValue.get() + 1);
return observedValue.get() > 500000 ? 1 : 0;
}
public int getObservedValue() {
return observedValue.get();
}
public IntegerProperty observedValueProperty() {
return observedValue;
}
public void setObservedValue(int observedValue) {
this.observedValue.set(observedValue);
}
}
The redraw happens in another class:
import javafx.beans.value.ChangeListener;
public class ViewController {
private View view;
private Simulator simulator;
private Model model;
public ViewController(Simulator simulator) {
this.simulator = simulator;
this.view = new View();
setModel(simulator.model);
view.nextStep.setOnMouseClicked(click -> {
simulator.nextStep();
});
view.startSim.setOnMouseClicked(click -> {
simulator.startSimulationService();
});
view.stopSim.setOnMouseClicked(click ->{
simulator.stopSimulationService();
});
}
public View getView() {
return view;
}
private final ChangeListener<Number> observedValueListener = (observableValue, oldInt, newInt) -> {
view.updateView(newInt.intValue());
};
public void setModel(Model m) {
this.model = m;
m.observedValueProperty().addListener(observedValueListener);
}
}
The corresponding view:
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.text.Text;
public class View extends BorderPane {
Button nextStep = new Button("next step");
Button startSim = new Button("start");
Button stopSim = new Button("stop");
GridPane buttons = new GridPane();
Text num = new Text();
public View(){
buttons.add(nextStep,0,0);
buttons.add(startSim,0,1);
buttons.add(stopSim,0,2);
buttons.setAlignment(Pos.BOTTOM_LEFT);
setCenter(buttons);
setTop(num);
}
public void updateView(int num){
this.num.setText("" + num);
}
}
Main:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
ViewController c = new ViewController(new Simulator(new Model()));
Scene scene = new Scene(c.getView(),200,200);
stage.setScene(scene);
stage.show();
}
}

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.

Working with DateTime as StringProperty

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

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 +
'}';
}
}
}

CursorLoader not updating listView, but cursor has correct results

I´m implementing a searchView to an pre-android 3.0 compatible app and am struggling with proper listView reloaing.
I think there is some problem with the support library, or maybe even even with my rare implementation via textChangeListener on EditText istea of onQueryTextChangedListener that I can´t use.
Any help?
Here is the code:
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.BaseColumns;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.ListView;
import com.actionbarsherlock.app.SherlockFragmentActivity;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuItem;
import com.actionbarsherlock.view.Window;
public class RecipesActivity extends SherlockFragmentActivity {
public static String category;
private ListMenu listMenu;
public static EditText et;
#Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(R.style.Theme_Sherlock);
requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
super.onCreate(savedInstanceState);
setContentView(R.layout.list_activity_layout);
getSupportActionBar().setBackgroundDrawable(getResources()
.getDrawable(R.drawable.ab_bg_black));
category=getIntent().getExtras().getString("category");
FragmentTransaction ft=getSupportFragmentManager().beginTransaction();
listMenu=new ListMenu();
ft.add(R.id.listFragment,listMenu).commit();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
menu.add("Search")
.setIcon(R.drawable.search)
.setActionView(R.layout.collapsible_search)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS |
MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
et=(EditText)menu.getItem(0).getActionView();
return true;
}
public static class ListMenu extends ListFragment
implements LoaderManager.LoaderCallbacks<Cursor> {
private String category;
private String mCurFilter;
private MyCursorAdapter adapter;
private ContentResolver resolver;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.list_layout, container, false);
return view;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//initialization
category=getActivity().getIntent().getStringExtra("category");
setHasOptionsMenu(true);
resolver = this.getActivity().getContentResolver();
final Cursor cursor;
String[] projection=
{BaseColumns._ID,Recipes.NAME,Recipes.KEYWORDS,Recipes.STARRED};
if(category.equals("xxx")){
cursor = resolver.query(Recipes.CONTENT_URI_DRINKS, projection, null, null,
Recipes.STARRED);
} else
if(category.equals("yyy")){
cursor = resolver.query(Recipes.CONTENT_URI_HANGOVERS, projection, null, null,
Recipes.STARRED);
} else
{cursor = resolver.query(Recipes.CONTENT_URI_GAMES, projection, null, null,
Recipes.STARRED);
}
//getActivity().getSupportLoaderManager().initLoader(0, null, this);
getLoaderManager().initLoader(0, null, this);
adapter=new MyCursorAdapter(getActivity(), cursor, true);
setListAdapter(adapter);
et.addTextChangedListener(new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
String text=s.toString();
String newFilter = (String) (!TextUtils.isEmpty(text) ? text : null);
Log.i("text changed listener","ontextchanged:"+newFilter);
if (mCurFilter == null && newFilter == null){;}else
if(mCurFilter != null && mCurFilter.equals(newFilter)){;}else
{
mCurFilter = newFilter;
getLoaderManager().restartLoader(0, null, ListMenu.this);
Log.i("QueryListMenu","onQueryTextChange");
}
}
#Override public void onListItemClick(ListView l, View v, int position, long id) {
Intent intent=new Intent(getActivity(),Viewer.class);
String[] projection = {Recipes.PATH };
String selection= "("+BaseColumns._ID+"="+id+")";
Cursor c=resolver.query(Recipes.getUri(category), projection, selection, null,
Recipes.STARRED+"DESC"+","+Recipes.NAME+" ASC");
c.moveToFirst();
String path=c.getString(c.getColumnIndex("path"));
intent.putExtra("path", category+"/"+path);
Log.i("onlistitemclick","path:"+path+" with id:"+id);
startActivity(intent);
}
#Override
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
Uri firstUri=Recipes.getUri(category);
String selection;
String[] projection = {BaseColumns._ID,Recipes.NAME,Recipes.STARRED};
if (mCurFilter != null) {
selection = "(" + Recipes.NAME + " NOTNULL) AND ("
+ Recipes.KEYWORDS + " like '%"+mCurFilter+"%')";
} else {
selection=null;
}
CursorLoader cursorLoader = new CursorLoader(getActivity(),
firstUri, projection, selection, null, Recipes.STARRED+"
DESC"+","+Recipes.NAME+" ASC");
Log.i("recipes activity","mCurFilter="+mCurFilter);
return cursorLoader;
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
adapter.changeCursor(data);
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
adapter.changeCursor(null);
}
}
So the magic was in just a few lines.
You must add a FilterQueryProvider to the adapter:
adapter=new MyCursorAdapter(this, c, true);
adapter.setFilterQueryProvider(new FilterQueryProvider() {
#Override
public Cursor runQuery(CharSequence constraint) {
String[] projection = { Recipes._ID, Recipes.NAME, Recipes.KEYWORDS,
Recipes.PATH, Recipes.STARRED };
String selection = Recipes.KEYWORDS + " like '%" + constraint.toString() +"%'";
return getContentResolver().query(Recipes.getUri(cat),projection,
selection, null, Recipes.STARRED + " DESC"
+ "," + Recipes.NAME + " ASC");
}
});

Resources