0

I have a ScreenController class that allows easy switching of the root Panes in a JavaFX program. It does this with a HashMap that stores Pane values with String keys.

I tried to set it up so that whenever new Panes are added to a SceneController object, it creates a full-screen version of the Pane given, and adds that to a second HashMap with the same String key as the first.

However, whenever I switch screens now, the full-screen version of the Pane is always used.

It seems like the goFullscreen() method I am using to create the full-screen Pane versions is modifying both even though Java is pass-by-value.

How can I get the HashMap attribute in my ScreenController class, windowedRootMap, return the original Panes without full-screen scaling?

/**
 * Creates an appropriately scaled fullscreen version of the argument Pane object.
 * 
 * @param contentPane The Pane to create a fullscreen version of.
 * @return Pane
 */
private static Pane goFullscreen(Pane contentPane) {

   // get original dimensions and their ratio.
   final double windowedWidth = 1280.0;
   final double windowedHeight = 800.0;
   final double windowedRatio = windowedWidth / windowedHeight;

   // get fullscreen width and height from monitor dimensions
   final double fullscreenWidth = Screen.getPrimary().getBounds().getWidth();
   final double fullscreenHeight = Screen.getPrimary().getBounds().getHeight();

   // find how much to scale by
   double scaleFactor;
   if (fullscreenWidth / fullscreenHeight > windowedRatio)
      scaleFactor = fullscreenHeight / windowedHeight;
   else
      scaleFactor = fullscreenWidth / windowedWidth;

   // scale the contents of the Pane appropriately
   Scale scale = new Scale(scaleFactor, scaleFactor);
   contentPane.getTransforms().setAll(scale);
   contentPane.setPrefWidth(fullscreenWidth / scaleFactor);
   contentPane.setPrefWidth(fullscreenHeight / scaleFactor);

   return contentPane;
}

/**
 * Allows switching root nodes easily.
 */
private class ScreenController {

   private HashMap<String, Pane> windowedRootMap = new HashMap<>();
   private HashMap<String, Pane> fullscreenRootMap = new HashMap<>();
   private Scene currentScene;

   private ScreenController(Scene currentScene) {

      this.currentScene = currentScene;
   }

   private void addScreen(String name, Pane pane) {

      this.windowedRootMap.put(name, pane);
      this.fullscreenRootMap.put(name, goFullscreen(pane));
   }

   private void activate(String name) {

      this.currentScene.setRoot(this.windowedRootMap.get(name));
   }
}
LuminousNutria
  • 1,883
  • 2
  • 18
  • 44
  • 2
    [Java is _pass-by-value_](https://stackoverflow.com/questions/40480/), but the value that's passed is the reference to the object (primitives are different). So you're actually using the same `Pane` instance for each map. – Slaw Jun 12 '19 at 23:03
  • @Slaw thank you. I thought that might be the case. Do I have to scale each Pane back and forth each time the program switches from windowed mode to full-screen and vice versa, or is there another way to handle this? – LuminousNutria Jun 13 '19 at 11:56

1 Answers1

2

You're correct that Java is pass-by-value, but wrong about what "value" is being passed. In Java, it's the reference to the object that's copied—but that new reference still points to the same object instance. This is explained in greater detail by the many great answers to the following question:

Assuming you don't want to maintain two different Pane instances, one option is to simply set the properties depending on what mode you're in. I'm not exactly sure what you want your code to look like in the end but it might look something like:

import java.util.Map;
import java.util.HashMap;
import javafx.geometry.Dimension2D;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.transform.Scale;

public class ScreenController {

    private final Map<String, Foo> map = new HashMap<>();
    private final Scene scene;

    public ScreenController(Scene scene) {
        this.scene = scene;
    }

    public void register(String name, Pane pane) {
        map.put(name, pane);
    }

    public void activate(String name) {
        scene.setRoot(map.get(name).pane);
    }

    private static class Foo {

        private final Pane pane;
        private final Dimension2D normalSize;
        private final Dimension2D fullScreenSize;
        private final Scale scale;

        private Foo(Pane pane) {
            this.pane = pane;
            // set the other fields...
        }

        private void enterFullScreenMode() {
             pane.setPrefSize(fullScreenSize.getWidth(), fullScreenSize.getHeight());
             pane.getTransforms().add(scale);
        }

        private void exitFullScreenMode() {
             pane.setPrefSize(normalSize.getWidth(), normalSize.getHeight());
             pane.getTransforms().remove(scale);
        }

    }

}

Note: I didn't include any parameter or state validation.

Keep in mind, however, that the root of a Scene is sized differently than "regular" nodes in the scene graph. Setting the preferred size will likely have no effect. From the documentation.

The application must specify the root Node for the scene graph by setting the root property. If a Group is used as the root, the contents of the scene graph will be clipped by the scene's width and height and changes to the scene's size (if user resizes the stage) will not alter the layout of the scene graph. If a resizable node (layout Region or Control) is set as the root, then the root's size will track the scene's size, causing the contents to be relayed out as necessary.


If you only have a set number of screens consider using an enum as the key of the Map, instead of a String. Also take a look at java.util.EnumMap.

Slaw
  • 37,820
  • 8
  • 53
  • 80