Expand table row on mouse click - javafx

I found these examples:
http://www.jeasyui.com/tutorial/datagrid/datagrid21.php\
Can a table row expand and close?
Basically I want to create a JavaFX table which I can expand in order to see more data. Is there any similar example written in JavaFX?

EDIT
So, after reworking the problem with tableView specifics, I (sort of) quickly hacked together this example. Keep in mind, I didn't use the animation mentioned in the original answer, although it would be easy enough to adapt, and I didn't replicate the provided example exactly at all, since I honestly, didn't have time. But this gives the basic accordion feel, where you would just need to spend time messing around with various width and height properties of different fields to achieve something that was exactly that. (in the handler you might want to even insert a row where the first column has a huge width and a nested table view to achieve sort of exactly what they were doing). again, this is with 1 column, and it shows the basics of adding a bit of added information on expansion, you could take this as far as you want:
fileChooserExample.java:
package filechooserexample;
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.event.*;
import javafx.geometry.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.*;
import javafx.util.Callback;
public class FileChooserExample extends Application {
public static void main(String[] args) { launch(args); }
#Override public void start(final Stage stage) {
stage.setTitle("People");
// stage.getIcons().add(new Image("http://icons.iconarchive.com/icons/icons-land/vista-people/72/Historical-Viking-Female-icon.png")); // icon license: Linkware (Backlink to http://www.icons-land.com required)
// create a table.
final TableView<Person> table = new TableView<>(
FXCollections.observableArrayList(
new Person("Jacob", "Smith"),
new Person("Isabella", "Johnson"),
new Person("Ethan", "Williams"),
new Person("Emma", "Jones"),
new Person("Michael", "Brown")
)
);
// define the table columns.
TableColumn<Person, Boolean> actionCol = new TableColumn<>("Action");
actionCol.setSortable(false);
actionCol.setPrefWidth(1000);
// define a simple boolean cell value for the action column so that the column will only be shown for non-empty rows.
actionCol.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Person, Boolean>, ObservableValue<Boolean>>() {
#Override public ObservableValue<Boolean> call(TableColumn.CellDataFeatures<Person, Boolean> features) {
return new SimpleBooleanProperty(features.getValue() != null);
}
});
// create a cell value factory with an add button for each row in the table.
actionCol.setCellFactory(new Callback<TableColumn<Person, Boolean>, TableCell<Person, Boolean>>() {
#Override public TableCell<Person, Boolean> call(TableColumn<Person, Boolean> personBooleanTableColumn) {
return new AddPersonCell(stage, table);
}
});
table.getColumns().setAll(actionCol);
table.setColumnResizePolicy(TableView.UNCONSTRAINED_RESIZE_POLICY);
stage.setScene(new Scene(table));
stage.show();
}
/** A table cell containing a button for adding a new person. */
private class AddPersonCell extends TableCell<Person, Boolean> {
// a button for adding a new person.
final Button addButton = new Button("Add");
// pads and centers the add button in the cell.
final VBox paddedButton = new VBox();
final HBox mainHolder = new HBox();
// records the y pos of the last button press so that the add person dialog can be shown next to the cell.
final DoubleProperty buttonY = new SimpleDoubleProperty();
/**
* AddPersonCell constructor
* #param stage the stage in which the table is placed.
* #param table the table to which a new person can be added.
*/
AddPersonCell(final Stage stage, final TableView table) {
paddedButton.setPadding(new Insets(3));
paddedButton.getChildren().add(addButton);
mainHolder.getChildren().add(paddedButton);
addButton.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent mouseEvent) {
buttonY.set(mouseEvent.getScreenY());
if (getTableRow().getPrefHeight() == 100){
getTableRow().setPrefHeight(35);
paddedButton.getChildren().remove(1);
getTableRow().autosize();
}
else{
getTableRow().setPrefHeight(100);
Label myLabel = new Label();
myLabel.setText("This is new label text!");
myLabel.setTextFill(Color.BLACK);
paddedButton.getChildren().add(myLabel);
getTableRow().autosize();
}
}
});
addButton.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent actionEvent) {
table.getSelectionModel().select(getTableRow().getIndex());
}
});
}
/** places an add button in the row only if the row is not empty. */
#Override protected void updateItem(Boolean item, boolean empty) {
super.updateItem(item, empty);
if (!empty) {
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
setGraphic(paddedButton);
}
}
}
}
Person.java:
package filechooserexample;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Person {
private StringProperty firstName;
private StringProperty lastName;
public Person(String firstName, String lastName) {
setFirstName(firstName);
setLastName(lastName);
}
public final void setFirstName(String value) { firstNameProperty().set(value); }
public final void setLastName(String value) { lastNameProperty().set(value); }
public String getFirstName() { return firstNameProperty().get(); }
public String getLastName() { return lastNameProperty().get(); }
public StringProperty firstNameProperty() {
if (firstName == null) firstName = new SimpleStringProperty(this, "firstName");
return firstName;
}
public StringProperty lastNameProperty() {
if (lastName == null) lastName = new SimpleStringProperty(this, "lastName");
return lastName;
}
}
Again, pardon the seemingly hackery of adding the various buttons with the named columns that do nothing, It just got super busy here so I borrowed the main table structure from :
original SO table dynamic row addition question
Who did a wonderful job of adding additional rows to a table.
again, if this is not at all what you need let me know, and I'll try to help as best I can.

