JavaFX TableView Click on Header - java

Is there a way to get an mouse-click on an Header of a Table?
Why i need this?
I have a Table with many Columns. But only a specific witdth for the whole Table.
To avoid scrolling, i want to give each Column an specific width (50 or so), and just if you click on an header, this column will expand so you can read the content. If you click on another header, the previous one collapse.
Hopefully someone can help me:)

Unfortunately there isn't a nice way to do this. The only public API option is to replace the "graphic" of the column with your own label, and then add a mouse listener to that. For this to work you also need to clear any existing column text.
Note that columns by default have click listeners to implement sorting, it seems you don't want this behaviour, so you'll also need to call column.setSortable(false)
#Override
public void start(Stage primaryStage) throws Exception {
TableView<String> tableView = new TableView<>();
TableColumn<String, Object> x = new TableColumn<>("x");
tableView.getColumns().add(x);
TableColumn<String, Object> y = new TableColumn<>("");
tableView.getColumns().add(y);
x.setSortable(false);
y.setSortable(false);
makeHeader(x, "X", 0);
makeHeader(y, "Y", 1);
EventHandler<? super MouseEvent> handler = event -> {
System.out.println("Column clicked " + ((Node)event.getTarget()).getProperties().get("index"));
};
x.getGraphic().addEventFilter(MouseEvent.MOUSE_CLICKED, handler);
y.getGraphic().addEventFilter(MouseEvent.MOUSE_CLICKED, handler);
primaryStage.setScene(new Scene(tableView));
primaryStage.show();
}
private void makeHeader(TableColumn<?, ?> target, String name, int index) {
VBox vBox = new VBox(new Label(name));
vBox.setAlignment(Pos.CENTER);
vBox.getProperties().put("index", index);
target.setGraphic(vBox);
target.setText("");
}

// Step 0: call setOnShown(...).
stage.setOnShown(event -> {
setHeaderClickListeners(); // Call this method when the stage is shown.
});
And then you create a method setHeaderClickListeners(), as follows:
private void setHeaderClickListeners() {
// Step 1: Get the table header row.
TableHeaderRow headerRow = null;
for (Node n : ((TableViewSkin<?>) tableView.getSkin()).getChildren()) {
if (n instanceof TableHeaderRow) {
headerRow = (TableHeaderRow) n;
}
}
if (headerRow == null) {
return;
}
// Step 2: Get the list of the header columns.
NestedTableColumnHeader ntch = (NestedTableColumnHeader) headerRow.getChildren().get(1);
ObservableList<TableColumnHeader> headers = ntch.getColumnHeaders();
// Step 3: Add click listener to the header columns.
for (int i = 0; i < headers.size(); i++) {
TableColumnHeader header = headers.get(i);
final int index = i;
header.setOnMouseClicked(mouseEvent -> {
// Optional:
// Get the TableColumnBase (which is the object responsible
// for displaying the content of the column.)
TableColumnBase column = header.getTableColumn();
// Step 4: Handle double mouse click event.
if (mouseEvent.getButton() == MouseButton.PRIMARY && mouseEvent.getClickCount() == 2) {
P.x("Header cell " + index + " clicked! " + column.getText());
}
});
}
}

Related

Change icon on a selected row in a Vaadin Grid

So, I have a grid called "eventGrid". I added a component column with a down-arrow icon. When a user clicks on a row, addition information is displayed for that row. I want to change the down arrow to an up arrow for the selected row. How can I achieve this?
eventGrid.addColumn(Person::getFirst).setHeader("First Name");
eventGrid.addColumn(Person::getLast).setHeader("Last Name");
eventGrid.addColumn(Person::getNumber).setHeader("Phone Number");
eventGrid.addComponentColumn(item -> {
Icon icon = new Icon("lumo", "angle-down");
return icon;
}).setWidth("6em").setFlexGrow(0);
There are two steps to the solution. First you need to make the renderer react differently for selected items and second, you need to make the grid re-render the row for which the selected status changes.
The first part can be handled in the component renderer:
eventGrid.addComponentColumn(item -> {
String name = eventGrid.getSelectedItems().contains(item) ? "angle-down" : "angle-up";
Icon icon = new Icon("lumo", name);
return icon;
}).setWidth("6em").setFlexGrow(0);
The second part is a listener that uses DataProvider::refreshItem to trigger specific rows to be rendered again. This is implemented through asSingeSelect to get an event that contains both the old and the new value.
eventGrid.asSingleSelect().addValueChangeListener(event -> {
String newValue = event.getValue();
String oldValue = event.getOldValue();
if (newValue != null) {
eventGrid.getDataProvider().refreshItem(newValue);
}
if (oldValue != null) {
eventGrid.getDataProvider().refreshItem(oldValue);
}
});
Assuming you have an in-memory DataProvider:
Grid<Person> eventGrid = new Grid<Person>();
eventGrid.addColumn(Person::getFirst).setHeader("First Name");
eventGrid.addColumn(Person::getLast).setHeader("Last Name");
eventGrid.addColumn(Person::getNumber).setHeader("Phone Number");
eventGrid.addComponentColumn(item -> {
Icon icon = new Icon("lumo", "angle-down");
if (item.equals(eventGrid.asSingleSelect().getValue())) {
icon = new Icon("lumo", "angle-up");
}
return icon;
}).setWidth("6em").setFlexGrow(0);
eventGrid.addSelectionListener(e -> {
eventGrid.getListDataView().refreshAll();
});

new items destroy layout of grid

By refreshing the items within a grid sometimes it happen that the grid shows completely crazy:
That's how I update the data every time. The instance of the Grid stays the same:
dataProvider = new ListDataProvider<>(newItems);
grid.setItems(dataProvider);
Does someone have an idea whether I do something wrong or there is a bug within Vaadin?
Complete code:
if (!columnsIntialized) {
for (Method method : gridProperties) {
Column<T> column = grid.addColumn(data -> {
Object value = method.invoke(data);
return getFormattedValue(value, method.getReturnType());
});
String resourceKey = method.getDeclaredAnnotation(GridProperties.class).resourceKey();
column.setKey(resourceKey);
column.setHeader(AppResources.getString(resourceKey));
int width = method.getDeclaredAnnotation(GridProperties.class).width();
if (width == -1) {
column.setAutoWidth(true);
} else {
column.setWidth(width + "px");
}
}
columnsIntialized = true;
}
dataProvider = new ListDataProvider<>(items);
grid.setItems(dataProvider);
grid.recalculateColumnWidths();
Vaadin 22.0.3 is scheduled to be released on Monday with the fix.

Is it possible to add a Bulletted list mode in TextArea JavaFX

Just like in Word or WYSIWYG editor, I want the ability to make TextArea perform a simple bullet list.
I was able to figure it out using RichTextFX StyleClassedTextArea :
public static final String BULLET_CODE = " \u2022\t";
textArea.setOnKeyPressed(event ->
{
KeyCode code = event.getCode();
if (KeyCode.ENTER.equals(code))
{
textArea.moveTo(textArea.getCaretPosition() - 1);
textArea.selectParagraph();
if (StringUtils.startsWith(textArea.getSelectedText(), BULLET_CODE))
{
textArea.moveTo(textArea.getCaretPosition() + 1);
textArea.lineStart(NavigationActions.SelectionPolicy.CLEAR);
textArea.insertText(textArea.getCaretPosition(), BULLET_CODE);
} else
{
textArea.moveTo(textArea.getCaretPosition() + 1);
}
textArea.deselect();
}
});
And adding a button that inserts a bullet:
addBullet.setOnAction((ActionEvent t) ->
{
textArea.selectLine();
if (!StringUtils.startsWith(textArea.getSelectedText(), BULLET_CODE))
{
textArea.deselect();
textArea.lineStart(NavigationActions.SelectionPolicy.CLEAR);
textArea.insertText(spellCheckArea.getTextArea().getCaretPosition(), BULLET_CODE);
textArea.lineEnd(NavigationActions.SelectionPolicy.CLEAR);
}
textArea.deselect();
textArea.requestFocus();
});
Nevertheless you can insert bullet point unicode character in your TextArea
with the code "\u2022".

How to hide or remove javafx tooltip when it is triggered manually?

I have been trying to figure out a way to hide this tooltip when it is triggered by a function and shown in the scene. This is my function.
private void ValidateRequired(TextField field){
Tooltip errorTip = null;
if(field.getText().equals("")){
field.getStyleClass().add("errorField");
errorTip = new Tooltip("This is required");
errorTip.getStyleClass().removeAll();
Scene scene = field.getScene();
scene.getStylesheets().add(this.getClass().getResource("../css/sale.css").toExternalForm());
errorTip.getStyleClass().add("errorTip");
Point2D p = field.localToScene(0.0, 0.0);
errorTip.show(field,p.getX()
+ field.getScene().getX() + field.getScene().getWindow().getX(), p.getY()
+ field.getScene().getY() + field.getScene().getWindow().getY()+field.getHeight()+2);
}
else{
errorTip.hide();
}
}
This function is called when Textfield lose focus. This function get called by the following listener.
currentField.focusedProperty().addListener((ov, oldV, newV) -> {
if (!newV) {
ValidateRequired(currentField);
}
else{
}
});
You are creating a new Tooltip every time the ValidateRequired method (BTW please use proper naming conventions) is called. So when the method is called with non-empty text, you are not hiding the same tooltip you previously showed (and so the previously-showed one remains shown).
Instead, create one tooltip and show/hide it:
private final Tooltip errorTip = new Tooltip();
// ...
private void ValidateRequired(TextField field){
if(field.getText().equals("")){
field.getStyleClass().add("errorField");
errorTip.setText("This is required");
errorTip.getStyleClass().removeAll();
Scene scene = field.getScene();
scene.getStylesheets().add(this.getClass().getResource("../css/sale.css").toExternalForm());
errorTip.getStyleClass().add("errorTip");
Point2D p = field.localToScene(0.0, 0.0);
errorTip.show(field,p.getX()
+ field.getScene().getX() + field.getScene().getWindow().getX(), p.getY()
+ field.getScene().getY() + field.getScene().getWindow().getY()+field.getHeight()+2);
}
else{
errorTip.setText("");
errorTip.hide();
}
}

Having a String or int with a binding to a Property

I need to make a programm in JavaFX where I need to manage a movie library. I have a tableview that is filled by an observable list. Then I made bindings between the Properties and the Textboxes or Labels next to the tableview. Now the problem is, I have also pictures that are related to the movies, like cinema posters. and at the moment I just use a hardcoded one:
imgPoster = new Image(getClass().getResourceAsStream("../resources/images/posters/" + 5 + ".jpg"));
In the datafile there is one column with the ID of the movie and the same number is also the picture for it. So I need to replace that "5" in the sample code with a binding or so that it actively changes the picture as soon as I click on a row in the tableview.
How do I do that?
edit:
After the first comment i did that:
imgOscars = new Image(getClass().getResourceAsStream("../resources/images/oscar/Oscar-logo.png"));
oscars = new ImageView(imgOscars);
oscars.setPreserveRatio(true);
oscars.setFitHeight(45);
but question stays
Either add a listener to the selected item property in the table (I am just guessing at the model you are using for the table):
TableView<Movie> table = ... ;
table.getSelectionModel().selectedItemProperty().addListener((obs, oldSelectedMovie, newSelectedMovie) -> {
if (newSelectedMovie == null) {
oscars.setImage(null);
} else {
// get image from new selection
Image image = new Image(getClass().getResourceAsStream("../resources/images/oscar/"+newSelectedMoviegetId()+".png"));
oscars.setImage(image);
}
});
or use a binding:
oscars.imageProperty().bind(Bindings.createObjectBinding(() -> {
if (table.getSelectionModel().getSelectedItem() == null) {
return null ;
} else {
Movie selected = table.getSelectionModel().getSelectedItem();
return new Image(getClass().getResourceAsStream("../resources/images/oscar/"+selected.getId()+".png")); ;
},
table.getSelectionModel().selectedItemProperty());

Categories

Resources