Edit: My code works totally fine if I use StackPane instead of Pane in the start method. like
public void start(Stage primaryStage) {
StackPane pane = new StackPane();
so the problem lies with the Pane class?
I created a custom pane class that extends JavaFX Pane class and added a setWidth() method to resize its width.
I want to place a circle at the center of the stage and resize my shape when I use my mouse to drag and resize the window. But the result is weird.
- At first, it is a tiny point at the top-left corner. Should it be nothing at all? I think the circle as an instance of MyPane should have a width and height both as 0.
- No matter you enlarge or reduce the window's size, the circle continuously becomes bigger.
can someone help me understand the mechanism within this process?
Thank you in advance.
Here is my code.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class Exercise_14_11 extends Application {
public void start(Stage primaryStage) {
Pane pane = new Pane();
MyPane circle = new MyPane();
pane.getChildren().add(circle);
Scene scene = new Scene(pane, 400, 400);
primaryStage.setTitle("Exercise_14_11");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
class MyPane extends Pane {
public MyPane() {
paint();
}
public void paint() {
double centerX = getWidth() / 2;
double centerY = getHeight() / 2;
double radius = centerX;
Circle face = new Circle(centerX, centerY, radius);
face.setFill(Color.WHITE);
face.setStroke(Color.BLACK);
getChildren().clear();
getChildren().addAll(face);
}
@Override
public void setWidth(double width) {
super.setWidth(width);
paint();
}
I have read Pane's document and Region's document https://docs.oracle.com/javase/8/javafx/api/javafx/scene/layout/Region.html, but still don't have a hint.
I know property binding can achieve my goal. I am more interested in the process of handling the width of a pane.
In the JavaFX layout system, a
PaneSo your root pane (
pane) will resize your custom pane (circle) to its preferred size. Since you don't override the calculation of the preferred size, the preferred width and height (see the "Resizable Range" section of the documentation are calculated as theFor a
Shape(such asCircle), the preferred width/height will be computed as its layout bounds, which are the geometric bounds ("geometry plus stroke").The behavior you observe happens because you do not account for the stroke in calculating the radius of the circle.
Basically, when
paint()is called, you set the radius to half the width. The layout bounds of the circle are double the radius (the "geometry") plus the stroke, which will be the width of the pane plus the width of the stroke (i.e., by default, one pixel more than the width of the pane).The next time the pane (
circle) is laid out by its parent (pane), its width is increased by one pixel to accommodate the layout bounds of the circle. This is accomplished by an internal call, at some point, tosetWidth(..), which increases the width, causing the circle's radius to increase again, and then the next time the pane is laid out, the pane increases its size again as its preferred width has increased. So every time the pane is laid out, it increases in size.This simply isn't the way to subclass
Pane. Any time you subclass a library class, you must do so in a way that is documented. Inheritance is essentially incompatible with encapsulation, so any time you create a subclass you risk interfering with the superclass' functionality.Indeed, the documentation for a pane's width (inherited from
Region) explicitly saysTherefore, you must not set it, and if you do, the results may (as you have discovered) be unpredictable.
I normally advocate not subclassing JavaFX library classes, except for the cell classes in virtualized controls (which are specifically designed for extension). The one exception to this is when you want to manage the layout of non-resizable nodes (
Shape,ImageView, andMediaView). So this might be a case where subclassingPaneorRegionis appropriate.If you do so, you should do so in the manner described in the documentation (here the documentation for
Region:So you can do something like
This does things the "right way around": the minimum, preferred (and, trivially, maximum) size of the custom region are determined by the minimum and preferred size of the circle, and the circle is (correctly) sized for the width and height allocated to the region.
Now, putting this region in a plain pane will simply always size it to its preferred size, so you always end up with a circle of radius 20 (or less, if there is not enough room). To allow the pane containing the circle to grow, you should put it in a layout pane that allocates more space to it. Either use a
StackPane, which (again, read the documentation)or in the center of a
BorderPane, so itComplete example: