Setup:
The following code, renders a 3D scene with a visible co-ordinate axis positioned at the origin, from a camera displaced by -156 units in the Z direction. Also, the camera's Z position is a function of mouse scroll, such that scrolling up/down will move the camera further/closer from the origin.
Problem:
upon initial startup of the program, the red and green axis are rendered at/near the origin, when in the physical world, it would be impossible to see them there from the current camera view. (blue axis blocking them). Also, when you scroll backwards and forwards, you can see glitches/flashes where the red/green axis are visible behind the blue axis, which should not occur.
Screenshot of result (with my manual adding of issue description):
initial_screenshot
Question:
1) Is this a problem with my setup? or JavaFX?
2) if this is a problem with my setup, then can someone please explain what I can do to remedy this issue?
Code:
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package testproblemjavafx01;
/**
*
* #author ad
*/
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.event.EventHandler;
import javafx.scene.Camera;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.input.ScrollEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.stage.Stage;
public class TestProblemJavaFX01 extends Application {
#Override
public void start(Stage primaryStage) {
Group root = new Group();
buildAxes(root);
Scene scene = new Scene(root, 600, 400, true, SceneAntialiasing.BALANCED);
PerspectiveCamera camera = new PerspectiveCamera(true);
scene.setFill(Color.WHITE);
camera.setNearClip(0);
camera.setFarClip(1000.0);
camera.setTranslateX(0);
camera.setTranslateY(0);
camera.setTranslateZ(-156);
scene.setCamera(camera);
setMouseEvents(scene);
primaryStage.setResizable(false);
primaryStage.setTitle("TestProblemJavaFX01");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
private void buildAxes(Group root) {
final PhongMaterial redMaterial = new PhongMaterial();
redMaterial.setDiffuseColor(Color.DARKRED);
redMaterial.setSpecularColor(Color.RED);
final PhongMaterial greenMaterial = new PhongMaterial();
greenMaterial.setDiffuseColor(Color.DARKGREEN);
greenMaterial.setSpecularColor(Color.GREEN);
final PhongMaterial blueMaterial = new PhongMaterial();
blueMaterial.setDiffuseColor(Color.DARKBLUE);
blueMaterial.setSpecularColor(Color.BLUE);
final Box xAxis = new Box(240.0, 1, 1);
final Box yAxis = new Box(1, 240.0, 1);
final Box zAxis = new Box(1, 1, 240.0);
xAxis.setMaterial(redMaterial);
yAxis.setMaterial(greenMaterial);
zAxis.setMaterial(blueMaterial);
root.getChildren().addAll(xAxis, yAxis, zAxis);
}
private void setMouseEvents(final Scene scene) {
scene.setOnScroll(
new EventHandler<ScrollEvent>() {
#Override
public void handle(ScrollEvent event) {
double deltaY = event.getDeltaY();
Camera camera = scene.getCamera();
camera.setTranslateZ(camera.getTranslateZ() + deltaY);
event.consume();
}
});
}
}
I think the problem is within the line camera.setNearClip(0);
From the documentation of setNearClip:
Specifies the distance from the eye of the near clipping plane of this
Camera in the eye coordinate space. Objects closer to the eye than
nearClip are not drawn. nearClip is specified as a value greater than
zero. A value less than or equal to zero is treated as a very small
positive number.
Try to set the value to its default value of 0.1. Or just remove the line.
Related
I am currently working on this assignment and I can not seem to get this program to run even though I don't have any errors really popping up ? I am trying to add a time stamp to the pane as well but every time I add the "ts" name for the time stamp to the Pane or Hbox's get children code it goes red.. I am not sure what exactly I'm doing wrong if anyone can point me in the right direction id greatly appreciate it...
package PCK1;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import java.sql.Timestamp;
import java.util.Date;
import java.text.SimpleDateFormat;
public class MainClass
{
public static void start(Stage stage)
{
// Time Stamp
Date date = new Date();
Timestamp ts=new Timestamp(date.getTime());
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(formatter.format(ts));
//Create a Circle
Circle c1 = new Circle(75,100,20);
//Create a Pane
Pane p = new Pane();
p.setMinSize(100, 150);
p.setBackground(new Background(new BackgroundFill( Color.rgb(190, 220, 190), null, null)
));
p.getChildren().addAll(c1);
//Create a Button
Button btnUp = new Button("Up");
btnUp.setOnAction((ActionEvent e) -> {double y = c1.getCenterY();
y -= 20.0;
c1.setCenterY(y);
});
Button btnDown = new Button("Down");
btnDown.setOnAction((ActionEvent e) -> {double y = c1.getCenterY();
y += 20.0;
c1.setCenterY(y);
});
//Create a HBox
HBox hb = new HBox();
hb.getChildren().addAll(btnUp, btnDown, p, ts);
hb.setBackground(new Background(new BackgroundFill(Color.rgb(150,200,150),null,null)));
hb.setMinSize(100, 50);
hb.setPadding(new Insets(10,10,10,10));
Scene scene = new Scene(hb);
stage.setScene(scene);
stage.setTitle("JavaFx");
stage.setWidth(250);
stage.setHeight(250);
stage.show();
}
}
Answer for displaying a timestamp
Specifically, for the timestamp question, see the following example code:
private Label createTimestampLabel() {
LocalDateTime now = LocalDateTime.now();
String formattedTimestamp = now.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
return new Label(formattedTimestamp);
}
It uses the java.time APIs explained in the Oracle Date Time tutorial to get the current time from LocalDateTime and format it as a String using a standard format.
It sets the formatted timestamp string as the text of a Label node.
Now that the returned element is a Node, it can be placed in the scene graph without generating the compile error you saw in your original example.
Using the java.time APIs is preferred over the java.sql.Timestamp and java.util.Date code in your question. You are not working with SQL, so you should not be using java.sql.Timestamp. The java.time classes also have many improvements over obsolete date and time functions used in other Java packages like java.util.
Answer in context with a re-write of your example code
There were a lot of things about the provided example application that were either wrong or annoyed me.
So I re-wrote it to match a bit more closely how I would normally write such an application.
There are maybe a hundred different small decisions made in the choices for how to implement the re-write and explaining them all here would be too verbose.
Hopefully, you can compare the re-write to your original code, note some of the differences, and learn some things from it.
GraphicControlApp.java
package org.example.javafx.demo.graphiccontrol;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class GraphicControlApp extends Application {
public void start(Stage stage) {
GraphicController graphicController = new GraphicController();
Scene scene = new Scene(graphicController.getUI());
stage.setScene(scene);
stage.setTitle("JavaFX Interactive Graphic Control Demonstration");
stage.show();
}
}
GraphicController.java
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* UI creator and controller for application logic.
*
* Normally, most UI elements would be defined externally in FXML,
* however, for a simple application, we define the UI via private functions in this class.
*/
public class GraphicController {
// amount to move the circle across the surface on interaction.
private static final double MOVEMENT_DELTA = 20.0;
// default spacing between UI elements.
private static final double SPACING = 10;
// normally the styles would be configured in an external css stylesheet,
// but we place the background definitions here for a simple application.
private static final Color SURFACE_COLOR = Color.rgb(190, 220, 190);
private static final Background surfaceBackground = createBackground(SURFACE_COLOR);
private static final Color APP_BACKGROUND_COLOR = Color.rgb(150, 200, 150);
private static final Background appBackground = createBackground(APP_BACKGROUND_COLOR);
private Button up;
private Button down;
/**
* #return the complete layout for the application with event handlers attached for logic control.
*/
public Pane getUI() {
Circle circle = new Circle(75, 100, 20);
Pane surface = createSurface(circle);
HBox controls = createControls(circle);
Label timestampLabel = createTimestampLabel();
Pane layout = createLayout(surface, controls, timestampLabel);
attachKeyboardHandlers(layout);
return layout;
}
/**
* Create a label formatted with the current time in ISO standard format (e.g. '2011-12-03T10:15:30')
*
* #return label with the current timestamp.
*/
private Label createTimestampLabel() {
LocalDateTime now = LocalDateTime.now();
String formattedTimestamp = now.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
return new Label(formattedTimestamp);
}
/**
* Create a surface on which a circle can move.
*
* #param circle the circle which can move on the surface.
* #return the created surface.
*/
private Pane createSurface(Circle circle) {
Pane surface = new Pane();
surface.setMinSize(100, 150);
surface.setBackground(surfaceBackground);
surface.getChildren().addAll(circle);
// we must define a clip on the surface to ensure that elements
// in the surface do not render outside the surface.
Rectangle clip = new Rectangle();
clip.widthProperty().bind(surface.widthProperty());
clip.heightProperty().bind(surface.heightProperty());
surface.setClip(clip);
return surface;
}
private VBox createLayout(Pane surface, HBox controls, Label timestampLabel) {
VBox layout = new VBox(SPACING, controls, surface, timestampLabel);
layout.setBackground(appBackground);
layout.setPadding(new Insets(SPACING));
VBox.setVgrow(surface, Priority.ALWAYS);
return layout;
}
/**
* Create controls which can control the movement of a circle.
*
* #param circle the circle which can be controlled
* #return the created controls with handlers attached for circle movement control.
*/
private HBox createControls(Circle circle) {
up = new Button("Up");
up.setOnAction(e -> moveVertically(circle, -MOVEMENT_DELTA));
down = new Button("Down");
down.setOnAction(e -> moveVertically(circle, MOVEMENT_DELTA));
return new HBox(SPACING, up, down);
}
private void moveVertically(Circle circle, double delta) {
double y = circle.getCenterY();
// we only restrict movement in the up direction,
// but allow unlimited movement in the down direction
// (even if that movement would mean that the circle would extend totally
// outside the current visible boundary of the surface).
if ((y + delta) < 0) {
return;
}
circle.setCenterY(y + delta);
}
/**
* Adds standard keyboard handling logic to the UI.
*
* Handlers are attached to the relevant scene whenever
* the scene containing the UI changes.
*
* #param layout the UI which will respond to keyboard input.
*/
private void attachKeyboardHandlers(Pane layout) {
EventHandler<KeyEvent> keyEventHandler = event -> {
switch (event.getCode()) {
case UP -> { up.requestFocus(); up.fire(); }
case DOWN -> { down.requestFocus(); down.fire(); }
}
};
layout.sceneProperty().addListener((observable, oldScene, newScene) -> {
if (oldScene != null) {
oldScene.removeEventFilter(
KeyEvent.KEY_PRESSED,
keyEventHandler
);
}
if (newScene != null) {
newScene.addEventFilter(
KeyEvent.KEY_PRESSED,
keyEventHandler
);
}
});
}
private static Background createBackground(Color surfaceColor) {
return new Background(new BackgroundFill(surfaceColor, null, null));
}
}
You should show the timestamp as text with the TextField (Doc) :
TextField myText = new TextField();
myText.setText("Time: " + formatter.format(ts));
// set what you want to the TextField object: padding, size, color etc...
p.getChildren().addAll(myText);
For the life of me, I can't seem to get help on this. I have a JavaFX screen and I am trying to get to show fullscreen on my 2nd monitor. I tried the following based on other recommendations but to no avail. I know the coordinates are right but it KEEPS going full screen on my MAIN monitor. Please help.
if (mainSet.getBoolean("fullScr", false)) {
int count = mainSet.getInt("MonSel", 0);
if (count > 0) {
int i = 0;
for (Screen screen: Screen.getScreens()) {
if (count == i) {
Rectangle2D bounds = screen.getBounds();
primaryStage.setX(bounds.getMinX());
System.out.println(bounds.getMinX());
System.out.println(bounds.getMinY());
primaryStage.setY(bounds.getMinY());
}
i++;
}
}
primaryStage.setFullScreen(true);
}
The first if checks a preference to see if fullscreen is set. the 2nd if sees if another monitor besides the first one was selected. It's 1, so that should be the 2nd monitor. The program loops through all screens and tries to move the program and THEN will go full screen. I know the coordinates are the same but no dice, it still goes full screen on the main screen. Please help.
I don't know if I truly understand your problem, but if you have two screens, why loop through the screens? Why not just use the info associated with the screen in position two/index one of the ObservableList? I am posting a sample app that demos how to display full screen on a second monitor.
import javafx.application.Application;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Screen;
import javafx.stage.Stage;
/**
*
* #author blj0011
*/
public class JavaFXApplication257 extends Application
{
#Override
public void start(Stage primaryStage)
{
ObservableList<Screen> screens = Screen.getScreens();//Get list of Screens
Button btn = new Button();
btn.setText("Full Screen - Screen 1");
btn.setOnAction((ActionEvent event) -> {
Rectangle2D bounds = screens.get(0).getVisualBounds();
primaryStage.setX(bounds.getMinX());
primaryStage.setY(bounds.getMinY());
primaryStage.setFullScreen(true);
//primaryStage.setWidth(bounds.getWidth());
//primaryStage.setHeight(bounds.getHeight());
});
Button btn2 = new Button();
btn2.setText("Full Screen - Screen 2");
btn2.setOnAction((ActionEvent event) -> {
if (screens.size() > 0) {
Rectangle2D bounds = screens.get(1).getVisualBounds();
primaryStage.setX(bounds.getMinX());
primaryStage.setY(bounds.getMinY());
primaryStage.setFullScreen(true);
//primaryStage.setWidth(bounds.getWidth());
//primaryStage.setHeight(bounds.getHeight());
}
});
StackPane root = new StackPane();
root.getChildren().add(new VBox(btn, btn2));
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
}
I was programming a game inspired by Conway's "Game of Life".
Although I have the overall game logic figured out (but not coded), I am still having trouble with getting the fill colors of my rectangle objects to change once the player's first turn is over. When I run my program it skips over the requirement for player one's color (Color.BLUE) and goes straight to player two's color (Color.RED).
Here is the code:
//William Fisher
//July.11.2017
package cellularautomatagame;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.scene.*;
import javafx.scene.paint.*;
import javafx.scene.canvas.*;
import javafx.scene.input.MouseEvent;
import static javafx.scene.paint.Color.*;
public class CellularAutomataGame extends Application {
#Override
public void start(Stage stage) {
Group root = new Group();
Scene s = new Scene(root, 300, 300, Color.BLACK);
Canvas canvas = new Canvas(1280,720);
GraphicsContext gc = canvas.getGraphicsContext2D();
root.getChildren().add(canvas);
stage.setScene(s);
stage.show();
gc.setFill(WHITE);
gc.fillRect(0, 0, 5, 720);
gc.fillRect(0, 0, 1280, 5);
gc.fillRect(0, 715, 1280, 5);
gc.fillRect(1275, 0, 5, 720);
Player player1 = new Player();
Player player2 = new Player();
player1.playerFirstMove(root,canvas,Color.BLUE);
player2.playerFirstMove(root,canvas,Color.RED);
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
//William Fisher
// July.11.2017
package cellularautomatagame;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import static javafx.scene.paint.Color.*;
import javafx.scene.shape.Rectangle;
public class Player {
int firstMove = 0;
public void playerFirstMove(Group root,Canvas canvas,Color color){
canvas.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>(){
#Override
public void handle (MouseEvent e){
while(firstMove < 1){
if(e.getClickCount() == 1){
Rectangle r = new Rectangle(e.getX(),e.getY(),5,5);
r.setFill(color);
root.getChildren().add(r);
firstMove++;
}
}
}
});
firstMove--;
}
}
/** (07/11/2017)Current Problem: The first player is unable to make their first move. Only the
* second player is able to make a first move.
*
* (07/16/2017)Current Problem: Same as previous problem, changed the code so that a rectangle
* object would spawn upon mouse click event. Problem possibly has to do with "setFill()" function.
*/
On line 52 of the main JavaFX method where it shows player1's first turn, it should call the playerFirstMove method and allow a blue rectangle to spawn once the mouse is clicked, as shown in the playerFirstMove method starting on line 18 of the Player class. However when the mouse is clicked, one red rectangle is spawned instead of a blue one. It is as though the program skipped over player1.playerfirstMove(...) on line 52 and went straight to the player2.playerfirstMove(...) on line 53. I've tried for hours to fix this small problem, reading the JavaFX API and searching the internet. The program is doing what I want it to do (spawn only one rectangle per mouse click) but it seems as though it is skipping the instructions on line 52 (player1.playerfirstMove(...)).
To me it seems as though there may be a bug involving the setFill() function?
I would deeply appreciate any help.
Thanks All!
If I understand correctly every mouse click should draw a rectangle of the current player and pass turn to the next player. If so, I reworked your code to have Player with color and draw rectangle logic only:
class Player {
private final Color color;
Player(final Color color) {
this.color = color;
}
void doSomething(final Group root, final double x, final double y) {
Rectangle r = new Rectangle(x, y, 5, 5);
r.setFill(color);
root.getChildren().add(r);
}
}
In main class I have organized cycled iteration (by using Google guava collection utils) and the iterator allows to work only with the current player:
Player player1 = new Player(Color.BLUE);
Player player2 = new Player(Color.RED);
Player player3 = new Player(Color.YELLOW);
final Iterator<Player> playerIterator = Iterators.cycle(player1, player2, player3);
canvas.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> {
if (e.getClickCount() == 1) {
playerIterator.next().doSomething(root, e.getX(), e.getY());
}
});
As result I may have even 3 players and each click triggers only the next one:
BTW, this solution allows to have as many players as needed.
I use certain events to place small rectangles on an HBox. Their placement is perfect when the window has not been resized, but when you for example go from small to fullscreen, their placement is wrong (of course, because they get a certain value for X at the time of placement - this is measured by getting the width of the HBox at that specific moment).
Question:
How can I make these positions dynamic, so when I resize the window, they stay in proportion?
Pictures:
Code:
#FXML HBox tagLine; // initializes the HBox
...
public void addTag(String sort) {
Rectangle rect = new Rectangle(20, tagLine.getHeight());
double pos = timeSlider.getValue() / 100 * tagLine.getWidth(); // retrieves position at the moment of being called
rect.setTranslateX(pos);
rect.setOnMouseEntered(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
showNotification("Gemarkeerde gebeurtenis: " + sort);
}
});
rect.setOnMouseExited(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
notificationHide.play();
}
});
tagLine.getChildren().add(rect);
}
Few things that you need to take into account while translating a shape with accountable size is that you need to :
Translate the shape from its center
If the translation is dependent on the width of a Node, listen to the changes made to the with of that particular node and make changes to the translate property accordingly
Both of the above points seem to be missing in your implementation. You are never listening to width property of HBox. Neither is your calculation for pos taking the center of Rectangle into account.
Here is an example which try to keep the Rectangle at the center no matter what the size of your HBox is.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Main extends Application {
private static final int SIDE = 40;
private static final double DEFAULT_WIDTH = 200;
private static final double DEFAULT_POSITION = 100;
#Override
public void start(Stage primaryStage) {
Rectangle rectangle = new Rectangle(SIDE, SIDE);
HBox root = new HBox(rectangle);
root.setPrefWidth(DEFAULT_WIDTH);
rectangle.translateXProperty().bind(root.widthProperty().multiply(DEFAULT_POSITION/DEFAULT_WIDTH).subtract(SIDE/2));
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
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.