Layouting problems with JavaFX Region - layout

I want to write a new class that extends Region containing a StackPane inside. But I'm getting into trouble when I add insets to it like padding or a border. Here is a simplified example of the class:
public class CustomPane extends Region
{
private ToggleButton testControl = new ToggleButton("just a test control");
private StackPane rootPane = new StackPane(testControl);
public CustomPane()
{
getChildren().add(rootPane);
setStyle("-fx-border-color: #257165; -fx-border-width: 10;");
}
}
And that is how the result looks like:
If I try to move the StackPane by calling
rootPane.setLayoutX(10);
rootPane.setLayoutY(10);
then the Region just grows:
But really I wanted it to look like this:
(The third image was created by extending StackPane instead of Region, which already manages the layouting stuff correctly. Unfortunately I have to extend Region since I want to keep getChildren() protected.)
Okay, I tried to handle the layout calculation but I didn't come up with it. Could experts give me some advise?

StackPane uses the insets for layouting the (managed) children. Region doesn't do this by default. Therefore you need to override the layoutChildren with something that uses these insets, e.g.:
#Override
protected void layoutChildren() {
Insets insets = getInsets();
double top = insets.getTop(),
left = insets.getLeft(),
width = getWidth() - left - insets.getRight(),
height = getHeight() - top - insets.getBottom();
// layout all managed children (there's only rootPane in this case)
layoutInArea(rootPane,
left, top, // offset of layout area
width, height, // available size for content
0,
HPos.LEFT,
VPos.TOP);
}

Related

Parent View Scaling OnClickListener

Solution
Original: Window Service > Parent Layout > Views
Fixed: Window Service > Parent Layout > Parent Layout 2 > Views
You cannot use a parent layout that is not a child to another layout before being added to window service. Only graphics will scale if you do so.
Issue
I have a RelativeLayout that parents some views like a TextView, SeekBar, and a Button. Using setScaleX/Y, I am successfully able to scale the views visually. A problem I now have is that although the graphics have scaled, the onClickListeners touch area have not scaled along with the graphics. I need to touch the original position in order to trigger any of these events. I want the touch area to scale with the graphics without scaling the children individually.
Current Scaling Code:
size.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
#Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
/** Max Progress is 100 **/
float scale = 1.0f;
scale = scale + 0.01f * progress;
/** mLayout is Parent Layout **/
mLayout.setScaleX(scale);
mLayout.setScaleY(scale);
mLayout.invalidate();
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
Solution
Original: Window Service > Parent Layout > Views
Fixed: Window Service > Parent Layout > Parent Layout 2 > Views
You cannot use a parent layout that is not a child to another layout before being added to window service. Only graphics will scale if you do so.

When to use translate and when relocate - What is the difference between translate and layout coordinates?

When to use translate and when relocate in order to move a node? In the end of the day it seems they do the same thing (visually); move the node; the first by doing a translation on the origin (the x, y stays the same), the second by changing the x, y coords.
So suppose i want to move a node in a specific point in the screen.. should i use node.relocate(x,y) or node.setTranslateX(x), node.setTranslateY(y)?
To demostrate what I mean I have made a sample program you can play with:
A rectangle on the screen, whose position is determined by 4 sliders (2 of them controlling the layout x, y the other two controlling the translate x, y).
/* imports are missing */
public class TransReloc extends Application{
#Override
public void start(Stage primaryStage) throws Exception {
Group root = new Group();
Rectangle rect = new Rectangle(100, 50, Color.BLUE);
root.getChildren().add(rect);
VBox controlGroup = new VBox();
Slider relocX = new Slider(-100, 100, 0 );
Slider relocY = new Slider(-100, 100, 0 );
Slider transX = new Slider(-100, 100, 0 );
Slider transY = new Slider(-100, 100, 0 );
rect.layoutXProperty().bind(relocX.valueProperty());
rect.layoutYProperty().bind(relocY.valueProperty());
rect.translateXProperty().bind(transX.valueProperty());
rect.translateYProperty().bind(transY.valueProperty());
controlGroup.getChildren().addAll(relocX, relocY, transX, transY);
root.getChildren().add(controlGroup);
controlGroup.relocate(0, 300);
Scene scene = new Scene(root, 300, 400, Color.ALICEBLUE);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
layout coordinates are used by Layout Managers like StackPane or VBox to control their children location. Group (and Pane) leaves children layout to developer thus there is no difference from translate functionality.
So generally you should only change translate coordinates for fine location tuning and leave layout to layout managers (actually you can't change layoutX, layoutY for nodes inside non Group/Pane layout managers)
As an example try to run next code and resize window to see how StackPane recalculates layoutBounds
public void start(Stage primaryStage) throws Exception {
StackPane root = new StackPane();
Rectangle rect = new Rectangle(100, 50, Color.BLUE);
root.getChildren().add(rect);
Scene scene = new Scene(root, 300, 300, Color.ALICEBLUE);
rect.layoutXProperty().addListener( (e) -> {
System.out.println(rect.getLayoutX() + ":" + rect.getLayoutY());
});
primaryStage.setScene(scene);
primaryStage.show();
}
Another difference is that when you call Node.getBoundsInLocal(), will calculate the LayoutX, LayoutY and all the applied Effects. But, Node.getBoundsInParent() will get calculated with LayoutX, LayoutY, all applied Effects plus all transformations (rotation, translation and scaling). So you can use LayoutX/Y properties as a main position and use translateX/Y as a second or an alternative way to move the node. And the other difference is discussed above, I mean not to copy from Sergey Grinev.

Scale background image during screen rotation in android using Tabris

I have following server side code in the top level page to set background image for all pages in the app.
#Override
public void createContent(final Composite parent, final PageData oData) {
Image bg = ResourceManager.getImage( LnfSettings.IMAGE_PAGE_BACKGROUND );
Composite comp = parent.getParent();
int width = comp.getDisplay().getClientArea().width;
int height = comp.getDisplay().getClientArea().height;
comp.setBackgroundImage( new Image( bg.getDevice(), bg.getImageData().scaledTo(
width, height ) ) );
... more code here to create layout and contents
}
Above code correctly sets background for all pages and also scaled image to fit with the different screen sizes. But if I rotate the screen, image doesn't get scaled according to the new screen dimension. How to deal with this issue? I am using Tabris 1.4.
You can add a resize listener to your composite like this:
comp.addListener(SWT.Resize, listenerComp);
Listener listenerComp = new Listener() {
#Override
public void handleEvent(Event event) {
...
}
};
Just get sure you cache your scaled Images so you do not scale and create new images on every rotation.

Add fixed positioned layer to FlowPane [duplicate]

This question already has answers here:
Add fixed positioned Combobox inside FlowPane
(2 answers)
Closed 9 years ago.
I have a FlowPane with panels which will be used to display data in front of the user.
![enter image description here][1]
I added also scrollpane when the number of the panels is bigger than the visible area.
I also want to add filter which will sort the panels by type and will display only the appropriate. The red area will hold the ComboBox which will be the filter.
And as you can see the red are pushes down the FlowPane which will make a gap between the top component and the scroll when I make the area transparent.
Is there a way to use the z-index and place the red are in front of the FlowPane? Or some other solution?
This is the result that I would like to get:
![enter image description here][2]
Investigate this example based on your code in previous questions:
public class Demo extends Application {
#Override
public void start(Stage stage) throws Exception {
StackPane stackPane = new StackPane();
stackPane.setAlignment(Pos.TOP_LEFT);
stackPane.getChildren().addAll(infrastructurePane(), getFilterPane());
Scene scene = new Scene(stackPane);
stage.setScene(scene);
stage.show();
}
public Pane getFilterPane() {
ObservableList<String> options =
FXCollections.observableArrayList(
"Option 1", "Option 2", "Option 3");
ComboBox<String> combo = new ComboBox<String>(options);
HBox pane = new HBox();
pane.setPadding(new Insets(20));
pane.setStyle("-fx-background-color: rgba(255,0,85,0.4)");
pane.getChildren().add(combo);
pane.setMaxHeight(40);
// Optional
//pane.setEffect(new DropShadow(15, Color.RED));
return pane;
}
public ScrollPane infrastructurePane() {
final FlowPane flow = new FlowPane();
flow.setPadding(new Insets(5, 5, 5, 5));
flow.setVgap(5);
flow.setHgap(5);
flow.setAlignment(Pos.CENTER);
final ScrollPane scroll = new ScrollPane();
scroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED); // Horizontal scroll bar
scroll.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED); // Vertical scroll bar
scroll.setFitToHeight(true);
scroll.setFitToWidth(true);
scroll.setContent(flow);
// scroll.viewportBoundsProperty().addListener(new ChangeListener<Bounds>() {
// #Override
// public void changed(ObservableValue<? extends Bounds> ov, Bounds oldBounds, Bounds bounds) {
// flow.setPrefWidth(bounds.getWidth());
// flow.setPrefHeight(bounds.getHeight());
// }
// });
//flow.setPrefWrapLength(170); // preferred width allows for two columns
flow.setStyle("-fx-background-color: yellow;");
for (int i = 0; i < 28; i++) {
flow.getChildren().add(generateRectangle());
}
String cssURL = "/com/dx57dc/css/ButtonsDemo.css";
String css = this.getClass().getResource(cssURL).toExternalForm();
flow.getStylesheets().add(css);
return scroll;
}
public Rectangle generateRectangle() {
final Rectangle rect2 = new Rectangle(10, 10, 10, 10);
rect2.setId("app");
rect2.setArcHeight(8);
rect2.setArcWidth(8);
//rect2.setX(10);
//rect2.setY(160);
rect2.setStrokeWidth(1);
rect2.setStroke(Color.WHITE);
rect2.setWidth(220);
rect2.setHeight(180);
rect2.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
rect2.setFill(Color.ALICEBLUE);
}
});
return rect2;
}
public static void main(String[] args) {
launch(args);
}
}
EDIT:
As per comment, here is the combo without pane. Since there is no pane the mouse events will not be blocked. Replace only this method with above one:
public ComboBox getFilterPane() {
ObservableList<String> options =
FXCollections.observableArrayList(
"Option 1", "Option 2", "Option 3");
ComboBox<String> combo = new ComboBox<String>(options);
combo.setTranslateX(10);
combo.setTranslateY(10);
return combo;
}
if you're using JavaFX 8, you can try a Notification Pane from ControlsFX project
It looks like:
It's pretty unclear to get which behaviour you don't want and which one you want.
This sentence "And as you can see the red are pushes down the FlowPane which will make a gap between the top component and the scroll when I make the area transparent." is particularly hard to understand.
But if you just want to "use the z-index and place the red are in front of the FlowPane?", maybe all you're asking for is just a StackPane ?
StackPane lays out its children in a back-to-front stack.
The z-order of the children is defined by the order of the children
list with the 0th child being the bottom and last child on top. If a
border and/or padding have been set, the children will be layed out
within those insets.
http://docs.oracle.com/javafx/2/api/javafx/scene/layout/StackPane.html
If you want the red area be part of the ScrollPane:
Create a VBox
Add The Red Area Component to VBox
Add the FlowPane to VBox
Set VBox as the ScrollPanes Content
If the Layout with VBox's doenst look statisfying try Borderpane and set the "Red Area" top and your flowpane as center.
Is there a way to use the z-index and place the red are in front of the FlowPane? Or some other solution?
see QuidNovi's answer

Scaling in JavaFX and ScrollPanes

I've been trying to work with the scaling transform in JavaFX, but haven't quite been able to wrap my head around it. Basically, I have a Pane containing a complex graph and would like to be able to rescale it. The scaling part itself works fine, however, the enclosing scroll pane will not adapt to the graph.
For simplicity's sake, i'll post a short example in which my graph is replaced by a label:
public class TestApp extends Application {
#Override public void start(final Stage stage) throws Exception {
final Label label = new Label("Hello World");
label.getTransforms().setAll(new Scale(0.5, 0.5));
label.setStyle("-fx-background-color:blue");
label.setFont(new Font(200));
final ScrollPane scrollPane = new ScrollPane();
scrollPane.setContent(label);
stage.setScene(new Scene(scrollPane));
stage.setWidth(200);
stage.setHeight(100);
stage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
The label will scale correctly, but the enclosing scroll pane's bars will still accomodate a component of the original size.
I've tried so far:
Playing around with the labels min and pref size
wrapping the label inside a Group (no scrollbars will appear whatsoever)
scaling the enclosing Group rather than the label
What am I missing? What can I do to make the ScrollPane adapt to the content view?
Thanks for your help.
According to the ScrollPane document you might try to wrap a Pane in a Group so the ScrollPane is scroll by visual bound not the actual layout bound.
ScrollPane layout calculations are based on the layoutBounds rather than the
boundsInParent (visual bounds) of the scroll node. If an application wants the
scrolling to be based on the visual bounds of the node (for scaled content etc.),
they need to wrap the scroll node in a Group.
I implemented scaling in a ScrollPane for Graphs and other nodes in
this example of scrollpane viewports, transforms and layout bounds in JavaFX.
The code was implemented when I was first learning JavaFX, so certainly the code could be cleaner and perhaps there are simpler ways to accomplish this (e.g. using a Group as the container for the scaled node as suggested in the ScrollPane documentation).
One key to getting the solution I wanted (ScrollBars only appearing when you are zoomed in and the node is larger than the visible viewport), was this code:
// create a container for the viewable node.
final StackPane nodeContainer = new StackPane();
nodeContainer.getChildren().add(node);
// place the container in the scrollpane and adjust the pane's viewports as required.
final ScrollPane scrollPane = new ScrollPane();
scrollPane.setContent(nodeContainer);
scrollPane.viewportBoundsProperty().addListener(
new ChangeListener<Bounds>() {
#Override public void changed(ObservableValue<? extends Bounds> observableValue, Bounds oldBounds, Bounds newBounds) {
nodeContainer.setPrefSize(
Math.max(node.getBoundsInParent().getMaxX(), newBounds.getWidth()),
Math.max(node.getBoundsInParent().getMaxY(), newBounds.getHeight())
);
}
});
...
// adjust the view layout based on the node scalefactor.
final ToggleButton scale = new ToggleButton("Scale");
scale.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent actionEvent) {
if (scale.isSelected()) {
node.setScaleX(3); node.setScaleY(3);
} else {
node.setScaleX(1); node.setScaleY(1);
}
// runlater as we want to size the container after a layout pass has been performed on the scaled node.
Platform.runLater(new Runnable() {
#Override public void run() {
nodeContainer.setPrefSize(
Math.max(nodeContainer.getBoundsInParent().getMaxX(), scrollPane.getViewportBounds().getWidth()),
Math.max(nodeContainer.getBoundsInParent().getMaxY(), scrollPane.getViewportBounds().getHeight())
);
}
});
}
});

Resources