Description tooltips for Vaadin Grid header cells - java

I'd like to define descriptions for Grid header cells, similarly to how AbstractComponent.setDescription(String description) works (i.e. tooltip shown on mouse hover). As the Grid doesn't support this in itself, I tried adding a Label component into the header cell, and then use setDescription() on the label. I can get the info tooltip working like this, but the downside is that clicking on the label component doesn't trigger sorting. If I want to sort the column, I need to click the header cell on the really narrow area that's left between the right edge of the label component and the column border, where the sorting indicator will be shown. If you look at the screenshot below, the highlighted area is the label component, and in order to trigger sorting, the user needs to click on the space on the right side of the component.
Is there a better way to apply descriptions to header cells than the one I described? And if not, is there a way to make the sorting work properly when the header cell contains a Component?

Based on the answer from kukis, I managed to come up with a simpler solution that doesn't require any JavaScript. Instead of adding a Label component into the header cell, I'm adding a div element manually with StaticCell.setHtml(), and setting the title attribute on it:
#Override
protected void init(VaadinRequest request) {
Grid grid = new Grid();
grid.addColumn("to");
grid.addColumn("the");
grid.addColumn("moon");
Grid.HeaderRow headerRow = grid.getDefaultHeaderRow();
headerRow.getCell("to").setHtml("<div title='Hello world'>to</div>");
headerRow.getCell("the").setHtml("<div title='Hello world 2'>the</div>");
headerRow.getCell("moon").setHtml("<div title='Hello world 3'>moon</div>");
grid.addRow("1","2","3");
grid.addRow("d","v","w");
grid.addRow("g","s","h");
setContent(new VerticalLayout(grid));
}

Feature added to Vaadin 8.4.0
Feature added to Grid in Vaadin 8.4.0.
Ticket:
https://github.com/vaadin/framework/pull/10489
Release notes:
https://vaadin.com/download/release/8.4/8.4.0/release-notes.html
Grid headers and footers now support tooltips.

Well, since Grid doesn't support it by itself you can always use JavaScript to achieve desired behaviour. SSCCE:
private final String SCRIPT;
{
StringBuilder b = new StringBuilder();
b.append("var grid = document.getElementById('mygrid');\n");
b.append("var child = grid.getElementsByClassName('v-grid-tablewrapper')[0];\n");
b.append("child = child.firstChild.firstChild.firstChild;\n");
b.append("child.childNodes[0].title='Hello world';\n");
b.append("child.childNodes[1].title='Hello world 2';\n");
b.append("child.childNodes[2].title='Hello world 3';\n");
SCRIPT = b.toString();
}
#Override
protected void init(VaadinRequest request) {
final VerticalLayout layout = new VerticalLayout();
Grid grid = new Grid();
grid.addColumn("to");
grid.addColumn("the");
grid.addColumn("moon");
grid.addRow("1","2","3");
grid.addRow("d","v","w");
grid.addRow("g","s","h");
grid.setId("mygrid");
setContent(layout);
layout.addComponent(grid);
JavaScript.getCurrent().execute(SCRIPT);
}
Another possibility would be to develop your own Grid in GWT based on the Grid provided by Vaadin Team but it is a way higher cost approach.
Another solution would be to, as you have tried, put label in a column and propagate the label-clicked-event to the Grid.

I use my own utillity finction:
public static Grid setHeaderCellDescription(Grid grid, int rowIndex, String property, String description) {
Grid.HeaderCell cell;
String cellHeader = "<span title=\"%s\">%s</span>";
cell = grid.getHeaderRow(rowIndex).getCell(property);
cell.setHtml(String.format(cellHeader, description, cell.getText()));
return grid;
}
You may add some additional checks if need (existing of cell and row number).
Or other variant - instead setHtml use cetComponent.
Grid.HeaderCell cell = grid.getHeaderRow(rowIndex).getCell(property);
Label newLabel = new Label(cell.getText());
newLabel.setDescription(description);
cell.setComponent(newLabel);

