Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8350560

SwingNode breaks layout (in)validation

XMLWordPrintable

    • generic
    • generic

      ADDITIONAL SYSTEM INFORMATION :
      Could reproduce with Java and JavaFX 17.0.9, 21.0.4 and 23.0.2. Tested on macOS.


      A DESCRIPTION OF THE PROBLEM :
      JavaFX and Swing widgets can automatically resize children if their contents change.
      However, SwingNode does not propagate those requests, even though there is some evidence in the code that this should work.

      Workaround:

      The following internal methods can fix this behavior:
      - SwingNodeHelper.setSwingPrefHeight
      - SwingNodeHelper.setSwingPrefWidth
      - NodeHelper.notifyLayoutBoundsChanged

      SwingNodeInteropN.SwingNodeContent#preferredSizeChanged does have these calls, but it's never triggered.
      There is JLightweightFrame#layoutSizeListener, but that listener is only registered in a ContainerListener that is never called for the initial SwingNode content (the container listener also incorrectly removes the PropertyChangeListener, but I digress).

      I've added the needed calls to the source code, simply uncomment them to see it working as expected.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      - Insert a SwingNode with some content into a JavaFX widget that adjusts itself to size changes of its children, such as StackPane
      - Trigger a layout change in the contents of the SwingNode (e.g. put more lines in a text area)


      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      The layout change is picked up by the SwingNode, propagated to the FX parent, and the SwingNode expands or shrinks to fit the new contents.

      ACTUAL -
      The SwingNode remains unchanged and its contents are cropped.


      ---------- BEGIN SOURCE ----------
      import java.awt.BasicStroke;
      import java.awt.EventQueue;
      import java.util.Timer;
      import java.util.TimerTask;
      import java.util.stream.IntStream;

      import javax.swing.JTextArea;
      import javax.swing.border.StrokeBorder;

      import javafx.application.Application;
      import javafx.application.Platform;
      import javafx.embed.swing.SwingNode;
      import javafx.scene.Scene;
      import javafx.scene.layout.Border;
      import javafx.scene.layout.BorderStroke;
      import javafx.scene.layout.BorderStrokeStyle;
      import javafx.scene.layout.HBox;
      import javafx.scene.layout.Pane;
      import javafx.scene.layout.StackPane;
      import javafx.scene.layout.VBox;
      import javafx.scene.paint.Color;
      import javafx.scene.text.Text;
      import javafx.stage.Stage;

      public class ExpandingSwingNodeContentsMain extends Application {

        @Override
        public void start(Stage stage) throws Exception {

          // FX text widget, which will nicely expand.
          Text fxText = new Text();
          Pane pane = new StackPane(fxText);
          pane.setBorder(new Border(new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, null, null)));
          VBox left = new VBox(pane);

          // Swing text widget, which will remain stuck at its initial size.
          JTextArea swingText = new JTextArea("line1\nline2");
          swingText.setBorder(new StrokeBorder(new BasicStroke()));
          SwingNode swingNode = new SwingNode();
          swingNode.setContent(swingText);
          VBox right = new VBox(swingNode);

          // Add lines to both text widgets.
          // You'll only see the FX text widget expand to accommodate the extra content.
          Timer timer = new Timer();
          int[] lines = {0};
          timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
              lines[0]++;
              String text = IntStream.range(1, lines[0] + 1).mapToObj(i -> "line " + i + "/" + lines[0]).reduce((left1, right1) -> left1 + "\n" + right1).orElseThrow();
              Platform.runLater(() -> fxText.setText("FX\n" + text));
              EventQueue.invokeLater(() -> {
                swingText.setText("Swing\n" + text);
                // uncomment to work around the issue, needs:
                // --add-exports javafx.swing/com.sun.javafx.embed.swing=ALL-UNNAMED
                // --add-exports javafx.graphics/com.sun.javafx.scene=ALL-UNNAMED
                // Platform.runLater(() -> {
                // SwingNodeHelper.setSwingPrefHeight(swingNode, swingText.getPreferredSize().height);
                // SwingNodeHelper.setSwingPrefWidth(swingNode, swingText.getPreferredSize().width);
                // NodeHelper.notifyLayoutBoundsChanged(swingNode);
                // });
              });
            }
          }, 0, 2000);

          Scene scene = new Scene(new HBox(left, right), 400, 400);
          stage.setScene(scene);
          stage.setOnCloseRequest(e -> Platform.exit());
          stage.show();
        }
      }

      ---------- END SOURCE ----------


       

        1. screenshot.png
          25 kB
          Anupam Dev
        2. Test.java
          3 kB
          Anupam Dev

            psadhukhan Prasanta Sadhukhan
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

              Created:
              Updated: