3 Replies Latest reply: Nov 15, 2011 4:14 AM by jsmith RSS

    How can I implement a status bar at the bottom of a resizable application?

    895831
      Hello all,

      I am a JavaFx newbie and I am implementing an application (a Sokoban game), with a menu at the top of the frame and a gaming area covering the rest of the frame. To support the game, I have to load images at certain positions in the gaming area.

      The game also includes a level editor with another menubar and where images are set to other positions.
      I implemented this in another view, swiching from the game mode to the level editor mode and vice versa is just done by setting the other view visible. Up to now this works, here the important statements building these details:
      Group root = new Group();
      gameView = new Group(); // for gaming mode
      le_view = new Group()   // for level editor mode
      MenuBar gameMenubar = new MenuBar();
      Menu menuGame = new Menu(bundle.getString("MenuGame"));
      ... building the menu items and menues ...
      gameView.getChildren().add(gameMenubar);
      
      ImageView buildingView[][] = new ImageView[22][22];
      for (nCol = 0; nCol < 22; nCol++) {
          for (nRow = 0; nRow < 22; nRow++) {
              buildingView[nCol][nRow] = new ImageView();
              buildingView[nCol][nRow].setX(nCol * 26 + 5);
              buildingView[nCol][nRow].setY(nRow * 26 + 40);
              gameView.getChildren().add(buildingView[nCol][nRow]);
          }
      }
      gameView.setVisible(true);
      root.getChildren().add(gameView);
      
      ... same stuff to build the le_view ...
      
      le_View.setVisible(false);
      root.getChildren().add(le_View);
      Scene scene = new Scene(root, 800, 600, Color.CORNSILK);
      Now I want to introduce a status bar at the bottom of the frame, which of course has to follow the bottom of the frame, if it is resized. And of course the menu and the status bar should not grow vertically, if the height of the frame is increased.

      The implementation seems to be easy with StackPane for the frame and one BorderPane for each mode.
      For the first step I only tried implementing the game mode with only one BorderPane (just setting the menu, the gaming area and the status bar each into a HBox and setting these three HBoxes at the top, into the center and at the bottom). I also tried this via GridPane and via VBox; I always get any erroneous behaviour:

      Either the menubar is visible, but the menus do not pop up the menu items, or the anchor point of the menu and of gaming area are set 100 pixels left of the left frame border and move into the frame when the frame width is increased, or the menu is set 20 pixels below the top of the frame, or HBox with the menu grows when the frame height is increased, so that the anchor point of the gaming area moves down.

      Can you describe me a correct construction of such a frame? Thanks in advance.

      Best regards
      Gerhard
        • 1. Re: How can I implement a status bar at the bottom of a resizable application?
          jsmith
          The following code, probably does what you want. It has a game menu nailed to the top of the screen and a status menu bar at the bottom of the screen. If the game menu is selected the game view is shown in the center of the screen and if the level editor menu is selected the level editor view is shown in the center of the screen. As the views switch the text in the status bar changes.
          import javafx.application.Application;
          import javafx.event.ActionEvent;
          import javafx.event.EventHandler;
          import javafx.scene.Group;
          import javafx.scene.Scene;
          import javafx.scene.control.Menu;
          import javafx.scene.control.MenuBar;
          import javafx.scene.control.MenuItem;
          import javafx.scene.image.Image;
          import javafx.scene.image.ImageView;
          import javafx.scene.layout.BorderPane;
          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 GameLayoutTest extends Application {
            public static void main(String[] args) { Application.launch(args); }
            public void start(Stage stage) throws Exception {
              final BorderPane gameLayout = new BorderPane();
              final Group gameView = new Group();
              MenuBar gameMenubar = new MenuBar();
              Menu gameMenu = new Menu("Mode");
              MenuItem playGameMenu = new MenuItem("Play Game");
              MenuItem levelEditMenu = new MenuItem("Edit Levels");
              gameMenu.getItems().add(playGameMenu);
              gameMenu.getItems().add(levelEditMenu);
              gameMenubar.getMenus().add(gameMenu);
          
              final StackPane levelEditView = new StackPane();
              levelEditView.getChildren().add(new Text("Level Editor"));
          
              ImageView buildingView[][] = new ImageView[22][22];
              Image img = new Image("http://www.julepstudios.com/images/close-icon.png");
              for (int nCol = 0; nCol < 22; nCol++) {
                for (int nRow = 0; nRow < 22; nRow++) {
                  ImageView imgView = new ImageView(img);
                  imgView.setScaleX(0.5);
                  imgView.setScaleY(0.5);
                  buildingView[nCol][nRow] = imgView;
                  buildingView[nCol][nRow].setX(nCol * 20 + 5);
                  buildingView[nCol][nRow].setY(nRow * 20 + 40);
                  gameView.getChildren().add(buildingView[nCol][nRow]);
                }
              }
          
              VBox statusBar = new VBox();
              statusBar.setStyle("-fx-background-color: gainsboro");
              final Text statusText = new Text("Playing Game");
              statusBar.getChildren().add(statusText);
          
              gameLayout.setCenter(gameView);
              gameLayout.setTop(gameMenubar);
              gameLayout.setBottom(statusBar);
              
              playGameMenu.setOnAction(new EventHandler<ActionEvent>() {
                public void handle(ActionEvent event) {
                  gameLayout.setCenter(gameView);
                  statusText.setText("Playing Game");
                }
              });
          
              levelEditMenu.setOnAction(new EventHandler<ActionEvent>() {
                public void handle(ActionEvent event) {
                  gameLayout.setCenter(levelEditView);
                  statusText.setText("Editing Level");
                }
              });
              
              Scene scene = new Scene(gameLayout, 800, 600, Color.CORNSILK);
              stage.setScene(scene);
              stage.show();
            }
          }
          • 2. Re: How can I implement a status bar at the bottom of a resizable application?
            895831
            Hello,

            thank you for this draft, with some modifications it works as desired.

            A group is not resizable. Therefore, if the Group gameView is directly set into the center of the BorderPane, expanding the application frame causes a gap between the menu and the upper limit of the Group and a gap between the lower limit of the group and the status bar. For my game I want the images located in fixed positions, because to handle mouse clicks I need to calculate the row number and column number from the X and Y position of the clicked point.

            So I set the Group gameView into a VBox and this VBox into the center of the BorderPane. This works fine now.

            As I did the same with StackPane levelEditView, the effect was that the anchor of the levelEditView was located about 600 or 1000 pixels above the upper border of the application window; only when I expanded the window to a large size, at least the lowest images of the levelEditView came into the application window. I replaced StackPane levelEditorView by Group levelEditView, and also set this into a VBox and the VBox centered into the frame; this also works fine.

            Now the only point here which is still to be improved is the performance: Since these changes, it takes very long time until the menues are operatable. When I start the game, menu bar, gameView and status bar are visible at once, the game is operatable at once (keyboard events move the game pawn and dialogs are also invokable by keyboard events), but it takes about a minute until a click on a menue pulls down the menu items. After the minute it behaves correctly.

            But the rest is very fine. Thank you.

            Best regards
            Gerhard
            • 3. Re: How can I implement a status bar at the bottom of a resizable application?
              jsmith
              Hello Gerhard,

              Glad the code helped, thanks for a fun layout exercise.
              For the draft code I just pulled an icon off the internet over a http connection.
              If you haven't done so already place any icons and graphics you need local to your project, so that resource lookups like:
              Image img = new Image("http://www.julepstudios.com/images/close-icon.png");
              become
              Image img = new Image("close-icon.png");
              then performance may improve.

              Another possible reason for your performance problem could be that when you use a vbox, the vbox content can overflow the area of the borderpane center and might be sitting on top of the menu pane, making you unable to click the menu (which is what happens to me when I try that with the draft program with the vbox wrapping mod, then resize the scene to make it smaller). This was a trick which caught me and the reason that I used a Group originally rather than a vbox. I found a vbox still works but you need to tweak things a bit. The trick I saw was that the order in which you add stuff to the borderpane is important. The borderpane acts like a stack where the last thing added floats over the top of everything else if you size the scene small enough. For your project you want the menu on top always, so it always needs to be the last thing added to the borderpane, but when you swap in the level pane for the game pane, then back out again, the game pane can end up on top of the menu which makes the menu seem like you can't click it (only when the scene is sized small enough). It was quite a subtle bug which took me a little while to work out what was happening. For me the solution was to add just one vbox to the center of the border, and then swap the game pane and the level editor in and out of the vbox, that way the center of the layout always stayed behind the menu bar and the status bar.

              I added some revisions to reflect the comments above and placed some comments in the code to note what was changed and why.
              public class SampleGameLayoutRevised extends Application {
                public static void main(String[] args) { Application.launch(args); }
                public void start(Stage stage) throws Exception {
                  final BorderPane gameLayout = new BorderPane();
                  final Group gameView = new Group();
                  final MenuBar gameMenubar = new MenuBar();
                  final Menu gameMenu = new Menu("Mode");
                  final VBox centerView = new VBox();
                  centerView.setStyle("-fx-background-color: darkgreen");  // we set a background color on the center view to check if it overwrites the game menu.
                  MenuItem playGameMenu = new MenuItem("Play Game");
                  MenuItem levelEditMenu = new MenuItem("Edit Levels");
                  gameMenu.getItems().add(playGameMenu);
                  gameMenu.getItems().add(levelEditMenu);
                  gameMenubar.getMenus().add(gameMenu);
                  
                  final StackPane levelEditView = new StackPane();
                  levelEditView.getChildren().add(new Text("Level Editor"));
              
                  ImageView buildingView[][] = new ImageView[22][22];
                  Image img = new Image("http://www.julepstudios.com/images/close-icon.png");  // use of http here is just for example, instead use an image resource from your project files.
                  for (int nCol = 0; nCol < 22; nCol++) {
                    for (int nRow = 0; nRow < 22; nRow++) {
                      ImageView imgView = new ImageView(img);
                      imgView.setScaleX(0.5);
                      imgView.setScaleY(0.5);
                      buildingView[nCol][nRow] = imgView;
                      buildingView[nCol][nRow].setX(nCol * 20 + 5);
                      buildingView[nCol][nRow].setY(nRow * 20 + 40);
                      gameView.getChildren().add(buildingView[nCol][nRow]);
                    }
                  }
              
                  final VBox statusBar = new VBox();
                  final Text statusText = new Text("Playing Game");
                  statusBar.getChildren().add(statusText);
                  statusBar.setStyle("-fx-background-color: cornsilk"); // we set a background color on the status bar, 
                                                                        // because we can't rely on the scene background color 
                                                                        // because, if the scene is sized small, the status bar will start to overlay the game view 
                                                                        // and if we don't explicitly set the statusBar background the center view will start
                                                                        // to bleed through the transparent background of the statusBar.
              
                  gameLayout.setCenter(centerView); // we add the centerview first and we never change it, instead we put it's changeable contents in a vbox and change out the vbox content.
                  gameLayout.setBottom(statusBar);
                  gameLayout.setTop(gameMenubar);   // note the game layout is the last thing added to the borderpane so it will always stay on top if the border pane is resized.
              
                  playGameMenu.setOnAction(new EventHandler<ActionEvent>() {
                    public void handle(ActionEvent event) {
                      centerView.getChildren().clear();  // here we perform a centerview vbox content swap.
                      centerView.getChildren().add(gameView);
                      statusText.setText("Playing Game");
                    }
                  });
              
                  levelEditMenu.setOnAction(new EventHandler<ActionEvent>() {
                    public void handle(ActionEvent event) {
                      centerView.getChildren().clear();  // here we perform a centerview vbox content swap.
                      centerView.getChildren().add(levelEditView);
                      statusText.setText("Editing Level");
                    }
                  });
              
                  playGameMenu.fire();
              
                  Scene scene = new Scene(gameLayout, 800, 600, Color.CORNSILK);
                  stage.setScene(scene);
                  stage.show();
                }
              }
              Other than that I am not sure of a reason for the slowdown you are seeing. In my experience JavaFX has been quick and responsive for the tasks I have been using it for. Admittedly, I just use if for a bunch of small trial projects, but I've never seen it unresponsive for a minute.

              - John