Update for Vaadin 23: you can use your own Component as a column header with this method: com.vaadin.flow.component.grid.Grid.Column#setHeader(com.vaadin.flow.component.Component).
So you can use e.g. a Span with a title:
Span headerComponent = new Span();
headerComponent.setText("Your header text");
headerComponent.getElement().setProperty("title", "Your tooltip text");
column.setHeader(headerComponent);

Related

NatTable: colored row selected, what about the selected cell?

I have a NatTable and some colored rows, via the label "mylabel".
"mylabel" is assigned by a ConfigLabelAccumulator:
final AggregateConfigLabelAccumulator labelAccumulator = new AggregateConfigLabelAccumulator();
labelAccumulator.add(new ColumnLabelAccumulator());
labelAccumulator.add(new IConfigLabelAccumulator() {
#Override
public void accumulateConfigLabels(final LabelStack configLabels, final int columnPosition, final int rowPosition) {
if (<my condition>) configLabels.addLabelOnTop("mylabel");
}
});
Styles for "mylabel" are assigned via ConfigRegistry, "YELLOW" for unselected rows, "DARK_YELLOW" for selected rows:
final ConfigRegistry configRegistry = new ConfigRegistry();
final Style style = new Style();
style.setAttributeValue(CellStyleAttributes.BACKGROUND_COLOR, GUIHelper.COLOR_YELLOW);
configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_STYLE, style, DisplayMode.NORMAL, "mylabel");
final Style styleSelected = new Style();
styleSelected.setAttributeValue(CellStyleAttributes.BACKGROUND_COLOR, Display.getDefault().getSystemColor(SWT.COLOR_DARK_YELLOW));
configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_STYLE, styleSelected, DisplayMode.SELECT, "mylabel");
(sidenote) After the condition (see <my condition>) changes, I do natTable.doCommand(new VisualRefreshCommand()); to instantly refresh the table.
It works like a charm, but for one thing: The selected cell!
How can I tell the selected cell to have a different color when <my condition> is true?
Example pictures:
Both rows are selected in both pictures (=> dark yellow), only the selection anchor is different.
The cell containing 529... should have a different style when selected.
The cell containing /E0001 should stay like it is.
Thank you very much, Dirk !!!
I ended up with this solution, tweaking the SelectionLayer's DefaultSelectionLayerConfiguration and DefaultSelectionStyleConfiguration:
this.selectionLayer = new SelectionLayer(glazedListsEventLayer, false);
this.selectionLayer.addConfiguration(new DefaultSelectionLayerConfiguration() {
#Override
protected void addSelectionStyleConfig() {
final DefaultSelectionStyleConfiguration dssc = new DefaultSelectionStyleConfiguration();
dssc.anchorBgColor = null;
dssc.anchorFgColor = null;
dssc.anchorBorderStyle = new BorderStyle(1, GUIHelper.COLOR_RED, LineStyleEnum.SOLID);
addConfiguration(dssc);
}
});
IIUC you are talking about the cell that has the focus in a selected row/column. That is called the selection anchor. And the selection anchor is styled specifically via the label SelectionStyleLabels.SELECTION_ANCHOR_STYLE to distinguish the selected cell that has the focus from other selected cells in a multi selection scenario.
That said, you need to configure the style for the selection anchor additionally. But as it is not possible to configure styles for multi-labels, the only approach I know is to remove the background styling for the selection anchor so the background color is inherited from the general selection style. And if you want to highlight the selection anchor, use some other style bit, e.g. setting the border.
IStyle anchorStyle = new Style();
anchorStyle.setAttributeValue(
CellStyleAttributes.BORDER_STYLE,
new BorderStyle(1, GUIHelper.COLOR_RED, LineStyleEnum.SOLID));
configRegistry.registerConfigAttribute(
CellConfigAttributes.CELL_STYLE,
anchorStyle,
DisplayMode.SELECT,
SelectionStyleLabels.SELECTION_ANCHOR_STYLE);

iText 7 borderless table (no border)

This code below does not work.
Table table = new Table(2);
table.setBorder(Border.NO_BORDER);
I am new to iText 7 and all I wanted is to have my table borderless.
Like how to do it?
The table itself is by default not responsible for borders in iText7, the cells are. You need to set every cell to be borderless if you want a borderless table (or set the outer cells to have no border on the edge if you still want inside borders).
Cell cell = new Cell();
cell.add("contents go here");
cell.setBorder(Border.NO_BORDER);
table.addCell(cell);
You could write a method which runs though all children of a Table and sets NO_BORDER.
private static void RemoveBorder(Table table)
{
for (IElement iElement : table.getChildren()) {
((Cell)iElement).setBorder(Border.NO_BORDER);
}
}
This gives you the advantage that you can still use
table.add("whatever");
table.add("whatever");
RemoveBorder(table);
instead of changing it on all cells manual.

Grouping objects in JavaFX?

So I'm currently making a JavaFX GUI (with SceneBuilder) for a Connect4 game, and I was wondering if there's a way to 'group' objects together so that I can perform an action on all of them together?
Examples of what I mean:
I have 7 buttons for the columns (colButton1-7) and I want to disable all of them at once.
I use Ellipses for counters (counter1-40) and would like to change the color of all of them to white.
I searched around but couldn't find anything. I know how to do both for an individual object, but can't think of a way to easily apply the changes to all of them at the same time. Any help would be appreciated :)
There is no grouping mechanism to perform a single action on all of the members of the same group. On the contrary, you can have a single group/container to hold all your controls and apply the same action to each of its member.
For example, lets say I have a VBox containing Buttons and I want to disable them all.
for(Node node:vBox.getChildren()) {
node.setDisable(true);
}
or, to set Styling
for(Node node:vBox.getChildren()) {
node.setStyle("-fx-something");
}
For disabling, if you disable a node, then all it's child nodes will have disabled set to true. So you can do:
VBox buttonHolder = new VBox();
Button button = new Button(...);
buttonHolder.getChildren().add(button);
// repeat as necessary...
buttonHolder.setDisable(true); // all buttons in the VBox will now be disbaled
For styled properties, such as the fill of a shape, you should use an external style sheet. If you change the style class of the parent, then with an appropriate external style sheet you can change the style of all the children in one shot.
E.g.
Pane counterPane = new Pane();
for (int i=0; i<numCounters; i++) {
Ellipse counter = new Ellipse(...);
counter.getStyleClass().add("counter");
counterPane.getChildren().add(counter);
}
// ...
counterPane.getStyleClass().add("counter-pane"); // all counters white
// change to red:
counterPane.getStyleClass().remove("counter-pane");
counterPane.getStyleClass().add("warning");
External style sheet:
.counter-pane > .counter {
-fx-fill: white ;
}
.warning > .counter {
-fx-fill : red ;
}

How to add a tooltip to a TableView header cell in JavaFX 8

Does anyone know how to add a tooltip to a column header in a TableView ?
There are many places where are explained how to add the tooltip to data cells, but I didn't find a way to add the tooltip to header.
Using the tool ScenicView, I can see that the headers are Labels inside a TableColumnHeader object, but It seems that It is not a public object.
Any suggestions ?
TableColumn<Person, String> firstNameCol = new TableColumn<>();
Label firstNameLabel = new Label("First Name");
firstNameLabel.setTooltip(new Tooltip("This column shows the first name"));
firstNameCol.setGraphic(firstNameLabel);
This is an extended answer to James_D. (I don't have the reputation to comment):
To make the label connect with textProperty of the column and just hide the original text, so it does not mess up the rest of the table functionality:
nameLabel.textProperty().bindBidirectional(textProperty());
nameLabel.getStyleClass().add("column-header-label");
nameLabel.setMaxWidth(Double.MAX_VALUE); //Makes it take up the full width of the table column header and tooltip is shown more easily.
css:
.table-view .column-header .label{
-fx-content-display: graphic-only;
}
.table-view .column-header .label .column-header-label{
-fx-content-display: text-only;
}
The solution
Alternatively, you can look up the label that's already there and just give it a tool tip.
In order to .lookup() an element the table needs to be rendered already. To be sure that your code runs after the table is rendered, just wrap your code in a Platform.runlater().
// We need to do this after the table has been rendered (we can't look up elements until then)
Platform.runLater(() -> {
// Prepare a tooltip
Tooltip tooltip = new Tooltip("This is a super cool control; here's how to work it...");
tooltip.setWrapText(true);
tooltip.setMaxWidth(200);
// Get column's column header
TableColumnHeader header = (TableColumnHeader) historyTable.lookup("#" + column.getId());
// Get column header's (untooltipped) label
Label label = (Label) header.lookup(".label");
// Give the label a tooltip
label.setTooltip(tooltip);
// Makes the tooltip display, no matter where the mouse is inside the column header.
label.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
}
The solution at scale
If you want to do this for your whole set of columns, you might put them all in a LinkedHashMap (this will just help you organizationally by keeping your columns associated with their messages):
// We'll use this to associate columns with messages for right now.
LinkedHashMap<TableColumn<Person, String>, String> tableColumns = new LinkedHashMap<>();
// Each column gets a helpful message
tableColumns.put(numberOfBoxesColumn, "The total number of boxes that have arrived");
tableColumns.put(backordersColumn, "Use these columns as a measure of how urgently the Purchase Order needs to be processed.");
/*... put each column in along with it's message */
... then use a for-each loop to loop through and give each column a tooltip ...
// Make a tooltip out of each message. Give each column('s label) it's tooltip.
for (Map.Entry<TableColumn<Person, String>, String> pair : tableColumns.entrySet()) {
TableColumn<Person, String> column;
String message;
// Get the column and message
column = pair.getKey();
message = pair.getValue();
// Prepare a tooltip
Tooltip tooltip = new Tooltip(message);
tooltip.setWrapText(true);
tooltip.setMaxWidth(200);
// Get column's column header
TableColumnHeader header = (TableColumnHeader) historyTable.lookup("#" + column.getId());
// Get column header's (untooltipped) label
Label label = (Label) header.lookup(".label");
// Give the label a tooltip
label.setTooltip(tooltip);
// Makes the tooltip display, no matter where the mouse is inside the column header.
label.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
}
NOTE: I tried the combination of Jesper and James' solutions before coming up with this one. Sadly, when I did, the CSS caused all of my labels to disappear when I look at my .fxml layout in SceneBuilder. And we just can't have that, can we? (okay maybe I just couldn't have that).

using LayoutClickListener to terminary replace components

I have grid layout witch some fields added like that:
private Component userDetailsTab(final User user) {
final GridLayout details = new GridLayout(2, 1);
details.setMargin(true);
details.setSpacing(true);
details.addComponent(createDetailLabel(Messages.User_Name));
final Component username = createDetailValue(user.getName());
details.addComponent(username);
...
I have also Layout click listener which replace labels on text field, it looks like that:
final TextField tf = new TextField();
details.addListener(new LayoutClickListener() {
private static final long serialVersionUID = -7374243623325736476L;
#Override
public void layoutClick(LayoutClickEvent event) {
Component com = event.getChildComponent();
if (event.getChildComponent() instanceof Label) {
Label label = (Label)event.getChildComponent();
details.replaceComponent(com, tf);
tf.setValue(label.getValue());
}
}
});
In future I want to enable click on label, edit it and write changes to database after clicking somewhere else (on different label for example).
Now when I click on 1st label and then on 2nd label, effect is: 1st has value of 2nd and 2nd is text field witch value of 2nd. Why it's going that way? What should i do to after clicking on 1st and then 2nd get 1st label witch value of 1st?
You don't need to swap between Labels and TextFields, you can just use a TextField and style it look like a Label when it's not focused.
When I tried to create click-to-edit labels, it created a ton of extra work for me. I'd discourage it (and do as Patton suggests in the comments).
However, if you're going to insist on trying to create in-place editing, you will want to do the following:
Create a new class that extends a layout (e.g. HorizontalLayout), which can swap out a label for a text field
use LayoutClickListener to removeComponent(myLabel) and addComponent(myTextField)
use BlurListener to swap back to the label
use ValueChangeListener on the text field to copy its value to the label
This is a still a bad idea because:
Users cannot see affordances as easily (they can't tell what's editable)
Users cannot use the keyboard to tab to the field they want to edit
It adds unncessary complexity (maintenance time, etc).
I would recommend, if you want in-place editing, just show the text field, and save the new value with the BlurListener.

Categories