Dynamically change JavaFX css property - java

In my FXML I have a simple slider:
<Slider fx:id="timeSlider" showTickLabels="true" GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="4"/>
This slider is part of a desktop music application I am writing and it signifies how far into the song has been played. I have written a change event that is called everytime the the song time is changed, and this successfully accomplishes sliding the knob/thumb down.
Now I am trying to also to color the left part (already played) blue and the right part (yet to be played) white as the knob slides down. If I hard code this line into my CSS i can accomplish fading from blue/white at the 50% mark.
.slider .track {-fx-background-color: linear-gradient(to right, #90C7E0 50%, white 50%);}
However, I need this to be dynamic based on how far I am in the song. I have written the following code, but can't seem to get the CSS style to apply
song.getMediaPlayer().currentTimeProperty().addListener(new ChangeListener<Duration>() {
#Override
public void changed(ObservableValue<? extends Duration> observable, Duration oldValue, Duration newValue) {
if (newValue != null && timeSlider != null) {
timeSlider.setValue(newValue.toSeconds());
double percentage = (newValue.toSeconds()/song.getDuration())*100;
String css = ".slider .track{-fx-background-color: linear-gradient(to right, #90C7E0 " +
percentage + "%, white " + percentage + "%);}";
timeSlider.getStyleClass().add(css);
}
}
});
I believe it's something to do with how I am adding the CSS as a style, because even adding a simple non dynamic style does not work. I get no errors so I am not sure what I am doing wrong.
Any help is appreciated, and better ways to accomplish this are also appreciated. Thanks!

The method getStyleClass() returns a list of CSS class names associated with the node. These class names are used to determine which rules in an external CSS file are applied to the node. You cannot pass CSS selectors and rules in here.
Instead, write an external CSS file which contains a rule for the background color for the track. You can use a "looked-up color" here, which basically works like a "CSS variable". Define the "looked-up color" for the slider, and use it in a rule for the track to set the background color:
style.css:
.slider {
-track-color: white ;
}
.slider .track {
-fx-background-color: -track-color ;
}
Now, in the Java code, you can update the value for the looked-up color by calling setStyle("-track-color: /* someColor */") on the slider. Here is a quick example:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Slider;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class SliderTest extends Application {
#Override
public void start(Stage primaryStage) {
Slider slider = new Slider();
slider.valueProperty().addListener((obs, oldValue, newValue) -> {
double percentage = 100.0 * (newValue.doubleValue() - slider.getMin()) / (slider.getMax() - slider.getMin());
slider.setStyle("-track-color: linear-gradient(to right, #90C7E0 " + percentage+"%, white "+percentage+("%);"));
});
StackPane root = new StackPane(slider);
root.setPadding(new Insets(20));
Scene scene = new Scene(root);
scene.getStylesheets().add("style.css");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
In your application, you can do the same using the current time of the media player (or just register a listener on the slider's value property, as that is changing too).

With the help of those in the comments I have come up with a solution,
.getStyleClass.add(...) did not do what I originally thought, and I needed to use .setStyle(...) instead however, I needed to set the style on the slider TRACK not the slider itself. Here is my code now...
public void changed(ObservableValue<? extends Duration> observable, Duration oldValue, Duration newValue) {
if (newValue != null && timeSlider != null) {
double percentage = (newValue.toSeconds()/song.getDuration())*100;
String cssValue = "-fx-background-color: linear-gradient(to right, #90C7E0 " +
percentage + "%, white " + percentage + "%)";
StackPane sliderTrack = (StackPane) timeSlider.lookup(".track");
sliderTrack.setStyle(cssValue);
timeSlider.setValue(newValue.toSeconds());
}
}

Related

How can I change the color of a JFXSlider's thumb according to the color of the slider at the current position of the thumb on the slider?

I'm using a JFXSlider in JavaFX and I've used a linear gradient for the color of the JFXSlider's track (with CSS). However, I'd also like to change the color of the thumb to that of the slider for that position. I've used the following CSS for the slider's linear gradient and for getting rid of the default green color of the JFXSlider:
.jfx-slider .track {
-fx-pref-height: 10;
-fx-background-color: linear-gradient(to right,red,orange);
}
.jfx-slider .colored-track {
-fx-background-color: transparent;
}
I tried the following CSS code to get the thumb color to be the same as that of the slider at the current position, but it didn't work.
.jfx-slider .thumb {
-fx-background-color: linear-gradient(to right,red,orange);
}
I guess it's probably that the code I tried only provides an internal linear gradient for the thumb's background color. Does anyone know how to solve this problem? P.S. I'm using JFoenix 9.0.10, JavaFX 15, and JDK 15.
One possible solution would be to add a global CSS variable and change it depending on the JFXSlider current value. For example :
.root {
-fx-custom-color : red;
}
And then use this variable on your jfx-slider css rules like :
/* Styling the slider thumb */
.jfx-slider>.thumb {
-fx-background-color: -fx-custom-color;
}
/* Styling the animated thumb */
.jfx-slider>.animated-thumb {
-fx-background-color: -fx-custom-color;
}
After that, you need to figure out how to update the "-fx-custom-color" variable and how to determine which color you need to set for the specific value of the Slider (or rather location).
First, you should add a listener to the value property to listen for value changes. Second, use the interpolate method of the Color class to determine the color, and finally, update the new value for the -fx-custom-color using inline CSS style to the JFXSlider.
Here is a complete example :
import com.jfoenix.controls.JFXSlider;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class SliderTesting extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
JFXSlider slider = new JFXSlider(0, 100, 0);
slider.valueProperty().addListener(e -> {
Color imageColor = Color.RED.interpolate(Color.ORANGE,
slider.getValue() / 100);
slider.setStyle("-fx-custom-color : " + colorToHex(imageColor) + ";");
});
VBox box = new VBox(slider);
box.setPadding(new Insets(20));
box.setPrefSize(400, 400);
box.setAlignment(Pos.CENTER);
Scene scene = new Scene(box);
scene.getStylesheets()
.add(this.getClass().getResource("custom-jfoenix.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}
public static String colorToHex(Color color) {
return String.format("#%02X%02X%02X", (int) (color.getRed() * 255),
(int) (color.getGreen() * 255), (int) (color.getBlue() * 255));
}
}
And the "custom-jfoenix.css" file
.root {
-fx-custom-color : red;
}
/* Styling the slider track */
.jfx-slider>.track {
-fx-pref-height: 10;
}
/* Styling the slider thumb */
.jfx-slider>.thumb {
-fx-background-color: -fx-custom-color;
}
/* Styling the filled track */
.jfx-slider>.colored-track {
-fx-background-color: linear-gradient(to right, red, orange);
}
/* Styling the animated thumb */
.jfx-slider>.animated-thumb {
-fx-background-color: -fx-custom-color;
}
And the result :

javafx custom ui component FadeTransition doesn't working correctly

Fade transition doesn't working correctly
I created new javafx ui component and added FadeTransition, but unfortunately fade transition doesn't working.
When I was entered JFXButton background color changed but fade transition doesn't working.
How can I fixed it ?
Here my code
Launcher class
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
AnimationButton animationButton = new AnimationButton();
Scene scene = new Scene(animationButton);
scene.getStylesheets().add(getClass().getResource("btn.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.setTitle("Custom Control");
primaryStage.setWidth(300);
primaryStage.setHeight(200);
primaryStage.show();
}
AnimationButton.java
public class AnimationButton extends AnchorPane{
private Duration fadeDuration = Duration.millis(1000);
private FadeTransition fadeTransition;
#FXML
private JFXButton animationButton;
public AnimationButton() {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("AnimationButton.fxml"));
fxmlLoader.setRoot(new AnchorPane());
fxmlLoader.setController(this);
try {
fxmlLoader.load();
} catch (IOException exception) {
throw new RuntimeException(exception);
}
animationButton.getStyleClass().add("animation-button");
fadeDuration = new Duration(3000);
fadeTransition = new FadeTransition(fadeDuration, animationButton);
fadeTransition.setAutoReverse(true);
fadeTransition.setFromValue(0);
fadeTransition.setToValue(1);
}
#FXML
public void mouseEntered(MouseEvent event) {
fadeTransition.setCycleCount(1); // this way autoreverse wouldn't kick
fadeTransition.playFromStart();
}
#FXML
public void mouseExited(MouseEvent event) {
fadeTransition.setCycleCount(2); // starting from autoreverse
fadeTransition.playFrom(fadeDuration);
}
...
}
Here my fxml file
<?xml version="1.0" encoding="UTF-8"?>
<?import com.jfoenix.controls.JFXButton?>
<?import javafx.scene.layout.*?>
<fx:root type="AnchorPane" xmlns="http://javafx.com/javafx/8.0.112"
xmlns:fx="http://javafx.com/fxml/1">
<children>
<JFXButton text="Enjoy it!" id="animationButton" onMouseEntered="#mouseEntered" onMouseExited="#mouseExited"/>
</children>
</fx:root>
It isn't completely clear what about your current code isn't working, but I'm assuming the following:
You want your button to fade in when the mouse enters and fade out when the mouse exits.
The fade functionality isn't working quite the way you want.
Trying something similar to your setup I noticed the node won't fade out if the mouse exits before the animation has finished.
Problem
In what appears to be an attempt to reverse the animation you are modifying the cycleCount property. That property does not affect the direction of play but rather how many cycles the animation plays before stopping:
Defines the number of cycles in this animation. The cycleCount may be INDEFINITE for animations that repeat indefinitely, but must otherwise be > 0.
It is not possible to change the cycleCount of a running Animation. If the value of cycleCount is changed for a running Animation, the animation has to be stopped and started again to pick up the new value.
You combine setting cycleCount with seting autoReverse to true in the hopes that will reverse the animation when you set cycleCount to 2. The autoReverse property:
Defines whether this Animation reverses direction on alternating cycles. If true, the Animation will proceed forward on the first cycle, then reverses on the second cycle, and so on. Otherwise, animation will loop such that each cycle proceeds forward from the start. It is not possible to change the autoReverse flag of a running Animation. If the value of autoReverse is changed for a running Animation, the animation has to be stopped and started again to pick up the new value.
This setup may be working somewhat, especially with the use of playFromStart() and playFrom(fadeDuration), but is not the correct way to do what you want.
Solution
What you want is to modify the rate property based on whether the mouse has entered or exited. The rate property:
Defines the direction/speed at which the Animation is expected to be played.
The absolute value of rate indicates the speed at which the Animation is to be played, while the sign of rate indicates the direction. A positive value of rate indicates forward play, a negative value indicates backward play and 0.0 to stop a running Animation.
Rate 1.0 is normal play, 2.0 is2time normal,-1.0` is backwards, etc.
Inverting the rate of a running Animation will cause the Animation to reverse direction in place and play back over the portion of the Animation that has already elapsed.
Here's a small example. It uses Button instead of JFXButton because I didn't feel like pulling in the dependency. Also, it uses the hover property but is functionally equivalent to using mouse-entered/mouse-exited handlers.
import javafx.animation.FadeTransition;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
Button button = new Button("Click me!");
button.setOnAction(event -> {
event.consume();
System.out.println("Button clicked!");
});
installAnimation(button);
primaryStage.setScene(new Scene(new StackPane(button), 300.0, 150.0));
primaryStage.setTitle("Animation Example");
primaryStage.show();
}
private void installAnimation(Button button) {
FadeTransition transition = new FadeTransition(Duration.millis(250.0), button);
transition.setFromValue(0.2);
transition.setToValue(1.0);
button.hoverProperty().addListener((obs, wasHover, isHover) -> {
transition.setRate(isHover ? 1.0 : -1.0);
transition.play();
});
button.setOpacity(transition.getFromValue());
}
}
Notice the following:
The rate is set to 1.0 when the mouse is hovering (entered) and -1.0 when the mouse is not hovering (exited).
The autoReverse flag remains false.
The cycleCount is kept at 1.
I call play(), not playFromStart() or playFrom(Duration). This is important because play:
Plays Animation from current position in the direction indicated by rate. If the Animation is running, it has no effect.
When rate > 0 (forward play), if an Animation is already positioned at the end, the first cycle will not be played, it is considered to have already finished. This also applies to a backward (rate < 0) cycle if an Animation is positioned at the beginning. However, if the Animation has cycleCount > 1, following cycle(s) will be played as usual.
When the Animation reaches the end, the Animation is stopped and the play head remains at the end.

How to change slider range background color using css? [duplicate]

This question already has answers here:
How to style HTML5 range input to have different color before and after slider?
(13 answers)
Closed 2 years ago.
I currently have a slider that I am styling with CSS. What I want is the range to change color when the thumb is slid. Similar to this picture:
How can I do this using only CSS? Below is what I have tried so far.
.range-slider .range-bar {
-fx-background-color: red;
}
You can do this by using a linear-gradient for the track background, and binding the point where the gradient changes to the slider's value. The basic idea would be that when, e.g., the slider's value is at 50%, the background should be defined by
.slider .track {
-fx-background-color: linear-gradient(to right, red 0%, red 50%, -fx-base 50%, -fx-base 100%);
}
but the 50% should change according to the slider's value.
So define the following in a CSS file (I introduced some extra looked-up colors to make it easier to modify the style):
.slider {
/* default track color: */
-slider-filled-track-color: red ;
-slider-track-color: -slider-filled-track-color ;
}
/* Make thumb same color as filled part of track */
.slider .thumb {
-fx-background-color: -slider-filled-track-color ;
}
.slider .track {
-fx-background-color: -slider-track-color ;
}
and then you can do
slider.styleProperty().bind(Bindings.createStringBinding(() -> {
double percentage = (slider.getValue() - slider.getMin()) / (slider.getMax() - slider.getMin()) * 100.0 ;
return String.format("-slider-track-color: linear-gradient(to right, -slider-filled-track-color 0%%, "
+ "-slider-filled-track-color %f%%, -fx-base %f%%, -fx-base 100%%);",
percentage, percentage);
}, slider.valueProperty(), slider.minProperty(), slider.maxProperty()));
to bind the place where the color changes to the value of the slider.
Here's a SSCCE:
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Slider;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class SliderStyleTest extends Application {
private static final String SLIDER_STYLE_FORMAT =
"-slider-track-color: linear-gradient(to right, -slider-filled-track-color 0%%, "
+ "-slider-filled-track-color %1$f%%, -fx-base %1$f%%, -fx-base 100%%);";
#Override
public void start(Stage primaryStage) {
Slider slider = new Slider();
slider.styleProperty().bind(Bindings.createStringBinding(() -> {
double percentage = (slider.getValue() - slider.getMin()) / (slider.getMax() - slider.getMin()) * 100.0 ;
return String.format(SLIDER_STYLE_FORMAT, percentage);
}, slider.valueProperty(), slider.minProperty(), slider.maxProperty()));
StackPane root = new StackPane(slider);
root.setPadding(new Insets(10));
Scene scene = new Scene(root);
scene.getStylesheets().add("style.css");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
where style.css is just the CSS file above. This gives:

JavaFX 2 TextArea that changes height depending on its contents

In JavaFX 2.2, is there any way to make TextArea (with setWrapText(true) and constant maxWidth) change its height depending on contents?
The desired behaviour: while user is typing something inside the TextArea it resizes when another line is needed and decreases when the line is needed no more.
Or is there a better JavaFX control that could be used in this situation?
You can bind the prefHeight of the text area to the height of the text it contains. This is a bit of a hack, because you need a lookup to get the text contained in the text area, but it seems to work. You need to ensure that you lookup the text node after CSS has been applied. (Typically this means after it has appeared on the screen...)
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class ResizingTextArea extends Application {
#Override
public void start(Stage primaryStage) {
TextArea textArea = new TextArea();
textArea.setWrapText(true);
textArea.sceneProperty().addListener(new ChangeListener<Scene>() {
#Override
public void changed(ObservableValue<? extends Scene> obs, Scene oldScene, Scene newScene) {
if (newScene != null) {
textArea.applyCSS();
Node text = textArea.lookup(".text");
textArea.prefHeightProperty().bind(Bindings.createDoubleBinding(new Callable<Double>() {
#Override
public Double call() {
return 2+text.getBoundsInLocal().getHeight();
}
}), text.boundsInLocalProperty()));
}
}
});
VBox root = new VBox(textArea);
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Two things to add to James_D's answer (because I lack the rep to comment):
1) For big fonts like size 36+, the text area size was wrong at first but corrected itself when I clicked inside the text area. You can call textArea.layout() after applying CSS, but the text area still does not resize immediately after the window is maximized. To get around this, call textArea.requestLayout() asynchronously in a Change Listener after any change to the Text object's local bounds. See below.
2) The text area was still a few pixels short and the scroll bar still visible. If you replace the 2 with textArea.getFont().getSize() in the binding, the height fits perfectly to the text, no matter whether the font size is tiny or huge.
class CustomTextArea extends TextArea {
CustomTextArea() {
setWrapText(true);
setFont(Font.font("Arial Black", 72));
sceneProperty().addListener((observableNewScene, oldScene, newScene) -> {
if (newScene != null) {
applyCss();
Node text = lookup(".text");
// 2)
prefHeightProperty().bind(Bindings.createDoubleBinding(() -> {
return getFont().getSize() + text.getBoundsInLocal().getHeight();
}, text.boundsInLocalProperty()));
// 1)
text.boundsInLocalProperty().addListener((observableBoundsAfter, boundsBefore, boundsAfter) -> {
Platform.runLater(() -> requestLayout());
});
}
});
}
}
(The above compiles for Java 8. For Java 7, replace the listener lambdas with Change Listeners according to the JavaFX API, and replace the empty ()-> lambdas with Runnable.)

ScrollPane is not refreshing its contents

RESUME
Good day StackOverflow community.
I've been trying for some time to develop a program that enables users to put objects in an area, allowing this area to be moved by the mouse. For this type of program, I decided to use a ScrollPane, because the user can add various contents in the area which I call the canvas. For some reason, something strange is happening in my program.
EXPLANATION OF PROGRAM
What I basically did was create a group of objects, and define this group as the ScrollPane content. Within the group, there is a Rectangle object that was added to serve as canvas boundaries. This object has larger dimensions (such as 1500 x 1000, for example), and is used in calculations that prevent nodes from moving beyond its limits. This is just the logical behind the existing large rectangle in my program, but in reality, there is no Node object with the mouse movement. What exists is the random distribution of Shape objects by the rectangle area.
For ScrollPane has its scrollbars moved, I use the setHvalue setVvalue methods. Unfortunately for my purposes, this method does not change the position of the ScrollPane's viewport with pixel values​​, but values ​​that are in a range between 0f and 1f. So I can move the viewport with the mouse, I used a equation known as Rule of 3 (here in my Country, as I know), which we equate values ​​and cross multiply.
For example, say I want to move the viewport of the ScrollPane with the mouse horizontally, and that my canvas area has a width of 2000 pixels. Finding how far (in pixels) the mouse was dragged from one point to another, I need to know how this value represents in a range 0f to 1f. Suppose I have dragged the mouse in 3 pixels, I could find the representation of the 0f to 1f with the following comparison:
2000 px ---- 1f
3 px ---- xf
Multiplying crossed, I'll get the following result:
xf = 3 / 2000
xf = 0.0015
Note: I believe you all know that. I'm not teaching math to anyone,
just want to explain the logic of my problem.
SOURCE CODE
Here is my program class:
import testes.util.TestesUtil;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.geometry.Bounds;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.StrokeType;
import javafx.stage.Stage;
public class ScrollTest4 extends Application
{
// #########################################################################################################
// MAIN
// #########################################################################################################
public static void main(String[] args)
{
Application.launch(args);
}
// #########################################################################################################
// INSTÂNCIAS
// #########################################################################################################
// OUTSIDE
private BorderPane root;
private Button but_moreH;
private Button but_lessH;
private Button but_moreV;
private Button but_lessV;
// LOG
private VBox vbox_south;
private Label lab_hValue;
private Label lab_vValue;
private Label lab_viewport;
// INSIDE
private Rectangle rec_canvas;
private ScrollPane scroll;
private Group grp_objects;
// MOUSE
private double mouse_x = 0;
private double mouse_y = 0;
// MISC
private AnimationTimer timer;
// EDITED - 08/02/2014
private boolean moreH = false;
private boolean moreV = false; // Purposely unused.
private boolean lessH = false;
private boolean lessV = false; // Purposely unused.
// #########################################################################################################
// INÍCIO FX
// #########################################################################################################
#Override public void start(Stage estagio) throws Exception
{
this.iniFX();
this.confFX();
this.adFX();
this.evFX();
Scene cenario = new Scene(this.root , 640 , 480);
estagio.setScene(cenario);
estagio.setTitle("Programa JavaFX");
estagio.show();
}
protected void iniFX()
{
// OUTSIDE
this.root = new BorderPane();
this.but_moreH = new Button();
this.but_lessH = new Button();
this.but_moreV = new Button();
this.but_lessV = new Button();
// LOG
this.vbox_south = new VBox();
this.lab_hValue = new Label();
this.lab_vValue = new Label();
this.lab_viewport = new Label();
// INSIDE
this.scroll = new ScrollPane();
this.grp_objects = new Group();
this.rec_canvas = new Rectangle();
// MISC
this.timer = new AnimationTimer()
{
#Override public void handle(long now)
{
// EDITED - 08/02/2014
if(but_moreH.isArmed() || moreH)
{
// scroll.hvalueProperty().set(scroll.hvalueProperty().get() + 0.003f);
scroll.setHvalue(scroll.getHvalue() + 0.003f);
}
// EDITED - 08/02/2014
if(but_lessH.isArmed() || lessH)
{
// scroll.hvalueProperty().set(scroll.hvalueProperty().get() - 0.003f);
scroll.setHvalue(scroll.getHvalue() - 0.003f);
}
if(but_moreV.isArmed())
{
scroll.setVvalue(scroll.getVvalue() + 0.003f);
}
if(but_lessV.isArmed())
{
scroll.setVvalue(scroll.getVvalue() - 0.003f);
}
}
};
this.timer.start();
}
protected void confFX()
{
// OUTSIDE
this.but_moreH.setText("More H");
this.but_moreH.setMaxHeight(Double.MAX_VALUE);
this.but_lessH.setText("Less H");
this.but_lessH.setMaxHeight(Double.MAX_VALUE);
this.but_moreV.setText("More V");
this.but_moreV.setMaxWidth(Double.MAX_VALUE);
this.but_lessV.setText("Less V");
this.but_lessV.setMaxWidth(Double.MAX_VALUE);
// LOG
this.updateHvalue();
this.updateVvalue();
this.updateViewport();
// INSIDE
this.rec_canvas.setWidth(1200);
this.rec_canvas.setHeight(1000);
this.rec_canvas.setFill(Color.INDIANRED);
this.rec_canvas.setStroke(Color.RED);
this.rec_canvas.setStrokeType(StrokeType.INSIDE);
this.rec_canvas.setStrokeWidth(1);
}
protected void adFX()
{
// LOG
this.vbox_south.getChildren().add(this.but_moreV);
this.vbox_south.getChildren().addAll(this.lab_hValue , this.lab_vValue , this.lab_viewport);
// OUTSIDE
this.root.setCenter(this.scroll);
this.root.setTop(this.but_lessV);
this.root.setBottom(this.vbox_south);
this.root.setRight(this.but_moreH);
this.root.setLeft(this.but_lessH);
// INSIDE
this.grp_objects.getChildren().add(this.rec_canvas);
this.scroll.setContent(this.grp_objects);
// MISC
StrokeType[] strokes = {StrokeType.CENTERED , StrokeType.INSIDE , StrokeType.OUTSIDE};
for(int cont = 0 ; cont < 20 ; cont++)
{
Rectangle node = new Rectangle(Math.random() * 100 + 50 , Math.random() * 100 + 50);
node.setFill(TestesUtil.getCorAleatoria(false));
node.setStroke(TestesUtil.getCorAleatoria(false));
node.setStrokeType(strokes[(int) (Math.random() * 2)]);
node.setStrokeWidth(Math.random() * 9 + 1);
node.setRotate(Math.random() * 360);
node.setMouseTransparent(true);
// EDITED - 08/02/2014
TestsUtil.putRandomlyIn(
node ,
rec_canvas.getBoundsInParent().getMinY() ,
rec_canvas.getBoundsInParent().getMinY() + rec_canvas.getBoundsInParent().getHeight() ,
rec_canvas.getBoundsInParent().getMinX() + rec_canvas.getBoundsInParent().getWidth() ,
rec_canvas.getBoundsInParent().getMinX() );
this.grp_objects.getChildren().add(node);
}
}
protected void evFX()
{
// ##########################
// SCROLL PROPERTIES
// ##########################
this.scroll.hvalueProperty().addListener(new ChangeListener<Number>()
{
#Override public void changed(ObservableValue<? extends Number> observable,Number oldValue, Number newValue)
{
updateHvalue();
updateViewport();
}
});
this.scroll.vvalueProperty().addListener(new ChangeListener<Number>()
{
#Override public void changed(ObservableValue<? extends Number> observable,Number oldValue, Number newValue)
{
updateVvalue();
updateViewport();
}
});
this.scroll.setOnKeyPressed(new EventHandler<KeyEvent>()
{
#Override public void handle(KeyEvent e)
{
if(e.getCode() == KeyCode.RIGHT)
{
moreH = true;
}
else if(e.getCode() == KeyCode.LEFT)
{
lessH = true;
}
}
});
this.scroll.setOnKeyReleased(new EventHandler<KeyEvent>()
{
#Override public void handle(KeyEvent e)
{
if(e.getCode() == KeyCode.RIGHT)
{
moreH = false;
}
else if(e.getCode() == KeyCode.LEFT)
{
lessH = false;
}
}
});
// ##########################
// CANVAS
// ##########################
this.rec_canvas.setOnMousePressed(new EventHandler<MouseEvent>()
{
#Override public void handle(MouseEvent e)
{
// The XY distance from the upper left corner of the canvas.
mouse_x = e.getX();
mouse_y = e.getY();
}
});
this.rec_canvas.setOnMouseDragged(new EventHandler<MouseEvent>()
{
#Override public void handle(MouseEvent e)
{
// ##########################
// PIXELS
// ##########################
// The distance between mouse movements (drag events).
double xPixelsMoved = e.getX() - mouse_x;
// double yPixelsMoved = e.getY() - mouse_y;
// ##########################
// TO 1F
// ##########################
double h_of_1f = xPixelsMoved / rec_canvas.getBoundsInParent().getWidth();
double h_of_1f_inverted = h_of_1f * -1;
double currentH = scroll.getHvalue();
scroll.setHvalue(currentH + h_of_1f);
// scroll.hvalueProperty().set(scroll.getHvalue() + h_de_x);
// scroll.vvalueProperty().set(scroll.getVvalue() + v_de_y);
// ##########################
// DEBUG
// ##########################
System.out.printf("xPixelsMoved: %f , h_of_1f: %f , h_of_1f_inverted: %f %n",
xPixelsMoved , h_of_1f , h_of_1f_inverted);
// ##########################
// UPDATE FROM
// EVENT TO EVENT
// ##########################
// Writes last mouse position to update on new motion event.
mouse_x = e.getX();
mouse_y = e.getY();
}
});
}
// #########################################################################################################
// MISC.
// #########################################################################################################
protected void updateViewport()
{
Bounds vport = this.scroll.getViewportBounds();
this.lab_viewport.setText(String.format("Viewport - [X: %f , Y: %f , W: %f , H: %f]",
vport.getMinX() , vport.getMinY() , vport.getWidth() , vport.getHeight() ));
}
protected void updateHvalue()
{
this.lab_hValue.setText("H value: " + this.scroll.getHvalue() );
}
protected void updateVvalue()
{
this.lab_vValue.setText("V value: " + this.scroll.getVvalue() );
}
}
THE PROBLEM
Clicking the mouse button on the canvas area and drag it, you can see that the program moves the ScrollPane viewport horizontally. The program seems to work perfectly (or not). However, something goes wrong at the time when the mouse is dragged sometimes abruptly (...or not!). At certain times the ScrollPane Viewport is not visually updated. This is a strange behavior, because even if viewport is not visually updated, the scrollbars are still updated.
I put other ways to move the ScrollPane viewport horizontally using the same method, and for some reason, only the approach using the mouse makes it happen. I thought this could be solved by making a request for layout using requestLayout, also causing a request to a pulse, but it does not work.
THE TEST OUTPUT
The odd thing is that everything returns to normal when the window of my application is resized. Here's a video that shows what happens to my program:
VIDEO & MIRROR 1
I no longer know what else to do. Can anyone help me with this please?
EDIT (08/02/2014 10:08 AM GMT - 3:00)
The original source code of my application is found written in Portuguese, so you may be seeing something unknown. Basically TestesUtil is a utility class with static methods that define shortcuts to other client classes. I changed the call from my source code shown here previously and am now putting some methods of my class TestesUtil, translated into English as TestsUtil:
public static void putRandomlyIn(Node node , double northPoint , double southPoint , double eastPoint , double westPoint)
{
node.setLayoutX(Math.random() * pontoLeste);
node.setLayoutY(Math.random() * pontoSul);
fixEasternBoundary(node , eastPoint);
fixNorthernBoundary(node , northPoint);
fixWesternBoundary(node , westPoint);
fixSouthernBoundary(node , southPoint);
}
There is no mystery here. This method simply calculates a value from an interval, and defines the LayoutXY properties for the Node argument. Methods "fix ..." just check the boundsInParent bounds of the node compared to the point in the argument, and then adjust the layoutXYproperties from the Node object. Even if I remove the random distribution of objects, the problem still happens. So I'm sure this problem is not being caused by this.
The source code of the original post was changed with the addition of the ability to move the scroll bars with the arrow keys. Even if it is already an existing function of ScrollPane, adding that could reproduce the error seen with the mouse (now with arrows). Some things were also translated into English for better understanding by the community.
Please, I ask for help. I'm getting dizzy not knowing what to do. This type of situation could be happening because of some bug in JavaFX? Ahhrr... Please somebody help me in this. :'(
Thank you for your attention anyway.
EDIT (09/02/2014 10:50 AM GMT - 3:00)
Forgot to mention... My program was initially written and tested using JDK 8 b123. Currently I installed the JDK 8 b128 version and am still getting the same problem. My operating system is Windows 7 64x.
I am almost certain that this is a bug. Are you guys getting the same result as me? Or am I the only one to find this kind of problem? If this is a bug, which procedure should be taken?
Thank you for your attention.
EDIT (10/02/2014 09:45 AM GMT - 3:00)
A bounty was started.
UPDATE
This bug has now been fixed for JavaFX 8u20.
Bug description
This is a bug, that can be easily verified by executing the following code with JavaFx JRE 8:
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
final ScrollPane sp = new ScrollPane();
final Image[] images = new Image[5];
final ImageView[] pics = new ImageView[5];
final VBox vb = new VBox();
final Label fileName = new Label();
final String [] imageNames = new String [] {"fw1.jpg", "fw2.jpg",
"fw3.jpg", "fw4.jpg", "fw5.jpg"};
#Override
public void start(Stage stage) {
VBox box = new VBox();
Scene scene = new Scene(box, 180, 180);
stage.setScene(scene);
stage.setTitle("Scroll Pane");
box.getChildren().addAll(sp, fileName);
VBox.setVgrow(sp, Priority.ALWAYS);
fileName.setLayoutX(30);
fileName.setLayoutY(160);
for (int i = 0; i < 5; i++) {
images[i] = new Image(getClass().getResourceAsStream(imageNames[i]));
pics[i] = new ImageView(images[i]);
pics[i].setFitWidth(100);
pics[i].setPreserveRatio(true);
vb.getChildren().add(pics[i]);
}
sp.setVmax(440);
sp.setPrefSize(115, 150);
sp.setContent(vb);
sp.vvalueProperty().addListener(new ChangeListener<Number>() {
public void changed(ObservableValue<? extends Number> ov,
Number old_val, Number new_val) {
fileName.setText(imageNames[(new_val.intValue() - 1)/100]);
}
});
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
This code comes directly from the JavaFX ScrollPane Tutorial.
If one randomly moves the vertical scroll bar with the mouse very rapidly, then at some time the screen will freeze and no longer get updated. Although one is still able to move the scroll bar around, the displayed images will stay fixed. Only if one resizes the frame, the display of the images will be updated and the ScrollPane reverts to its previous state. Note, that this bug will only happen in JRE 8, it is not reproducible in JRE 7.
The only workaround for the problem, that I could find, is adding
sp.snapshot(new SnapshotParameters(), new WritableImage(1, 1));
to the listener:
sp.vvalueProperty().addListener(new ChangeListener<Number>() {
public void changed(ObservableValue<? extends Number> ov,
Number old_val, Number new_val) {
fileName.setText(imageNames[(new_val.intValue() - 1)/100]);
sp.snapshot(new SnapshotParameters(), new WritableImage(1, 1));
}
});
Calling snapshot on the ScrollPane seems to force the update every time the vvalueProperty changes. This seems to be a known workaround for several update problems with JavaFX - see here.

Categories

Resources