JavaFX Spinner change is slow with click and hold of mouse button - javafx

The speed of Spinner update is slow when I click and hold the up/down arrow buttons. Is there a way to increase the change speed?
When I click, click, click with the mouse, the spinner values change as fast as I click. It also changes fast if I use the up/down arrows on the keyboard for each key press or if I hold down the up/down arrow keys. I want the values to change that fast when I click and hold on the arrow buttons.
Anyone know a way to do that?

The SpinnerBehavior of the SpinnerSkin triggers updates every 750 ms. Unfortunately there is no way to simply set/modify this behavour without using reflection to access private members. Therefore the only way to do this without reflection is using event filters to trigger the updates at a faster rate:
private static final PseudoClass PRESSED = PseudoClass.getPseudoClass("pressed");
#Override
public void start(Stage primaryStage) {
Spinner<Integer> spinner = new Spinner(Integer.MIN_VALUE, Integer.MAX_VALUE, 0);
class IncrementHandler implements EventHandler<MouseEvent> {
private Spinner spinner;
private boolean increment;
private long startTimestamp;
private static final long DELAY = 1000l * 1000L * 750L; // 0.75 sec
private Node button;
private final AnimationTimer timer = new AnimationTimer() {
#Override
public void handle(long now) {
if (now - startTimestamp >= DELAY) {
// trigger updates every frame once the initial delay is over
if (increment) {
spinner.increment();
} else {
spinner.decrement();
}
}
}
};
#Override
public void handle(MouseEvent event) {
if (event.getButton() == MouseButton.PRIMARY) {
Spinner source = (Spinner) event.getSource();
Node node = event.getPickResult().getIntersectedNode();
Boolean increment = null;
// find which kind of button was pressed and if one was pressed
while (increment == null && node != source) {
if (node.getStyleClass().contains("increment-arrow-button")) {
increment = Boolean.TRUE;
} else if (node.getStyleClass().contains("decrement-arrow-button")) {
increment = Boolean.FALSE;
} else {
node = node.getParent();
}
}
if (increment != null) {
event.consume();
source.requestFocus();
spinner = source;
this.increment = increment;
// timestamp to calculate the delay
startTimestamp = System.nanoTime();
button = node;
// update for css styling
node.pseudoClassStateChanged(PRESSED, true);
// first value update
timer.handle(startTimestamp + DELAY);
// trigger timer for more updates later
timer.start();
}
}
}
public void stop() {
timer.stop();
button.pseudoClassStateChanged(PRESSED, false);
button = null;
spinner = null;
}
}
IncrementHandler handler = new IncrementHandler();
spinner.addEventFilter(MouseEvent.MOUSE_PRESSED, handler);
spinner.addEventFilter(MouseEvent.MOUSE_RELEASED, evt -> {
if (evt.getButton() == MouseButton.PRIMARY) {
handler.stop();
}
});
Scene scene = new Scene(spinner);
primaryStage.setScene(scene);
primaryStage.show();
}

I modified the answer of fabian a little bit to decrease the speed of the spinner while holding mouse down:
private int currentFrame = 0;
private int previousFrame = 0;
#Override
public void handle(long now)
{
if (now - startTimestamp >= initialDelay)
{
// Single or holded mouse click
if (currentFrame == previousFrame || currentFrame % 10 == 0)
{
if (increment)
{
spinner.increment();
}
else
{
spinner.decrement();
}
}
}
++currentFrame;
}
And after stopping the timer we adjust previousFrame again:
public void stop()
{
previousFrame = currentFrame;
[...]
}

A small improvement to Fabian's answer. Making the following mod to the MOUSE_RELEASED addEventerFilter will stop a NullPointerException caused when clicking the textfield associated with the spinner. Cheers Fabian!
spinner.addEventFilter(MouseEvent.MOUSE_RELEASED, evt -> {
Node node = evt.getPickResult().getIntersectedNode();
if (node.getStyleClass().contains("increment-arrow-button") ||
node.getStyleClass().contains("decrement-arrow-button")) {
if (evt.getButton() == MouseButton.PRIMARY) {
handler.stop();
}
}
});

