javafx.scene.control.ScrollPane Maven / Gradle / Ivy
/*
* Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javafx.scene.control;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javafx.beans.DefaultProperty;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.WritableValue;
import javafx.css.CssMetaData;
import javafx.css.PseudoClass;
import javafx.css.StyleableBooleanProperty;
import javafx.css.StyleableObjectProperty;
import javafx.css.StyleableProperty;
import javafx.geometry.BoundingBox;
import javafx.geometry.Bounds;
import javafx.scene.AccessibleAttribute;
import javafx.scene.AccessibleRole;
import javafx.scene.Node;
import javafx.css.converter.BooleanConverter;
import javafx.css.converter.EnumConverter;
import javafx.scene.control.skin.ScrollPaneSkin;
import javafx.css.Styleable;
/**
* A Control that provides a scrolled, clipped viewport of its contents. It
* allows the user to scroll the content around either directly (panning) or
* by using scroll bars. The ScrollPane allows specification of the scroll
* bar policy, which determines when scroll bars are displayed: always, never,
* or only when they are needed. The scroll bar policy can be specified
* independently for the horizontal and vertical scroll bars.
*
* The ScrollPane allows the application to set the current, minimum, and
* maximum values for positioning the contents in the horizontal and
* vertical directions. These values are mapped proportionally onto the
* {@link javafx.scene.Node#layoutBoundsProperty layoutBounds} of the contained node.
*
* ScrollPane layout calculations are based on the layoutBounds rather than
* the boundsInParent (visual bounds) of the scroll node.
* If an application wants the scrolling to be based on the visual bounds
* of the node (for scaled content etc.), it needs to wrap the scroll
* node in a Group.
*
* ScrollPane sets focusTraversable to false.
*
*
*
* This example creates a ScrollPane, which contains a Rectangle:
*
Rectangle rect = new Rectangle(200, 200, Color.RED);
* ScrollPane s1 = new ScrollPane();
* s1.setPrefSize(120, 120);
* s1.setContent(rect);
*
*
*
* @since JavaFX 2.0
*/
@DefaultProperty("content")
public class ScrollPane extends Control {
/* *************************************************************************
* *
* Constructors *
* *
**************************************************************************/
/**
* Creates a new ScrollPane.
*/
public ScrollPane() {
getStyleClass().setAll(DEFAULT_STYLE_CLASS);
setAccessibleRole(AccessibleRole.SCROLL_PANE);
// focusTraversable is styleable through css. Calling setFocusTraversable
// makes it look to css like the user set the value and css will not
// override. Initializing focusTraversable by calling applyStyle with
// null StyleOrigin ensures that css will be able to override the value.
((StyleableProperty)focusTraversableProperty()).applyStyle(null, Boolean.FALSE);
}
/**
* Creates a new ScrollPane.
* @param content the initial content for the ScrollPane
* @since JavaFX 8.0
*/
public ScrollPane(Node content) {
this();
setContent(content);
}
/* *************************************************************************
* *
* Properties *
* *
**************************************************************************/
/**
* Specifies the policy for showing the horizontal scroll bar.
*/
private ObjectProperty hbarPolicy;
public final void setHbarPolicy(ScrollBarPolicy value) {
hbarPolicyProperty().set(value);
}
public final ScrollBarPolicy getHbarPolicy() {
return hbarPolicy == null ? ScrollBarPolicy.AS_NEEDED : hbarPolicy.get();
}
public final ObjectProperty hbarPolicyProperty() {
if (hbarPolicy == null) {
hbarPolicy = new StyleableObjectProperty(ScrollBarPolicy.AS_NEEDED) {
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.HBAR_POLICY;
}
@Override
public Object getBean() {
return ScrollPane.this;
}
@Override
public String getName() {
return "hbarPolicy";
}
};
}
return hbarPolicy;
}
/**
* Specifies the policy for showing the vertical scroll bar.
*/
private ObjectProperty vbarPolicy;
public final void setVbarPolicy(ScrollBarPolicy value) {
vbarPolicyProperty().set(value);
}
public final ScrollBarPolicy getVbarPolicy() {
return vbarPolicy == null ? ScrollBarPolicy.AS_NEEDED : vbarPolicy.get();
}
public final ObjectProperty vbarPolicyProperty() {
if (vbarPolicy == null) {
vbarPolicy = new StyleableObjectProperty(ScrollBarPolicy.AS_NEEDED) {
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.VBAR_POLICY;
}
@Override
public Object getBean() {
return ScrollPane.this;
}
@Override
public String getName() {
return "vbarPolicy";
}
};
}
return vbarPolicy;
}
/**
* The node used as the content of this ScrollPane.
*/
private ObjectProperty content;
public final void setContent(Node value) {
contentProperty().set(value);
}
public final Node getContent() {
return content == null ? null : content.get();
}
public final ObjectProperty contentProperty() {
if (content == null) {
content = new SimpleObjectProperty<>(this, "content");
}
return content;
}
/**
* The current horizontal scroll position of the ScrollPane. This value
* may be set by the application to scroll the view programmatically.
* The ScrollPane will update this value whenever the viewport is
* scrolled or panned by the user. This value must always be within
* the range of {@link #hminProperty hmin} to {@link #hmaxProperty hmax}. When {@link #hvalueProperty hvalue}
* equals {@link #hminProperty hmin}, the contained node is positioned so that
* its layoutBounds {@link javafx.geometry.Bounds#getMinX minX} is visible. When {@link #hvalueProperty hvalue}
* equals {@link #hmaxProperty hmax}, the contained node is positioned so that its
* layoutBounds {@link javafx.geometry.Bounds#getMaxX maxX} is visible. When {@link #hvalueProperty hvalue} is between
* {@link #hminProperty hmin} and {@link #hmaxProperty hmax}, the contained node is positioned
* proportionally between layoutBounds {@link javafx.geometry.Bounds#getMinX minX} and
* layoutBounds {@link javafx.geometry.Bounds#getMaxX maxX}.
*/
private DoubleProperty hvalue;
public final void setHvalue(double value) {
hvalueProperty().set(value);
}
public final double getHvalue() {
return hvalue == null ? 0.0 : hvalue.get();
}
public final DoubleProperty hvalueProperty() {
if (hvalue == null) {
hvalue = new SimpleDoubleProperty(this, "hvalue");
}
return hvalue;
}
/**
* The current vertical scroll position of the ScrollPane. This value
* may be set by the application to scroll the view programmatically.
* The ScrollPane will update this value whenever the viewport is
* scrolled or panned by the user. This value must always be within
* the range of {@link #vminProperty vmin} to {@link #vmaxProperty vmax}. When {@link #vvalueProperty vvalue}
* equals {@link #vminProperty vmin}, the contained node is positioned so that
* its layoutBounds {@link javafx.geometry.Bounds#getMinY minY} is visible. When {@link #vvalueProperty vvalue}
* equals {@link #vmaxProperty vmax}, the contained node is positioned so that its
* layoutBounds {@link javafx.geometry.Bounds#getMaxY maxY} is visible. When {@link #vvalueProperty vvalue} is between
* {@link #vminProperty vmin} and {@link #vmaxProperty vmax}, the contained node is positioned
* proportionally between layoutBounds {@link javafx.geometry.Bounds#getMinY minY} and
* layoutBounds {@link javafx.geometry.Bounds#getMaxY maxY}.
*/
private DoubleProperty vvalue;
public final void setVvalue(double value) {
vvalueProperty().set(value);
}
public final double getVvalue() {
return vvalue == null ? 0.0 : vvalue.get();
}
public final DoubleProperty vvalueProperty() {
if (vvalue == null) {
vvalue = new SimpleDoubleProperty(this, "vvalue");
}
return vvalue;
}
/**
* The minimum allowable {@link #hvalueProperty hvalue} for this ScrollPane.
* Default value is 0.
*/
private DoubleProperty hmin;
public final void setHmin(double value) {
hminProperty().set(value);
}
public final double getHmin() {
return hmin == null ? 0.0F : hmin.get();
}
public final DoubleProperty hminProperty() {
if (hmin == null) {
hmin = new SimpleDoubleProperty(this, "hmin", 0.0);
}
return hmin;
}
/**
* The minimum allowable {@link #vvalueProperty vvalue} for this ScrollPane.
* Default value is 0.
*/
private DoubleProperty vmin;
public final void setVmin(double value) {
vminProperty().set(value);
}
public final double getVmin() {
return vmin == null ? 0.0F : vmin.get();
}
public final DoubleProperty vminProperty() {
if (vmin == null) {
vmin = new SimpleDoubleProperty(this, "vmin", 0.0);
}
return vmin;
}
/**
* The maximum allowable {@link #hvalueProperty hvalue} for this ScrollPane.
* Default value is 1.
*/
private DoubleProperty hmax;
public final void setHmax(double value) {
hmaxProperty().set(value);
}
public final double getHmax() {
return hmax == null ? 1.0F : hmax.get();
}
public final DoubleProperty hmaxProperty() {
if (hmax == null) {
hmax = new SimpleDoubleProperty(this, "hmax", 1.0);
}
return hmax;
}
/**
* The maximum allowable {@link #vvalueProperty vvalue} for this ScrollPane.
* Default value is 1.
*/
private DoubleProperty vmax;
public final void setVmax(double value) {
vmaxProperty().set(value);
}
public final double getVmax() {
return vmax == null ? 1.0F : vmax.get();
}
public final DoubleProperty vmaxProperty() {
if (vmax == null) {
vmax = new SimpleDoubleProperty(this, "vmax", 1.0);
}
return vmax;
}
/**
* If true and if the contained node is a Resizable, then the node will be
* kept resized to match the width of the ScrollPane's viewport. If the
* contained node is not a Resizable, this value is ignored.
*/
private BooleanProperty fitToWidth;
public final void setFitToWidth(boolean value) {
fitToWidthProperty().set(value);
}
public final boolean isFitToWidth() {
return fitToWidth == null ? false : fitToWidth.get();
}
public final BooleanProperty fitToWidthProperty() {
if (fitToWidth == null) {
fitToWidth = new StyleableBooleanProperty(false) {
@Override public void invalidated() {
pseudoClassStateChanged(FIT_TO_WIDTH_PSEUDOCLASS_STATE, get());
}
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.FIT_TO_WIDTH;
}
@Override
public Object getBean() {
return ScrollPane.this;
}
@Override
public String getName() {
return "fitToWidth";
}
};
}
return fitToWidth;
}
/**
* If true and if the contained node is a Resizable, then the node will be
* kept resized to match the height of the ScrollPane's viewport. If the
* contained node is not a Resizable, this value is ignored.
*/
private BooleanProperty fitToHeight;
public final void setFitToHeight(boolean value) {
fitToHeightProperty().set(value);
}
public final boolean isFitToHeight() {
return fitToHeight == null ? false : fitToHeight.get();
}
public final BooleanProperty fitToHeightProperty() {
if (fitToHeight == null) {
fitToHeight = new StyleableBooleanProperty(false) {
@Override public void invalidated() {
pseudoClassStateChanged(FIT_TO_HEIGHT_PSEUDOCLASS_STATE, get());
}
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.FIT_TO_HEIGHT;
}
@Override
public Object getBean() {
return ScrollPane.this;
}
@Override
public String getName() {
return "fitToHeight";
}
};
}
return fitToHeight;
}
/**
* Specifies whether the user should be able to pan the viewport by using
* the mouse. If mouse events reach the ScrollPane (that is, if mouse
* events are not blocked by the contained node or one of its children)
* then {@link #pannableProperty pannable} is consulted to determine if the events should be
* used for panning.
*/
private BooleanProperty pannable;
public final void setPannable(boolean value) {
pannableProperty().set(value);
}
public final boolean isPannable() {
return pannable == null ? false : pannable.get();
}
public final BooleanProperty pannableProperty() {
if (pannable == null) {
pannable = new StyleableBooleanProperty(false) {
@Override public void invalidated() {
pseudoClassStateChanged(PANNABLE_PSEUDOCLASS_STATE, get());
}
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.PANNABLE;
}
@Override
public Object getBean() {
return ScrollPane.this;
}
@Override
public String getName() {
return "pannable";
}
};
}
return pannable;
}
/**
* Specify the preferred width of the ScrollPane Viewport.
* This is the width that will be available to the content node.
* The overall width of the ScrollPane is the ViewportWidth + padding
*/
private DoubleProperty prefViewportWidth;
public final void setPrefViewportWidth(double value) {
prefViewportWidthProperty().set(value);
}
public final double getPrefViewportWidth() {
return prefViewportWidth == null ? 0.0F : prefViewportWidth.get();
}
public final DoubleProperty prefViewportWidthProperty() {
if (prefViewportWidth == null) {
prefViewportWidth = new SimpleDoubleProperty(this, "prefViewportWidth");
}
return prefViewportWidth;
}
/**
* Specify the preferred height of the ScrollPane Viewport.
* This is the height that will be available to the content node.
* The overall height of the ScrollPane is the ViewportHeight + padding
*/
private DoubleProperty prefViewportHeight;
public final void setPrefViewportHeight(double value) {
prefViewportHeightProperty().set(value);
}
public final double getPrefViewportHeight() {
return prefViewportHeight == null ? 0.0F : prefViewportHeight.get();
}
public final DoubleProperty prefViewportHeightProperty() {
if (prefViewportHeight == null) {
prefViewportHeight = new SimpleDoubleProperty(this, "prefViewportHeight");
}
return prefViewportHeight;
}
/**
* Specify the minimum width of the ScrollPane Viewport.
* This is the width that will be available to the content node.
*
* @since JavaFX 8u40
* @see #prefViewportWidthProperty()
*/
private DoubleProperty minViewportWidth;
public final void setMinViewportWidth(double value) {
minViewportWidthProperty().set(value);
}
public final double getMinViewportWidth() {
return minViewportWidth == null ? 0.0F : minViewportWidth.get();
}
public final DoubleProperty minViewportWidthProperty() {
if (minViewportWidth == null) {
minViewportWidth = new SimpleDoubleProperty(this, "minViewportWidth");
}
return minViewportWidth;
}
/**
* Specify the minimum height of the ScrollPane Viewport.
* This is the height that will be available to the content node.
*
* @since JavaFX 8u40
* @see #prefViewportHeightProperty()
*/
private DoubleProperty minViewportHeight;
public final void setMinViewportHeight(double value) {
minViewportHeightProperty().set(value);
}
public final double getMinViewportHeight() {
return minViewportHeight == null ? 0.0F : minViewportHeight.get();
}
public final DoubleProperty minViewportHeightProperty() {
if (minViewportHeight == null) {
minViewportHeight = new SimpleDoubleProperty(this, "minViewportHeight");
}
return minViewportHeight;
}
/**
* The actual Bounds of the ScrollPane Viewport.
* This is the Bounds of the content node.
*/
private ObjectProperty viewportBounds;
public final void setViewportBounds(Bounds value) {
viewportBoundsProperty().set(value);
}
public final Bounds getViewportBounds() {
return viewportBounds == null ? new BoundingBox(0,0,0,0) : viewportBounds.get();
}
public final ObjectProperty viewportBoundsProperty() {
if (viewportBounds == null) {
viewportBounds = new SimpleObjectProperty<>(this, "viewportBounds", new BoundingBox(0,0,0,0));
}
return viewportBounds;
}
/* *************************************************************************
* *
* Methods *
* *
**************************************************************************/
/*
* TODO The unit increment and block increment variables have been
* removed from the public API. These are intended to be mapped to
* the corresponding variables of the scrollbars. However, the problem
* is that they are specified in terms of the logical corrdinate space
* of the ScrollPane (that is, [hmin..hmax] by [vmin..vmax]. This is
* incorrect. Scrolling is a user action and should properly be based
* on how much of the content is visible, not on some abstract
* coordinate space. At some later date we may add a finer-grained
* API to allow applications to control this. Meanwhile, the skin should
* set unit and block increments for the scroll bars to do something
* reasonable based on the viewport size, e.g. the block increment
* should scroll 90% of the pixel size of the viewport, and the unit
* increment should scroll 10% of the pixel size of the viewport.
*/
/**
* Defines the horizontal unit increment amount. Typically this is used when clicking on the
* increment or decrement arrow buttons of the horizontal scroll bar.
*/
// public var hunitIncrement:Number = 20.0;
/**
* Defines the vertical unit increment amount. Typically this is used when clicking on the
* increment or decrement arrow buttons of the vertical scroll bar.
*/
// public var vunitIncrement:Number = 20.0;
/**
* Defines the horizontal block increment amount. Typically this is used when clicking on the
* track of the scroll bar.
*/
// public var hblockIncrement:Number = -1;
/**
* Defines the vertical block increment amount. Typically this is used when clicking on the
* track of the scroll bar.
*/
// public var vblockIncrement:Number = -1;
/** {@inheritDoc} */
@Override protected Skin> createDefaultSkin() {
return new ScrollPaneSkin(this);
}
/* *************************************************************************
* *
* Stylesheet Handling *
* *
**************************************************************************/
/**
* Initialize the style class to 'scroll-view'.
*
* This is the selector class from which CSS can be used to style
* this control.
*/
private static final String DEFAULT_STYLE_CLASS = "scroll-pane";
private static class StyleableProperties {
private static final CssMetaData HBAR_POLICY =
new CssMetaData<>("-fx-hbar-policy",
new EnumConverter<>(ScrollBarPolicy.class),
ScrollBarPolicy.AS_NEEDED){
@Override
public boolean isSettable(ScrollPane n) {
return n.hbarPolicy == null || !n.hbarPolicy.isBound();
}
@Override
public StyleableProperty getStyleableProperty(ScrollPane n) {
return (StyleableProperty)(WritableValue)n.hbarPolicyProperty();
}
};
private static final CssMetaData VBAR_POLICY =
new CssMetaData<>("-fx-vbar-policy",
new EnumConverter<>(ScrollBarPolicy.class),
ScrollBarPolicy.AS_NEEDED){
@Override
public boolean isSettable(ScrollPane n) {
return n.vbarPolicy == null || !n.vbarPolicy.isBound();
}
@Override
public StyleableProperty getStyleableProperty(ScrollPane n) {
return (StyleableProperty)(WritableValue)n.vbarPolicyProperty();
}
};
private static final CssMetaData FIT_TO_WIDTH =
new CssMetaData<>("-fx-fit-to-width",
BooleanConverter.getInstance(), Boolean.FALSE){
@Override
public boolean isSettable(ScrollPane n) {
return n.fitToWidth == null || !n.fitToWidth.isBound();
}
@Override
public StyleableProperty getStyleableProperty(ScrollPane n) {
return (StyleableProperty)n.fitToWidthProperty();
}
};
private static final CssMetaData FIT_TO_HEIGHT =
new CssMetaData<>("-fx-fit-to-height",
BooleanConverter.getInstance(), Boolean.FALSE){
@Override
public boolean isSettable(ScrollPane n) {
return n.fitToHeight == null || !n.fitToHeight.isBound();
}
@Override
public StyleableProperty getStyleableProperty(ScrollPane n) {
return (StyleableProperty)n.fitToHeightProperty();
}
};
private static final CssMetaData PANNABLE =
new CssMetaData<>("-fx-pannable",
BooleanConverter.getInstance(), Boolean.FALSE){
@Override
public boolean isSettable(ScrollPane n) {
return n.pannable == null || !n.pannable.isBound();
}
@Override
public StyleableProperty getStyleableProperty(ScrollPane n) {
return (StyleableProperty)n.pannableProperty();
}
};
private static final List> STYLEABLES;
static {
final List> styleables =
new ArrayList<>(Control.getClassCssMetaData());
styleables.add(HBAR_POLICY);
styleables.add(VBAR_POLICY);
styleables.add(FIT_TO_WIDTH);
styleables.add(FIT_TO_HEIGHT);
styleables.add(PANNABLE);
STYLEABLES = Collections.unmodifiableList(styleables);
}
}
/**
* Gets the {@code CssMetaData} associated with this class, which may include the
* {@code CssMetaData} of its superclasses.
* @return the {@code CssMetaData}
* @since JavaFX 8.0
*/
public static List> getClassCssMetaData() {
return StyleableProperties.STYLEABLES;
}
/**
* {@inheritDoc}
* @since JavaFX 8.0
*/
@Override
public List> getControlCssMetaData() {
return getClassCssMetaData();
}
private static final PseudoClass PANNABLE_PSEUDOCLASS_STATE =
PseudoClass.getPseudoClass("pannable");
private static final PseudoClass FIT_TO_WIDTH_PSEUDOCLASS_STATE =
PseudoClass.getPseudoClass("fitToWidth");
private static final PseudoClass FIT_TO_HEIGHT_PSEUDOCLASS_STATE =
PseudoClass.getPseudoClass("fitToHeight");
/**
* Returns the initial focus traversable state of this control, for use
* by the JavaFX CSS engine to correctly set its initial value. This method
* is overridden as by default UI controls have focus traversable set to true,
* but that is not appropriate for this control.
*
* @since 9
*/
@Override protected Boolean getInitialFocusTraversable() {
return Boolean.FALSE;
}
/* *************************************************************************
* *
* Accessibility handling *
* *
**************************************************************************/
/** {@inheritDoc} */
@Override
public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
switch (attribute) {
case CONTENTS: return getContent();
default: return super.queryAccessibleAttribute(attribute, parameters);
}
}
/* *************************************************************************
* *
* Support classes *
* *
**************************************************************************/
/**
* An enumeration denoting the policy to be used by a scrollable
* Control in deciding whether to show a scroll bar.
* @since JavaFX 2.0
*/
public static enum ScrollBarPolicy {
/**
* Indicates that a scroll bar should never be shown.
*/
NEVER,
/**
* Indicates that a scroll bar should always be shown.
*/
ALWAYS,
/**
* Indicates that a scroll bar should be shown when required.
*/
AS_NEEDED
}
}