All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jfoenix.controls.JFXDecorator Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package com.jfoenix.controls;

import java.util.ArrayList;
import java.util.List;

import com.jfoenix.svg.SVGGlyph;

import javafx.animation.Animation;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.geometry.BoundingBox;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.geometry.Rectangle2D;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.control.Tooltip;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.Border;
import javafx.scene.layout.BorderStroke;
import javafx.scene.layout.BorderStrokeStyle;
import javafx.scene.layout.BorderWidths;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.stage.WindowEvent;
import javafx.util.Duration;

/**
 * Window Decorator allow to resize/move its content Note: the default close button will call
 * stage.close() which will only close the current stage. it will not close the java application,
 * however it can be customized by calling {@link #setOnCloseButtonAction(Runnable)}
 *
 * @author Shadi Shaheen
 * @version 1.0
 * @since 2016-03-09
 */
public class JFXDecorator extends VBox
{

   private Stage primaryStage;

   private double xOffset = 0;
   private double yOffset = 0;
   private double newX, newY, initX, initY, initWidth = -1, initHeight = -1, initStageX = -1, initStageY = -1;

   private boolean allowMove = false;
   private boolean isDragging = false;
   private Timeline windowDecoratorAnimation;
   private StackPane contentPlaceHolder = new StackPane();
   private HBox buttonsContainer;

   private ObjectProperty onCloseButtonAction = new SimpleObjectProperty<>(() -> primaryStage.fireEvent(new WindowEvent(primaryStage,
                                                                                                                                  WindowEvent.WINDOW_CLOSE_REQUEST)));

   private BooleanProperty customMaximize = new SimpleBooleanProperty(false);
   private boolean maximized = false;
   private BoundingBox originalBox;
   private BoundingBox maximizedBox;

   protected JFXButton btnMax;
   protected JFXButton btnFull;
   protected JFXButton btnClose;
   protected JFXButton btnMin;

   protected StringProperty title = new SimpleStringProperty();
   protected Text text;
   protected Node graphic;
   protected HBox graphicContainer;

   /**
    * Create a window decorator for the specified node with the options: - full screen - maximize -
    * minimize
    *
    * @param stage the primary stage used by the application
    * @param node  the node to be decorated
    */
   public JFXDecorator(Stage stage, Node node)
   {
      this(stage, node, true, true, true);
   }

   /**
    * Create a window decorator for the specified node with the options: - full screen - maximize -
    * minimize
    *
    * @param stage      the primary stage used by the application
    * @param node       the node to be decorated
    * @param fullScreen indicates whether to show full screen option or not
    * @param max        indicates whether to show maximize option or not
    * @param min        indicates whether to show minimize option or not
    */
   public JFXDecorator(Stage stage, Node node, boolean fullScreen, boolean max, boolean min)
   {
      primaryStage = stage;
      // Note that setting the style to TRANSPARENT is causing performance
      // degradation, as an alternative we set it to UNDECORATED instead.
      primaryStage.initStyle(StageStyle.UNDECORATED);

      setPickOnBounds(false);
      getStyleClass().add("jfx-decorator");

      initializeButtons();
      initializeContainers(node, fullScreen, max, min);

      primaryStage.fullScreenProperty().addListener((o, oldVal, newVal) ->
      {
         if (newVal)
         {
            // remove border
            contentPlaceHolder.getStyleClass().remove("resize-border");
            /*
             * note the border property MUST NOT be bound to another property when going full screen mode, thus
             * the binding will be lost if exisited
             */
            contentPlaceHolder.borderProperty().unbind();
            contentPlaceHolder.setBorder(Border.EMPTY);
            if (windowDecoratorAnimation != null)
            {
               windowDecoratorAnimation.stop();
            }
            windowDecoratorAnimation = new Timeline(new KeyFrame(Duration.millis(320),
                                                                 new KeyValue(this.translateYProperty(),
                                                                              -buttonsContainer.getHeight(),
                                                                              Interpolator.EASE_BOTH)));
            windowDecoratorAnimation.setOnFinished((finish) ->
            {
               this.getChildren().remove(buttonsContainer);
               this.setTranslateY(0);
            });
            windowDecoratorAnimation.play();
         }
         else
         {
            // add border
            if (windowDecoratorAnimation != null)
            {
               if (windowDecoratorAnimation.getStatus() == Animation.Status.RUNNING)
               {
                  windowDecoratorAnimation.stop();
               }
               else
               {
                  this.getChildren().add(0, buttonsContainer);
               }
            }
            this.setTranslateY(-buttonsContainer.getHeight());
            windowDecoratorAnimation = new Timeline(new KeyFrame(Duration.millis(320), new KeyValue(this.translateYProperty(), 0, Interpolator.EASE_BOTH)));
            windowDecoratorAnimation.setOnFinished((finish) ->
            {
               contentPlaceHolder.setBorder(new Border(new BorderStroke(Color.BLACK,
                                                                        BorderStrokeStyle.SOLID,
                                                                        CornerRadii.EMPTY,
                                                                        new BorderWidths(0, 4, 4, 4))));
               contentPlaceHolder.getStyleClass().add("resize-border");
            });
            windowDecoratorAnimation.play();
         }
      });

      contentPlaceHolder.addEventHandler(MouseEvent.MOUSE_PRESSED, (mouseEvent) -> updateInitMouseValues(mouseEvent));
      buttonsContainer.addEventHandler(MouseEvent.MOUSE_PRESSED, (mouseEvent) -> updateInitMouseValues(mouseEvent));

      // show the drag cursor on the borders
      addEventFilter(MouseEvent.MOUSE_MOVED, (mouseEvent) -> showDragCursorOnBorders(mouseEvent));

      // handle drag events on the decorator pane
      addEventFilter(MouseEvent.MOUSE_RELEASED, (mouseEvent) -> isDragging = false);
      this.setOnMouseDragged((mouseEvent) -> handleDragEventOnDecoratorPane(mouseEvent));
   }

   private void initializeButtons()
   {

      SVGGlyph full = new SVGGlyph(0,
                                   "FULLSCREEN",
                                   "M598 214h212v212h-84v-128h-128v-84zM726 726v-128h84v212h-212v-84h128zM214 426v-212h212v84h-128v128h-84zM298 598v128h128v84h-212v-212h84z",
                                   Color.WHITE);
      full.setSize(16, 16);
      SVGGlyph minus = new SVGGlyph(0,
                                    "MINUS",
                                    "M804.571 420.571v109.714q0 22.857-16 38.857t-38.857 16h-694.857q-22.857 0-38.857-16t-16-38.857v-109.714q0-22.857 16-38.857t38.857-16h694.857q22.857 0 38.857 16t16 38.857z",
                                    Color.WHITE);
      minus.setSize(12, 2);
      minus.setTranslateY(4);
      SVGGlyph resizeMax = new SVGGlyph(0,
                                        "RESIZE_MAX",
                                        "M726 810v-596h-428v596h428zM726 44q34 0 59 25t25 59v768q0 34-25 60t-59 26h-428q-34 0-59-26t-25-60v-768q0-34 25-60t59-26z",
                                        Color.WHITE);
      resizeMax.setSize(12, 12);
      SVGGlyph resizeMin = new SVGGlyph(0,
                                        "RESIZE_MIN",
                                        "M80.842 943.158v-377.264h565.894v377.264h-565.894zM0 404.21v619.79h727.578v-619.79h-727.578zM377.264 161.684h565.894v377.264h-134.736v80.842h215.578v-619.79h-727.578v323.37h80.842v-161.686z",
                                        Color.WHITE);
      resizeMin.setSize(12, 12);
      SVGGlyph close = new SVGGlyph(0, "CLOSE", "M810 274l-238 238 238 238-60 60-238-238-238 238-60-60 238-238-238-238 60-60 238 238 238-238z", Color.WHITE);
      close.setSize(12, 12);
      btnFull = new JFXButton();
      btnFull.getStyleClass().add("jfx-decorator-button");
      btnFull.setCursor(Cursor.HAND);
      btnFull.setOnAction((action) -> primaryStage.setFullScreen(!primaryStage.isFullScreen()));
      btnFull.setGraphic(full);
      btnFull.setTranslateX(-30);
      btnFull.setRipplerFill(Color.WHITE);

      btnClose = new JFXButton();
      btnClose.getStyleClass().add("jfx-decorator-button");
      btnClose.setCursor(Cursor.HAND);
      btnClose.setOnAction((action) -> onCloseButtonAction.get().run());
      btnClose.setGraphic(close);
      btnClose.setRipplerFill(Color.WHITE);

      btnMin = new JFXButton();
      btnMin.getStyleClass().add("jfx-decorator-button");
      btnMin.setCursor(Cursor.HAND);
      btnMin.setOnAction((action) -> primaryStage.setIconified(true));
      btnMin.setGraphic(minus);
      btnMin.setRipplerFill(Color.WHITE);

      btnMax = new JFXButton();
      btnMax.getStyleClass().add("jfx-decorator-button");
      btnMax.setCursor(Cursor.HAND);
      btnMax.setRipplerFill(Color.WHITE);
      btnMax.setOnAction((action) -> maximize(resizeMin, resizeMax));
      btnMax.setGraphic(resizeMax);
   }

