javafx: Display hyperlink in column and opens a new window - javafx

I want to display only one column cell in a table with hyperlink and by clicking that it should display some text in new window. Please help me with this

You need to implement a custom cell type to do this:
public class HyperlinkTableCell<S, T> extends TableCell<S, T> {
private final Hyperlink link;
private final Set<T> visitedLinks;
private final Function<? super T, String> converter;
private HyperlinkTableCell(Set<T> visitedLinks, final Consumer<? super T> handler, Function<? super T, String> converter) {
link = new Hyperlink();
if (handler != null) {
link.setOnAction(evt -> {
T item = getItem();
handler.accept(item);
// keep track of visited links
visitedLinks.add(item);
});
}
this.visitedLinks = visitedLinks;
this.converter = converter;
}
#Override
protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setGraphic(null);
} else {
setGraphic(link);
link.setText(converter.apply(item));
// restore visited state
link.setVisited(visitedLinks.contains(item));
}
}
public static <U, V> Callback<TableColumn<U, V>, TableCell<U, V>> forTableColumn(
final Consumer<? super V> handler,
final Function<? super V, String> converter) {
final Set<V> set = new HashSet<>();
return c -> new HyperlinkTableCell<>(set, handler, converter);
}
public static <U, V> Callback<TableColumn<U, V>, TableCell<U, V>> forTableColumn(
final Consumer<? super V> handler) {
return forTableColumn(handler, item -> item == null ? "" : item.toString());
}
}
Usage
column.setCellFactory(HyperlinkTableCell.forTableColumn(item -> {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setContentText("You clicked " + item);
alert.show();
}));

Related

Bind CheckBoxTableCell to BooleanBinding