Related

Update index of row in Tableview in JavaFX [duplicate]

I have a JavaFX TableView that I'm populating with an ObservableList of Tasks. I've been trying to create a column that displays the index of each row, which serves as the ID for the tasks in the table, but I've tried the method here and similar methods from other sources with little success.
My code for reference, which has no superficial errors (as far as Eclipse can tell):
#FXML private TableColumn<Task, String> taskIndexCol;
Callback<TableColumn<Task, String>, TableCell<Task, String>> cb =
new Callback<TableColumn<Task, String>, TableCell<Task, String>>(){
#Override
public TableCell<Task, String> call(TableColumn<Task, String> col) {
TableCell<Task, String> cell = new TableCell<Task, String>() {
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (item == null) {
setText("");
} else {
setText(getIndex()+"");
}
}
};
return cell;
}
};
taskIndexCol.setCellFactory(cb);
Currently, my code gives me a NullPointerException when I try to set the CellFactory of the column. I've tried it with a populated task list, but that didn't help. I've been stumped for a very long time -- and theoretically this should be pretty easy, since it's just numbering the rows? It feels like I'm jumping through a million hoops to do something frustratingly simple.
Edit: The last line gives me the NPE.
It's impossible to tell the cause of the Null pointer exception, because you haven't shown the stack trace, identified the line which throws the exception, or posted enough of your code (none of the code in your callback can throw a null pointer exception, so something is wrong somewhere else).
For your actual cell implementation, you didn't show if you had a cellValueFactory set on the column. If you don't, then the item will always be null, and so you will never see any text in the cells in that column. You can check the empty property (or method parameter) as a means to check if the cell is in an empty row or one with actual data. (Note this means the column really doesn't need to provide any data at all: it can be a TableColumn<Task, Void>.)
Additionally, it's probably safer to rely on using updateIndex(...) instead of updateItem(...). updateIndex is guaranteed to be called when the index changes; if you implement updateItem you are assuming the index is set before the item, which means you are relying on an implementation detail.
Your code is a lot shorter and easier to read if you use Java 8 lambda expressions:
taskIndexCol.setCellFactory(col -> new TableCell<Task, String>() {
#Override
protected void updateIndex(int index) {
super.updateIndex(index);
if (isEmpty() || index < 0) {
setText(null);
} else {
setText(Integer.toString(index));
}
}
});
or alternatively
taskIndexCol.setCellFactory(col -> {
TableCell<Task, String> cell = new TableCell<>();
cell.textProperty().bind(Bindings.when(cell.emptyProperty())
.then("")
.otherwise(cell.indexProperty().asString()));
return cell ;
});
Here is a SSCCE:
import java.util.function.Function;
import static javafx.beans.binding.Bindings.when ;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class TableViewWithIndexColumn extends Application {
#Override
public void start(Stage primaryStage) {
TableView<Person> table = new TableView<>();
table.setEditable(true);
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"));
TableColumn<Person, String> firstNameCol = createColumn("First Name",
Person::firstNameProperty, 150);
TableColumn<Person, String> lastNameCol = createColumn("Last Name",
Person::lastNameProperty, 150);
TableColumn<Person, String> emailCol = createColumn("Email",
Person::emailProperty, 150);
// index column doesn't even need data...
TableColumn<Person, Void> indexCol = createColumn("Index", person -> null, 50);
// cell factory to display the index:
// indexCol.setCellFactory(col -> {
//
// // just a default table cell:
// TableCell<Person, Void> cell = new TableCell<>();
//
// cell.textProperty().bind(
// when(cell.emptyProperty())
// .then("")
// .otherwise(cell.indexProperty().asString()));
//
// return cell ;
// });
indexCol.setCellFactory(col -> new TableCell<Person, Void>() {
#Override
public void updateIndex(int index) {
super.updateIndex(index);
if (isEmpty() || index < 0) {
setText(null);
} else {
setText(Integer.toString(index));
}
}
});
table.getColumns().add(indexCol);
table.getColumns().add(firstNameCol);
table.getColumns().add(lastNameCol);
table.getColumns().add(emailCol);
primaryStage.setScene(new Scene(new BorderPane(table), 600, 400));
primaryStage.show();
}
private <S, T> TableColumn<S, T> createColumn(String title,
Function<S, ObservableValue<T>> property, double width) {
TableColumn<S, T> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
col.setPrefWidth(width);
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() {
this("", "", "");
}
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);
}
}

position of TableColumn in Scene

I have a TableView with several TableColumns and I want to place a Node below a certain TableColumn. How do I get the exact position (x,y-coordinates) of the TableColumn so I can bind the translate properties of my node?
Here is a snippet of how I placed a button on the top right corner of my TableView:
button.translateXProperty().unbind();
button.translateXProperty().bind(tableView.widthProperty().divide(2.0).subtract(button.getWidth() / 2.0 + 2.0) + tableView.localToScene(0.0, 0.0).getX());
This works fine, but obviously only for the TableView. The TableColumns don't have those translate properties or the localToScene methods, so I can't directly get the position to which I would like to bind my Node.
My current solution (which doesn't really work that well) is to do the following:
I read out the position of my TableView in the Scene (PointA) and then go through the list of all columns (tableView.getColumns()) and check if each of them is visible, and if so, add their width to the X-value of PointA. I do this until I find the actual column that I want to place the Node below.
Now the problem is, that I can't really just bind the Nodes position to this point, because when I change the order of the columns, or make one of them invisible, my column changes position on the screen. I would have to add a listener to the column order and visibility...
Is there any more efficient way to do what I want? :D
I generally dislike using lookups, but you can retrieve the label that is used to display the column header using the lookup .table-view .column-header .label and then bind your button's layout properties using the bounds of that label.
Example:
import java.util.Optional;
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class TableColumnLocationExample extends Application {
#Override
public void start(Stage primaryStage) {
TableView<Person> table = new TableView<>();
table.getColumns().add(column("First Name", Person::firstNameProperty, 120));
table.getColumns().add(column("Last Name", Person::lastNameProperty, 120));
table.getColumns().add(column("Email", Person::emailProperty, 250));
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")
);
Pane root = new Pane(table);
Scene scene = new Scene(root, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
for (TableColumn<Person, ?> col : table.getColumns()) {
Optional<Label> header = findLabelForTableColumnHeader(col.getText(), root);
header.ifPresent(label -> {
Button button = new Button(col.getText());
button.prefWidthProperty().bind(Bindings.createDoubleBinding(() ->
label.getBoundsInLocal().getWidth(), label.boundsInLocalProperty()));
button.minWidthProperty().bind(button.prefWidthProperty());
button.maxWidthProperty().bind(button.prefWidthProperty());
button.layoutXProperty().bind(Bindings.createDoubleBinding(() ->
label.getLocalToSceneTransform().transform(label.getBoundsInLocal()).getMinX(),
label.boundsInLocalProperty(), label.localToSceneTransformProperty()));
button.layoutYProperty().bind(Bindings.createDoubleBinding(() ->
table.getBoundsInParent().getMaxY() ,table.boundsInParentProperty()));
root.getChildren().add(button);
});
}
}
private Optional<Label> findLabelForTableColumnHeader(String text, Parent root) {
return root.lookupAll(".table-view .column-header .label")
.stream()
.map(Label.class::cast)
.filter(label -> label.getText().equals(text))
.findAny(); // assumes all columns have unique text...
}
private <S,T> TableColumn<S,T> column(String title, Function<S,ObservableValue<T>> property, double width) {
TableColumn<S,T> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
col.setPrefWidth(width);
return col ;
}
public static class Person {
private StringProperty firstName = new SimpleStringProperty();
private StringProperty lastName = new SimpleStringProperty();
private 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 String getFirstName() {
return this.firstNameProperty().get();
}
public final void setFirstName(final String firstName) {
this.firstNameProperty().set(firstName);
}
public final StringProperty lastNameProperty() {
return this.lastName;
}
public final String getLastName() {
return this.lastNameProperty().get();
}
public final void setLastName(final String lastName) {
this.lastNameProperty().set(lastName);
}
public final StringProperty emailProperty() {
return this.email;
}
public final String getEmail() {
return this.emailProperty().get();
}
public final void setEmail(final String email) {
this.emailProperty().set(email);
}
}
public static void main(String[] args) {
launch(args);
}
}
If you are allowed to use non-public api, you might consider to access the TableColumnHeader via its skin, provided it's of type TableViewSkinBase: it has api to access the TableRowHeader which is the container of all TableColumnHeaders and has api to find the header for any column it contains.
Code snippet (the width/location binding is copied from James' example, just to the header instead of the label)
private void buttonsPerHeader(TableView<Person> table, Pane root) {
if (!(table.getSkin() instanceof TableViewSkinBase)) return;
TableViewSkinBase skin = (TableViewSkinBase) table.getSkin();
TableHeaderRow headerRow = skin.getTableHeaderRow();
for (TableColumn col : table.getColumns()) {
TableColumnHeader header = headerRow.getColumnHeaderFor(col);
Button button = new Button(col.getText());
button.prefWidthProperty().bind(Bindings.createDoubleBinding(() ->
header.getBoundsInLocal().getWidth(), header.boundsInLocalProperty()));
button.minWidthProperty().bind(button.prefWidthProperty());
button.maxWidthProperty().bind(button.prefWidthProperty());
button.layoutXProperty().bind(Bindings.createDoubleBinding(() ->
header.getLocalToSceneTransform().transform(header.getBoundsInLocal()).getMinX(),
header.boundsInLocalProperty(), header.localToSceneTransformProperty()));
button.layoutYProperty().bind(Bindings.createDoubleBinding(() ->
table.getBoundsInParent().getMaxY() ,table.boundsInParentProperty()));
root.getChildren().add(button);
}
}

JavaFX TableView Filter Right Mouse Button Selection

When I press the Right Mouse button on a JavaFX TreeTableView it selects the node I clicked on. I can understand why this may be desirable in most cases, however not in all cases.
I am working on an application where I have multiple columns in the tree table with one column using a canvas widget for custom graphics (waveforms). The graphics column needs to be able to be interacted with for various reasons (setting markers, zooming, etc.) with the mouse. Because of this I don't want the mouse buttons to select the row in the table (or interact with the table).
I was able filter out the click with the first mouse button by putting a addEventFilter() for MouseEvent.MOUSE_CLICKED with a event.consume(). I can then process the mouse click the way I want.
However when I right click on the cell the selection in the table changes to that row. I have tried putting event filters on the cell, the table, the row, the column, nothing seems to work to filter the right click selection change. Note the canvas widget is set to be mouse transparent.
Here is an example using the standard address book example, except I replaced the email column with a canvas widget.
package sample;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.Callback;
public class Main extends Application {
private TableView<Person> table = new TableView<Person>();
private ContextMenu menu = new ContextMenu();
private final ObservableList<Person> data =
FXCollections.observableArrayList(
new Person("Jacob", "Smith", "jacob.smith#example.com"),
new Person("Isabella", "Johnson", "isabella.johnson#example.com"),
new Person("Ethan", "Williams", "ethan.williams#example.com"),
new Person("Emma", "Jones", "emma.jones#example.com"),
new Person("Michael", "Brown", "michael.brown#example.com")
);
class MyTableCell extends TableCell<Person, String> {
public MyTableCell(ContextMenu menu) {
super();
setContextMenu(menu);
}
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
setText(item);
setGraphic(null);
}
}
class MySpecialCell extends MyTableCell {
Canvas canvas = new Canvas(200.0, 12.0);
public MySpecialCell() {
super(null);
canvas.setMouseTransparent(true);
addEventFilter(MouseEvent.ANY, e -> e.consume());
}
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
setText(null);
if (! empty) {
canvas.getGraphicsContext2D().strokeText(item, 5.0, 10.0);
setGraphic(canvas);
} else {
setGraphic(null);
}
}
}
#Override
public void start(Stage stage) throws Exception{
Scene scene = new Scene(new Group());
stage.setTitle("Table View Sample");
stage.setWidth(450);
stage.setHeight(500);
final Label label = new Label("Address Book");
label.setFont(new Font("Arial", 20));
table.setEditable(true);
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
menu.getItems().add(new MenuItem("Hello World"));
Callback cellFactory = param -> new MyTableCell(menu);
TableColumn firstNameCol = new TableColumn("First Name");
firstNameCol.setMinWidth(100);
firstNameCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("firstName"));
firstNameCol.setCellFactory(cellFactory);
TableColumn lastNameCol = new TableColumn("Last Name");
lastNameCol.setMinWidth(100);
lastNameCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("lastName"));
lastNameCol.setCellFactory(cellFactory);
TableColumn emailCol = new TableColumn("Email");
emailCol.setMinWidth(200);
emailCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("email"));
emailCol.setCellFactory(param -> new MySpecialCell());
table.setItems(data);
table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(10, 0, 0, 10));
vbox.getChildren().addAll(label, table);
((Group) scene.getRoot()).getChildren().addAll(vbox);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
public static class Person {
private final SimpleStringProperty firstName;
private final SimpleStringProperty lastName;
private final SimpleStringProperty email;
private Person(String fName, String lName, String email) {
this.firstName = new SimpleStringProperty(fName);
this.lastName = new SimpleStringProperty(lName);
this.email = new SimpleStringProperty(email);
}
public String getFirstName() {
return firstName.get();
}
public void setFirstName(String fName) {
firstName.set(fName);
}
public String getLastName() {
return lastName.get();
}
public void setLastName(String fName) {
lastName.set(fName);
}
public String getEmail() {
return email.get();
}
public void setEmail(String fName) {
email.set(fName);
}
}
}
This is using Java 8u20. In this example the MyTableCell class is used for the cells in the first two columns. It adds a context menu to those columns. The MySpecialCell class is for the last column and it puts a canvas in the table with the email text in it. It also goes way overboard and filters ANY mouse event.
If you run this you will see that if you use mouse button 1 in the first two columns you can change the selection in the table. Mouse button 1 in the third column does not change selection. However Mouse button 2 (right mouse button) DOES change selection in the third column.
I want it to not change selection. Can someone give me a hint on how to prevent selection from changing when using the right mouse button in the column?
NOTE: I have tried having the canvas process the mouse input (actually not make it mouse transparent and having it filter all mouse events) and the table still changes selection on right click. For various reasons (where the data is, the fact that the canvas is actually a stack of canvas widgets, the difficulty of figuring out what node the mouse is interacting with in the table, etc) in my real application I wanted the cell to handle the mouse input.
Thanks!
To repeat my comments: I think the behaviour might be rather a bug than a feature - I would expect that consuming any mouseEvent would prevent the right (secondary) button just the same as the left (primary) button normal actions.
Was evaluated as a feature: the missing piece was to consume the contextMenuEvents in addition to the mouseEvents, something like
public MySpecialCell() {
super(null);
canvas.setMouseTransparent(true);
addEventFilter(MouseEvent.ANY, e -> e.consume());
// move Jonathan's code from table to cell level:
// need to consume contextMenuEvents as well
addEventFilter(ContextMenuEvent.ANY, e -> e.consume());
}
Back to disagreeing with the evaluation:
all mouse buttons should behave consistently to consuming all mouseEvents
contextMenuEvent is basically unrelated to selection, so consuming it should have no effect (in either way) on selection
The hack below might still be needed if contextMenu's triggered by keyboard on the special column are required, didn't dig further, though.
Anyway, a quick hack-around is to replace the default behaviour by a custom implementation. The collaborators
a custom TableCellBehaviour that overrides doSelect to do nothing on receiving a secondary button event
a custom TableCellSkin: that's only needed to plug-in the custom behaviour - needs to extend TableCellSkinBase (and c&p the implementation of its abstract methods from TableCellSkin) because only then we have a constructor taking our custom behaviour
let the custom TableCell create the custom skin instead of the default
Something like (note: while working, it's not properly tested):
class MySpecialCellBehavior extends TableCellBehavior {
public MySpecialCellBehavior(TableCell control) {
super(control);
}
#Override
protected void doSelect(double x, double y, MouseButton button,
int clickCount, boolean shiftDown, boolean shortcutDown) {
// do nothing on secondary button
if (button == MouseButton.SECONDARY) return;
super.doSelect(x, y, button, clickCount, shiftDown, shortcutDown);
}
}
class MySpecialCellSkin extends TableCellSkinBase {
private final TableColumn tableColumn;
public MySpecialCellSkin(TableCell tableCell) {
super(tableCell, new MySpecialCellBehavior(tableCell));
// doesn't make a difference
//consumeMouseEvents(true);
this.tableColumn = tableCell.getTableColumn();
super.init(tableCell);
}
#Override protected BooleanProperty columnVisibleProperty() {
return tableColumn.visibleProperty();
}
#Override protected ReadOnlyDoubleProperty columnWidthProperty() {
return tableColumn.widthProperty();
}
}
class MySpecialCell extends MyTableCell {
Canvas canvas = new Canvas(200.0, 12.0);
public MySpecialCell() {
super(null);
canvas.setMouseTransparent(true);
addEventFilter(MouseEvent.ANY, e -> e.consume());
}
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
setText(null);
if (! empty) {
canvas.getGraphicsContext2D().strokeText(item, 5.0, 10.0);
setGraphic(canvas);
} else {
setGraphic(null);
}
}
// create and return the custom skin
#Override
protected Skin<?> createDefaultSkin() {
return new MySpecialCellSkin(this);
}
}

