JavaFX CSS for nested SplitPanes - css

I'm tasked with styling/skinning a JavaFX application. I'm trying to do as much in CSS stylesheets as possible rather than in the code. I've got a question about nested SplitPanes: when the application is rendered, is there anything that is created surrounding the inner SplitPane that gets in between the outer SplitPane and the inner SplitPane in the parent/child hierarchy of nodes.
Here's a little test application that mimics my full application:
BorderPane root = new BorderPane();
root.setId("root");
Label mws_header = new Label("This is the header");
root.setTop(mws_header);
Label sidebar = new Label("This is the sidebar");
root.setLeft(sidebar);
SplitPane outer = new SplitPane();
outer.setId("mws_pano_360_split");
outer.setOrientation(Orientation.VERTICAL);
root.setCenter(outer);
Label bigGraphic = new Label("This is the top");
outer.getItems().add(bigGraphic);
SplitPane inner = new SplitPane();
inner.setId("flat_data_split");
inner.setOrientation(Orientation.HORIZONTAL);
Label label1 = new Label("This is the left");
Label label2 = new Label("This is the right");
inner.getItems().addAll(label1, label2);
outer.getItems().add(inner);
In the CSS, what I'd like to do is get to the inner SplitPane using direct child syntax:
BorderPane > SplitPane > SplitPane {
/* all kinds of styling of background colors */
}
But instead, what I have to do is treat it as a descendant:
BorderPane > SplitPane SplitPane {
/* all kinds of styling of background colors */
}
So what might be getting in between the outer SplitPane and the inner SplitPane? I'd love to have a tool that lets you inspect the parent/child nodes in the hierarchy but so far haven't found one.

Related

Region width not refreshing automatically after modifying Grid Pane to which the region width is bound - JavaFX

I'm making a simple Java GUI app using JavaFX that has a Border Pane as the root node.
In the top section of the Border Pane, there is a Grid Pane with three columns (top Grid Pane from now on).
In the first column of the top Grid Pane, there is a Home Button, in the second column, there is an empty Region that only serves as spacer between the first and third column of the top Grid Pane, and in the third column, there is another GridPane (right Grid Pane from now on).
The right Grid Pane contains one Button (Log In Button) on start. However, when a user successfully logs into the app, two other Buttons and a Label are added to the right Grid Pane as part of the Log In Button click event.
The spacer maxWidthProperty and minWidthProperty are bound to the top Grid Pane (tgp) widthProperty and the right Grid Pane(rgp) widthProperty like this:
spacer.minWidthProperty().bind(tgp.widthProperty().subtract(80).subtract(rgp.widthProperty()).subtract(3));
spacer.maxWidthProperty().bind(tgp.widthProperty().subtract(80).subtract(rgp.widthProperty()).subtract(3));
which makes the right Grid Pane move nicely with its buttons staying on the right side of the scene when a user resizes the main stage.
However, a problem occurs when the user logs in and additional buttons are added to the right Grid Pane. The spacer somehow misses this change and its width stays the same, which makes the additional Buttons appear outside of the current stage width. The only way to refresh the spacer width is to interact with the stage somehow, by clicking minimize/maximize/restore or by clicking any button on the scene.
Is there a way to automatically refresh Region width after the nodes to which its width is bound to are modified? Or, is there a better approach to making a top Grid Pane with one button on the left and modifiable number of buttons (nodes) on the right?
Edit: Here is a demonstration of the problem with several screenshots stacked on one another:
Minimal reproducible example:
BorderPane root = new BorderPane();
GridPane tgp = new GridPane();
tgp.minWidthProperty().bind(root.widthProperty());
tgp.maxWidthProperty().bind(root.widthProperty());
tgp.setStyle("-fx-background-color: WHITE; -fx-border-color: LIGHTGREY;");
tgp.setMinHeight(37);
tgp.setMaxHeight(37);
root.setTop(tgp);
Button homeButton = new Button("Home"));
homeButton.setMinHeight(35);
homeButton.setMaxHeight(35);
homeButton.setMinWidth(80);
homeButton.setMaxWidth(80);
tgp.add(homeButton, 0, 0);
GridPane rgp = new GridPane(); // Right Grid Pane - holds User related nodes
rgp.setHgap(5);
tgp.add(rgp, 2, 0);
Label unl = new Label("My Profile");
unl.setFont(new Font("Calibri", 15));
unl.setTextFill(Color.RED);
unl.setMinWidth(Region.USE_PREF_SIZE);
Button wlButton = new Button("Watchlist");
wlButton.setMinHeight(35);
wlButton.setMaxHeight(35);
wlButton.setMinWidth(80);
wlButton.setMaxWidth(80);
Button cartButton = new Button("Cart");
cartButton.setMinHeight(35);
cartButton.setMaxHeight(35);
cartButton.setMinWidth(60);
cartButton.setMaxWidth(60);
Button logInOutButton = new Button("Log In");
logInOutButton.setMinHeight(35);
logInOutButton.setMaxHeight(35);
logInOutButton.setMinWidth(60);
logInOutButton.setMaxWidth(60);
rgp.add(logInOutButton, 3, 0);
logInOutButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
if (logInOutButton.getText().equals("Log In")) {
LogInStage lis = new LogInStage();
lis.initStage();
if (lis.username != null) {
logInOutButton.setText("Log Out");
rgp.add(unl, 0, 0);
rgp.add(wlButton, 1, 0);
rgp.add(cartButton, 2, 0);
}
} else if (logInOutButton.getText().equals("Log Out")) {
logInOutButton.setText("Log In");
rgp.getChildren().remove(unl);
rgp.getChildren().remove(wlButton);
rgp.getChildren().remove(cartButton);
}
}
});
Region spacer = new Region();
spacer.minWidthProperty().bind(tgp.widthProperty().subtract(80).subtract(rgp.widthProperty()).subtract(3));
spacer.maxWidthProperty().bind(tgp.widthProperty().subtract(80).subtract(rgp.widthProperty()).subtract(3));
tgp.add(spacer, 1, 0)
It's always a bad idea to use bindings, if you can avoid it. Any changes to the size constraints can lead to a new layout pass being scheduled, but during the layout pass they are assumed to be constant. If you now introduce a binding the following sequence of events could happen:
A layout pass is requested for the GridPane, setting a flag to indicate layout is required
A layout pass happens. During the layout pass the children are resized. This triggers an update of the constraints of the children with the bindings.
The flag is cleared, but the changes to the contraints already happened. The layout won't reflect this. The GridPane gets another reason to do a layout.
I don't know, how your scene is set up in detail, but I recommend using column constraints: Set the grow priorities for the outer ones to SOMETIMES and the one for the center to ALWAYS. If you require some spacing around the children, you could use GridPane.setMargin (or the padding of the GridPane itself, if you require the a distance to the edges for all children).
#Override
public void start(Stage primaryStage) {
Button[] rightContent = new Button[3];
for (int i = 0; i < rightContent.length; i++) {
Button btn = new Button(Integer.toString(i));
GridPane.setColumnIndex(btn, i);
rightContent[i] = btn;
}
Button cycle = new Button("cycle");
GridPane rgp = new GridPane(); // I would usually use a HBox here
// don't grow larger than needed
rgp.setMaxWidth(Region.USE_PREF_SIZE);
// cycle though 0 to 3 buttons on the right
cycle.setOnAction(new EventHandler<ActionEvent>() {
int nextIndex = 0;
#Override
public void handle(ActionEvent event) {
if (nextIndex >= rightContent.length) {
rgp.getChildren().clear();
nextIndex = 0;
} else {
rgp.getChildren().add(rightContent[nextIndex]);
nextIndex++;
}
}
});
ColumnConstraints sideConstraints = new ColumnConstraints();
sideConstraints.setHgrow(Priority.SOMETIMES);
ColumnConstraints centerConstraints = new ColumnConstraints();
centerConstraints.setHgrow(Priority.ALWAYS);
//prefer to grow the center part of the GridPane
GridPane root = new GridPane();
root.getColumnConstraints().addAll(sideConstraints, centerConstraints, sideConstraints);
root.add(cycle, 0, 0);
root.add(rgp, 2, 0);
// add something to visualize the center part
// you could simply leave this part out
Region center = new Region();
center.setStyle("-fx-border-radius: 10;-fx-border-width: 1;-fx-border-color:black;");
root.add(center, 1, 0);
Scene scene = new Scene(root, 400, 300);
primaryStage.setScene(scene);
primaryStage.show();
}
As mentioned in the comments, the center region is not actually needed.

Javafx Apply CSS to element of Custom-Menu-Item

I have a context menu with a single custom-menu-item. The custom-menu-item consists of a pane which in turns holds a vbox.
My goal is to get rid of the blue highlighting. I have successfully got rid of blue highlighting with the context-menu. But cannot access the layer below it (the pane or vbox). The context menu has been assigned .context as a class name.
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/resources/fxml/ThePane.fxml"));
Pane pane = (Pane) fxmlLoader.load();
customMenuItem = new CustomMenuItem(pane);
.context .custom-menu-item:focused{
-fx-background-color:white;
}
.context .custom-menu-item{
-fx-background-color:white;
}

Resizeable Gridpane or other container

Hi I'm trying to create a simple layout that looks like this using JavaFX.
I would like the user to be able to drag/resize the middle bar. I've tried to make this using a GridPane. But I can't figure out how to get the middle resized. Perhaps GridPane is not the way to go. Both panes will contain a lot of other program code later on. Thanks!
Rectangle2D primaryScreenBounds = Screen.getPrimary().getVisualBounds();
stageRef.setMaxWidth(primaryScreenBounds.getWidth());
stageRef.setMaxHeight(primaryScreenBounds.getHeight());
GridPane gridPane = new GridPane();
gridPane.setGridLinesVisible(true);
gridPane.setPadding(new Insets(10));
Scene scene = new Scene(gridPane);
VBox vBoxMain = new VBox();
vBoxMain.setPrefWidth((primaryScreenBounds.getWidth()/5)*4);
vBoxMain.setPrefHeight(primaryScreenBounds.getHeight());
TextArea textArea = new TextArea();
textArea.setWrapText(true);
textArea.setPrefHeight(primaryScreenBounds.getHeight());
vBoxMain.getChildren().addAll(textArea);
vBoxMain.isResizable();
VBox vBoxSide = new VBox();
vBoxSide.setPrefWidth((primaryScreenBounds.getWidth()/5));
vBoxSide.setPrefHeight(primaryScreenBounds.getHeight());
vBoxSide.isResizable();
gridPane.add(vBoxSide, 1,1,1,1);
gridPane.add(vBoxMain, 2,1,4,1);
stageRef.setScene(scene);
stageRef.show();
You could use a SplitPane:
A control that has two or more sides, each separated by a divider,
which can be dragged by the user to give more space to one of the
sides, resulting in the other side shrinking by an equal amount.
Then you add two others containers to this pane allowing the user to change the position of the divider. You can also set minimum widths for each component within the pane or set the position of each divider within your code.

Adjustment of contents in FlowPane

I have a flowpane in center and i applied a slider effect which gets invoke on a click of button on the right (so slider moves from right to left when expanded). I have followed JewelSea slider tutorial mentioned here Slider
Now i have two different flowpanes in two different nodes. Both the flowpane contains array of labels but the only difference is, One flowpane contains scrollbar and is contained in TitlePane while the other is without scrollbar and no titlepane.
So now if i click on slider the contents in the flowpane(without scrollbar & titlepane) gets automatically adjusted but its not the same case with the flowpane containing scrollbar.
Here is relevant code for flowpane with scrollbar-
public void loadCase() {
ScrollPane s = null;
if (!homeController.mainTabPane.getTabs().contains(testTab)) {
int app = 0;
if (appareaList.size() > 0) {
FlowPane fpTestmoduleContainer = new FlowPane();
FlowPane example = new FlowPane();
for (ApplicationAreas appttribute : appareaList) {
appTestTitledPane[app] = new TitledPane();
appTestTitledPane[app].setText(appttribute.getApplication_name());
appTestTitledPane[app].setPrefSize(Control.USE_COMPUTED_SIZE, Control.USE_COMPUTED_SIZE);
/*Module loop start*/
fpTestmoduleContainer.setHgap(10);
fpTestmoduleContainer.setVgap(10);
// fpTestmoduleContainer.setPrefSize(Control.USE_COMPUTED_SIZE, Control.USE_COMPUTED_SIZE);
List<TestModuleAttribute> testmoduleList = WSData.getTestingModuleList(appttribute.getApplication_id());
ArrayList<Label> listTestlbs = new ArrayList<Label>(testmoduleList.size());
System.out.println("testmoduleList.size()" + testmoduleList.size());
int i = 0;
for (TestModuleAttribute testmattribute : testmoduleList) {
listTestlbs.add(new Label());
listTestlbs.get(i).setText(testmattribute.getModule_name());
listTestlbs.get(i).setAlignment(Pos.CENTER);
listTestlbs.get(i).setTextAlignment(TextAlignment.CENTER);
listTestlbs.get(i).setWrapText(true);
listTestlbs.get(i).setPrefSize(Control.USE_COMPUTED_SIZE, Control.USE_COMPUTED_SIZE);
listTestlbs.get(i).setId(testmattribute.getFxnode_css());
Image imgInstalled = new Image(getClass().getResourceAsStream("/upgradeworkbench/View/Icons/ok.png"));
listTestlbs.get(i).setGraphic(new ImageView(imgInstalled));
listTestlbs.get(i).setContentDisplay(ContentDisplay.BOTTOM);
Tooltip testtp = new Tooltip();
testtp.setText("Total No. Of test Cases :" + testmattribute.getTest_case());
testtp.setWrapText(true);
listTestlbs.get(i).setTooltip(testtp);
addModuleMouseClickListener(listTestlbs.get(i), testmattribute.getModule_name(), testmattribute.getFxnode_css(), testmattribute.getTest_case());
i = i + 1;
}
s = new ScrollPane();
s.setContent(fpTestmoduleContainer);
fpTestmoduleContainer.setPrefWidth(1500);
fpTestmoduleContainer.getChildren().addAll(listTestlbs);
//appTestTitledPane[app].setContent(fpTestmoduleContainer[app]);
listTestlbs.clear();
app = app + 1;
}
appareaTestmoduleContainer.getPanes().addAll(appTestTitledPane);
appareaTestmoduleContainer.setExpandedPane(appTestTitledPane[0]);
testTab.setText("Test Cases Wizard");
testTab.setText("Testing Application Foot Print");
//mainTab.setClosable(true);
// testTab.getContent().setVisible(true);
HBox hb = new HBox();
testTab.setContent(s);
}
}
}
Image of slider working as expected - before sliding
After sliding (without scrollbar) the 4 modules get to the next row as space is occupied by the slider
After adding scrollpane and embedding flowpane inside it. Slider overlaps the flowpane contents as shown
I want to know why the scrollbar causing issue in auto adjustment of contents inside the flowpane and how can i fix it ?
Here the width of your scrollpane is fixed. And then so is the width of the flow pane.You need to change the size of your scrollpane so that its content gets reset.
Use the following code.
scroll[app].setFitToHeight(true);
scroll[app].setFitToWidth(true);
This code will set the size of scrollpane according to the view. The flowpane will also adjust accordingly then.

JavaFX borderpane.setCenter replaces entire scene

I'm trying to load a FXML into a FXML. The main FXML has a borderpane, and the second contains a VBox. I load both of these with the FXMLLoader. When I try to set the center of the boarderpane, the entire screen gets replaced with the second FXML. Here is the code
BorderPane riskAnalysis = new BorderPane((BorderPane) FXMLLoader.load(getClass().getResource("./proposaldevelopment/riskAnalysis.fxml")));
VBox center = new VBox((VBox) FXMLLoader.load(getClass().getResource("./proposaldevelopment/openRiskAssessmentVbox.fxml")));
riskAnalysis.setCenter(center);
root = riskAnalysis;
stage.getScene().setRoot(root);
Any ideas that could cause this? I also tried making a new label and putting that into the borderpane center with the same result.
Try as
BorderPane riskAnalysis = (BorderPane) FXMLLoader.load(getClass().getResource("./proposaldevelopment/riskAnalysis.fxml"));
VBox center = (VBox) FXMLLoader.load(getClass().getResource("./proposaldevelopment/openRiskAssessmentVbox.fxml"));
riskAnalysis.setCenter(center);
stage.getScene().setRoot(riskAnalysis);

Resources