I want to bind a CheckBox in a TableViewCell to a BooleanBinding. The following sample consists of a TableView with a column name and isEffectiveRequired. The checkbox in the column is bound to the Expression:
isRequired.or(name.isEqualTo("X"))
So an item is "effectivly required" when the item in the row is required OR the name is an X, then the expression should be true.
Unfortunately the CheckBox does not reflect the change. For debugging I added a textfield, showing the nameProperty, requiredProperty and the computed effectiveRequiredProperty.
Interestingly when returning just the isRequiredProperty instead of the binding the checkbox works.
public ObservableBooleanValue effectiveRequiredProperty() {
// Bindings with this work:
// return isRequired;
// with this not
return isRequired.or(name.isEqualTo(SPECIAL_STRING));
}
So what is the difference between a Property and a ObservableValue in regard to a CheckBox?
public class TableCellCBBinding extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
init(primaryStage);
primaryStage.show();
}
private void init(Stage primaryStage) {
primaryStage.setScene(new Scene(buildContent()));
}
private Parent buildContent() {
TableView<ViewModel> tableView = new TableView<>();
tableView.setItems(sampleEntries());
tableView.setEditable(true);
tableView.getColumns().add(buildRequiredColumn());
tableView.getColumns().add(buildNameColumn());
// Add a Textfield to show the values for the first item
// As soon as the name is set to "X", the effectiveRequiredProperty should evaluate to true and the CheckBox should reflect this but it does not
TextField text = new TextField();
ViewModel firstItem = tableView.getItems().get(0);
text.textProperty()
.bind(Bindings.format("%s | %s | %s", firstItem.nameProperty(), firstItem.isRequiredProperty(), firstItem.effectiveRequiredProperty()));
return new HBox(text, tableView);
}
private TableColumn<ViewModel, String> buildNameColumn() {
TableColumn<ViewModel, String> nameColumn = new TableColumn<>("Name");
nameColumn.setCellValueFactory(new PropertyValueFactory<>("name"));
nameColumn.setCellFactory(TextFieldTableCell.forTableColumn());
nameColumn.setEditable(true);
return nameColumn;
}
private TableColumn<ViewModel, Boolean> buildRequiredColumn() {
TableColumn<ViewModel, Boolean> requiredColumn = new TableColumn<>("isEffectiveRequired");
requiredColumn.setMinWidth(50);
// This is should bind my BindingExpression from to ViewModel to the CheckBox
requiredColumn.setCellValueFactory( p -> p.getValue().effectiveRequiredProperty());
requiredColumn.setCellFactory( CheckBoxTableCell.forTableColumn(requiredColumn));
return requiredColumn;
}
private ObservableList<ViewModel> sampleEntries() {
return FXCollections.observableArrayList(
new ViewModel(false, "A"),
new ViewModel(true, "B"),
new ViewModel(false, "C"),
new ViewModel(true, "D"),
new ViewModel(false, "E"));
}
public static class ViewModel {
public static final String SPECIAL_STRING = "X";
private final StringProperty name;
private final BooleanProperty isRequired;
public ViewModel(boolean isRequired, String name) {
this.name = new SimpleStringProperty(this, "name", name);
this.isRequired = new SimpleBooleanProperty(this, "isRequired", isRequired);
this.name.addListener((observable, oldValue, newValue) -> System.out.println(newValue));
}
public StringProperty nameProperty() {return name;}
public final String getName(){return name.get();}
public final void setName(String value){
name.set(value);}
public boolean isRequired() {
return isRequired.get();
}
public BooleanProperty isRequiredProperty() {
return isRequired;
}
public void setRequired(final boolean required) {
this.isRequired.set(required);
}
public ObservableBooleanValue effectiveRequiredProperty() {
// Bindings with this work:
// return isRequired;
// with this not
return isRequired.or(name.isEqualTo(SPECIAL_STRING));
}
}
}
When typing an X into the name the checkbox in the row should be checked.
When typing an X into the name the checkbox in the row is not checked. It's never checked like it is not bound at all.
CheckBoxXXCells don't live up to their doc when it comes to binding their selected state, f.i. (citing here just for signature, even if not set explicitely):
public final Callback <Integer,​ObservableValue<Boolean>> getSelectedStateCallback()
Returns the Callback that is bound to by the CheckBox shown on screen.
clearly talks about an ObservableValue, so we would expect that it at least shows the selection state.
Actually, the implementation does exactly nothing if it's not a property, the relevant part from its updateItem:
StringConverter<T> c = getConverter();
if (showLabel) {
setText(c.toString(item));
}
setGraphic(checkBox);
if (booleanProperty instanceof BooleanProperty) {
checkBox.selectedProperty().unbindBidirectional((BooleanProperty)booleanProperty);
}
ObservableValue<?> obsValue = getSelectedProperty();
if (obsValue instanceof BooleanProperty) {
booleanProperty = (ObservableValue<Boolean>) obsValue;
checkBox.selectedProperty().bindBidirectional((BooleanProperty)booleanProperty);
}
checkBox.disableProperty().bind(Bindings.not(
getTableView().editableProperty().and(
getTableColumn().editableProperty()).and(
editableProperty())
));
To work around, use a custom cell that updates the selected state in its updateItem. With the added quirk that we need to disable the check's firing to really keep the visuals in sync with backing state:
requiredColumn.setCellFactory(cc -> {
TableCell<ViewModel, Boolean> cell = new TableCell<>() {
CheckBox check = new CheckBox() {
#Override
public void fire() {
// do nothing - visualizing read-only property
// could do better, like actually changing the table's
// selection
}
};
{
getStyleClass().add("check-box-table-cell");
check.setOnAction(e -> {
e.consume();
});
}
#Override
protected void updateItem(Boolean item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
setGraphic(null);
} else {
check.setSelected(item);
setGraphic(check);
}
}
};
return cell;
});

How to get previous table cell value inside cell's updateItem method in JavaFx?

I want to have a TableCell with a custom graphic that animates on value change, where the animation type depends on the nature of the change, so I need to know the previous value to compare to the current one.
Here's your typical custom table cell (Kotlin code):
class MyTableCell<S, T> : TableCell<S, T>() {
override fun updateItem(item: T?, empty: Boolean) {
if (empty || field == null) {
text = null
graphic = null
} else {
// need to get the old value here
}
}
I see that the super method in javafx/scene/control/TableCell.java does know the old value and uses it compare it to the current one, but the override only gets the newValue:
private void updateItem(int oldIndex) {
...
final T oldValue = getItem();
...
final T newValue = currentObservableValue == null ? null : currentObservableValue.getValue();
...
if (oldIndex == index) {
if (!isItemChanged(oldValue, newValue)) {
...
}
...
}
...
updateItem(newValue, false); // sadly, `oldValue` is not passed
I can only think of an ugly workaround, so I wonder if there is some idiomatic way to get the old cell value?
Here is a sample app:
import javafx.application.Application
import javafx.beans.property.SimpleDoubleProperty
import javafx.collections.FXCollections
import javafx.scene.Scene
import javafx.scene.control.*
import javafx.scene.control.cell.PropertyValueFactory
import javafx.stage.Stage
import kotlinx.coroutines.experimental.delay
import kotlinx.coroutines.experimental.launch
import tornadofx.*
class Foo {
val barProperty = SimpleDoubleProperty()
var bar: Double
get() = barProperty.get()
set(value) = barProperty.set(value)
}
class FooApp: Application() {
override fun start(primaryStage: Stage) {
val foos = FXCollections.observableArrayList(
Foo().apply { bar = 42.0 }
)
val table = TableView<Foo>(foos)
val barColumn = TableColumn<Foo, Double>("Bar")
barColumn.cellValueFactory = PropertyValueFactory<Foo, Double>("bar")
barColumn.setCellFactory {
FooTableCell<Foo, Double> { "%.2f".format(it) }
}
table.columns.add(barColumn)
val scene = Scene(table, 400.0, 200.0)
primaryStage.scene = scene
primaryStage.title = "Table Cell"
primaryStage.show()
launch {
while (isActive) {
delay(500)
val oldFoo = foos[0]
// Replacing the old Foo instance with a new one,
// updating the value of the `bar` field:
foos[0] = Foo().apply {
bar = oldFoo.bar - 1.0 + Math.random() * 2.0
}
// because a change to a field cannot be detected by an observable list
// and so does not propagates to the table. This won't result in
// a visible change:
// foos[0].bar = foos[0].bar - 1.0 + Math.random() * 2.0
}
}
}
}
class FooTableCell<S, T>(private val format: (T) -> String) : TableCell<S, T>() {
init {
contentDisplay = ContentDisplay.GRAPHIC_ONLY
itemProperty().addListener(ChangeListener { obs, oldItem, newItem ->
if (newItem != null && oldItem != null && newItem != oldItem) {
// This is never true.
println("!!! Old: $oldItem, New: $newItem")
} else {
println("Change listener:\nOld: $oldItem, New: $newItem\n")
}
})
}
override fun updateItem(item: T?, empty: Boolean) {
val oldItem = this.item
super.updateItem(item, empty)
if (item != null && oldItem != null && item != oldItem) {
// This is never true.
println("!!! Old: $oldItem, New: $item")
} else {
println("updateItem:\nOld: $oldItem, New: $item\n")
}
if (empty || item == null) {
graphic = null
text = null
} else if (tableRow != null) {
val cell = this
graphic = Label().apply {
textProperty().bindBidirectional(cell.textProperty())
}
text = format(item)
}
}
}
fun main(args: Array<String>) {
Application.launch(FooApp::class.java, *args)
}
The actual value of the item property is changed by the default implementation of the updateItem() method, so just get the value before calling the default implementation:
public class MyTableCell<S,T> extends TableCell<S,T> {
#Override
protected void updateItem(T item, boolean empty) {
T oldItem = getItem();
super.updateItem(item, empty) ;
// ...
}
}
Alternatively, you can just register a change listener with the itemProperty():
public class MyTableCell<S,T> extends TableCell<S,T> {
public MyTableCell() {
itemProperty().addListener((obs, oldItem, newItem) -> {
// do animation here...
});
}
#Override
protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
// other functionality here...
}
}
Here is a SSCCE demonstrating both techniques:
import java.util.Random;
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class TableCellWithChange extends Application {
public static class ChangeAwareCell<S,T> extends TableCell<S,T> {
public ChangeAwareCell() {
itemProperty().addListener((obs, oldItem, newItem) -> {
System.out.printf("In listener, value for %s changed from %s to %s%n", getTableRow().getItem(), oldItem, newItem);
});
}
#Override
protected void updateItem(T item, boolean empty) {
T oldItem = getItem();
super.updateItem(item, empty);
if (empty) {
setText(null);
} else {
setText(item.toString());
System.out.printf("Change in %s from %s to %s %n", getTableView().getItems().get(getIndex()), oldItem, item);
}
}
}
#Override
public void start(Stage primaryStage) {
TableView<Item> table = new TableView<>();
TableColumn<Item, String> itemCol = column("Item", Item::nameProperty);
table.getColumns().add(itemCol);
TableColumn<Item, Number> valueCol = column("Value", Item:: valueProperty);
table.getColumns().add(valueCol);
valueCol.setCellFactory(tc -> new ChangeAwareCell<>());
TableColumn<Item, Void> changeCol = new TableColumn<>();
changeCol.setCellFactory(tc -> new TableCell<>() {
private Button incButton = new Button("^");
private Button decButton = new Button("v");
private HBox graphic = new HBox(2, incButton, decButton);
{
incButton.setOnAction(e -> {
Item item = getTableRow().getItem();
item.setValue(item.getValue()+1);
});
decButton.setOnAction(e -> {
Item item = getTableRow().getItem();
item.setValue(item.getValue()-1);
});
}
#Override
protected void updateItem(Void item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
} else {
setGraphic(graphic);
}
}
});
table.getColumns().add(changeCol);
Random rng = new Random();
for (int i = 1 ; i <= 20 ; i++) {
table.getItems().add(new Item("Item "+i, rng.nextInt(100)));
}
Scene scene = new Scene(table);
primaryStage.setScene(scene);
primaryStage.show();
}
private <S,T> TableColumn<S,T> column(String text, Function<S, ObservableValue<T>> property) {
TableColumn<S,T> col = new TableColumn<>(text);
col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
col.setPrefWidth(150);
return col ;
}
public static class Item {
private final StringProperty name = new SimpleStringProperty();
private final IntegerProperty value = new SimpleIntegerProperty();
public Item(String name, int value) {
setName(name);
setValue(value);
}
#Override
public String toString() {
return getName();
}
public final StringProperty nameProperty() {
return this.name;
}
public final String getName() {
return this.nameProperty().get();
}
public final void setName(final String name) {
this.nameProperty().set(name);
}
public final IntegerProperty valueProperty() {
return this.value;
}
public final int getValue() {
return this.valueProperty().get();
}
public final void setValue(final int value) {
this.valueProperty().set(value);
}
}
public static void main(String[] args) {
launch(args);
}
}
Of course, these items will also change, e.g. when the user scrolls around the table, or if the cells are reused in other ways; so this might not be exactly when you want. You might want to add a listener to the appropriate property in the model instead. The simplest way to do this is probably to store a reference to the actual property from the model in the cell, and update that reference when the cell is updated:
public static class ChangeAwareCell<S,T> extends TableCell<S,T> {
private Function<S, ObservableValue<T>> property ;
private ObservableValue<T> lastObservableValue ;
private ChangeListener<T> listener = (obs, oldValue, newValue) -> valueChanged(oldValue, newValue);
public ChangeAwareCell(Function<S, ObservableValue<T>> property) {
this.property = property ;
}
private void valueChanged(T oldValue, T newValue) {
System.out.printf("Value changed from %s to %s %n", oldValue, newValue);
}
#Override
protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (lastObservableValue != null) {
lastObservableValue.removeListener(listener);
}
if (empty) {
setText(null);
} else {
lastObservableValue = property.apply(getTableRow().getItem());
lastObservableValue.addListener(listener);
setText(item.toString());
}
}
}
And of course make the corresponding change:
valueCol.setCellFactory(tc -> new ChangeAwareCell<>(Item::valueProperty));

Property in Cell Factory

Hi I have a little problem in this Cell Factory :
private Callback<TableColumn<Member, String>,
TableCell<Member, String>> setPhotoCellFactory() {
Callback<TableColumn<Member, String>, TableCell<Member, String>> callback = new Callback<TableColumn<Member, String>, TableCell<Member, String>>() {
#Override
public TableCell<Member, String> call(TableColumn<Member, String> param) {
TableCell<Member, String> cell = new TableCell<Member, String>() {
#Override
protected void updateItem(String item, boolean empty) {
System.out.println(item);
super.updateItem(item, empty);
ImageView imageview = new ImageView();
imageview.setFitHeight(TABLE_IMAGE_MEMBER_HEIGHT);
imageview.setFitWidth(TABLE_IMAGE_MEMBER_WIDTH);
if(item != null) {
imageview.setImage(new Image(item));
setGraphic(imageview);
} else {
if(!empty) {
imageview.setImage(new Image(ImageFiles.GENERIC_MALE.toString()));
setGraphic(imageview);
} else {
setGraphic(null);
}
}
}
};
return cell;
}
};
return callback;
}
In Member object when Photo is null getter return Image corresponding a his gender. In this callback it seems that getter is not used.
How can I acces member property in this case ??
#EDIT
In Member class :
public String getPhoto() {
if (photo.getValue() != null && !photo.getValue().equals("")) {
return photo.get();
} else if (getGender().equals(Gender.F)) {
return ImageFiles.GENERIC_FEMALE.toString();
} else {
return ImageFiles.GENERIC_MALE.toString();
}
}
adding CellValueFactory :
this.simPhotoColumn.setCellValueFactory(data -> data.getValue().photoProperty());
OK I was found the solution. My error that I was searching how set image in CellFactory.
The response is to set custom CellValueFactory.
I was replaced this :
this.simPhotoColumn.setCellValueFactory(data -> data.getValue().photoProperty());
by :
this.memberPhoto.setCellValueFactory(getPhotoValueFactory());
private Callback<TableColumn.CellDataFeatures<Member, String>, ObservableValue<String>> getPhotoValueFactory() {
Callback<TableColumn.CellDataFeatures<Member, String>, ObservableValue<String>> callback = param -> new ReadOnlyObjectWrapper<>(param.getValue().getPhoto());
return callback;
}

