View PDF document in JavaFx programme - javafx

I want to view pdf document already saved in the hard disk using JavaFX by clicking the table view button, I used the below code for update data. I made a button, but I couldn't make the function for the view of the pdf. could you please help me? thank you...
public void loadTpnTable(ObservableList<TPN> obList) {
colDate.setCellValueFactory(new PropertyValueFactory("date"));
colTpnno.setCellValueFactory(new PropertyValueFactory("tpnno"));
colSubject.setCellValueFactory(new PropertyValueFactory("subject"));
tblTpn.setItems(tpnList);
//create callback instance
Callback<TableColumn<TPN, Void>, TableCell<TPN, Void>> updateFactory
= new Callback<TableColumn<TPN, Void>, TableCell<TPN, Void>>() {
#Override
//create new cell
public TableCell<TPN, Void> call(TableColumn<TPN, Void> param) {
final TableCell<TPN, Void> updateCell = new TableCell<TPN, Void>() {
private final Button btnUpdate = new Button("Update");
{
btnUpdate.setOnAction((ActionEvent a) -> {
//select row
int rowIndex = getTableRow().getIndex();
getTableView().getSelectionModel().select(rowIndex);
updateTpn = getTableView().getSelectionModel().getSelectedItem();
txtTpnno.setText(updateTpn.getTpnno());
txtSubject.setText(updateTpn.getSubject());
updateMode(); //disable save button and enable update button
});
}
#Override
public void updateItem(Void item, boolean b) {
super.updateItem(item, b);
if (b) {
setGraphic(null);
} else {
setGraphic(btnUpdate);
}
}
};
return updateCell;
}
};
Callback<TableColumn<TPN, Void>, TableCell<TPN, Void>> openFactory //deleteFactory
= new Callback<TableColumn<TPN, Void>, TableCell<TPN, Void>>() {
#Override
public TableCell<TPN, Void> call(TableColumn<TPN, Void> param) {
final TableCell<TPN, Void> openCell = new TableCell<TPN, Void>() {
private final Button btnOpen = new Button("Open");
{
btnOpen.setOnAction((ActionEvent a) -> {
//select row
int rowIndex = getTableRow().getIndex();
getTableView().getSelectionModel().select(rowIndex);

I use Apache PDFBox for that purpose. I have prepared a little demo how that can be used in JavaFX some time ago which can be found here: https://github.com/mipastgt/JFXToolsAndDemos/tree/master/jfxtools-awtimage-demos/src/main/java/de/mpmediasoft/jfxtools/awtimage/demo

Related

JavaFX add/remove button in TableView cell based on row value

This code has a TavbleView in it, the final column has buttons to act on the column, view & clear. the clear button should only be present or enabled (either would be fine) if the given row is in a known state. Here is the code to always display both buttons, it is coded right now that when the "canClear" property is false, no action is taken:
private void addActionsToTable() {
TableColumn<searchResults, Void> actionColumn = new TableColumn("Action");
actionColumn.setCellFactory(col -> new TableCell<searchResults, Void>() {
private final HBox container;
{
Button viewBtn = new Button("View");
Button clearBtn = new Button("Clear");
viewBtn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
searchResults data = getTableView().getItems().get(getIndex());
gotoView(data.getLogNo());
}
});
clearBtn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
searchResults data = getTableView().getItems().get(getIndex());
String logNo = data.getLogNo();
String serialNumber = data.getSerial();
Boolean canClear = data.getCanClear();
if(canClear)
{
// Take action that has been cut for simplicity
}
}
});
container = new HBox(5, viewBtn, clearBtn);
}
#Override
public void updateItem(Void item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
} else {
setGraphic(container);
}
}
});
SearchTable.getColumns().add(actionColumn);
actionColumn.setPrefWidth(175);
}
What need to happen so that the Clear button is either disabled or not displayed when data.getCanClear() is false?
Assuming your searchResults (sic) class has a BooleanProperty canClearProperty() method:
actionColumn.setCellFactory(col -> new TableCell<searchResults, Void>() {
private final HBox container;
private final Button clearButton ;
{
Button viewBtn = new Button("View");
clearBtn = new Button("Clear");
viewBtn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
searchResults data = getTableView().getItems().get(getIndex());
gotoView(data.getLogNo());
}
});
clearBtn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
searchResults data = getTableView().getItems().get(getIndex());
String logNo = data.getLogNo();
String serialNumber = data.getSerial();
Boolean canClear = data.getCanClear();
if(canClear)
{
// Take action that has been cut for simplicity
}
}
});
container = new HBox(5, viewBtn, clearBtn);
}
#Override
public void updateItem(Void item, boolean empty) {
clearButton.disableProperty().unbind();
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
} else {
clearButton.disableProperty().bind(
getTableView().getItems().get(getIndex())
.canClearProperty().not());
setGraphic(container);
}
}
});

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;
});

Updating current row when clicked upon in TableView JavaFX

So I have code that works if the user selects a different row than the one currently selected
table.getSelectionModel().selectedItemProperty().addListener(
(observable, oldValue, newValue) -> {
if (newValue == null) {
updateDetails(oldValue);
return;
}
updateDetails(newValue);
});
}
However, I want this to work if the user clicks on the same value as well - basically, there's a part of the code that modifies an image shown but that image doesn't update itself unless I click on another row then go back to the row I was previously on. I would like to be able to update the row I'm on simply by clicking on it (which would call updateDetails) but can't seem to figure this out...
Create a custom rowFactory and add a mouse listener to it.
Example
This displays the old value property of the last item clicked and the new item clicked as text of the Label.
TableView<Item> tv = new TableView<>(FXCollections.observableArrayList(new Item("foo"), new Item("bar"), new Item("42")));
Label label = new Label();
TableColumn<Item, String> valueColumn = new TableColumn<>("value");
valueColumn.setCellValueFactory(d -> d.getValue().valueProperty());
tv.getColumns().add(valueColumn);
EventHandler<MouseEvent> eventHandler = new EventHandler<MouseEvent>() {
private Item lastItem;
#Override
public void handle(MouseEvent event) {
if (event.getButton() == MouseButton.PRIMARY) {
TableRow<Item> source = (TableRow<Item>) event.getSource();
if (!source.isEmpty()) {
label.setText(MessageFormat.format("old: {0}; new: {1}", lastItem == null ? null : lastItem.getValue(), (lastItem = source.getItem()).getValue()));
}
}
}
};
tv.setRowFactory(t -> {
TableRow<Item> row = new TableRow();
row.setOnMouseClicked(eventHandler);
return row;
});
public class Item {
public Item() {
}
public Item(String value) {
this.value.set(value);
}
private final StringProperty value = new SimpleStringProperty();
public String getValue() {
return value.get();
}
public void setValue(String val) {
value.set(val);
}
public StringProperty valueProperty() {
return value;
}
}

How to load Different adapters on same listview on Button Click-[Android]?

My requirement is to load different adapters on same listview using buttons to switch between adapters.
I've tried this
public class MainActivity extends ActionBarActivity implements View.OnClickListener {
ImageButton fo,fl,ci,ani;
ListView l;
String flo[],foo[],cit[],an[];
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String flo[] = {"Rose","Lily","SunFlower","Lotus","Tulips"};
String foo[] = {"Maggie","Manchurian","Pizza","Burger","French Fries"};
String cit[] = {"New York","Los Angeles","Las Vegas","Texas","Manhattan"};
String an[] = {"Lion","Tiger","Penguins","Panda","Elephant"};
fo = (ImageButton)findViewById(R.id.imageButton2);
fl = (ImageButton)findViewById(R.id.imageButton);
ci = (ImageButton)findViewById(R.id.imageButton3);
ani = (ImageButton)findViewById(R.id.imageButton4);
l =(ListView)findViewById(R.id.listView);
fo.setOnClickListener(this);
fl.setOnClickListener(this);
ci.setOnClickListener(this);
ani.setOnClickListener(this);
}
#Override
public void onClick(View v) {
if (v.getId()==R.id.imageButton2)
{
food f = new food(this,foo);
l.setAdapter(f);
}
}
}
And I'm getting the following Error:
FATAL EXCEPTION: main
Process: com.example.nike.assignmentadapter, PID: 2565
java.lang.NullPointerException: Attempt to get length of null array
at com.example.nike.assignmentadapter.food.getCount(food.java:41)
at android.widget.ListView.setAdapter(ListView.java:487)
at com.example.nike.assignmentadapter.MainActivity.onClick(MainActivity.java:74)
at android.view.View.performClick(View.java:4756)
at android.view.View$PerformClick.run(View.java:19749)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5221)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)
Food class
public class food extends BaseAdapter implements View.OnClickListener {
String s[];
Context c;
Button li;
Button sh ;
int a[] = {R.drawable.fl1,R.drawable.fl2,R.drawable.fl3,R.drawable.fl4,R.drawable.fl5};
public food(Context context, String[] xperia) {
s = xperia;
c = context;
}
#Override
public int getCount() {
return s.length;
}
#Override
public Object getItem(int position) {
return position;
}
#Override
public long getItemId(int position) {
return s.length;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater ln = (LayoutInflater) c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = ln.inflate(R.layout.food, null);
TextView t= (TextView)v.findViewById(R.id.textView4);
ImageView i = (ImageView)v.findViewById(R.id.imageView4);
li = (Button)v.findViewById(R.id.like4);
sh = (Button)v.findViewById(R.id.share4);
String s1=s[position];
t.setText(s1);
i.setImageResource(a[position]);
li.setOnClickListener(this);
sh.setOnClickListener(this);
return v;
}
#Override
public void onClick(View v) {
if(v.getId()==R.id.like4)
{
AlertDialog.Builder builder= new AlertDialog.Builder(c);
builder.setTitle("Thanks Note");
builder.setMessage("Thank you for Liking ");
builder.setIcon(R.drawable.foo_t);
builder.setPositiveButton("To Share", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// User clicked OK button
Uri webpage = Uri.parse("http://www.sonymobile.com");
Intent webIntent = new Intent(Intent.ACTION_VIEW, webpage);
c.startActivity(webIntent);
}
});
builder.show();
}
else
{
AlertDialog.Builder builder= new AlertDialog.Builder(c);
builder.setTitle("Thanks Note");
builder.setMessage("Thank you for Sharing on Your Facebook");
builder.setIcon(R.drawable.foo_t);
builder.setPositiveButton("To Facebook", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// User clicked OK button
Uri webpage = Uri.parse("https://www.facebook.com");
Intent webIntent = new Intent(Intent.ACTION_VIEW, webpage);
c.startActivity(webIntent);
}
});
builder.show();
}
}
}
public class MainActivity extends ActionBarActivity implements OnClickListener {
ImageButton fo,fl,ci,ani;
ListView l;
String flo[],foo[],cit[],an[];
// these are your global variables available to access
// in any part of the coding. Currently they have no
// data assigned to them in other words they are null
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// here we have assigned data to the global variables declared
// outside of the OnCreate function and available to the onClick
// call.
// Using String flo[] = {John, Doe} means you are creating
// new local versions of the String arrays that are not available
// globally. Instead of assigning the data to the variables
// available globally, and to the onClick call, the data is given
// to the local variables only accessible in the OnCreate function
// function and available until OnCreate finishes.
flo = new String[]{"Rose","Lily","SunFlower","Lotus","Tulips"};
foo = new String[]{"Maggie","Manchurian","Pizza","Burger","French Fries"};
cit = new String[]{"New York","Los Angeles","Las Vegas","Texas","Manhattan"};
an = new String[]{"Lion","Tiger","Penguins","Panda","Elephant"};
fo = (ImageButton)findViewById(R.id.imageButton1);
l =(ListView)findViewById(R.id.listView1);
fo.setOnClickListener(this);
}
#Override
public void onClick(View v) {
if (v.getId()==R.id.imageButton1)
{
System.out.println("foo: " + foo);
food f = new food(this,foo);
l.setAdapter(f);
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}

Type to edit in TableView

Problem
I'd like to switch to edit mode in my TableView as soon as I type. I don't want to doubleclick or press to enter on each and every cell first, that's annoying.
I've come up with the following piece of code. Problem is that it is more or less side-effect programming and I suspect troubles. When you use KEY_RELEASED in order to switch the table into edit mode, the 1st key press gets lost.
So you have to use KEY_PRESSED. It all seems to work fine now, but once in a while you get a race condition and the caret in the TextField cell editor is before the typed text instead of after it. But when you continue typing, then the text gets appended correctly after the existing text.
It appears okay, but from a developing point of view it seems like a mess with race conditions.
Question
Does anyone have a proper way of doing a "type-to-edit" functionality?
Code
Here's the code I've got so far:
public class InlineEditingTableView extends Application {
private final ObservableList<Data> data =
FXCollections.observableArrayList(
new Data(1.,5.),
new Data(2.,6.),
new Data(3.,7.),
new Data(4.,8.)
);
private TableView<Data> table;
#Override
public void start(Stage stage) {
// create edtiable table
table = new TableView<Data>();
table.setEditable(true);
// column 1 contains numbers
TableColumn<Data, Number> number1Col = new TableColumn<>("Number 1");
number1Col.setMinWidth(100);
number1Col.setCellValueFactory( cellData -> cellData.getValue().number1Property());
number1Col.setCellFactory( createNumberCellFactory());
number1Col.setOnEditCommit(new EventHandler<CellEditEvent<Data, Number>>() {
#Override
public void handle(CellEditEvent<Data, Number> t) {
System.out.println( t);
// ((Person) t.getTableView().getItems().get(t.getTablePosition().getRow())).setFirstName(t.getNewValue());
}
});
// column 2 contains numbers
TableColumn<Data, Number> number2Col = new TableColumn<>("Number 2");
number2Col.setMinWidth(100);
number2Col.setCellValueFactory( cellData -> cellData.getValue().number2Property());
number2Col.setCellFactory( createNumberCellFactory());
// add columns & data to table
table.setItems(data);
table.getColumns().addAll( number1Col, number2Col);
// switch to edit mode on keypress
// this must be KeyEvent.KEY_PRESSED so that the key gets forwarded to the editing cell; it wouldn't be forwarded on KEY_RELEASED
table.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if( event.getCode() == KeyCode.ENTER) {
// event.consume(); // don't consume the event or else the values won't be updated;
return;
}
// switch to edit mode on keypress, but only if we aren't already in edit mode
if( table.getEditingCell() == null) {
if( event.getCode().isLetterKey() || event.getCode().isDigitKey()) {
TablePosition focusedCellPosition = table.getFocusModel().getFocusedCell();
table.edit(focusedCellPosition.getRow(), focusedCellPosition.getTableColumn());
}
}
}
});
table.addEventFilter(KeyEvent.KEY_RELEASED, new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if( event.getCode() == KeyCode.ENTER) {
table.getSelectionModel().selectBelowCell();
}
}
});
// single cell selection mode
table.getSelectionModel().setCellSelectionEnabled(true);
table.getSelectionModel().selectFirst();
// add nodes to stage
BorderPane root = new BorderPane();
root.setCenter(table);
Scene scene = new Scene( root, 800,600);
stage.setScene(scene);
stage.show();
}
/**
* Number cell factory which converts strings to numbers and vice versa.
* #return
*/
private Callback<TableColumn<Data, Number>, TableCell<Data, Number>> createNumberCellFactory() {
Callback<TableColumn<Data, Number>, TableCell<Data, Number>> factory = TextFieldTableCell.forTableColumn( new StringConverter<Number>() {
#Override
public Number fromString(String string) {
return Double.parseDouble(string);
}
#Override
public String toString(Number object) {
return object.toString();
}
});
return factory;
}
/**
* Table data container
*/
public static class Data {
private final SimpleDoubleProperty number1;
private final SimpleDoubleProperty number2;
private Data( Double number1, Double number2) {
this.number1 = new SimpleDoubleProperty(number1);
this.number2 = new SimpleDoubleProperty(number2);
}
public final DoubleProperty number1Property() {
return this.number1;
}
public final double getNumber1() {
return this.number1Property().get();
}
public final void setNumber1(final double number1) {
this.number1Property().set(number1);
}
public final DoubleProperty number2Property() {
return this.number2;
}
public final double getNumber2() {
return this.number2Property().get();
}
public final void setNumber2(final double number2) {
this.number2Property().set(number2);
}
}
public static void main(String[] args) {
launch(args);
}
}
To edit immediately on clicking a cell, it makes more sense to me to have the TextFields permanently displayed in the table, instead of transitioning to a special "edit mode" and switch from a Label to a TextField. (I would think of this as having all cells always in "edit mode", which I think makes sense with the behavior you want.)
If that kind of UI works for your requirements, you can just render text fields in the cell and bind bidirectionally the text field's textProperty to the appropriate property in your model. The tricky part here is getting hold of that property: you have to go from the cell to the table row, then to the item for the table row, and then to the property you need. At any time, one of those may change (possibly to null), so you have to deal with those possibilities.
Give the usual example:
public class Person {
// ...
public StringProperty firstNameProperty() { ... }
// etc...
}
You can do
TableView<Person> table = new TableView<>();
TableColumn<Person, String> firstNameCol = new TableColumn<>("First Name");
firstNameCol.setCellValueFactory(cellData -> cellData.getValue().firstNameProperty());
firstNameCol.setCellFactory(col -> {
TableCell<Person, String> cell = new TableCell<>();
TextField textField = new TextField();
cell.graphicProperty().bind(Bindings.when(cell.emptyProperty())
.then((Node)null)
.otherwise(textField));
ChangeListener<Person> rowItemListener = (obs, oldPerson, newPerson) -> {
if (oldPerson != null) {
textField.textProperty().unbindBidirectional(((Person) oldPerson).firstNameProperty());
}
if (newPerson != null) {
textField.textProperty().bindBidirectional(((Person) newPerson).firstNameProperty());
}
};
cell.tableRowProperty().addListener((obs, oldRow, newRow) -> {
if (oldRow != null) {
oldRow.itemProperty().removeListener(rowItemListener);
if (oldRow.getItem() != null) {
textField.textProperty().unbindBidirectional(((Person) oldRow.getItem()).firstNameProperty());
}
}
if (newRow != null) {
newRow.itemProperty().addListener(rowItemListener);
if (newRow.getItem() != null) {
textField.textProperty().bindBidirectional(((Person) newRow.getItem()).firstNameProperty());
}
}
});
return cell ;
});
You can greatly reduce the code complexity here by using the EasyBind framework, which provides (among other things) ways to get "properties of properties" with appropriate handling for null:
TableColumn<Person, String> firstNameCol = new TableColumn<>("First Name");
firstNameCol.setCellValueFactory(cellData -> cellData.getValue().firstNameProperty());
firstNameCol.setCellFactory(col -> {
TableCell<Person, String> cell = new TableCell<>();
TextField textField = new TextField();
cell.graphicProperty().bind(Bindings.when(cell.emptyProperty())
.then((Node)null)
.otherwise(textField));
textField.textProperty().bindBidirectional(
EasyBind.monadic(cell.tableRowProperty())
.selectProperty(TableRow::itemProperty)
.selectProperty(p -> ((Person)p).firstNameProperty()));
return cell ;
});
Here is a complete example, where I factored the cell factory code above into a more general method:
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import org.fxmisc.easybind.EasyBind;
public class LiveTableViewCell extends Application {
#Override
public void start(Stage primaryStage) {
TableView<Person> table = new TableView<>();
table.getItems().addAll(
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")
);
table.getColumns().addAll(
createColumn("First Name", Person::firstNameProperty),
createColumn("Last Name", Person::lastNameProperty),
createColumn("Email", Person::emailProperty)
);
Button button = new Button("Debug");
button.setOnAction(e -> table.getItems().stream().map(p -> String.format("%s %s %s", p.getFirstName(), p.getLastName(), p.getEmail())).forEach(System.out::println));
primaryStage.setScene(new Scene(new BorderPane(table, null, null, button, null), 600, 120));
primaryStage.show();
}
private TableColumn<Person, String> createColumn(String title, Function<Person, Property<String>> property) {
TableColumn<Person, String> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
col.setCellFactory(column -> {
TableCell<Person, String> cell = new TableCell<>();
TextField textField = new TextField();
// Example of maintaining selection behavior when text field gains
// focus. You can also call getSelectedCells().add(...) on the selection
// model if you want to maintain multiple selected cells, etc.
textField.focusedProperty().addListener((obs, wasFocused, isFocused) -> {
if (isFocused) {
cell.getTableView().getSelectionModel().select(cell.getIndex(), cell.getTableColumn());
}
});
cell.graphicProperty().bind(Bindings.when(cell.emptyProperty())
.then((Node)null)
.otherwise(textField));
// If not using EasyBind, you need the following commented-out code in place of the next statement:
// ChangeListener<Person> rowItemListener = (obs, oldPerson, newPerson) -> {
// if (oldPerson != null) {
// textField.textProperty().unbindBidirectional(property.apply((Person)oldPerson));
// }
// if (newPerson != null) {
// textField.textProperty().bindBidirectional(property.apply((Person)newPerson));
// }
// };
// cell.tableRowProperty().addListener((obs, oldRow, newRow) -> {
// if (oldRow != null) {
// oldRow.itemProperty().removeListener(rowItemListener);
// if (oldRow.getItem() != null) {
// textField.textProperty().unbindBidirectional(property.apply((Person)oldRow.getItem()));
// }
// }
// if (newRow != null) {
// newRow.itemProperty().addListener(rowItemListener);
// if (newRow.getItem() != null) {
// textField.textProperty().bindBidirectional(property.apply((Person)newRow.getItem()));
// }
// }
// });
textField.textProperty().bindBidirectional(EasyBind.monadic(cell.tableRowProperty())
.selectProperty(TableRow::itemProperty)
.selectProperty(p -> (property.apply((Person)p))));
return cell ;
});
return col ;
}
public static class Person {
private final StringProperty firstName = new SimpleStringProperty();
private final StringProperty lastName = new SimpleStringProperty();
private final StringProperty email = new SimpleStringProperty();
public Person(String firstName, String lastName, String email) {
setFirstName(firstName);
setLastName(lastName);
setEmail(email);
}
public final StringProperty firstNameProperty() {
return this.firstName;
}
public final java.lang.String getFirstName() {
return this.firstNameProperty().get();
}
public final void setFirstName(final java.lang.String firstName) {
this.firstNameProperty().set(firstName);
}
public final StringProperty lastNameProperty() {
return this.lastName;
}
public final java.lang.String getLastName() {
return this.lastNameProperty().get();
}
public final void setLastName(final java.lang.String lastName) {
this.lastNameProperty().set(lastName);
}
public final StringProperty emailProperty() {
return this.email;
}
public final java.lang.String getEmail() {
return this.emailProperty().get();
}
public final void setEmail(final java.lang.String email) {
this.emailProperty().set(email);
}
}
public static void main(String[] args) {
launch(args);
}
}
(The annoying downcasts here are because TableCell<S,T>.getTableRow() returns a raw TableRow object, instead of a TableRow<S>, for reasons I have never understood.)
I think you can avoid it by implementing custom text field tablecell, where you can put the caret at the end of the item text manually on entering edit mode.
Another approach is to enter edit mode on focus:
table.getFocusModel().focusedCellProperty().addListener(
( ObservableValue<? extends TablePosition> observable, TablePosition oldValue, TablePosition newValue ) ->
{
if ( newValue != null )
{
Platform.runLater( () ->
{
table.edit( newValue.getRow(), newValue.getTableColumn() );
} );
}
}
);
a couple of years late, but I actually found a solution to this (using a Robot).
this.setOnKeyTyped(x -> {
String typed = x.getCharacter();
//can make editing start only when certain keys (e.g. digits) are typed.
if(typed != null && typed.matches("[0-9]")) {
Robot robot = new Robot();
robot.keyPress(KeyCode.ENTER);
}
});

Resources