   private void maximize(SVGGlyph resizeMin, SVGGlyph resizeMax)
   {
      if (!isCustomMaximize())
      {
         primaryStage.setMaximized(!primaryStage.isMaximized());
         maximized = primaryStage.isMaximized();
         if (primaryStage.isMaximized())
         {
            btnMax.setGraphic(resizeMin);
            btnMax.setTooltip(new Tooltip("Restore Down"));
         }
         else
         {
            btnMax.setGraphic(resizeMax);
            btnMax.setTooltip(new Tooltip("Maximize"));
         }
      }
      else
      {
         if (!maximized)
         {
            // store original bounds
            originalBox = new BoundingBox(primaryStage.getX(), primaryStage.getY(), primaryStage.getWidth(), primaryStage.getHeight());
            // get the max stage bounds
            Screen screen = Screen.getScreensForRectangle(primaryStage.getX(), primaryStage.getY(), primaryStage.getWidth(), primaryStage.getHeight()).get(0);
            Rectangle2D bounds = screen.getVisualBounds();
            maximizedBox = new BoundingBox(bounds.getMinX(), bounds.getMinY(), bounds.getWidth(), bounds.getHeight());
            // maximized the stage
            primaryStage.setX(maximizedBox.getMinX());
            primaryStage.setY(maximizedBox.getMinY());
            primaryStage.setWidth(maximizedBox.getWidth());
            primaryStage.setHeight(maximizedBox.getHeight());
            btnMax.setGraphic(resizeMin);
            btnMax.setTooltip(new Tooltip("Restore Down"));
         }
         else
         {
            // restore stage to its original size
            primaryStage.setX(originalBox.getMinX());
            primaryStage.setY(originalBox.getMinY());
            primaryStage.setWidth(originalBox.getWidth());
            primaryStage.setHeight(originalBox.getHeight());
            originalBox = null;
            btnMax.setGraphic(resizeMax);
            btnMax.setTooltip(new Tooltip("Maximize"));
         }
         maximized = !maximized;
      }
   }

   private void initializeContainers(Node node, boolean fullScreen, boolean max, boolean min)
   {
      buttonsContainer = new HBox();
      buttonsContainer.getStyleClass().add("jfx-decorator-buttons-container");
      buttonsContainer.setBackground(new Background(new BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY)));
      // BINDING
      buttonsContainer.setPadding(new Insets(4));
      buttonsContainer.setAlignment(Pos.CENTER_RIGHT);

