How to dynamically resize an svg in javafx according to the size of the region - svg

I'm keen on svg and would like to put many of them in my User Interface. But I have a problem with the size of svg. I would like to load any svg I retrieve as a parameter and resize it dynamically to the size of the control.
All the examples I found are resize thanks to the "rescale" method (as found in the following article JavaFX: How to resize button containing svg image.
But since I have no idea of the size of the original svg I don't know what factor to apply in the rescale method.
So, my question is how do I generify the following code:
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.scene.shape.SVGPath;
import javafx.stage.Stage;
public class Main extends Application{
private final int MIN_BUTTON_SIZE = 10;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
HBox root = new HBox();
root.setAlignment(Pos.CENTER);
SVGPath svg = new SVGPath();
svg.setContent("M87.5,50.002C87.5,29.293,70.712,12.5,50,12.5c-20.712,0-37.5,16.793-37.5,37.502C12.5,70.712,29.288,87.5,50,87.5" +
"c6.668,0,12.918-1.756,18.342-4.809c0.61-0.22,1.049-0.799,1.049-1.486c0-0.622-0.361-1.153-0.882-1.413l0.003-0.004l-6.529-4.002" +
"L61.98,75.79c-0.274-0.227-0.621-0.369-1.005-0.369c-0.238,0-0.461,0.056-0.663,0.149l-0.014-0.012" +
"C57.115,76.847,53.64,77.561,50,77.561c-15.199,0-27.56-12.362-27.56-27.559c0-15.195,12.362-27.562,27.56-27.562" +
"c14.322,0,26.121,10.984,27.434,24.967C77.428,57.419,73.059,63,69.631,63c-1.847,0-3.254-1.23-3.254-3.957" +
"c0-0.527,0.176-1.672,0.264-2.111l4.163-19.918l-0.018,0c0.012-0.071,0.042-0.136,0.042-0.21c0-0.734-0.596-1.33-1.33-1.33h-7.23" +
"c-0.657,0-1.178,0.485-1.286,1.112l-0.025-0.001l-0.737,3.549c-1.847-3.342-5.629-5.893-10.994-5.893" +
"c-10.202,0-19.877,9.764-19.877,21.549c0,8.531,5.101,14.775,13.632,14.775c4.75,0,9.587-2.727,12.665-7.035l0.088,0.527" +
"c0.615,3.342,9.843,7.576,15.121,7.576c7.651,0,16.617-5.156,16.617-19.932l-0.022-0.009C87.477,51.13,87.5,50.569,87.5,50.002z" +
"M56.615,56.844c-1.935,2.727-5.101,5.805-9.763,5.805c-4.486,0-7.212-3.166-7.212-7.738c0-6.422,5.013-12.754,12.049-12.754" +
"c3.958,0,6.245,2.551,7.124,4.486L56.615,56.844z");
Button buttonWithGraphics = new Button();
buttonWithGraphics.setGraphic(svg);
// Bind the Image scale property to the buttons size
svg.scaleXProperty().bind(buttonWithGraphics.widthProperty().divide(100));
svg.scaleYProperty().bind(buttonWithGraphics.heightProperty().divide(100));
// Declare a minimum size for the button
buttonWithGraphics.setMinSize(MIN_BUTTON_SIZE, MIN_BUTTON_SIZE);
root.getChildren().addAll(buttonWithGraphics);
root.layoutBoundsProperty().addListener((observableValue, oldBounds, newBounds) -> {
double size = Math.max(MIN_BUTTON_SIZE, Math.min(newBounds.getWidth(), newBounds.getHeight()));
buttonWithGraphics.setPrefSize(size, size);
}
);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
I guess this is linked with the following lines:
svg.scaleXProperty().bind(buttonWithGraphics.widthProperty().divide(100));

create a resizable canvas, you can find details here
canvas can be scaled, and also graphic context can also be scale
var gc = canvas.getGraphicsContext2D();
gc.scale(0.1, 0.1);
use gc to draw the svg path, line, circle, etc.
sample code here:
var canvas = new ResizableCanvas() {
#Override
public void draw() {
var gc = getGraphicsContext2D();
gc.save();//make sure you save the status here and restore after all operations are finished
//System.out.print(getWidth()+" ");
//System.out.println(getHeight());
var width = getWidth();
var height = getHeight();
gc.clearRect(0,0,width, height);
gc.scale(width/512.002, height/512.002);
gc.beginPath();
gc.setFill(Color.web("#2D4961"));
gc.appendSVGPath("""
M399.994,0H112.008C94.337,0,80.009,14.327,80.009,31.999v342.624
c0.08,16.159,8.288,31.191,21.839,39.998l145.433,94.796c5.304,3.448,12.135,3.448,17.439,0l145.433-94.556
c13.551-8.808,21.759-23.839,21.839-39.998V31.999C431.993,14.327,417.665,0,399.994,0z M399.994,68.477
c-6.872-6.24-15.399-10.352-24.559-11.839c-1.496-9.2-5.64-17.759-11.919-24.639h36.478V68.477z M148.486,31.999
c-6.264,6.864-10.408,15.391-11.919,24.559c-9.168,1.512-17.695,5.656-24.559,11.919V31.999H148.486z M256.001,476.858
l-26.479-17.199c11.047-4.6,20.327-12.623,26.479-22.879c6.152,10.256,15.431,18.279,26.479,22.879L256.001,476.858z
M399.994,374.622c0.008,5.424-2.728,10.48-7.28,13.439l-91.756,59.917c-20.895-1.592-37.022-19.039-36.958-39.998
c0-4.416-3.584-8-8-8s-8,3.584-8,8c0.064,20.959-16.063,38.406-36.958,39.998l-91.756-59.917c-4.552-2.96-7.288-8.016-7.28-13.439
V103.995c0-17.671,14.327-31.998,31.998-31.998c4.416,0,8-3.584,8-8c0-17.671,14.327-31.999,31.999-31.999h143.993
c17.671,0,31.999,14.327,31.999,31.999c0,4.416,3.584,8,8,8c17.671,0,31.999,14.327,31.999,31.998L399.994,374.622L399.994,374.622z""");
gc.fill();
gc.setFill(Color.web("#44637F"));
gc.beginPath();
gc.appendSVGPath("""
M80.009,31.999v271.987l0,0c8.84,0,15.999-7.16,15.999-15.999V31.999
c0-8.84,7.16-15.999,15.999-15.999h271.987c8.84,0,15.999-7.16,15.999-15.999H112.008C94.329,0,80.009,14.327,80.009,31.999z""");
gc.fill();
gc.setFill(Color.web("#123247"));
gc.beginPath();
gc.appendSVGPath("""
M410.154,414.861c13.551-8.808,21.759-23.839,21.839-39.998V55.997l0,0
c-8.84,0-15.999,7.16-15.999,15.999v304.466c0,8.04-4.024,15.551-10.719,19.999L269.28,487.097
c-8.304,5.56-13.287,14.887-13.279,24.879l0,0c3.096,0.008,6.12-0.88,8.72-2.56L410.154,414.861z""");
gc.fill();
gc.restore();
}
};
canvas.widthProperty().bind(canvas.heightProperty());
canvas.heightProperty().bind(stage.getScene().heightProperty().multiply(0.1));

The solution above tries to do it dynamically, although (as you said) fails to do it in respect to the original SvgPath size.
I would recommend doing it like that:
double size = 30;
svg.setScaleX(size / svg.boundsInLocalProperty().get().getWidth());
svg.setScaleY(size / svg.boundsInLocalProperty().get().getHeight());
This will scale your SvgPath to size.

Related

How to add space between menus in javafx

I am trying to implement a menu. This is my code :
Menu menuFile1 = new Menu("ADD");
Menu menuFile2 = new Menu("EDIT");
Menu menuFile3 = new Menu("VIEW");
Menu menuFile4 = new Menu("HELP");
How can I put some space between each menu (that is between ADD,EDIT,VIEW and HELP) ?
Answer
Space around menus is controlled by padding (see the Region css guide).
For example:
menu.setStyle("-fx-padding: 5 10 8 10;");
sets the padding around the menu to 5 pixels on the top, 10 pixels on the right, 8 pixels on the bottom and 10 pixels on the left.
Sample
The following is a bit overcomplicated for a code sample to demonstrate this effect, but you could run it to see the effect of varying padding values.
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.StringExpression;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class SpacedOut extends Application {
#Override
public void start(final Stage stage) {
MenuBar menuBar = createMenuBar();
VBox controlPane = createControlPane(menuBar);
VBox layout = new VBox(10,
menuBar,
controlPane
);
VBox.setVgrow(controlPane, Priority.ALWAYS);
stage.setScene(new Scene(layout, 400, 200));
stage.show();
}
private MenuBar createMenuBar() {
MenuBar menuBar = new MenuBar();
menuBar.getMenus().addAll(
new Menu("ADD"),
new Menu("EDIT"),
new Menu("VIEW"),
new Menu("HELP")
);
return menuBar;
}
private VBox createControlPane(MenuBar menuBar) {
CheckBox useCustomPadding = new CheckBox("Use Custom Padding");
useCustomPadding.setSelected(false);
Slider padAmount = new Slider(0, 30, 15);
padAmount.setShowTickMarks(true);
padAmount.setShowTickLabels(true);
padAmount.setMajorTickUnit(10);
padAmount.setMaxWidth(200);
padAmount.disableProperty().bind(
useCustomPadding.selectedProperty().not()
);
VBox contentPane = new VBox(10,
useCustomPadding,
padAmount
);
contentPane.setPadding(new Insets(10));
StringExpression paddingExpression = Bindings.concat(
"-fx-padding: ", padAmount.valueProperty(), "px;"
);
menuBar.getMenus().forEach(
menu -> menu.styleProperty().bind(
Bindings
.when(useCustomPadding.selectedProperty())
.then(paddingExpression)
.otherwise("")
)
);
return contentPane;
}
public static void main(String[] args) {
launch(args);
}
}
With the setStyle() Method you can pass one or more css styles in one string.
Like menuFile1.setStyle("-fx-border-color: red; -fx-effect: dropshadow( one-pass-box , red , 10,0.5,0,0 );");
Alternatively you could put your style information inside a css file and add it to the Scene through.
Scene somescene = new Scene(root)
somescene.getStylesheets().add("your.css");
See the css reference of Java FX 2 or this tutorial.

How can I make a TextArea stretch to fill the content, expanding the parent in the process?

So I have a TextArea and as the user pastes paragraphs into it, or just writes in it, I want it to expand vertically to reveal all the available text. I.e. not to use a scrollbar in the text field itself... much like what happens on many web pages. Many users, myself included, don't like to be forced to edit in a small window. Exactly how Facebook status updates box works.
I've tried
myTextArea.autoSize()
wrapped in an
myTextArea.textProperty().addListener(new ChangeListener()....);
but that doesn't work. I think it's happy autosizing to its current size.
The left, right & top anchors are set to it's parent AnchorPane. I've tried it with the bottom attached and not attached. Ideally I'd like to grow the anchor pane as the textarea grows.
I don't mind reading the TextProperty and calculating a trigger size which I set myself... but this seems a hacky approach IF there is already a best practise. The number of properties and sub objects of javafx is sufficiently daunting that it seems like a good point to ask the question here, rather than trying to figure out how many pixels the font/paragraphs etc are taking up.
Update:
So I thought maybe I was overthinking it, and all I needed to do was to switch the scrollbars off and the rest would happen. Alas, looking for available fields and methods for "scroll", "vertical", "vbar" comes up with nothing I can use. ScrollTopProperty looks like it's for something else.
The problem; the height of textArea is wanted to be grown or shrunk while its text is changing by either user's typing or copy-pasting. Here is another approach:
public class TextAreaDemo extends Application {
private Text textHolder = new Text();
private double oldHeight = 0;
#Override
public void start(Stage primaryStage) {
final TextArea textArea = new TextArea();
textArea.setPrefSize(200, 40);
textArea.setWrapText(true);
textHolder.textProperty().bind(textArea.textProperty());
textHolder.layoutBoundsProperty().addListener(new ChangeListener<Bounds>() {
#Override
public void changed(ObservableValue<? extends Bounds> observable, Bounds oldValue, Bounds newValue) {
if (oldHeight != newValue.getHeight()) {
System.out.println("newValue = " + newValue.getHeight());
oldHeight = newValue.getHeight();
textArea.setPrefHeight(textHolder.getLayoutBounds().getHeight() + 20); // +20 is for paddings
}
}
});
Group root = new Group(textArea);
Scene scene = new Scene(root, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
// See the explanation below of the following line.
// textHolder.setWrappingWidth(textArea.getWidth() - 10); // -10 for left-right padding. Exact value can be obtained from caspian.css
}
public static void main(String[] args) {
launch(args);
}
}
But it has a drawback; the textarea's height is changing only if there are line breaks (ie Enter keys) between multiple lines, if the user types long enough the text gets wrapped to multiple line but the height is not changing.
To workaround this drawback I added this line
textHolder.setWrappingWidth(textArea.getWidth() - 10);
after primaryStage.show();. It works well for long typings where user does not linebreaks. However this generates another problem. This problem occurs when the user is deleting the text by hitting "backspace". The problem occurs exactly when the textHolder height is changed and where the textArea's height is set to new value. IMO it maybe a bug, didn't observe deeper.
In both case the copy-pasting is handling properly.
Awaiting a better, i use this hacky solution.
lookup the vertical scrollbar of the textarea.
make it transparent
listen to its visible property
when the scrollbar become visible i add a row to the textarea.
The code:
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.geometry.Orientation;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.TextArea;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
public class GrowGrowTextArea extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
AnchorPane root = new AnchorPane();
root.setStyle("-fx-padding:20;-fx-background-color:dodgerblue;");
final TextArea textArea = new TextArea();
AnchorPane.setTopAnchor(textArea, 10.0);
AnchorPane.setLeftAnchor(textArea, 10.0);
AnchorPane.setRightAnchor(textArea, 10.0);
root.getChildren().add(textArea);
primaryStage.setScene(new Scene(root, 400, 300));
primaryStage.show();
ScrollBar scrollBar = lookupVerticalScrollBar(textArea);
scrollBar.setOpacity(0.0);
scrollBar.visibleProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> source,
Boolean wasVisible,
Boolean isVisible) {
if (isVisible) {
textArea.setPrefRowCount(textArea.getPrefRowCount() + 1);
textArea.requestLayout();
}
}
});
}
private ScrollBar lookupVerticalScrollBar(Node node) {
if (node instanceof ScrollBar && ((ScrollBar)node).getOrientation() == Orientation.VERTICAL) {
return (ScrollBar) node;
}
if (node instanceof Parent) {
ObservableList<Node> children = ((Parent) node).getChildrenUnmodifiable();
for (Node child : children) {
ScrollBar scrollBar = lookupVerticalScrollBar(child);
if (scrollBar != null) {
return scrollBar;
}
}
}
return null;
}
}
I had a similar problem with creating expanding TextArea. I was creating TextArea that looks like TextField and expand vertically every time when there is no more space in line.
I have tested all solutions that I could find on this topic on stack and other sources available. I found few good solutions but neither was good enough.
After many hours of fighting, I figured out this approach.
I extended TextArea class, override layoutChildren() method and add a listener on text height.
#Override
protected void layoutChildren() {
super.layoutChildren();
setWrapText(true);
addListenerToTextHeight();
}
private void addListenerToTextHeight() {
ScrollPane scrollPane = (ScrollPane) lookup(".scroll-pane");
scrollPane.setHbarPolicy(ScrollBarPolicy.NEVER);
scrollPane.setVbarPolicy(ScrollBarPolicy.NEVER);
StackPane viewport = (StackPane) scrollPane.lookup(".viewport");
Region content = (Region) viewport.lookup(".content");
Text text = (Text) content.lookup(".text");
text.textProperty().addListener(textHeightListener(text));
}
private InvalidationListener textHeightListener(Text text) {
return (property) -> {
// + 1 for little margin
double textHeight = text.getBoundsInLocal().getHeight() + 1;
//To prevent that our TextArena will be smaller than our TextField
//I used DEFAULT_HEIGHT = 18.0
if (textHeight < DEFAULT_HEIGHT) {
textHeight = DEFAULT_HEIGHT;
}
setMinHeight(textHeight);
setPrefHeight(textHeight);
setMaxHeight(textHeight);
};
}
I used some of the code found in the previous answers.
The growTextAreaIfNecessary method will increase the height of textArea until the scrollbar is not visible (limited to 20 lines in this example).
The problem with this approach is that the window needs to be redrawn several times until the perfect height is found.
private ScrollBar lookupVerticalScrollBar(Node node) {
if (node instanceof ScrollBar && ((ScrollBar) node).getOrientation() == Orientation.VERTICAL) {
return (ScrollBar) node;
}
if (node instanceof Parent) {
ObservableList<Node> children = ((Parent) node).getChildrenUnmodifiable();
for (Node child : children) {
ScrollBar scrollBar = lookupVerticalScrollBar(child);
if (scrollBar != null) {
return scrollBar;
}
}
}
return null;
}
private void growTextAreaIfNecessary(TextArea textArea) {
Platform.runLater(() -> {
ScrollBar lookupVerticalScrollBar = lookupVerticalScrollBar(textArea);
int prefRowCount = textArea.getPrefRowCount();
if (lookupVerticalScrollBar.isVisible() && prefRowCount < 20) {
textArea.setPrefRowCount(prefRowCount + 1);
System.out.println("increasing height to: " + (prefRowCount + 1));
growTextAreaIfNecessary(textArea);
}
});
}
I have tried many hacks, most of them had jitters while typing, this to me was the perfect result:
textArea.textProperty().addListener((obs,old,niu)->{
Text t = new Text(old+niu);
t.setFont(textArea.getFont());
StackPane pane = new StackPane(t);
pane.layout();
double height = t.getLayoutBounds().getHeight();
double padding = 20 ;
textArea.setMinHeight(height+padding);
});

Automatic resizing of LineChart fails if embedded in a pane

The following program fails to resize the line chart horizontally when embedded in a Pane (or borderpane of anchorpane for the matter)
If the line chart is directly parented to the VBox instead, then everything works as expected.
I found I needed to bind the chart size to the parent pane, which I assume must be done automatically by VBox and HBox.
After trying different combination of enclosing in HBox/VBox, setting growing and alignment policies, I am quite confused about how layouts work.
I observe that there are differences in how ui components behave wrt resizing.
Any clarification (or digest insight on javadoc unclear documentation) is appreciated.
Best regards.
Source edited and clarified
import javafx.application.Application;
import javafx.scene.*;
import javafx.scene.chart.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
public class App extends Application {
#Override
public void start(Stage stage) {
NumberAxis xAxis = new NumberAxis();
NumberAxis yAxis = new NumberAxis();
xAxis.setLabel("X");
yAxis.setLabel("Y");
final LineChart<Number, Number> lineChart = new LineChart<>(xAxis, yAxis);
lineChart.setTitle("x = f(y)");
XYChart.Series data = new XYChart.Series();
data.setName("Serie 1");
for (int i = 0; i < 10; i++) {
data.getData().add(new XYChart.Data(i, i * i));
}
lineChart.getData().add(data);
VBox vb = new VBox();
vb.setFillWidth(true);
HBox hb = new HBox();
hb.getChildren().add(lineChart);
hb.setFillHeight(true);
vb.getChildren().add(hb);
HBox.setHgrow(lineChart, Priority.ALWAYS);
VBox.setVgrow(hb, Priority.ALWAYS);
Scene scene = new Scene(vb);
stage.setScene(scene);
stage.centerOnScreen();
stage.setResizable(true);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
For the record, the problem of the provided example is solved after modifying this typo:
HBox.setHgrow(hb, Priority.ALWAYS);
to:
HBox.setHgrow(lineChart, Priority.ALWAYS);
This fixes resizing horizontally.
When embedded directly in VBox, the chart's size is recomputed on resize, as it is anchored to the VBox, which boundaries change.
When embedded in a HBox, we have to provide a hint for the HBox to grow horizontally, and vertically.
Vertically it's done with:
VBox.setVgrow(hb, Priority.ALWAYS);
Horizontally it's done by requesting its content to occupy all available space, which the fix above is about.

Center text on Canvas?

Can someone please give me an example on how to center text on a JavaFX 2 Canvas?
GraphicsContext has some functions like setTextAlign, but I am not sure on how to use all those methods and which of them I really need. I want to center my text vertically and horizontally.
Set the text align to center.
Set the text baseline to center.
Draw the text in the center of your canvas (by positioning it at half the canvas width and height).
Here is a sample:
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.geometry.VPos;
import javafx.scene.*;
import javafx.scene.canvas.*;
import javafx.scene.layout.StackPane;
import javafx.scene.text.TextAlignment;
import javafx.stage.Stage;
public class TextCanvas extends Application {
#Override public void start(Stage primaryStage) {
Canvas canvas = new Canvas(175, 40);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setTextAlign(TextAlignment.CENTER);
gc.setTextBaseline(VPos.CENTER);
gc.fillText(
"Text centered on your Canvas",
Math.round(canvas.getWidth() / 2),
Math.round(canvas.getHeight() / 2)
);
StackPane layout = new StackPane();
layout.getChildren().addAll(canvas);
primaryStage.setScene(new Scene(layout));
primaryStage.show();
}
public static void main(String[] args) { launch(args); }
}
you wanna use
control.setAlignment(Pos.CENTER); and
control.setStyle("-fx-alignment: CENTER;");
see here for some downloadable sample code of this in action.

JavaFX Scale and Translate Operation Results in Anomaly

I have a Pane (test_) as the center child of a border pane. The child fills its area and grows when the window is stretched as expected.
Now I scale test_. Normally it would be centered in its area, but I don't want that, so I translate it back to the upper-left corner of its area.
But now when I stretch the widow it pulls test_ away from the upper-left corner of its area. Can anyone explain why this happens? The sample below incorporates a slider that scale's test_.
Thank you.
package fxuicontrols;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Bounds;
import javafx.scene.Scene;
import javafx.scene.control.Slider;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class ScaleTest
extends Application
implements ChangeListener<Number>
{
private final Pane test_ = new Pane();
public static void main(String[] args)
{
launch();
}
#Override
public void start(Stage stage) throws Exception
{
test_.setStyle( "-fx-background-color: blue;" );
Text text = new Text( "Upper left corner" );
text.setFill( Color.WHITE );
text.relocate( 0, 0 );
test_.getChildren().add( text );
final Slider scaler = new Slider( .25, 1, 1 );
scaler.valueProperty().addListener( this );
test_.scaleXProperty().bind( scaler.valueProperty() );
test_.scaleYProperty().bind( scaler.valueProperty() );
BorderPane root = new BorderPane();
root.setPrefSize( 250, 250 );
root.setCenter( test_ );
root.setBottom( scaler );
stage.setScene( new Scene( root ) );
stage.show();
}
#Override
public void
changed( ObservableValue<? extends Number> oVal,
Number oldNm,
Number newNum
)
{
double scale = newNum.doubleValue();
Bounds bounds = test_.getLayoutBounds();
double width = bounds.getWidth();
double height = bounds.getHeight();
test_.setTranslateX( (scale * width - width) / 2 );
test_.setTranslateY( (scale * height - height) / 2 );
}
}
The reason your solution goes awry when the Scene is resized is because Panes are resizable nodes, so the layoutbounds of the Pane is changing as the Pane is being resized, but you aren't taking that into account in your translation calculations.
The following directly uses Scale and Translate transforms to avoid any resizing related issues. Does this sample code do what you want?
import javafx.application.Application;
import javafx.beans.value.*;
import javafx.geometry.*;
import javafx.scene.Scene;
import javafx.scene.control.Slider;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.scene.transform.*;
import javafx.stage.Stage;
// demonstrates scaling a test pane with content in it.
// slide the slider at the bottom of the scene around to shrink and grow the content.
public class ScaleTest extends Application {
public static void main(String[] args) { launch(); }
#Override public void start(Stage stage) throws Exception {
// create a test pane for scaling.
Pane testPane = new Pane();
// make the test pane background a different color if you want to see the extent of the pane.
testPane.setStyle("-fx-background-color: blue;");
// create some text content to place in the test pane.
Text text = new Text("Upper left corner");
text.setStyle("-fx-font-size: 30px;");
text.setFill(Color.WHITE);
text.setTextOrigin(VPos.TOP);
testPane.getChildren().add(text);
Scale scaleTransform = new Scale();
testPane.getTransforms().addAll(scaleTransform, new Translate(0, 0));
// slider to scale the test pane.
final Slider scaler = new Slider(.25, 3, 1);
scaleTransform.xProperty().bind(scaler.valueProperty());
scaleTransform.yProperty().bind(scaler.valueProperty());
// stackpane added to pad out empty space when testPane is scaled small.
// stackpane also clips the zoomed content when it gets larger than it's standard layout.
final StackPane stack = new StackPane();
stack.getChildren().addAll(testPane);
StackPane.setAlignment(testPane, Pos.TOP_LEFT);
stack.setStyle("-fx-background-color: blue;");
final Rectangle clip = new Rectangle();
testPane.layoutBoundsProperty().addListener(new ChangeListener<Bounds>() {
#Override public void changed(ObservableValue<? extends Bounds> observable, Bounds oldBounds, Bounds bounds) {
clip.setWidth(bounds.getWidth());
clip.setHeight(bounds.getHeight());
}
});
stack.setClip(clip);
// layout everything.
VBox layout = new VBox();
layout.setPrefSize(250, 250);
layout.getChildren().setAll(stack, scaler);
VBox.setVgrow(stack, Priority.ALWAYS);
// show the stage.
stage.setScene(new Scene(layout));
stage.show();
}
}

Resources