An alternative to changing the update speed might in some cases be adjusting the amount by which the value increments/decrements per update.
SpinnerValueFactory.IntegerSpinnerValueFactory intFactory =
(SpinnerValueFactory.IntegerSpinnerValueFactory) spinner.getValueFactory();
intFactory.setAmountToStepBy(100);
Reference: http://news.kynosarges.org/2016/10/28/javafx-spinner-for-numbers/

Related

JavaFX detect when scrollbar movement is complete

Swing provided java.awt.event.AdjustmentEvent.getValueIsAdjusting() which could be used to detect when a JScrollBar movement was completed. This was used in a legacy application so that it only requested data once rather than on every little scroll movement.
JFrame frame = new JFrame();
JScrollBar comp = new JScrollBar();
comp.addAdjustmentListener(e -> {
if (!e.getValueIsAdjusting()) {
System.out.println("Finished " + e.getValue());
}
});
frame.add(comp);
comp.setPreferredSize(new Dimension(25, 300));
frame.pack();
frame.setVisible(true);
I'm looking for an equivalent method in JavaFX with ScrollBar. It appears that ScrollBar only has a way of monitoring the current value. I've reviewed the code in com.sun.javafx.scene.control.behavior.ScrollBarBehavior but can see no obvious way to do this.
My workaround for now is fairly ugly. It uses a pair of event listeners to drag if the mouse is pressed, this state is used to maintain another valueProperty() which is only updated when the mouse is released.
Is there a better/easier way to do this?
#Override
public void start(Stage primaryStage) throws Exception {
ScrollBar scrollbar = new ScrollBar();
new ScrollBarFinishedAdjusting(scrollbar).valueProperty().addListener((obs, oldValue, newValue) -> {
System.out.println("Finished " + newValue);
});
primaryStage.setScene(new Scene(scrollbar));
primaryStage.show();
}
private class ScrollBarFinishedAdjusting {
private boolean mouseDown;
private DoubleProperty value = new SimpleDoubleProperty();
private ScrollBar scrollbar;
public ScrollBarFinishedAdjusting(ScrollBar scrollbar) {
this.scrollbar = scrollbar;
scrollbar.valueProperty().addListener((obs, oldValue, newValue) -> {
if (!mouseDown) {
update();
}
});
scrollbar.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
mouseDown = true;
});
scrollbar.addEventFilter(MouseEvent.MOUSE_RELEASED, event -> {
mouseDown = false;
update();
});
update();
}
private void update() {
value.set(scrollbar.getValue());
}
public ReadOnlyDoubleProperty valueProperty() {
return value;
}
}

don't recieve mouse press event on child node JavaFX

I want draw path between two imageview same as picture
.
this path start from one of these imageview by mouse press, continue by mouse press and move event on pane and must be end in another imageview by mouse press.here is the problem after first mouse press didn't recieve any mouse press event on imageviews, the event just recieves on the pane becuase of that draw line didn't stop. what is wrong in my code ?
here's my controller code :
public class DrawLine {
#FXML
ImageView imageView1 ;
#FXML
ImageView imageView2 ;
#FXML
AnchorPane pane ;
private Line currentLine ;
private String state ;
private DoubleProperty mouseX = new SimpleDoubleProperty();
private DoubleProperty mouseY = new SimpleDoubleProperty();
#FXML
private void initialize(){
state = "dfkl" ;
imageView1.setPreserveRatio( false);
imageView2.setPreserveRatio( false);
imageView1.setOnMousePressed( event -> {
imageMousePress( event);
});
imageView2.setOnMousePressed( event -> {
imageMousePress( event);
});
pane.setOnMousePressed( event -> {
paneMousePress( event) ;
});
imageView2.setPickOnBounds(false);
imageView1.setPickOnBounds(false);
pane.setOnMouseMoved( event -> {
paneMouseMove( event);
});
}
public void paneMouseMove( MouseEvent e) {
if( this.state.equals("DRAWLINE") && this.currentLine != null) {
makeLine( e);
}
}
public void paneMousePress( MouseEvent e) {
if( this.state.equals("DRAWLINE") && this.currentLine != null) {
endLine(e);
startLine(e);
}
}
private void startLine( MouseEvent e ){
currentLine = new Line();
currentLine.setStyle( "-fx-stroke: #a86a6a ; -fx-stroke-width: 5");
currentLine.setStartX( e.getSceneX());
currentLine.setStartY(e.getSceneY());
mouseX.set( e.getSceneX()) ;
mouseY.set( e.getSceneY());
currentLine.endXProperty().bind(mouseX);
currentLine.endYProperty().bind(mouseY);
pane.getChildren().add(currentLine);
}
private void endLine ( MouseEvent e){
currentLine.endXProperty().unbind();
currentLine.endYProperty().unbind();
currentLine.setEndX(e.getX());
currentLine.setEndY(e.getY());
currentLine = null;
}
private void makeLine( MouseEvent e){
mouseX.set(e.getX());
mouseY.set(e.getY());
}
private void imageMousePress( MouseEvent event){
if( currentLine == null){
startLine(event);
state = "DRAWLINE" ;
}else if( currentLine != null & state.equals("DRAWLINE")){
endLine( event);
}
}
}
help me please.
When dragging the end point of the line around, the end is positioned below the mouse cursor. This way the target of the mouse event is the Line, not the ImageView and since there is no event handler for the event for the Line that consumes it, the event is delivered to the parent of the Line which is the AnchorPane, not the ImageView.
To fix this set the mouseTransparent property of the Line to true:
private void startLine(MouseEvent e) {
currentLine = new Line();
currentLine.setMouseTransparent(true);
...
}
Also you should consume the events for the ImageViews to not trigger the event handler for the AnchorPane too:
imageView1.setOnMousePressed(event -> {
imageMousePress(event);
event.consume();
});
imageView2.setOnMousePressed(event -> {
imageMousePress(event);
event.consume();
});
Also note that x and y properties of the MouseEvent are relative to the coordinate system of the Node where the handler is added.
private void endLine(MouseEvent e) {
currentLine.endXProperty().unbind();
currentLine.endYProperty().unbind();
currentLine.setEndX(e.getX());
currentLine.setEndY(e.getY());
currentLine = null;
}
needs to be changed to
private void endLine(MouseEvent e) {
currentLine.endXProperty().unbind();
currentLine.endYProperty().unbind();
currentLine = null;
}
Furthermore if there are a limited number of states, I recommend using a enum instead since this way you get compile time checks for typos. Using strings for this purpose you could accidentally add bugs, e.g. if you accidentally use "DRAWLlNE" instead of "DRAWLINE" which can be hard to spot. Additionally enum constants can be compared using ==.
private enum States {
DRAWLINE
}

Wait until any button is pressed?

I am writing a TicTacToe game in JavaFX. I've decided to make a board as 9 (3x3) buttons with changing text: "" (if empty) or "X" or "O". Everything is going ok beside one thing... I got stuck here:
public void game() {
while(keepPlaying) {
if(computerTurn) {;
computerMove();
}else {
while(waitForUser) {
//wait until any of 9 buttons is pushed!
}
}
if (checkResult()) {
keepPlaying = false;
}
computerTurn = !computerTurn;
}
}
How to wait for user pushing any of those 9 buttons and then continue with computer turn??
I need something like waiting for scanner input in console application, but this input must be one of 9 buttons...
I know that there are few "possible duplicates", but in fact those problems were solved using methods I can't use here, for example timer. Correct me if I am wrong.
Blocking the application thread in JavaFX should not be done since it freezes the UI. For this reason a loop like this is not well suited for a JavaFX application. Instead you should react to user input:
public void game() {
if (keepPlaying && computerTurn) {
computerMove();
if (checkResult()) {
keepPlaying = false;
}
computerTurn = false;
}
}
// button event handler
private void button(ActionEvent event) {
if (keepPlaying) {
Button source = (Button) event.getSource();
// probably the following 2 coordinates are computed from GridPane indices
int x = getX(source);
int y = getY(source);
// TODO: update state according to button pressed
if (checkResult()) {
keepPlaying = false;
} else {
computerMove();
if (checkResult()) {
keepPlaying = false;
}
}
}
}
Starting with javafx 9 there is a public API for "pausing" on the application thread however:
private static class GridCoordinates {
int x,y;
GridCoordinates (int x, int y) {
this.x = x;
this.y = y;
}
}
private final Object loopKey = new Object();
public void game() {
while(keepPlaying) {
if(computerTurn) {
computerMove();
} else {
// wait for call of Platform.exitNestedEventLoop​(loopKey, *)
GridCoordinates coord = (GridCoordinates) Platform.enterNestedEventLoop​(loopKey);
// TODO: update state
}
if (checkResult()) {
keepPlaying = false;
}
computerTurn = !computerTurn;
}
}
private void button(ActionEvent event) {
if (keepPlaying) {
Button source = (Button) event.getSource();
// probably the following 2 coordinates are computed from GridPane indices
int x = getX(source);
int y = getY(source);
Platform.exitNestedEventLoop​(loopKey, new GridCoordinates(x, y));
}
}

JavaFX : ChangeListener to get the resolution of screen on mouse released

How do I get the resolution of the screen application (during resize) but only once the mouse has been released ?
I've looked around, but I found nothing. I've done this :
scene.widthProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
ConfigBusiness.getInstance().setScreenWidth(newValue.intValue());
}
});
scene.heightProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
ConfigBusiness.getInstance().setScreenHeight(newValue.intValue());
}
});
But as you expect, everytime the width / height changes, it calls the function to save the value (in my case, in the registry, which results in many calls).
Is there a way to get the value only when the mouse button has been released ? Or maybe use another kind of listener (setOnMouseDragRelease or something like that) ?
EDIT : I want the user to be able to resize the application window, and once he releases the mouse button after resizing I would like to trigger an event (and not during the whole resizing process).
Thanks
EDIT 2 : Following a bit the idea of #Tomas Bisciak, I came up with this idea.
scene.widthProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
if(!widthHasChanged()){
setWidthChanged(true);
System.out.println("Width changed");
}
}
});
scene.heightProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
if(!heightHasChanged()){
setHeightChanged(true);
System.out.println("Height changed");
}
}
});
scene.addEventFilter(MouseEvent.MOUSE_RELEASED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if(widthHasChanged()){
ConfigBusiness.getInstance().setScreenWidth(scene.getWidth());
System.out.println("Width changed > release");
setWidthChanged(false);
}
if(heightHasChanged()){
ConfigBusiness.getInstance().setScreenHeight(scene.getHeight());
System.out.println("Height changed > release");
setHeightChanged(false);
}
}
});
I flagged the changes done during the resizing process (with the help of widthProperty and heightProperty) and set them to true if changed, and then when releasing the mouse, I set the values if they have changed.
The problem is that the last event MOUSE_RELEASED is not triggered. I see the output from the changeListeners but not the eventFilter. Any ideas ?
Hook listener onto scene Handle mouse event anywhere with JavaFX (use MouseEvent.MOUSE_RELEASED) and on mouse release write values of the setScreenHeight/width(on every mouse release ),
if you want to only write values when drag happened with intention of resize , use boolean flag in change() "flag=true on change" methods to indicate that change has started, aferwards on mouse release just write values wherever you want and set flag to (flag=false).
If you want every time the mouse is released to get the resolution of the Monitor Screen here is the code:
Assuming you have the code for the Stage and Scene you can add a Listener for MouseReleased Event like this:
public double screenWidth ;
public double screenHeight;
.......
scene.setOnMouseReleased(m->{
screenWidth = Screen.getPrimary().getBounds().getWidth();
screenHeight = Screen.getPrimary().getBounds().getHeight();
});
......
In case you have an undecorated window that you resize it manually
when the mouse is dragged i recommend you using setOnMouseDragged();
Assuming that you are doing the above this code for moving the window
manually can be useful:
public int initialX;
public int initialY;
public double screenWidth = Screen.getPrimary().getBounds().getWidth() ;
public double screenHeight = Screen.getPrimary().getBounds().getHeight();
setOnMousePressed(m -> {
if (m.getButton() == MouseButton.PRIMARY) {
if (m.getClickCount() == 1) { // one Click
setCursor(Cursor.MOVE);
if (window.getWidth() < screenWidth ) {
initialX = (int) (window.getX() - m.getScreenX());
initialY = (int) (window.getY() - m.getScreenY());
} else
setFullScreen(false);
} else if (m.getClickCount() == 2) // two clicks
setFullScreen(true);
}
});
setOnMouseDragged(m -> {
if (window.getWidth() < screenWidth && m.getButton() == MouseButton.PRIMARY) {
window.setX(m.getScreenX() + initialX);
window.setY(m.getScreenY() + initialY);
}
});
setOnMouseReleased(m -> setCursor(Cursor.DEFAULT));
I finally found a way to accomplish "more or less" what I wanted. Here is the code for those who are looking for how to do it.
ChangeListener<Number> resizeListener = new ChangeListener<Number>() {
Timer timer = null; // used to schedule the saving task
final long delay = 200; // the delay between 2 changes
TimerTask task = null; // the task : saves resolution values
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
// cancels the old task as a new one has been queried
// at every change, we cancel the old task and start a new one
if(task != null) task.cancel();
// reset of the timer
if(timer == null) timer = new Timer();
task = new TimerTask() {
#Override
public void run() {
ConfigBusiness.getInstance().setScreenWidth(scene.getWidth());
ConfigBusiness.getInstance().setScreenHeight(scene.getHeight());
// stopping the timer once values are set
timer.cancel();
timer = null;
// stopping the task once values are set
task.cancel();
task = null;
}
};
timer.schedule(task, delay);
}
};
scene.widthProperty().addListener(resizeListener);
scene.heightProperty().addListener(resizeListener);
I added some comments just to help understanding the way it works. During resizing, a timer schedules a task to retrieve the width / height of the window and is started.
If another changes incomes, then the task is "reset" in order to get only the last values set.
Once the values are retrieved, it is important to clear the timer and the task, else threads will continue running.
Thanks to all who gave me hints on how to do it, hope this helps !

javafx Minesweeper: how to tell between right and left mouse button input

I am working on a javafx Minesweeper game, and currently am only using left mouse button input. I would like to use the right mouse button also so users can flag possible bombs. I looked at the Oracle webpage for Button class, and it says:
"When a button is pressed and released a ActionEvent is sent. Your application can perform some action based on this event by implementing an EventHandler to process the ActionEvent. Buttons can also respond to mouse events by implementing an EventHandler to process the MouseEvent."
https://docs.oracle.com/javafx/2/api/javafx/scene/control/Button.html
Ive tried this several different ways, with no success.
Included is my current EventHandler code. If anyone can explain the best way to handle right/left mouse clicks, or point me in the right direction of where to find that information, it is greatly appreciated.
MineButton is a custom class that extends Button. I would like on right click to mark as "m" and change cell color, and left click would remain the same.
for (int row = 0; row < 8; row++){
for (int col = 0; col <8; col++){
MineButton button = new MineButton(row, col);
button.setPrefSize(100, 100);
button.setText("?");
button.setStyle("-fx-font: 22 arial; -fx-base:#dcdcdc;");
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
if ( button.isAMine() == true){
button.setText("B");
for( int row1 = 0; row1 < 8; row1++){
for ( int col1 = 0; col1 < 8; col1++){
if (mineButtons[row1][col1].isAMine() == true){
mineButtons[row1][col1].setText("B");
mineButtons[row1][col1].setStyle("-fx- font: 22 arial; -fx-base: #dc143c;");
}
}
}
}
else{
recursion(mineButtons, button.getX(), button.getY());
}
}
});
You cannot handle right clicks on a button by attaching an event handler on action event. Instead you need to add a handler to the mouse event:
button.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
if(mouseEvent.getButton().equals(MouseButton.SECONDARY)){
System.out.println("Set flag on the button");
}
}
});
If u wanna handle the MouseEvent use this code. It will work.
button.addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if(event.getButton() == MouseButton.SECONDARY){
// Type code to set flag here
}
}
});
scene.setOnMousePressed(event ->{
if(event.getButton() == MouseButton.PRIMARY) {
anchorX = event.getSceneX();
anchorY = event.getSceneY();
anchorAngleX = angleX.get();
anchorAngleY = angleY.get();
}
});
scene.setOnMouseDragged((MouseEvent event) -> {
if(event.getButton() == MouseButton.PRIMARY) {
angleX.set(anchorAngleX - (anchorY - event.getSceneY()));
angleY.set(anchorAngleY - (anchorX - event.getSceneX()));
}
});
This code rotates a JavaFX group in a scene with a left mouse button and a drag. You can print the event.getButton to make certain your primary and secondary are working. I have tried many other ways including JavaFXtras. This is by far the simplest way to handle the primary and secondary mouse click events.

Resources