JavaFx Create Table Cell Accepts numbers only?

I have TableView with column inside it that must only accept numbers.
and I added onMouseClickListener to enter edit mode on the mouse click instead of double click on the cell
I want a way to not allowing the user to enter any character except numbers. My code is:
Callback<TableColumn<DailyDetails, String>, TableCell<DailyDetails, String>> defaultCellFactory
= TextFieldTableCell.<DailyDetails>forTableColumn();
dailyCredit.setCellFactory(column -> {
TableCell<DailyDetails, String> cell = defaultCellFactory.call(column);
cell.setOnMouseClicked(e -> {
if (!cell.isEditing() && !cell.isEmpty()) {
cell.getTableView().edit(cell.getIndex(), column);
}
});
return cell;
});
I implemented Table cell from the scratch:
class NumberCell extends TableCell<DailyDetails, String> {
private TextField textField;
public NumberCell() {
}
#Override
public void startEdit() {
super.startEdit();
if (textField == null) {
createTextField();
}
setGraphic(textField);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
textField.selectAll();
}
#Override
public void cancelEdit() {
super.cancelEdit();
setText(String.valueOf(getItem()));
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
#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());
}
setGraphic(textField);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
} else {
setText(getString());
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
}
}
private void createTextField() {
textField = new TextField(getString());
//textField.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);
textField.lengthProperty().addListener(new ChangeListener<Number>(){
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
if (newValue.intValue() > oldValue.intValue()) {
char ch = textField.getText().charAt(oldValue.intValue());
// Check if the new character is the number or other's
if (!(ch >= '0' && ch <= '9' )) {
// if it's not number then just setText to previous one
textField.setText(textField.getText().substring(0,textField.getText().length()-1));
}
}
}
});
}
private String getString() {
return getItem() == null ? "" : getItem().toString();
}
}
Callback<TableColumn<DailyDetails, String>,
TableCell<DailyDetails, String>> cellFactory
= (TableColumn<DailyDetails, String> p) -> new NumberCell();
dailyDebit.setCellFactory(cellFactory);
the problem is i lost the on mouse listener cell.setOnMouseClicked!!!
how do i get the cell again to assign the listener ???
Just for driving the new api into everybody's brain: a full example with a slightly different TextFormatter (than in the other answer) that is Locale-aware and (dirtily!) hooked into core TextFieldTableCell, can be used in any custom editing TableCell as well:
/**
* Example of how-to use a TextFormatter in a editing TableCell.
*/
public class CellFormatting extends Application {
private Parent getContent() {
ObservableList<IntData> data = FXCollections.observableArrayList(
new IntData(1), new IntData(2), new IntData(3)
);
TableView<IntData> table = new TableView<>(data);
table.setEditable(true);
TableColumn<IntData, Integer> column = new TableColumn<>("Data");
column.setCellValueFactory(new PropertyValueFactory("data"));
// core default: will throw exception on illegal values
// column.setCellFactory(TextFieldTableCell.forTableColumn(new IntegerStringConverter()));
NumberFormat format = NumberFormat.getIntegerInstance();
UnaryOperator<TextFormatter.Change> filter = c -> {
if (c.isContentChange()) {
ParsePosition parsePosition = new ParsePosition(0);
// NumberFormat evaluates the beginning of the text
format.parse(c.getControlNewText(), parsePosition);
if (parsePosition.getIndex() == 0 ||
parsePosition.getIndex() < c.getControlNewText().length()) {
// reject parsing the complete text failed
return null;
}
}
return c;
};
column.setCellFactory(c -> new ValidatingTextFieldTableCell<>(
// note: each cell needs its own formatter
// see comment by #SurprisedCoconut
new TextFormatter<Integer>(
// note: should use local-aware converter instead of core!
new IntegerStringConverter(), 0,
filter)));
table.getColumns().add(column);
VBox box = new VBox(table);
return box;
}
/**
* TextFieldTableCell that validates input with a TextFormatter.
* <p>
* Extends TextFieldTableCell, accesses super's private field reflectively.
*
*/
public static class ValidatingTextFieldTableCell<S, T> extends TextFieldTableCell<S, T> {
private TextFormatter<T> formatter;
private TextField textAlias;
public ValidatingTextFieldTableCell() {
this((StringConverter<T>)null);
}
public ValidatingTextFieldTableCell(StringConverter<T> converter) {
super(converter);
}
public ValidatingTextFieldTableCell(TextFormatter<T> formatter) {
super(formatter.getValueConverter());
this.formatter = formatter;
}
/**
* Overridden to install the formatter. <p>
*
* Beware: implementation detail! super creates and configures
* the textField lazy on first access, so have to install after
* calling super.
*/
#Override
public void startEdit() {
super.startEdit();
installFormatter();
}
private void installFormatter() {
if (formatter != null && isEditing() && textAlias == null) {
textAlias = invokeTextField();
textAlias.setTextFormatter(formatter);
}
}
private TextField invokeTextField() {
Class<?> clazz = TextFieldTableCell.class;
try {
Field field = clazz.getDeclaredField("textField");
field.setAccessible(true);
return (TextField) field.get(this);
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
}
public static class IntData {
IntegerProperty data = new SimpleIntegerProperty(this, "data");
public IntData(int value) {
setData(value);
}
public void setData(int value) {
data.set(value);
}
public int getData() {
return data.get();
}
public IntegerProperty dataProperty() {
return data;
}
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setScene(new Scene(getContent()));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
BTW, the formatter is re-used from another question where the task at hand was to restrict input into a Spinner.
Use a TextFormatter on the TextField like this:
TextFormatter<String> formatter = new TextFormatter<String>( change -> {
change.setText(change.getText().replaceAll("[^0-9.,]", ""));
return change;
});
textField.setTextFormatter(formatter);
Works with Java8u40 upwards. Use e. g. the TableView example from the Oracle site as base.

Custom Java-fx cellfactory messes up the setCellValueFactory

After messing around with Netbeans and Scenebuilder for a while I'm stuck at a problem I can't quite understand. I use a custom cellfactory to bind a doubleclick event to the cells in my tableview. But when I set the cellfactory and a cellValueFactory only the custom cellFactory has an effect.
I'm trying to populate a tableview with data from a number of objects and bind a double click event to the cells of the first column. Populating is not the problem, I just used
idNumber.setCellValueFactory(new PropertyValueFactory<LiveStock, String>("idNumber"));
status.setCellValueFactory(new PropertyValueFactory<LiveStock, String>("status"));
I then googled around to figure out how to bind a doubleclick event to the cells of the table and found javafx, TableView: detect a doubleclick on a cell
amongst others...
I defined a custom cellFactory like this:
Callback<TableColumn<LiveStock, String>, TableCell<LiveStock, String>> cellFactory =
new Callback<TableColumn<LiveStock, String>, TableCell<LiveStock, String>>() {
#Override
public TableCell call(TableColumn p) {
TableCell cell = new TableCell<LiveStock, String>() {};
cell.addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (event.getClickCount() == 2) {
System.out.println("double clicked!");
TableCell c = (TableCell) event.getSource();
System.out.println("Livestock ID: " + c.getId());
}
}
});
return cell;
}
I removed the update and toString methods just to see if they where the reason I ran in to problems.
So I tried
idNumber.setCellFactory(cellFactory);
idNumber.setCellValueFactory(new PropertyValueFactory<LiveStock, String>("idNumber"));
This results in my cells beeing empty, but having the double click binding
any ideas?
My LiveStock class looks like this:
package projekt1.fx;
import javafx.beans.property.SimpleStringProperty;
public class LiveStock {
private final int idNumber;
private final SimpleStringProperty ownerID;
private SimpleStringProperty status;
private double lat;
private double longd;
public LiveStock(int idNumber, String ownerID) {
this.idNumber = idNumber;
this.ownerID = new SimpleStringProperty(ownerID);
this.status = new SimpleStringProperty("ok");
}
public int getIdNumber() {
return this.idNumber;
}
// public void setIdNumber(int number) {
// this.idNumber = number;
// }
public String getOwnerID(){
return ownerID.get();
}
public void setOwnerID(String id){
ownerID.set(id);
}
public String getStatus(){
return status.get();
}
public void setStatus(String st){
status.set(st);
}
}
The cellfactory now looks like this:
Callback<TableColumn<LiveStock, String>, TableCell<LiveStock, String>> cellFactory =
new Callback<TableColumn<LiveStock, String>, TableCell<LiveStock, String>>() {
#Override
public TableCell call(TableColumn p) {
TableCell cell = new TableCell<LiveStock, String>() {
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
// setText("HELLO WORLD!");
setText(empty ? null : getString());
}
};
cell.addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (event.getClickCount() == 2) {
System.out.println("double clicked!");
TableCell c = (TableCell) event.getSource();
System.out.println("Livestock ID: " + c.getId());
togglePopup(null);
}
}
});
return cell;
}
};
Documentation of Cell API says:
Because by far the most common use case for cells is to show text to a
user, this use case is specially optimized for within Cell. This is
done by Cell extending from Labeled. This means that subclasses of
Cell need only set the text property, rather than create a separate
Label and set that within the Cell. ...
The current source code of Cell constructor sets the text to null:
public Cell() {
setText(null);
...
}
The subclass IndexedCell and sub-subclass TableCell, both of them don't set the text of Labeled.
The text is set by default cell factory of TableColumn in source code.
public static final Callback<TableColumn<?,?>, TableCell<?,?>> DEFAULT_CELL_FACTORY = new Callback<TableColumn<?,?>, TableCell<?,?>>() {
#Override public TableCell<?,?> call(TableColumn<?,?> param) {
return new TableCell() {
#Override protected void updateItem(Object item, boolean empty) {
if (item == getItem()) return;
super.updateItem(item, empty);
if (item == null) {
super.setText(null);
super.setGraphic(null);
} else if (item instanceof Node) {
super.setText(null);
super.setGraphic((Node)item);
} else {
super.setText(item.toString());
super.setGraphic(null);
}
}
};
}
};
However by defining your own cell factory that creates new TableCell but does not set the text in its overriden updateItem() method, will be resulting an empty (=null) column cell text. So yes the reason of the problem was removing updateItem method, which calls setText(...) internally.
EDIT:
Specify the generic types explicitly for TableColumns as,
TableColumn<LiveStock, Integer> idNumber = new TableColumn<LiveStock, Integer>("ID No");
This will avoid type mismatches or wrong type castings.
Then the cell factory callback for your use case will be
Callback<TableColumn<LiveStock, Integer>, TableCell<LiveStock, Integer>> cellFactory =
new Callback<TableColumn<LiveStock, Integer>, TableCell<LiveStock, Integer>>() {
public TableCell<LiveStock, Integer> call(TableColumn<LiveStock, Integer> p) {
TableCell<LiveStock, Integer> cell = new TableCell<LiveStock, Integer>() {
#Override
public void updateItem(Integer item, boolean empty) {
super.updateItem(item, empty);
setText((item == null || empty) ? null : item.toString());
setGraphic(null);
}
};
cell.addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (event.getClickCount() > 1) {
System.out.println("double clicked!");
TableCell c = (TableCell) event.getSource();
System.out.println("Cell text: " + c.getText());
}
}
});
return cell;
}
};
What is changed?
The type of idNumber in LiveStock is int. By defining new TableColumn<LiveStock, Integer> we say this is a column from LiveStock row for its attribute idNumber which has a type int, but the generic types must be a reference type, it cannot be TableCell<LiveStock, int> so we define TableCell<LiveStock, Integer>. The thumb of rule: row item class's attribute type should match the second generic type parameter of TableColumn and due to this the parameter of TableCell also.
getString method is defined in the referenced answer link mentioned by you. But it is just a user defined method, not mandatory to use it.

Resources