{"id":2269,"date":"2012-12-30T15:25:20","date_gmt":"2012-12-30T14:25:20","guid":{"rendered":"http:\/\/sahits.ch\/blog\/?p=2269"},"modified":"2012-12-31T12:19:46","modified_gmt":"2012-12-31T11:19:46","slug":"resizable-layout-and-resizable-image","status":"publish","type":"post","link":"http:\/\/sahits.ch\/blog\/blog\/2012\/12\/30\/resizable-layout-and-resizable-image\/","title":{"rendered":"Resizable Layout and resizable image"},"content":{"rendered":"<p>This is the follow up of the <a href=\"http:\/\/sahits.ch\/blog\/?p=2265\">previous article<\/a> on resize with JavaFX. In this article I will show you how I created a Layout manager that has top and left insets which stay constant when the window is resized to a size larger than the original, but reduce proportionally if the size becomes smaller. The second point addressed here are resizable images.<\/p>\n<p><!--more--><\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignleft size-medium wp-image-2270\" alt=\"ResizeLayout_nonResized\" src=\"http:\/\/sahits.ch\/blog\/wp-content\/uploads\/2012\/12\/ResizeLayout_nonResized-206x300.png\" width=\"103\" height=\"150\" srcset=\"http:\/\/sahits.ch\/blog\/wp-content\/uploads\/2012\/12\/ResizeLayout_nonResized-206x300.png 206w, http:\/\/sahits.ch\/blog\/wp-content\/uploads\/2012\/12\/ResizeLayout_nonResized.png 622w\" sizes=\"auto, (max-width: 103px) 85vw, 103px\" \/><img loading=\"lazy\" decoding=\"async\" class=\"alignleft size-medium wp-image-2271\" alt=\"ResizeLayout_enlarged\" src=\"http:\/\/sahits.ch\/blog\/wp-content\/uploads\/2012\/12\/ResizeLayout_enlarged-279x300.png\" width=\"140\" height=\"150\" srcset=\"http:\/\/sahits.ch\/blog\/wp-content\/uploads\/2012\/12\/ResizeLayout_enlarged-279x300.png 279w, http:\/\/sahits.ch\/blog\/wp-content\/uploads\/2012\/12\/ResizeLayout_enlarged.png 921w\" sizes=\"auto, (max-width: 140px) 85vw, 140px\" \/><img loading=\"lazy\" decoding=\"async\" class=\"alignleft size-medium wp-image-2272\" alt=\"ResizeLayout_small\" src=\"http:\/\/sahits.ch\/blog\/wp-content\/uploads\/2012\/12\/ResizeLayout_small-300x74.png\" width=\"150\" height=\"37\" srcset=\"http:\/\/sahits.ch\/blog\/wp-content\/uploads\/2012\/12\/ResizeLayout_small-300x74.png 300w, http:\/\/sahits.ch\/blog\/wp-content\/uploads\/2012\/12\/ResizeLayout_small.png 622w\" sizes=\"auto, (max-width: 150px) 85vw, 150px\" \/><\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>The code for this is split up into two classes, the layout manager:<\/p>\n<pre class=\"brush:java\">package javafxtest;\r\n\r\nimport java.util.List;\r\nimport javafx.geometry.Pos;\r\nimport javafx.geometry.VPos;\r\nimport javafx.scene.Node;\r\nimport javafx.scene.layout.StackPane;\r\n\r\npublic class StretchLeftBottomLayout extends StackPane {\r\n    private final int topMargin;\r\n    private final int leftMargin;\r\n    private Double originalWidth = null;\r\n    private Double originalHeight = null;\r\n\r\n    public StretchLeftBottomLayout(int topMargin, int leftMargin) {\r\n        this.topMargin = topMargin;\r\n        this.leftMargin = leftMargin;\r\n     }\r\n\r\n    @Override\r\n    protected void layoutChildren() {\r\n        List&lt;Node&gt; managed = getManagedChildren();\r\n        double width = getWidth();\r\n        double height = getHeight();\r\n        if (originalWidth == null) {\r\n            originalWidth = width;\r\n        }\r\n        if (originalHeight == null) {\r\n            originalHeight = height;\r\n        }\r\n        double scaleX = Math.min(computeScaleX(width), 1);\r\n        double scaleY = Math.min(computeScaleY(height), 1);\r\n        double top = getInsets().getTop() + topMargin*scaleY;\r\n        double right = getInsets().getRight();\r\n        double left = getInsets().getLeft() + leftMargin*scaleX;\r\n        double bottom = getInsets().getBottom();\r\nSystem.out.println(scaleX+\" -&gt; \"+left+\", \"+scaleY+\" -&gt; \"+top);\r\nSystem.out.println(width+\" \"+height+\" \"+top+\" \"+right+\" \"+left+\" \"+bottom+\" \"+getAlignment().getVpos()+\" \"+VPos.BASELINE);\r\n        double baselineOffset = height\/2;\r\n        for (int i = 0; i &lt; managed.size(); i++) {\r\n            Node child = managed.get(i);\r\n            Pos childAlignment = StretchLeftBottomLayout.getAlignment(child);\r\n            layoutInArea(child, left, top,\r\n                           width - left - right, height - top - bottom,\r\n                           baselineOffset, getMargin(child),\r\n                           childAlignment != null? childAlignment.getHpos() : getAlignment().getHpos(),\r\n                           childAlignment != null? childAlignment.getVpos() : getAlignment().getVpos());\r\n        }\r\n    }\r\n\r\n    private double computeScaleX(double width) {\r\n        return width \/ originalWidth;\r\n    }\r\n     private double computeScaleY(double height) {\r\n        return height \/ originalHeight;\r\n    }\r\n}<\/pre>\n<p>and the calling code:<\/p>\n<pre class=\"brush:java\">package javafxtest;\r\n\r\nimport java.io.File;\r\nimport java.io.FileInputStream;\r\nimport java.io.FileNotFoundException;\r\nimport javafx.application.Application;\r\nimport javafx.scene.Scene;\r\nimport javafx.scene.canvas.Canvas;\r\nimport javafx.scene.canvas.GraphicsContext;\r\nimport javafx.scene.image.Image;\r\nimport javafx.scene.layout.Region;\r\nimport javafx.scene.layout.StackPane;\r\nimport javafx.stage.Stage;\r\n\r\npublic class OverlayLayoutResize2Test extends Application {\r\n\r\n    @Override\r\n    public void start(Stage stage) throws FileNotFoundException {\r\n        final StackPane root = new StackPane();\r\n        File f = new File(\"\/home\/andi\/Pictures\/Adam.jpg\");\r\n        Image image = new Image(new FileInputStream(f));\r\n        final double width = image.getWidth();\r\n        final double height = image.getHeight();\r\n        Canvas background = new Canvas(width, height);\r\n        GraphicsContext context = background.getGraphicsContext2D();\r\n        context.drawImage(image, 0, 0);\r\n\r\n        root.getChildren().add(background);\r\n\r\n        StretchLeftBottomLayout animationPane = new StretchLeftBottomLayout(54, 23);\r\n        Region red = new Region();\r\n        red.setStyle(\"-fx-background-color: #FF0000;\");\r\n        animationPane.getChildren().add(red);\r\n        root.getChildren().add(animationPane);\r\n\r\n        Scene scene = new Scene(root, width, height);\r\n\r\n        stage.setTitle(\"Resize Layout 2 Overlay Test\");\r\n        stage.setScene(scene);\r\n        stage.show();\r\n    }\r\n\r\n    public static void main(String[] args) {\r\n        launch(args);\r\n    }\r\n}<\/pre>\n<p>As in a real application a red rectangle is not terribly exciting I need to have an image that can be resized and displayed. Unfortunately both Canvas and ImageView are not resizable. So it is supposed that the ImageView should become resizable some when. Therefore I had to tweek the code a bit on my own:<\/p>\n<pre class=\"brush:java\">package ch.sahits.javafx.test;\r\n\r\nimport java.io.IOException;\r\nimport java.util.logging.Level;\r\nimport java.util.logging.Logger;\r\nimport javafx.application.Application;\r\nimport javafx.beans.value.ChangeListener;\r\nimport javafx.beans.value.ObservableValue;\r\nimport javafx.scene.Scene;\r\nimport javafx.scene.canvas.Canvas;\r\nimport javafx.scene.canvas.GraphicsContext;\r\nimport javafx.scene.image.Image;\r\nimport javafx.scene.layout.Pane;\r\nimport javafx.scene.layout.StackPane;\r\nimport javafx.stage.Stage;\r\n\r\n\/**\r\n *\r\n * @author andi\r\n *\/\r\npublic class ResizeableCanvas extends Application {\r\n\r\n    @Override\r\n    public void start(Stage primaryStage) {\r\n        try {\r\n            StackPane root = new StackPane();\r\n            Scene scene = new Scene(root, 300, 250);\r\n            Image img = new Image(getClass().getResource(\"kajak4_small.png\").openStream());\r\n            Canvas canvas = new Canvas(root.getWidth(), root.getHeight());\r\n            GraphicsContext context = canvas.getGraphicsContext2D();\r\n            context.drawImage(img, 0, 0, root.getWidth(), root.getHeight());\r\n\r\n            \/\/ Binding\r\n            canvas.widthProperty().bind(root.widthProperty());\r\n            canvas.heightProperty().bind(root.heightProperty());\r\n\r\n            final ResizeChangeListener resizeChangeListener = new ResizeChangeListener(root, context, img);\r\n\r\n            canvas.widthProperty().addListener(resizeChangeListener);\r\n            canvas.heightProperty().addListener(resizeChangeListener);\r\n\r\n            root.getChildren().add(canvas);            \r\n\r\n            primaryStage.setTitle(\"Resizable Canvas Test\");\r\n            primaryStage.setScene(scene);\r\n            primaryStage.show();\r\n        } catch (IOException ex) {\r\n            Logger.getLogger(ResizeableCanvas.class.getName()).log(Level.SEVERE, null, ex);\r\n        }\r\n    }\r\n\r\n    public static void main(String[] args) {\r\n        launch(args);\r\n    }\r\n\r\n    private static class ResizeChangeListener implements ChangeListener&lt;Number&gt; {\r\n\r\n        private final Pane parent;\r\n        private final GraphicsContext context;\r\n        private final Image img;\r\n\r\n        public ResizeChangeListener(Pane parent, GraphicsContext context, Image image) {\r\n            this.parent = parent;\r\n            this.context = context;\r\n            this.img = image;\r\n        }\r\n\r\n        @Override\r\n        public void changed(ObservableValue&lt;? extends Number&gt; observable, Number oldValue, Number newValue) {\r\n            final double width = parent.getWidth();\r\n            final double height = parent.getHeight();\r\n            context.clearRect(0, 0, width, height);\r\n            context.drawImage(img, 0, 0, width, height);\r\n        }\r\n    }\r\n}<\/pre>\n<p>The main point here is that the width and height properties of the Canvas are bound to the same properties of the StackPane. Additionally there is a ChangeListener registered on these two properties of the Context which will redraw the Image with the correct proportions:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignright size-medium wp-image-2274\" alt=\"resizedJavaFXCanvas\" src=\"http:\/\/sahits.ch\/blog\/wp-content\/uploads\/2012\/12\/resizedJavaFXCanvas-300x92.png\" width=\"300\" height=\"92\" srcset=\"http:\/\/sahits.ch\/blog\/wp-content\/uploads\/2012\/12\/resizedJavaFXCanvas-300x92.png 300w, http:\/\/sahits.ch\/blog\/wp-content\/uploads\/2012\/12\/resizedJavaFXCanvas.png 577w\" sizes=\"auto, (max-width: 300px) 85vw, 300px\" \/><\/p>\n<p>The next step the would be to refactor this code into a separate component, which did not work, as <a href=\"https:\/\/forums.oracle.com\/forums\/thread.jspa?threadID=2480978&amp;tstart=0\" target=\"_blank\">this forum entry<\/a> can confirm.<\/p>\n<p>Never the less as I invested some time into figuring this out, I came across some useful resources on the web:<\/p>\n<ul>\n<li><a href=\"http:\/\/amyfowlersblog.wordpress.com\/2011\/06\/02\/javafx2-0-layout-a-class-tour\/\" target=\"_blank\">JavaFX 2.0 Layout: A Class Tour<\/a> illustrates the class hierarchy and dependencies of the Control. However I should not that the current class hierarchy is not the same (the Control class extends the Region for example)<\/li>\n<li><a href=\"http:\/\/www.guigarage.com\/2012\/11\/custom-ui-controls-with-javafx-part-1\/\" target=\"_blank\">Henrik&#8217;s article<\/a> about custom controls. He explains the intrinsic correlation between Control, CSS, Skin and behavior. Current code from the repository suggests, that the Behavior has dropped out and is merged into the Skin. That&#8217;s also what <a title=\"Gerrit Grunwald\" href=\"http:\/\/harmoniccode.blogspot.de\">Gerrit Grunwald<\/a> mentioned on his JUGS presentation.<\/li>\n<li><a href=\"http:\/\/www.drdobbs.com\/jvm\/a-javafx-20-custom-control\/229400781\" target=\"_blank\">A JavaFX 2.0 Custom Control<\/a> is another article on this subject by Eric Bruno<\/li>\n<li>Very important is also the link to the <a href=\"http:\/\/hg.openjdk.java.net\/openjfx\/8\/master\/rt\/file\/d146d333b063\" target=\"_blank\">OpenJFX repository<\/a>.<\/li>\n<li>Last but not least are the talks of <a href=\"https:\/\/oracleus.activeevents.com\/connect\/sessionDetail.ww?SESSION_ID=2425&amp;tclass=popup\" target=\"_blank\">Gerrit Grunwald<\/a> and <a href=\"https:\/\/oracleus.activeevents.com\/connect\/sessionDetail.ww?SESSION_ID=4726&amp;tclass=popup\" target=\"_blank\">Jonathan Giles<\/a> at <a title=\"JavaOne\" href=\"http:\/\/www.oracle.com\/javaone\/index.html\">JavaOne<\/a> 2012.<\/li>\n<\/ul>\n<p>The code for the resizeable canvas is available as NetBeans project <a href=\"http:\/\/sahits.ch\/blog\/?attachment_id=2277\" rel=\"attachment wp-att-2277\">export<\/a>. Contained is also the code of the not working (as expected) custom control.<\/p>\n<p>It was pointed out to me in the <a href=\"https:\/\/forums.oracle.com\/forums\/thread.jspa?threadID=2480978&amp;tstart=0\">OTN forum<\/a>, that the ImageViewPane from <a href=\"http:\/\/javafx-jira.kenai.com\/browse\/RT-10610\">this<\/a> feature request could be used:<\/p>\n<pre class=\"brush:java\">package ch.sahits.javafx.test;\r\n\r\nimport javafx.beans.property.ObjectProperty;\r\nimport javafx.beans.property.SimpleObjectProperty;\r\nimport javafx.beans.value.ChangeListener;\r\nimport javafx.beans.value.ObservableValue;\r\nimport javafx.geometry.HPos;\r\nimport javafx.geometry.VPos;\r\nimport javafx.scene.image.ImageView;\r\nimport javafx.scene.layout.Region;\r\n\r\n\/**\r\n *\r\n * @author akouznet\r\n *\/\r\npublic class ImageViewPane extends Region {\r\n\r\n    private ObjectProperty&lt;ImageView&gt; imageViewProperty = new SimpleObjectProperty&lt;&gt;();\r\n\r\n    public ObjectProperty&lt;ImageView&gt; imageViewProperty() {\r\n        return imageViewProperty;\r\n    }\r\n\r\n    public ImageView getImageView() {\r\n        return imageViewProperty.get();\r\n    }\r\n\r\n    public void setImageView(ImageView imageView) {\r\n        this.imageViewProperty.set(imageView);\r\n    }\r\n\r\n    public ImageViewPane() {\r\n        this(new ImageView());\r\n    }\r\n\r\n    @Override\r\n    protected void layoutChildren() {\r\n        ImageView imageView = imageViewProperty.get();\r\n        if (imageView != null) {\r\n            imageView.setFitWidth(getWidth());\r\n            imageView.setFitHeight(getHeight());\r\n            layoutInArea(imageView, 0, 0, getWidth(), getHeight(), 0, HPos.CENTER, VPos.CENTER);\r\n        }\r\n        super.layoutChildren();\r\n    }\r\n\r\n    public ImageViewPane(ImageView imageView) {\r\n        imageViewProperty.addListener(new ChangeListener&lt;ImageView&gt;() {\r\n\r\n            @Override\r\n            public void changed(ObservableValue&lt;? extends ImageView&gt; arg0, ImageView oldIV, ImageView newIV) {\r\n                if (oldIV != null) {\r\n                    getChildren().remove(oldIV);\r\n                }\r\n                if (newIV != null) {\r\n                    getChildren().add(newIV);\r\n                }\r\n            }\r\n        });\r\n        this.imageViewProperty.set(imageView);\r\n    }\r\n}\r\nThe calling code then looks like this:<\/pre>\n<pre class=\"brush:java\">package ch.sahits.javafx.test;\r\n\r\nimport java.io.IOException;\r\nimport java.util.logging.Level;\r\nimport java.util.logging.Logger;\r\nimport javafx.application.Application;\r\nimport javafx.scene.Scene;\r\nimport javafx.scene.image.Image;\r\nimport javafx.scene.image.ImageView;\r\nimport javafx.scene.layout.StackPane;\r\nimport javafx.stage.Stage;\r\n\r\npublic class ImageViewPaneTest extends Application {\r\n\r\n    @Override\r\n    public void start(Stage primaryStage) {\r\n        try {\r\n            Image img = new Image( getClass().getResource(\"kajak4_small.png\").openStream());\r\n            ImageViewPane imageViewPane = new ImageViewPane(new ImageView(img));\r\n\r\n            StackPane root = new StackPane();\r\n            root.getChildren().add(imageViewPane);\r\n\r\n            Scene scene = new Scene(root, 300, 250);\r\n\r\n            primaryStage.setTitle(\"ImageViewPaneTest\");\r\n            primaryStage.setScene(scene);\r\n            primaryStage.show();\r\n        } catch (IOException ex) {\r\n            Logger.getLogger(ImageViewPaneTest.class.getName()).log(Level.SEVERE, null, ex);\r\n        }\r\n    }\r\n\r\n    public static void main(String[] args) {\r\n        launch(args);\r\n    }\r\n}<\/pre>\n<pre class=\"brush:java\">\r\nand produces the same result as the first example with the inline Canvas.<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>This is the follow up of the previous article on resize with JavaFX. In this article I will show you how I created a Layout manager that has top and left insets which stay constant when the window is resized to a size larger than the original, but reduce proportionally if the size becomes smaller. &hellip; <a href=\"http:\/\/sahits.ch\/blog\/blog\/2012\/12\/30\/resizable-layout-and-resizable-image\/\" class=\"more-link\"><span class=\"screen-reader-text\">\u201eResizable Layout and resizable image\u201c <\/span>weiterlesen<\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7,6],"tags":[63,252,110,254],"class_list":["post-2269","post","type-post","status-publish","format-standard","hentry","category-java","category-programmieren","tag-image","tag-javafx","tag-layout","tag-resize"],"_links":{"self":[{"href":"http:\/\/sahits.ch\/blog\/wp-json\/wp\/v2\/posts\/2269","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/sahits.ch\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/sahits.ch\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/sahits.ch\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"http:\/\/sahits.ch\/blog\/wp-json\/wp\/v2\/comments?post=2269"}],"version-history":[{"count":6,"href":"http:\/\/sahits.ch\/blog\/wp-json\/wp\/v2\/posts\/2269\/revisions"}],"predecessor-version":[{"id":2278,"href":"http:\/\/sahits.ch\/blog\/wp-json\/wp\/v2\/posts\/2269\/revisions\/2278"}],"wp:attachment":[{"href":"http:\/\/sahits.ch\/blog\/wp-json\/wp\/v2\/media?parent=2269"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/sahits.ch\/blog\/wp-json\/wp\/v2\/categories?post=2269"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/sahits.ch\/blog\/wp-json\/wp\/v2\/tags?post=2269"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}