I've created my own Marquee class / control that works just fine after the first few seconds it's loaded. However, once the app first loads the marquee thinks that it's localbounds are maxWidth: 0.0 minWidth: 2.0. Below is the Marquee class code and the code I'm using to load it in my test app.
Marquee Class:
public class Marquee extends HBox {
/**
* Add a node to the Marquee control
* @param node - a node to add
*/
public void add(Node node) {
getChildren().add(node);
}
/**
* Add a list of nodes to the Marquee control
* @param observable list - an observable list to add
*/
public void addAll(ObservableList<Node> list) {
getChildren().addAll(list);
}
/**
* Default Constructor: Initializes the Marquee Object with default settings:
* Empty Array List of Nodes, initial delay, Direction.LEFT, Duration.seconds(10), Interpolator.LINEAR, 10)
*/
public Marquee()
{
this(FXCollections.observableArrayList(new ArrayList<Node>()), Duration.seconds(3), Direction.LEFT, Duration.seconds(10), Interpolator.LINEAR, 10.0);
}
/**
* Constructor: Initializes the Marquee Object with default settings
* @param observable list
*/
public Marquee(ObservableList<Node> nodes)
{
this(nodes, Duration.seconds(3), Direction.LEFT, Duration.seconds(10), Interpolator.LINEAR, 10.0);
}
/**
* Constructor: Initializes the Marquee Object with default settings
* @param observable list
* @param duration - usually in seconds i.e. Duration.seconds(10)
*/
public Marquee(ObservableList<Node> nodes, Duration duration) {
this(nodes, Duration.seconds(3), Direction.LEFT, duration, Interpolator.LINEAR, 10.0);
}
/**
* Constructor: Initializes the Marquee Object with default settings
* @param observable list
* @param direction - an enum, i.e Direction.LEFT or Direction.RIGHT
* @param duration - usually in seconds i.e. Duration.seconds(10)
*/
public Marquee(ObservableList<Node> nodes, Direction direction, Duration duration) {
this(nodes, Duration.seconds(3), direction, duration, Interpolator.LINEAR, 10.0);
}
/**
* Constructor: Initializes the Marquee Object with default settings
* @param observable list
* @param duration - usually in seconds i.e. Duration.seconds(10)
* @param interpolator - effects the translation behavior, i.e
* Interpolator.EASE_BOTH, or EASE_LINEAR
*/
public Marquee(ObservableList<Node> nodes, Duration duration, Interpolator interpolator)
{
this(nodes, Duration.seconds(3), Direction.LEFT, duration, interpolator, 10.0);
}
/**
* Constructor: Initializes the Marquee Object with default settings:
* @param observable list
* @param initialDelay - the amount of time before the marquee will begin scrolling
* after the application has loaded
* @param direction - an enum, i.e Direction.LEFT or Direction.RIGHT
* @param duration - usually in seconds i.e. Duration.seconds(10)
* @param interpolator - effects the translation behavior, i.e
* Interpolator.EASE_BOTH, or EASE_LINEAR
*/
public Marquee(ObservableList<Node> list, Duration initialDelay, Direction direction, Duration duration, Interpolator interpolator) {
this(list, initialDelay, direction, duration, interpolator, 10.0);
}
/**
* Preferred Constructor: Initializes the Marquee Object with your preferred settings
*
* @param observable list
* @param initialDelay - the amount of time before the marquee will begin scrolling
* after the application has loaded
* @param direction - an enum, i.e Direction.LEFT or Direction.RIGHT
* @param duration - usually in seconds i.e. Duration.seconds(10)
* @param interpolator - effects the translation behavior, i.e
* Interpolator.EASE_BOTH, or EASE_LINEAR
* @param nodeSpacing - a double value that determines how far apart
* each element in the marquee will be placed from one another
*/
public Marquee(ObservableList<Node> list, Duration initialDelay, Direction direction, Duration duration, Interpolator interpolator, double nodeSpacing) {
super();
getChildren().addAll(list);
setSpacing(nodeSpacing);
delay = initialDelay;
this.direction = direction;
this.duration = duration;
this.interpolator = interpolator;
}
public enum Direction {
LEFT, RIGHT
};
private Direction direction;
private TranslateTransition animation;
private Duration duration;
/**
* This begins the animation of the Marquee. By default this method
* calculates the width of the Marquee's parent and uses that as its
* start point. When the nodes inside the Marquee have reached the outer
* bounds of its parent the Marquee will stop and reset. Note: If the
* application is resized, the animation will need to be stopped and
* restarted. The Marquee will recalculate its translation requirements each
* cycle so if the user resizes it's parent, the Marquee will conform.
*
* @param duration
* the amount of time the translation should take
*/
public void animate() {
animation = new TranslateTransition(duration, this);
double maxWidth = getBoundsInLocal().getMaxX();
double minWidth = getBoundsInLocal().getMinX() - getContentsWidth();
switch (direction) {
case LEFT:
animation.setToX(minWidth);
animation.setFromX(maxWidth);
break;
case RIGHT:
animation.setToX(maxWidth);
animation.setFromX(minWidth);
break;
default:
animation.setToX(minWidth);
animation.setFromX(maxWidth);
break;
}
animation.setCycleCount(1);
animation.setInterpolator(getInterpolator());
animation.setDelay(delay);
animation.playFromStart();
animation.setOnFinished(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event)
{
stopAnimation();
recycleAnimation();
}
});
}
private Duration delay;
public Duration getDelay() {
return delay;
}
public void setDelay(Duration delay) {
this.delay = delay;
}
private Interpolator interpolator;
/**
* How the Marquee transitions its content into and out of FOV.
* Options are:
* DISCRETE (DO NOT USE), EASE_IN, EASE_OUT, EASE_BOTH, and LINEAR (DEFAULT).
* Any change to the Interpolator will take affect after the current cycle
* ends.
* Suggested Usage: setInterpolator(Interpolator.LINEAR)
*/
public void setInterpolator(Interpolator interpolator)
{
this.interpolator = interpolator;
}
/**
* The Interpolator of the Marquee.
* @return Interpolator
*/
public Interpolator getInterpolator()
{
return interpolator;
}
public void recycleAnimation()
{
setDelay(Duration.ZERO);
animate();
}
/**
* Stop animation of Marquee
*/
public void stopAnimation() {
animation.stop();
}
/**
* Set the default spacing between nodes in the Marquee Default is set to
* 5.0
*/
public void setNodeSpacing(double value) {
setSpacing(value);
}
/**
* Get the current spacing between nodes in the Marquee
*
* @return double
*/
public double getNodeSpacing() {
return getSpacing();
}
private int getContentsWidth()
{
int width = 0;
for(Node node : getChildrenUnmodifiable())
{
width += node.boundsInLocalProperty().get().getWidth();
}
return width;
}
}
and my Main Class
public class Main extends Application {
@Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
ObservableList<Node> labels = FXCollections.observableArrayList();
labels.add(new Label("Test Label 1"));
labels.add(new Label("Test Label 2"));
Marquee marqueeLeft = new Marquee(labels, Duration.ZERO, Direction.LEFT, Duration.seconds(10), Interpolator.EASE_BOTH, 10.0);
root.setTop(marqueeLeft);
final ObservableList<Node> labels2 = FXCollections.observableArrayList();
labels2.add(new Label("Test Label 3"));
labels2.add(new Label("Test Label 4"));
final Marquee marqueeRight = new Marquee(labels2, Duration.ZERO, Direction.RIGHT, Duration.seconds(10), Interpolator.EASE_BOTH, 10.0);
root.setBottom(marqueeRight);
marqueeLeft.animate();
marqueeRight.animate();
Button button = new Button();
button.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
System.out.println("Workin");
marqueeRight.add(new Label("Test Add Label"));
}
});
root.setCenter(button);
Scene scene = new Scene(root,600,300);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
I have tried waiting to load the marquee until after showing the stage and trying to get parentBounds, localBounds, you name it. It just always wants to start off at 0.0, 2.0.
Any advice would be greatly appreciated.