      // customize decorator buttons
      List btns = new ArrayList<>();
      if (fullScreen)
      {
         btns.add(btnFull);
      }
      if (min)
      {
         btns.add(btnMin);
      }
      if (max)
      {
         btns.add(btnMax);
         // maximize/restore the window on header double click
         buttonsContainer.addEventHandler(MouseEvent.MOUSE_CLICKED, (mouseEvent) ->
         {
            if (mouseEvent.getClickCount() == 2)
            {
               btnMax.fire();
            }
         });
      }
      btns.add(btnClose);

      text = new Text();
      text.getStyleClass().addAll("jfx-decorator-text", "title", "jfx-decorator-title");
      text.setFill(Color.WHITE);
      text.textProperty().bind(title); //binds the Text's text to title
      title.bind(primaryStage.titleProperty()); //binds title to the primaryStage's title

      graphicContainer = new HBox();
      graphicContainer.setPickOnBounds(false);
      graphicContainer.setAlignment(Pos.CENTER_LEFT);
      graphicContainer.getChildren().setAll(text);

      HBox graphicTextContainer = new HBox(graphicContainer, text);
      graphicTextContainer.getStyleClass().add("jfx-decorator-title-container");
      graphicTextContainer.setAlignment(Pos.CENTER_LEFT);
      graphicTextContainer.setPickOnBounds(false);
      HBox.setHgrow(graphicTextContainer, Priority.ALWAYS);
      HBox.setMargin(graphicContainer, new Insets(0, 8, 0, 8));

