Forcing layout change when Stage is resized in JavaFX - javafx

I ran into a possible bug in JavaFX, but having short time, I am looking for a workaround.
I have a bunch of windows containing a chart and a toolbar:
public void createGui() {
root = new VBox();
ToolBar toolbar = new ToolBar();
Button btnConfig = new Button(bundle.getString("chartWindow.menu.configure"));
btnConfig.setOnAction(e -> doChartConfig());
toolbar.getItems().addAll(btnConfig);
chartPane = new VBox();
root.getChildren().setAll(toolbar, chartPane);
VBox.setVgrow(chartPane, Priority.ALWAYS);
scene = new Scene(root);
setScene(scene);
updateChart(); // Creates a chart
}
I have to resize them from code. (Tiling the open charts.) The simplified tiling code is:
// bounds is the user available area of the monitors(s)
double windowWidth = bounds.getWidth() / tileRule.columns;
double windowHeight = bounds.getHeight() / tileRule.rows;
int r = 0;
int c = 0;
for (Window w : sel) {
w.setWidth(windowWidth);
w.setHeight(windowHeight);
w.setX(bounds.getMinX() + c * windowWidth);
w.setY(bounds.getMinY() + r * windowHeight);
c++;
if (c >= tileRule.getColumns()) {
c = 0;
r++;
if (r >= tileRule.rows) {
break;
}
}
}
When I do this, the windows are perfectly arranged:
However, as it is visible, some of the windows contents aren't resized with the window (Now, accidentally, they are the last 3, but it is not always the case. Sometimes there are more, and they are not always the last ones.)
It is clearly visible, that the scene is the one that isn't resized with the window.
As soon as I resize the window manually, the controls are layed out well.
I trled a bunch of things to hack this:
Calling the requestLayout method manually
Removing the root element from the scene and adding again
Removing the scene from the stage and adding again
Adding 100 ms delay between the resize operations
binding the root elements width to the window width (minus border size)
None of the above helped. (Yes, not even adding the scene again! It caused the most spectacular result, because the contents are resized to the window size, but with streaching its content.)
Does anyone have any other idea how to hack this bug?
I'm using Java 8u74.

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.

Display JavaFX Image without blur/dithering when zooming in

I'm trying to create a image editor tool, where I have X- and Y-axis. Now I want to support the user by seeing each pixel when zooming in so that they can work more exactly. In comparison you should take a look at paint.net where you can see the pixels when zooming in.
I'll give you a code snippet and perhaps you might have a hint for me where I should dig into (API or code examples). I've found nothing like it in the JAVAFX API documentation. I want to use an ImageView instance but not a Canvas.
ScrollPane scrollPane = new ScrollPane ();
BorderPane borderPane = new BorderPane();
Group group = new Group();
DoubleProperty zoomProperty = new SimpleDoubleProperty(1);
Scale scaleTransformation = new Scale();
scaleTransformation.xProperty().bind(zoomProperty);
scaleTransformation.yProperty().bind(zoomProperty);
ImageView viewer = new ImageView(getBackgroundImage());
viewer.setFitWidth(600);
viewer.setPreserveRatio(true);
viewer.setDisable(true);
viewer.setSmooth(false);
group.getChildren().add(viewer);
this.addEventFilter(ScrollEvent.SCROLL, new EventHandler<ScrollEvent>(){
#Override
public void handle(ScrollEvent se) {
if (se.isControlDown() && se.getDeltaY() > 0) {
double value = zoomProperty.get() * 1.1;
zoomProperty.set(value);
se.consume();
} else if (se.isControlDown() && se.getDeltaY() < 0) {
double value = zoomProperty.get() / 1.1;
zoomProperty.set(value);
se.consume();
}
}
});
borderPane.setCenter(group);
scrollPane.setHbarPolicy(ScrollBarPolicy.ALWAYS);
scrollPane.setVbarPolicy(ScrollBarPolicy.ALWAYS);
scrollPane setContent(new Group(borderPane));
Thanks for you help.
EDIT
The related question and it's answers do not solve my problem (JavaFX ImageView without any smoothing). The solution 1-4 do not work. Only the hack does. I guess I could be related the node structure I use. I've adjusted my code.
I have found one solution (referring to the possible duplicate) and using a variant of method 1.
In my image constructor I set the requested size to something large - so for an image that I know is about 1000 pixels wide I request an image 10000 pixels wide:
Image image = new Image(file.toURI().toString(), 10000.0, 10000.0, true, false);
I am not sure if this is giving a true view of the pixels, or if it is smoothing at a very low level...... but it works for my needs.

JavaFX - Slim images to prevent overloading the heapspace?

Due to programming an Image Viewer with JavaFX I implemented an image library with following structure:
HBox - ScrollPane - VBox
This is the basic structure, the library objects are constructed as following:
StackPane - ImageView - Label
Because there's no setAlignment method for ScrollPane, I put the whole thing in a HBox, even though I don't need the other features of it.
These methods are responsible for the generation and initialization of the library:
private HBox createScrollableLibrary()
{
libraryContent = new VBox();
libraryContent.setPadding(new Insets(10));
libraryContent.setAlignment(Pos.CENTER);
libraryContent.setStyle("-fx-background-color: transparent");
libraryContent.setSpacing(10);
libraryScroll = new ScrollPane();
libraryScroll.setHbarPolicy(ScrollBarPolicy.NEVER);
libraryScroll.setStyle("-fx-background: rgb(0, 0, 0, 0.3);"
+ "-fx-background-color: rgb(0, 0, 0, 0.3);");
libraryScroll.setMinWidth(135);
libraryScroll.setMaxWidth(135);
libraryScroll.setContent(libraryContent);
HBox root = new HBox();
root.setPickOnBounds(false);
root.getChildren().add(libraryScroll);
return root;
}
And:
public void initLibrary()
{
Runnable work = () ->
{
ObservableList<StackPane> previewList = FXCollections.observableArrayList();
ImageView preview;
Label label;
StackPane previewAndLabel;
for(int i = 0; i < picturePaths.size(); i++)
{
String path = picturePaths.get(i).toURI().toString();
preview = new ImageView(new Image(path));
preview.setPreserveRatio(true);
preview.setFitWidth(100);
//label = new Label(new File(path).getName());
label = new Label("IMAGE");
StackPane.setAlignment(label, Pos.CENTER_LEFT);
previewAndLabel = new StackPane();
previewAndLabel.getChildren().addAll(preview, label);
previewList.add(previewAndLabel);
}
Platform.runLater(() -> GUI.getLibraryContent().getChildren().addAll(previewList));
};
loading = new Thread(work);
if(loading.isAlive())
loading.interrupt();
loading.start();
}
Info: picturePaths is an ObservableList which contains the paths of all selected images which in turn are selected with a FileChooser.
Now my question(s):
I know it's very inefficient to use large sized pictures as preview images.
Even though I increased my heap space to 1GB it slows down and throws a java.lang.OutOfMemoryError exception when I'm adding more pictures, I'm guessing the limit is about 20-25 of large sized pictures.
How can I "slim" a normal picture with, for example a resolution of 1920x1080 to 1024x720 ? Or is there another opportunity to make my library use small and "fast" images? And can I buffer my already "calculated" images to make my Image Viewer faster?
Appreciate any tips and help. Please critisize my code if needed!
One can try setting the size constraints on the preview pictures during the Image constructor and not afterwards.

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.

flex: Drag and drop- object centering

In a drag+drop situation using Flex, I am trying to get the object center aligned to the point of drop- somehow, irrespective of the adjustments to height and width, it is always positioning drop point to left top.
here is the code..
imageX = SkinnableContainer(event.currentTarget).mouseX;
imageY = SkinnableContainer(event.currentTarget).mouseY;
// Error checks if imageX/imageY dont satisfy certain conditions- move to a default position
// img.width and img.height are both defined and traced to be 10- idea to center image to drop point
Image(event.dragInitiator).x = imageX-(img.width)/2;
Image(event.dragInitiator).y = imageY-(img.height)/2
The last 2 lines don't seem to have any effect. Any ideas why-must be something straightforward, that I am missing...
You can use the following snippet:
private function on_drag_start(event:MouseEvent):void
{
var drag_source:DragSource = new DragSource();
var drag_initiator:UIComponent = event.currentTarget as UIComponent;
var thumbnail:Image = new Image();
// Thumbnail initialization code goes here
var offset:Point = this.localToGlobal(new Point(0, 0));
offset.x -= event.stageX;
offset.y -= event.stageY;
DragManager.doDrag(drag_initiator, drag_source, event, thumbnail, offset.x + thumbnail.width / 2, offset.y + thumbnail.height / 2, 1.0);
}
Here is one important detail. The snippet uses stage coordinate system.
If you use event.localX and event.localY, this approach will fail in some cases. For example, you click-and-drag a movie clip. If you use localX and localY instead of stage coordinates, localX and localY will define coordinates in currently clicked part of the movie clip, not in the whole movie clip.
Use the xOffset and yOffset properties in the doDrag method of DragManager.
Look here for an example.

Resources