JavaFX how to highlight TableColumn instead of TableRow when mouse is hovered to column

I have tried to find a solution to highlight table column when mouse is on top of that column, but I have not been able to find a solution. Is someone already found some solution for that? I have already tried to take a look to css and components with ScenicView, but it is not helping very much.
If you're using Java 8, you can do this quite nicely using a CSS pseudoclass. The performance of CSS pseudoclasses is very good (anecdotally, at least, it is the fastest way to change the style of JavaFX controls), and since the table view only creates a small number of table cells, even for very large data sets, this should perform very well.
Define an external css class that sets the style of a table cell with a custom pseudoclass applied as follows:
.table-cell:column-hover {
-fx-background-color: -fx-cell-focus-inner-border, -fx-selection-bar ;
-fx-background-insets: 0, 0 0 1 0 ;
-fx-background: -fx-selection-bar ;
}
(The -fx-background-color here actually changes the background color: the -fx-background is just a trick to ensure the text fill stays a suitable color relative to the background.)
Now, for each column, define a boolean property. Create a cell factory for each column that sets the boolean property to true when the mouse moves over a cell, and sets it to false when the mouse moves off. Have each cell observe the boolean property and set the pseudoclass state for the cell when it changes.
Here's an example, using the usual Person table from the standard tutorial. The interesting code is in the createCol(...) method:
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.css.PseudoClass;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TableViewTest extends Application {
private static final PseudoClass COLUMN_HOVER_PSEUDO_CLASS = PseudoClass.getPseudoClass("column-hover");
#Override
public void start(Stage primaryStage) {
TableView<Person> table = new TableView<>();
TableColumn<Person, String> firstNameCol = createCol("First Name", Person::firstNameProperty, 150);
TableColumn<Person, String> lastNameCol = createCol("Last Name", Person::lastNameProperty, 150);
TableColumn<Person, String> emailCol = createCol("Email", Person::emailProperty, 200);
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().add(firstNameCol);
table.getColumns().add(lastNameCol);
table.getColumns().add(emailCol);
VBox root = new VBox(15, table);
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 800, 600);
scene.getStylesheets().add("table-column-hover.css");
primaryStage.setScene(scene);
primaryStage.show();
}
private TableColumn<Person, String> createCol(String title,
Function<Person, ObservableValue<String>> mapper, double size) {
TableColumn<Person, String> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> mapper.apply(cellData.getValue()));
// Is the column being hovered over with the mouse?
BooleanProperty columnHover = new SimpleBooleanProperty();
col.setCellFactory(column -> {
// basic table cell:
TableCell<Person, String> cell = new TableCell<Person, String>() ;
cell.textProperty().bind(cell.itemProperty());
// when the mouse hovers over the cell, set the columnHover to indicate
// the mouse is over the column:
cell.hoverProperty().addListener((obs, wasHovered, isNowHovered) -> {
columnHover.set(isNowHovered);
});
// update the column-hover pseudoclass state for this cell when the column is hovered over
// note this will activate the pseudoclass when the mouse is over any cell in this column
columnHover.addListener((obs, columnWasHovered, columnIsNowHovered) ->
cell.pseudoClassStateChanged(COLUMN_HOVER_PSEUDO_CLASS, columnIsNowHovered)
);
return cell ;
});
col.setPrefWidth(size);
return col ;
}
public class Person {
private final StringProperty firstName = new SimpleStringProperty(this, "firstName");
private final StringProperty lastName = new SimpleStringProperty(this, "lastName");
private final StringProperty email = new SimpleStringProperty(this, "email");
public Person(String firstName, String lastName, String email) {
this.firstName.set(firstName);
this.lastName.set(lastName);
this.email.set(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);
}
}
I guess that is only possible with many "hacks".
The TableColumn only exists in the data model of the TableView, the view part that is rendered based on the model gets rendered in rows, not in columns. So each TableCell has a table row as parent - not a table column.
If it were implemented the other way round, the table gets rendered in columns we could easily achieve this.
Now if you really want to do this, you would have to do the following:
Write a custom TableCell and register it as the CellRenderer for all columns.
If your custom cell gets (un-)hovered, retrieve its column and notify all other cells that are in the same column.
But: This will slow your application down if the table grows larger and / or the computer where the application is running is slow.

How to fetch value of selected item in choice box from a table view coloumn in javafx

How can i fetch the value of the selected choice from the choce box in the following table.
column3 has 13 choice box nodes populated using following code.I want to fetch the selected item.
final ObservableList LogLevelList=FXCollections.observableArrayList("FATAL", "ERROR", "WARN", "INFO", "INOUT", "DEBUG");
column3.setCellFactory(new Callback<TableColumn<Feature,String>,TableCell<Feature,String>>(){
#Override
public TableCell<Feature,String> call(TableColumn<Feature,String> param) {
TableCell<Feature,String> cell = new TableCell<Feature,String>(){
#Override
public void updateItem(String item, boolean empty) {
System.out.println("Inside UpdateItem");
ChoiceBox choice = new ChoiceBox(LogLevelList);
choice.getSelectionModel().select(LogLevelList.indexOf(item));
//SETTING ALL THE GRAPHICS COMPONENT FOR CELL
setGraphic(choice);
}
};
return cell;
}
});
Does the predefined ChoiceBoxTableCell do what you need?
column3.setCellFactory(ChoiceBoxTableCell.forTableColumn(logLevelList));
See if this helps:
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javafx.application.Application;
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.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.ChoiceBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class TableChoiceBoxTest extends Application {
#Override
public void start(Stage primaryStage) {
final TableView<Feature> table = new TableView<>();
table.setEditable(true);
final TableColumn<Feature, String> nameCol = new TableColumn<>("Name");
nameCol.setCellValueFactory(new PropertyValueFactory<>("name"));
final TableColumn<Feature, String> logLevelCol = new TableColumn<>("Log level");
logLevelCol.setCellValueFactory(new PropertyValueFactory<>("logLevel"));
logLevelCol.setPrefWidth(150);
final ObservableList<String> logLevelList = FXCollections.observableArrayList("FATAL", "ERROR", "WARN", "INFO", "INOUT", "DEBUG");
logLevelCol.setCellFactory(ChoiceBoxTableCell.forTableColumn(logLevelList));
table.getColumns().addAll(nameCol, logLevelCol);
table.getItems().setAll(
IntStream.rangeClosed(1, 20)
.mapToObj(i -> new Feature("Item "+i, "FATAL"))
.collect(Collectors.toList())
);
Button showDataButton = new Button("Dump data");
showDataButton.setOnAction(event -> table.getItems().forEach(System.out::println));
BorderPane root = new BorderPane();
root.setCenter(table);
root.setBottom(showDataButton);
Scene scene = new Scene(root, 400, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static class Feature {
private final StringProperty name ;
private final StringProperty logLevel ;
public Feature(String name, String logLevel) {
this.name = new SimpleStringProperty(this, "name", name);
this.logLevel = new SimpleStringProperty(this, "logLevel", logLevel);
}
public StringProperty nameProperty() {
return name ;
}
public final String getName() {
return name.get();
}
public final void setName(String name) {
this.name.set(name);
}
public StringProperty logLevelProperty() {
return logLevel ;
}
public final String getLogLevel() {
return logLevel.get();
}
public final void setLogLevel(String logLevel) {
this.logLevel.set(logLevel);
}
#Override
public String toString() {
return getName() + ": " + getLogLevel();
}
}
public static void main(String[] args) {
launch(args);
}
}
The provided ChoiceBoxTableCell updates the property of the associated item for you, so there's never any need to get the value from the ChoiceBox; you can just get the value from your model object.
I think there are mistakes in your code. You do not want to display your Choice box in each and every cell of that column (i.e Emptied Row's Cell) and Also you should call super class function.
Now for getting the selected value of ChoiceBox , instead of just displaying your choicebox with the values you will have to save them in some ArrayList or Map or best options is to save inside your Feature class. So that you can finally use
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item,empty);
if(item != null){
ChoiceBox choice = new ChoiceBox(LogLevelList);
choice.getSelectionModel().select(LogLevelList.indexOf(item));
choice.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> ov, String t, String t1) {
//either use : myMap.put(getIndex(),t1);
//or : item.setChoice(t1);
}
});
//SETTING ALL THE GRAPHICS COMPONENT FOR CELL
setGraphic(choice);
}
}
Also for demo of ChoiceBox in TableView there is one blog post for you :http://blog.ngopal.com.np/2011/10/01/tableview-cell-modifiy-in-javafx/

Resources