      buttonsContainer.getChildren().setAll(graphicTextContainer);
      buttonsContainer.getChildren().addAll(btns);
      buttonsContainer.addEventHandler(MouseEvent.MOUSE_ENTERED, (enter) -> allowMove = true);
      buttonsContainer.addEventHandler(MouseEvent.MOUSE_EXITED, (enter) ->
      {
         if (!isDragging)
         {
            allowMove = false;
         }
      });
      buttonsContainer.setMinWidth(180);
      contentPlaceHolder.getStyleClass().add("jfx-decorator-content-container");
      contentPlaceHolder.setMinSize(0, 0);
      StackPane clippedContainer = new StackPane(node);
      contentPlaceHolder.getChildren().add(clippedContainer);
      ((Region) node).setMinSize(0, 0);
      VBox.setVgrow(contentPlaceHolder, Priority.ALWAYS);
      contentPlaceHolder.getStyleClass().add("resize-border");
      contentPlaceHolder.setBorder(new Border(new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(0, 4, 4, 4))));
      // BINDING

      Rectangle clip = new Rectangle();
      clip.widthProperty().bind(clippedContainer.widthProperty());
      clip.heightProperty().bind(clippedContainer.heightProperty());
      clippedContainer.setClip(clip);
      this.getChildren().addAll(buttonsContainer, contentPlaceHolder);
   }

   private void showDragCursorOnBorders(MouseEvent mouseEvent)
   {
      if (primaryStage.isMaximized() || primaryStage.isFullScreen() || maximized)
      {
         this.setCursor(Cursor.DEFAULT);
         return; // maximized mode does not support resize
      }
      if (!primaryStage.isResizable())
      {
         return;
      }
      double x = mouseEvent.getX();
      double y = mouseEvent.getY();
      if (contentPlaceHolder.getBorder() != null && contentPlaceHolder.getBorder().getStrokes().size() > 0)
      {
         double borderWidth = contentPlaceHolder.snappedLeftInset();
         if (isRightEdge(x))
         {
            if (y < borderWidth)
            {
               this.setCursor(Cursor.NE_RESIZE);
            }
            else if (y > this.getHeight() - borderWidth)
            {
               this.setCursor(Cursor.SE_RESIZE);
            }
            else
            {
               this.setCursor(Cursor.E_RESIZE);
            }
         }
         else if (isLeftEdge(x))
         {
            if (y < borderWidth)
            {
               this.setCursor(Cursor.NW_RESIZE);
            }
            else if (y > this.getHeight() - borderWidth)
            {
               this.setCursor(Cursor.SW_RESIZE);
            }
            else
            {
               this.setCursor(Cursor.W_RESIZE);
            }
         }
         else if (isTopEdge(y))
         {
            this.setCursor(Cursor.N_RESIZE);
         }
         else if (isBottomEdge(y))
         {
            this.setCursor(Cursor.S_RESIZE);
         }
         else
         {
            this.setCursor(Cursor.DEFAULT);
         }
      }
   }

   private void handleDragEventOnDecoratorPane(MouseEvent mouseEvent)
   {
      isDragging = true;
      if (!mouseEvent.isPrimaryButtonDown() || (xOffset == -1 && yOffset == -1))
      {
         return;
      }
      /*
       * Long press generates drag event!
       */
      if (primaryStage.isFullScreen() || mouseEvent.isStillSincePress() || primaryStage.isMaximized() || maximized)
      {
         return;
      }

      newX = mouseEvent.getScreenX();
      newY = mouseEvent.getScreenY();

      double deltax = newX - initX;
      double deltay = newY - initY;
      Cursor cursor = this.getCursor();

      if (Cursor.E_RESIZE.equals(cursor))
      {
         setStageWidth(initWidth + deltax);
         mouseEvent.consume();
      }
      else if (Cursor.NE_RESIZE.equals(cursor))
      {
         if (setStageHeight(initHeight - deltay))
         {
            primaryStage.setY(initStageY + deltay);
         }
         setStageWidth(initWidth + deltax);
         mouseEvent.consume();
      }
      else if (Cursor.SE_RESIZE.equals(cursor))
      {
         setStageWidth(initWidth + deltax);
         setStageHeight(initHeight + deltay);
         mouseEvent.consume();
      }
      else if (Cursor.S_RESIZE.equals(cursor))
      {
         setStageHeight(initHeight + deltay);
         mouseEvent.consume();
      }
      else if (Cursor.W_RESIZE.equals(cursor))
      {
         if (setStageWidth(initWidth - deltax))
         {
            primaryStage.setX(initStageX + deltax);
         }
         mouseEvent.consume();
      }
      else if (Cursor.SW_RESIZE.equals(cursor))
      {
         if (setStageWidth(initWidth - deltax))
         {
            primaryStage.setX(initStageX + deltax);
         }
         setStageHeight(initHeight + deltay);
         mouseEvent.consume();
      }
      else if (Cursor.NW_RESIZE.equals(cursor))
      {
         if (setStageWidth(initWidth - deltax))
         {
            primaryStage.setX(initStageX + deltax);
         }
         if (setStageHeight(initHeight - deltay))
         {
            primaryStage.setY(initStageY + deltay);
         }
         mouseEvent.consume();
      }
      else if (Cursor.N_RESIZE.equals(cursor))
      {
         if (setStageHeight(initHeight - deltay))
         {
            primaryStage.setY(initStageY + deltay);
         }
         mouseEvent.consume();
      }
      else if (allowMove)
      {
         primaryStage.setX(mouseEvent.getScreenX() - xOffset);
         primaryStage.setY(mouseEvent.getScreenY() - yOffset);
         mouseEvent.consume();
      }
   }

   private void updateInitMouseValues(MouseEvent mouseEvent)
   {
      initStageX = primaryStage.getX();
      initStageY = primaryStage.getY();
      initWidth = primaryStage.getWidth();
      initHeight = primaryStage.getHeight();
      initX = mouseEvent.getScreenX();
      initY = mouseEvent.getScreenY();
      xOffset = mouseEvent.getSceneX();
      yOffset = mouseEvent.getSceneY();
   }

   private boolean isRightEdge(double x)
   {
      final double width = this.getWidth();
      return x < width && x > width - contentPlaceHolder.snappedLeftInset();
   }

   private boolean isTopEdge(double y)
   {
      return y >= 0 && y < contentPlaceHolder.snappedLeftInset();
   }

   private boolean isBottomEdge(double y)
   {
      final double height = this.getHeight();
      return y < height && y > height - contentPlaceHolder.snappedLeftInset();
   }

   private boolean isLeftEdge(double x)
   {
      return x >= 0 && x < contentPlaceHolder.snappedLeftInset();
   }

   boolean setStageWidth(double width)
   {
      if (width >= primaryStage.getMinWidth() && width >= buttonsContainer.getMinWidth())
      {
         primaryStage.setWidth(width);
         //            initX = newX;
         return true;
      }
      else if (width >= primaryStage.getMinWidth() && width <= buttonsContainer.getMinWidth())
      {
         width = buttonsContainer.getMinWidth();
         primaryStage.setWidth(width);
      }
      return false;
   }

   boolean setStageHeight(double height)
   {
      if (height >= primaryStage.getMinHeight() && height >= buttonsContainer.getHeight())
      {
         primaryStage.setHeight(height);
         //            initY = newY;
         return true;
      }
      else if (height >= primaryStage.getMinHeight() && height <= buttonsContainer.getHeight())
      {
         height = buttonsContainer.getHeight();
         primaryStage.setHeight(height);
      }
      return false;
   }

   /**
    * set a speficed runnable when clicking on the close button
    *
    * @param onCloseButtonAction runnable to be executed
    */
   public void setOnCloseButtonAction(Runnable onCloseButtonAction)
   {
      this.onCloseButtonAction.set(onCloseButtonAction);
   }

   /**
    * this property is used to replace JavaFX maximization with a custom one that prevents hiding
    * windows taskbar when the JFXDecorator is maximized.
    *
    * @return customMaximizeProperty whether to use custom maximization or not.
    */
   public final BooleanProperty customMaximizeProperty()
   {
      return this.customMaximize;
   }

   /**
    * @return whether customMaximizeProperty is active or not
    */
   public final boolean isCustomMaximize()
   {
      return this.customMaximizeProperty().get();
   }

   /**
    * set customMaximize property
    *
    * @param customMaximize
    */
   public final void setCustomMaximize(final boolean customMaximize)
   {
      this.customMaximizeProperty().set(customMaximize);
   }

   /**
    * @param maximized
    */
   public void setMaximized(boolean maximized)
   {
      if (this.maximized != maximized)
      {
         Platform.runLater(() ->
         {
            btnMax.fire();
         });
      }
   }

   /**
    * will change the decorator content
    *
    * @param content
    */
   public void setContent(Node content)
   {
      this.contentPlaceHolder.getChildren().setAll(content);
   }

   /**
    * will set the title
    *
    * @param text
    * @deprecated Use {@link JFXDecorator#setTitle(java.lang.String)} instead.
    */
   @Deprecated
   public void setText(String text)
   {
      setTitle(text);
   }

   /**
    * will get the title
    *
    * @deprecated Use {@link JFXDecorator#setTitle(java.lang.String)} instead.
    */
   @Deprecated
   public String getText()
   {
      return getTitle();
   }

   public String getTitle()
   {
      return title.get();
   }

   /**
    * By default this title property is bound to the primaryStage's title property.
    * 

* To change it to something else, use * *

    *     {@code
    * jfxDecorator.titleProperty().unbind();
    * }
* * first. */ public StringProperty titleProperty() { return title; } /** * If you want the {@code primaryStage}'s title and the {@code JFXDecorator}'s title to be * different, then go ahead and use this method. *

* By default, this title property is bound to the {@code primaryStage}'s title property-so merely * setting the {@code primaryStage}'s title, will set the {@code JFXDecorator}'s title. */ public void setTitle(String title) { this.title.unbind(); this.title.set(title); } public void setGraphic(Node node) { if (graphic != null) { graphicContainer.getChildren().remove(graphic); } if (node != null) { graphicContainer.getChildren().add(0, node); } graphic = node; } public Node getGraphic() { return graphic; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy