Set tooltip on TableColumn(JavaFX) without side effect - java

There is a way to set tooltip like that
if (tooltipText != null) {
Label lb = new Label(column.getText());
lb.setTooltip(new Tooltip(tooltipText));
column.setText(null);
column.setGraphic(lb);
}
Unfortunately then will be exists ugly side-effect.
We set null to text of column but menuItems invoke column.getText(). If we don't do this, there will be double name in header.How to solve it? Suppose by means of css..
This answer doesn't erase side-effect
How to add a tooltip to a TableView header cell in JavaFX 8

Note: The following code relies on internals of the TableView skin. These could be subject to change, since they reside in the com.sun packages.
CSS lookup can be used to get access to the TableColumnHeader nodes after the first layout pass on the TableView. Those can be used to retrieve the Label that is used to display the TableColumn's text property.
#Override
public void start(Stage primaryStage) {
TableView tv = createTableView();
Scene scene = new Scene(tv);
// generate layout pass
tv.applyCss();
tv.layout();
// assign tooltips to headers
tv.lookupAll(".column-header").stream().forEach(n -> {
TableColumnHeader header = (TableColumnHeader) n;
Tooltip tooltip = new Tooltip("Tooltip: " + header.getTableColumn().getText());
((Control) header.lookup(".label")).setTooltip(tooltip);
});
primaryStage.setScene(scene);
primaryStage.show();
}

Related

Accessing Arbitrary nodes from EventHandler target

I'm working on a small JavaFX application. In this application I have the following components :
BorderPane -> as the root element
HBox top, bottom -> top and bottom regions
VBox left, right -> left and right regions
FlowPane center -> central region
When the central region is clicked i need to access a node in the top region containing some text. In order to access it i climb the graph upwards from the event's target like this :
public EventHandler<MouseEvent> fieldClicked = (MouseEvent e) -> {
FlowPane target = (FlowPane)e.getTarget();
BorderPane root = (BorderPane)target.getParent();
HBox top = (HBox)root.getChildren().get(0);
HBox top_left = (HBox)top.getChildren().get(0);
Text total = (Text)top_left.getChildren().get(0);
ObservableList<Node> dices = target.getChildren();
/* Do some stuff with retrieved nodes */
};
Is there a better and less verbose way to access an arbitrary node in the scene graph beside iteratively calling Node.getParent()
If you do not store the field in some other way, no. You may attach some id to find a node via CSS selector (lookup) but in that case you're better of doing this a different way:
Store the nodes you need to access in fields (or effectively final local variables, if you register event handlers in the same scope where you create the nodes).
...
private BorderPane root;
private HBox top;
private Text total;
private FlowPane target;
public EventHandler<MouseEvent> fieldClicked = (MouseEvent e) -> {
ObservableList<Node> dices = target.getChildren();
/* Do some stuff with fields */
};
private void initializeNodes() {
...
total = new Text();
top = new HBox(total);
root.setTop(top);
target = new FlowPane();
root.setCenter(target);
...
}
It's better to decouple the modification of certain values from the layout of the scene as much as possible anyways, since this makes it easier for you to rearrange the scene without having to worry about event handlers navigating the scene correctly via up-/downward navigation though the scene. Furthermore you'll get into trouble, if you're using your approach in cases where you use a "parent" other than a Pane or Group, e.g. ScrollPane since the skin of ScrollPane inserts the content node into the scene as it's descendant, but not as it's child and it doesn't do this until the first layout pass.
BTW: Note that it's Event.getSource that yields the node the event handler was triggered for, not Event.getTarget.
To get a specific Node, you can use the lookup() method of the javafx.scene.Scene class.
For Example, you can set an ID on the node containing some text and then find it with scene.lookup("#theID");
public EventHandler<MouseEvent> fieldClicked = (MouseEvent e) -> {
FlowPane target = (FlowPane)e.getTarget();
Text total = (Text) target.getScene().lookup("#myTextID");
/* Do some stuff with retrieved nodes */
};
The ID you can set by:
Text text = new Text("My Text element somewhere");
text.setId("myTextID");
I'm new to JavaFX so I don't know if this is the best way either. But I hope, this is what you are looking for.
By the way, if you want to get to the root node, you can instead use:
public EventHandler<MouseEvent> fieldClicked = (MouseEvent e) -> {
FlowPane target = (FlowPane)e.getTarget();
BorderPane root = (BorderPane) target.getScene().getRoot();
};
Its maybe helpful when you have more Elements in your FlowPane, then you also don't have to call that often Node.getParent().
Hope it helps!

How to clone a node in the scene graph in JavaFX?

I have a HBox with prefHeight = 70 // no prefWidth or any width...
I also have a Pane with prefWidth = 50 // no prefHeight or any height...
I just want to add multiple instance of the pane to the HBox using some loop.
When I do add(pane) in the loop body it gives following error.
Exception in thread "JavaFX Application Thread" java.lang.IllegalArgumentException: Children: duplicate children added: parent = HBox[id=myHBox]
I need to find way to clone the pane(as it passes by value).
Can anybody help me please?
(taking snapshot are not work for me because prefHeight of the pane is not set/ computed using parent)
This error happens because you're trying to add the same instance of a Node to another Node. If you remove the comments from the example below you'll get that error as well. Loop, on the other hand, will work fine because in each iteration new Button instance is created.
#Override
public void start(Stage stage) {
FlowPane root = new FlowPane();
// Results in error
// Button b1 = new Button("Button");
// root.getChildren().addAll(b1,b1);
for (int i = 0; i < 4; i++) {
Button b = new Button("Button");
root.getChildren().add(b);
}
Scene scene = new Scene(root, 50, 100);
stage.setScene(scene);
stage.show();
}
Your pane is probably more complicated, but you have to use the same principle. Put the code responsible for creating your pane in a separate method, getPane() or such, and use it in a loop to obtain new instances.
JavaFX doesn't give you an out-of-the-box solution to make a deep copy of a Node. If your Node is crated statically you can:
Put the code responsible for creating it in a separate method and
use it throughtout your application every time you need to get a new
instance of your pane.
Define it in a FXML file and load it every time you need a new instance.
Things get significantly worse if your Node has properties or children that were created or modified dynamically by the user. In that case you have to inspect its elements and recreate them on your own.

Dialog with results

I have a map with data of a "benchmark" for my little algorithm.
Keys represent the name of the benchmark, values the time needed.
I want to create a Dialog with lists those results in an easy to read way.
This is what I got so far:
Platform.runLater(() -> {
Dialog<Object> dialog = new Dialog<>();
dialog.setTitle("Result");
DialogPane pane = dialog.getDialogPane();
VBox list = new VBox();
for (Entry<String, Long> entry : resultMap.entrySet()) {
BorderPane box = new BorderPane();
box.setLeft(new Text(entry.getKey() + ": "));
box.setRight(new Text(entry.getValue().toString() + "ms"));
list.getChildren().add(box);
}
list.getChildren().add(new Text("Check: " + (logic.getResult() ? "PASSED" : "FAILED")));
pane.getChildren().add(list);
//pane.setPrefSize(pane.getPrefWidth(), 200); needed otherwhise the dialog is cut off
dialog.show();
});
There are multiple problems with this.
This Dialog isn't closeable for whatever reason.
The "Time" result is
aligned left instead of right.
It works for me inside scene builder, but since I need to generate those dynamically that's no possible choice.
Any help is appreciated!
Rules about closing a dialog are described in the Dialog documentation under "Dialog Closing Rules". In short, you need to add a button of some kind, which you can do with
pane.getButtonTypes().add(ButtonType.OK);
The reason the layout of list is not working correctly is that you use
pane.getChildren().add(list);
The dialog pane has a specific structure (again, see the documentation), and if you "blindly" add a node to the list of children, the dialog pane doesn't know how to manage it. Use setContent(...) instead:
// pane.getChildren().add(list);
pane.setContent(list);
This will also avoid the need to set the preferred size of the dialog pane.

JavaFX How to add Wrapped Text to GridPane

When I was append some text dynamically to the TextArea it was wrapped correctly as follow:
But due to requirement changed I have to add some image as bullet for (in front of the) each text. Then I used GridPane to add those text with images as follow:
Code used to add components to GridPane:
// Set the GridPane empty
gridPane.getChildren().removeAll();
// Add image with each text
int index = 0;
for(String des : descriptionsList) {
HBox btnHb = new HBox();
ImageView passed = new ImageView();
passed.setImage(new Image(getClass().getResourceAsStream(GuiConstant.Image.IMAGE_PASSED)));
btnHb.getChildren().add(passed);
Text text = new Text(des);
btnHb.getChildren().add(text);
gridPane.addRow(index, btnHb);
index++;
}
Now (with GridPane) added Texts are not wrapped properly. How can I fix this issue. Thanks.
You need to set the wrappingWidth on the Text instances to be relative to the width of the GridPane. You may want to do that indirectly via the HBox instances:
btnHub.prefWidthProperty().bind(gridPane.widthProperty();
text.wrappingWidthProperty().bind(btnHub.widthProperty().subtract(passed.widthProperty().subtract(10));
I didn't actually try these changes, but they (or something very similar) should do the trick.

Placing an image into a JavaFX 2 table

I'm trying to do something pretty simple. I want to place an icon in a column for a particular row in a table. If it's a folder, display a folder icon. If it's a file, display a file icon.
Does anyone know how to do this in JavaFX 2?
I've tried so many things and this seems like it should be pretty simple or at least an example somewhere.
Okay so I had a huge dummy moment. Turns out that I had my image url path wrong.
I did find a site that provides a great example for adding elements for table. This helped me understand everything.
Now if the 4 different ways I tried before would've worked, I don't know because my image url path was wrong. But anyway here is the link and a code snippet.
Bottom line was that you need to have the CellValueFactory and the CellFactory. I was attempting to use either or. The updateItem template method in TableCell relies on the value dervied from CellValueFactory.
http://blog.ngopal.com.np/2011/10/01/tableview-cell-modifiy-in-javafx/
TableColumn albumArt = new TableColumn("Album Art");
albumArt.setCellValueFactory(new PropertyValueFactory("album"));
albumArt.setPrefWidth(200);
// SETTING THE CELL FACTORY FOR THE ALBUM ART
albumArt.setCellFactory(new Callback<TableColumn<Music,Album>,TableCell<Music,Album>>(){
#Override
public TableCell<Music, Album> call(TableColumn<Music, Album> param) {
TableCell<Music, Album> cell = new TableCell<Music, Album>(){
#Override
public void updateItem(Album item, boolean empty) {
if(item!=null){
HBox box= new HBox();
box.setSpacing(10) ;
VBox vbox = new VBox();
vbox.getChildren().add(new Label(item.getArtist()));
vbox.getChildren().add(new Label(item.getAlbum()));
ImageView imageview = new ImageView();
imageview.setFitHeight(50);
imageview.setFitWidth(50);
imageview.setImage(new Image(MusicTable.class.getResource("img").toString()+"/"+item.getFilename()));
box.getChildren().addAll(imageview,vbox);
//SETTING ALL THE GRAPHICS COMPONENT FOR CELL
setGraphic(box);
}
}
};
System.out.println(cell.getIndex());
return cell;
}
});
In case the provided answers did not work for you (like it didn't for me), this was the solution I found (Of course you still needs to create the tableView and add the columns to it):
//Create your column that will hold the image
private final TreeTableColumn<YourObjectClass,ImageView> columnImage= new TreeTableColumn<YourObjectClass,ImageView>("Image");
public void start() {
//Set your cellValueFactory to a SimpleObjectProperty
//Provided that your class has a method "getImage()" this will work beautifully!
columnImage.setCellValueFactory(c-> new SimpleObjectProperty<ImageView>(new ImageView(c.getValue().getValue().getImage())));
}

Categories

Resources