0

I want to implement a JavaFX layout component. The final goal is for that component to allow a sandwich-like stacking of several components, then to clip this stack and place the resulting clipped node into standard layout components.

When I implemented the component all went good, with one exception: I am not able to properly return the component's layoutBounds. I invested a lot of time googling and researching but did not find (Google) nor understand (in the JavaFX source code) how to report a selfmade component's layout bounds.

Some details: Node.getLayoutBounds() is final, so cannot be overridden. The layoutBoundsProperty is read-only and cannot be modified. In the JavaFx Node implementation are many helper operations and seemingly helpful comments, but all refer to package-private functionality, not accessible from the outside.

So the question is: How to implement a component so that it can compute its own layout bounds and report it in calls like getLayoutBounds() to its context?

Below is executable source code. Not sure if it helps a lot, but it asks the right questions at the right places.

package stackoverflow.demo;

import javafx.application.Application;
import javafx.geometry.Bounds;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;

public class LayoutBoundsTest extends Application {
    public static void main(String[] args) {
        Application.launch(args);
    }

    /**
     * How to implement this component so that it can return
     * its own computed layout bounds?
     */
    public static class CenterPane extends StackPane
    {
        public CenterPane()
        {
        }

        @Override
        public boolean isResizable()
        {
            return false;
        }

        protected void addLayoutComponent( Node node )
        {
            Bounds bounds = node.getLayoutBounds();
            // How to set 'bounds' on this CenterPane so that
            // they are reported via CenterPane.getLayoutBounds()?
            getChildren().add( node );
        }
    }

    @Override
    public void start(Stage stage) {
        CenterPane centerPane = new CenterPane();
        centerPane.addLayoutComponent( new Circle( 30, Color.YELLOW ) );

        Node circle = new Circle( 30, Color.GREEN );

        VBox root = new VBox();
        root.getChildren().addAll(centerPane, circle);

        Scene scene = new Scene(root);
        stage.setScene(scene);
        stage.setTitle("Testing LayoutBounds");
        stage.show();

        // Both calls below should report the same layout bounds.
        System.out.println("b0=" + centerPane.getLayoutBounds());
        System.out.println("b2=" + circle.getLayoutBounds());
    }
}
Michael B
  • 446
  • 1
  • 3
  • 11
  • 1
    Override `computePrefWidth`/`prefWidth` (and `Height`s and `max`/`min`). And place the children in `layoutChildren`, if you don't want to keep `StackPane`'s layout strategy. – fabian Mar 12 '18 at 19:58
  • The implementation of computePrefWidth() et al. on CenterPane has no impact on CenterPane's getLayoutBounds(). The layoutBounds stay invariably at all zeroes. – Michael B Mar 12 '18 at 20:58
  • Did you check after the first layout pass? If this is just about measuring the size, check after the first pass, see https://stackoverflow.com/questions/26152642/get-the-height-of-a-node-in-javafx-generate-a-layout-pass – fabian Mar 12 '18 at 21:03
  • The real problem is that I have to find a way to set the layout bounds that I have in the addLayoutComponent()-operation on the CenterPane instance. As soon as the first println( centerPane.getLayoutBounds() ) returns actual vlues instead of zeroes only, this will implicitly fix the layout problem. – Michael B Mar 13 '18 at 13:48
  • Why do you want `resizable` to be false? – James_D Mar 13 '18 at 16:00
  • Since the node is really not resizable. It can compute its own size, but does not want to be resized. This logic is a required part of the ultimate implementation. – Michael B Mar 13 '18 at 16:07

1 Answers1

0

I found JDK-8156089 : Provide official API so subclasses can override layout bounds computation

So it seems I have to wait for an implementation of that feature request, which is not available as of Java 9. Meanwhile a workaround is the following:

package stackoverflow.demo;

import java.lang.reflect.Field;

import javafx.application.Application;
import javafx.geometry.Bounds;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;

public class LayoutBoundsTest extends Application {
    public static void main(String[] args) {
        Application.launch(args);
    }

    /**
     * How to implement this component so that it can return
     * its own computed layout bounds?
     */
    public static class CenterPane extends StackPane
    {
        public CenterPane()
        {
        }

        @Override
        public boolean isResizable()
        {
            return false;
        }

        protected void addLayoutComponent( Node node )
        {
            Bounds bounds = node.getLayoutBounds();
            setBounds( bounds );
            getChildren().add( node );
        }

        /**
         * Do it the hard way (and don't try that at home).
         */
        private void setBounds( Bounds bounds )
        {
            try
            {
                // In Java 8 and 9 there's a private field in the
                // Region that is lazy-initialized.  We set this
                // manually.
                Field f = Region.class.getDeclaredField( "boundingBox" );
                f.setAccessible( true );
                f.set( this, bounds );
            }
            catch ( Exception e )
            {
                throw new RuntimeException( "No good.", e );
            }
        }
    }

    @Override
    public void start(Stage stage) {
        CenterPane centerPane = new CenterPane();
        centerPane.addLayoutComponent( new Circle( 30, Color.YELLOW ) );

        Node circle = new Circle( 30, Color.GREEN );

        VBox root = new VBox();
        root.getChildren().addAll(centerPane, circle);

        Scene scene = new Scene(root);
        stage.setScene(scene);
        stage.setTitle("Testing LayoutBounds");
        stage.show();

        // Both calls below should report the same layout bounds.
        System.out.println("b0=" + centerPane.getLayoutBounds());
        System.out.println("b2=" + circle.getLayoutBounds());
    }
}
Michael B
  • 446
  • 1
  • 3
  • 11