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.
Related
I noticed that when adding and deleting tabs from a TabPane, it fails to match the position of the order of tabs in the underlying list. This only happens when at least one tab is hidden entirely due to the width of the parent. Here's some code that replicates the issue:
public class TabPaneTester extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Scene scene = sizeScene();
primaryStage.setMinHeight(200);
primaryStage.setWidth(475);
primaryStage.setScene(scene);
primaryStage.show();
}
private Scene sizeScene(){
TabPane tabPane = new TabPane();
tabPane.setTabMinWidth(200);
tabPane.getTabs().addAll(newTabs(3));
Scene scene = new Scene(tabPane);
scene.setOnKeyPressed(e -> tabPane.getTabs().add(1, tabPane.getTabs().remove(0)));
return scene;
}
private static Tab[] newTabs(int numTabs){
Tab[] tabs = new Tab[numTabs];
for(int i = 0; i < numTabs; i++) {
Label label = new Label("Tab Number " + (i + 1));
Tab tab = new Tab();
tab.setGraphic(label);
tabs[i] = tab;
}
return tabs;
}
public static void main(String[] args) {
launch();
}
}
When you press a key, it removes the first tab (at index 0) and puts it back at index 1, effectively swapping the first two tabs. However, when run the tabs don't actually visually swap (even though the tab switcher menu does switch their position).
If you change the width of the screen to include even a pixel of the third tab that was hidden (replace 475 with 500), it works as intended. Any clues as to how to fix this?
This is indeed a bug and I couldn't find it reported in the public JIRA it is now reported at https://bugs.openjdk.java.net/browse/JDK-8193495.
All my analysis is based on the code in TabPaneSkin if you want to have a look yourself.
Summary
The problem arises when you remove and then add the tab "too quickly". When a tab is removed, asynchronous calls are made during the removal process. If you make another change such as adding a tab before the async calls finish (or at least "finish enough"), then the change procedure sees the pane at an invalid state.
Details
Removing a tab calls removeTabs, which is outlined below:
Various internal removal methods are called.
Then it checks if closing should be animated.
If yes (GROW),
an animation queues a call to a requestLayout method, which itself is invoked asynchronously,
and the animations starts (asynchronously) and the method returns.
If not (NONE),
requestLayout is called immediately and the method returns.
The time during which the pane is at an invalid state is the time from when the call returns until requestLayout returns (on another thread). This duration is equivalent to the duration of requestLayout plus the duration of the animation (if there is one), which is ANIMATION_SPEED = 150[ms]. Invoking addTabs during this time can cause undesired effects because the data needed to properly add the tab is not ready yet.
Workaround
Add an artificial pause between the calls:
ObservableList<Tab> tabs = tabPane.getTabs();
PauseTransition p = new PauseTransition(Duration.millis(150 + 20));
scene.setOnKeyPressed(e -> {
Tab remove = tabs.remove(0);
p.setOnFinished(e2 -> tabs.add(1, remove));
p.play();
});
This is enough time for the asynchronous calls to return (don't call the KeyPressed handler too quickly in succession because you will remove the tabs faster than they can be added). You can turn off the removal animation with
tabPane.setStyle("-fx-close-tab-animation: NONE;");
which allows you to decrease the pause duration. On my machine 15 was safe (here you can also call the KeyPressed handler quickly in succession because of the short delay).
Possible fix
Some synchronization on tabHeaderArea.
I'm having a problem with Label and TextField placement in a gridpane in response to toggled RadioButtons.
The window will display the same options except for two different labels and text fields which depend on which RadioButton the user has selected.(See images attached).
InHouse RB Selected
Outsourced RB Selected
I added the RadioButton objects to a Toggle Group, then coded the "on actions" to add the "Machine ID" or "Company Name" fields to the GridPane as needed when one of the options is selected.
My problem is that I can only select each option once, and the display of the second option only overlaps the first instance instead of replacing it. If I try to switch back again, I get a runtime error(in Netbeans) about adding the same object twice to the grid.
Any code that I have tried that could remove the node from the display had no affect on the menu's behavior.
ArrayList<Label> typeSpecLabels = new ArrayList<Label>();
ArrayList<TextField>typeSpecFields = new ArrayList<TextField>();
typeSpecLabels.add(machineIDLabel);
typeSpecLabels.add(companyLabel);
typeSpecFields.add(machineIDField);
typeSpecFields.add(companyNameField);
inHouseBtn.setOnAction(inHouseSpecificEvent ->
{
typeSpecLabels.add(machineIDLabel);
grid1.add(typeSpecLabels.get(0),0,8,1,1);
grid1.add(typeSpecFields.get(0), 1,8,1,1);
if(outSourceBtn.isArmed() == true){
grid1.getChildren().remove(companyLabel);
grid1.getChildren().remove(companyNameField);
}
});
outSourceBtn.setOnAction(outSourceSpecificEvent ->
{
typeSpecLabels.add(companyLabel);
grid1.add(companyLabel,0,8,1,1);
grid1.add(companyNameField,1,8,1,1);
if(outSourceBtn.isArmed() == true){
grid1.getChildren().remove(machineIDLabel);
grid1.getChildren().remove(machineIDField);
}
});
I have heard that I could try using 2 or 3 different scenes(one for each state of the RadioButtons), so I may try that. But if it can be done the way I have coded it so far, I would prefer to do it that way.
I would suggest to remove all type specific labels and fields from your grid and then add ones that you need. So the code will look like following:
inHouseBtn.setOnAction(inHouseSpecificEvent ->
{
grid1.getChildren().removeAll(machineIDLabel, companyLabel, machineIDField, companyNameField);
grid1.add(machineIDLabel,0,8,1,1);
grid1.add(machineIDField, 1,8,1,1);
});
outSourceBtn.setOnAction(outSourceSpecificEvent ->
{
grid1.getChildren().removeAll(machineIDLabel, companyLabel, machineIDField, companyNameField);
grid1.add(companyLabel,0,8,1,1);
grid1.add(companyNameField,1,8,1,1);
});
This code ended up working, as Pavlo suggested.
Although I just removed the objects specific to the opposing event.
The code works with no errors or overlapping.
inHouseBtn.setOnAction(inHouseSpecificEvent ->
{
grid1.add(machineIDLabel,0,8,1,1);
grid1.add(machineIDField,1,8,1,1);
grid1.getChildren().remove(companyLabel);
grid1.getChildren().remove(companyNameField);
});
outSourceBtn.setOnAction(outSourceSpecificEvent ->
{
grid1.add(companyLabel,0,8,1,1);
grid1.add(companyNameField,1,8,1,1);
grid1.getChildren().remove(machineIDLabel);
grid1.getChildren().remove(machineIDField);
});
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();
}
I've started building a small search engine for an index I created using Lucene. I use the GWT to create the GUI I like but I'm stuck in a problem. I am trying to add some results inside a FlowPanel which is inside the center of a DockLayoutPanel so I can have scrolling the page as I like. My problem is (as I found out ) that in order for scrolling to work ( and not have the browser window cut my results ) is to make every DockLayoutPanel child to not have position:absolute. My problem is that I cannot remove it ( or I don't know how ). When I disable it from chrome inspector scrolling works but when I re-enable it it cuts the flowPanel. Here is the java code :
public static void loadCellTrees(HashMap<Integer,LinkedHashMap<String,String>> searchResults, List<RootCategories> list)
{
VerticalPanel westTrees = new VerticalPanel();
TreeViewModel treeModel = new CellTreeWidget(list);
CellTree tree = new CellTree(treeModel, null);
HTMLBuilder builder = new HTMLBuilder(searchResults);
tree.setStyleName("tree");
tree.setAnimationEnabled(true);
dock.clear();
westTrees.clear();
westTrees.getElement().setAttribute("align", "center");
westTrees.add(tree);
FlowPanel resultsPanel = new FlowPanel();
resultsPanel.setStyleName("resultsPanel");
for(int i=0; i<searchResults.size(); i++)
{
HTML box = builder.toHTML(i);
box.setStyleName("resultBox");
resultsPanel.add(box);
}
dock.addWest(westTrees, 20);
dock.addNorth(RootPanel.get("wrap"),20);
dock.add(resultsPanel);
}
at the beginning of my code I add the dock (DockLayoutPanel ) to my RootLayoutPanel.
as I show at the first image... with position set to absolute I can have the scrollbar and the results are cut, but if I disable position: absolute as shown in the second pic
the scrollbar is enabled and I can have full access to all of my results.
The scrollbar is set like this :
RootLayoutPanel.get().getWidgetContainerElement(dock).getStyle().setOverflowY(Overflow.AUTO);
What I am trying to do is remove position for every dockLayoutPanel's child or set it to something else that doesn't creates me a problem.
I found a solution. On CSS styling simply I had to write
.cw-DockPanel > div {
position : initial !important;
}
If anyone has to suggest a better way than to use !important i would be glad to hear it.
I'm new to JavaFX, so this might be a trivial question. I have a ListView inside a dialog that shows when the user clicks a menu button. The problem is that when the dialog shows, the first item in the ListView is already selected. I have listView.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<NewChoices>()) as the listener for when a user selects an item, but since the first item is already selected, when I click on the first item nothing happens. I need to find a way to make sure that no item is selected when the dialog shows. I tried using listView.getSelectionModel().clearSelection() before showing the dialog, but if the program loses then regains focus, the first item is selected again without being clicked on. How can I prevent the first item, or any item for that matter, being preselected?
Here is the code:
Pane rootPane = new Pane();
ObservableList<NewChoices> listChoices = FXCollections.observableArrayList(NewChoices.values());
ListView<NewChoices> listView = new ListView<NewChoices>();
listView.setItems(listChoices);
rootPane.getChildren().add(listView);
Stage newDialog = new Stage(StageStyle.UTILITY);
newDialog.initModality(Modality.APPLICATION_MODAL);
newDialog.setTitle("New");
Scene newDialogScene = new Scene(rootPane);
newDialog.setScene(newDialogScene);
newDialog.show();
Should be fixed in 8u40 - see https://javafx-jira.kenai.com/browse/RT-38517
Since you have only a list on your new scene, with no buttons, when the scene gains the focus, as the only focusable node is the list, its first item gets selected.
Adding some OK/Cancel buttons may help since they can gain the focus instead, but for that they need to gain first the focus.
Any of these two options will work:
Put first the button, so its the first focusable node:
listView.setItems(listChoices);
Button button = new Button("Ok");
VBox rootPane = new VBox(10, button, listView);
Stage newDialog = new Stage(StageStyle.UTILITY);
newDialog.initModality(Modality.APPLICATION_MODAL);
newDialog.setTitle("New");
Scene newDialogScene = new Scene(rootPane);
newDialog.setScene(newDialogScene);
newDialog.show();
Remove temporaly the focusable property of the list:
listView.setItems(listChoices);
listView.setFocusTraversable(false);
Button button = new Button("Ok");
VBox rootPane = new VBox(10, listView, button);
Stage newDialog = new Stage(StageStyle.UTILITY);
newDialog.initModality(Modality.APPLICATION_MODAL);
newDialog.setTitle("New");
Scene newDialogScene = new Scene(rootPane);
newDialog.setScene(newDialogScene);
newDialog.show();
listView.setFocusTraversable(true);
In any case, both require an extra node. Anyway you will need it to close the modal stage, right?
If you still want to go without buttons, you could use JDK 8u40 early versions where the problem has already been fixed, as #wzberger points out, or if this is not possible, use ControlsFX new Dialogs API (a fork of the API that will ship in JDK 8u40), and you won't have any problem.
listView.setItems(listChoices);
final Dialog dialog = new Dialog();
dialog.getDialogPane().setContent(listView);
Optional<ButtonType> result = dialog.showAndWait();