JavaFX scroll started and ended - java

I have a very costly action to do on a mouse scroll on a pane. I currently use
pane.setOnScroll({myMethod()}).
The problem is that if you scroll a lot it computes everything many times. So what I want is to do my actions only when the scroll is finished. I hoped to use setOnScrollStarted, save the starting value and setOnScrollFinished to do my actions.
But I don't know why these two methods are never called. As a test I used
pane.setOnScroll({System.out.println("proof of action"});
and it was clearly never called.
Any idea on how to call my method only at the end of the scroll?
Thanks in advance, A

From the javadoc of ScrollEvent (emphasis mine):
When the scrolling is produced by a touch gesture (such as dragging a
finger over a touch screen), it is surrounded by the SCROLL_STARTED
and SCROLL_FINISHED events. Changing number of involved touch points
during the scrolling is considered a new gesture, so the pair of
SCROLL_FINISHED and SCROLL_STARTED notifications is delivered each
time the touchCount changes. When the scrolling is caused by a mouse
wheel rotation, only a one-time SCROLL event is delivered, without the
started/finished surroundings.
A possible workaround:
Increment a counter variable every time a scroll is detected. In the listener start a new thread that waits 1 second and performs the action that you want only if the counter equals to 1 (the last scrolling) then decrements the counter.
I created a Gist, but I copy here the code:
public class ScrollablePane extends Pane {
private Integer scrollCounter = 0;
private final ObjectProperty<EventHandler<? super ScrollEvent>> onScrollEnded = new SimpleObjectProperty<>();
public final ObjectProperty<EventHandler<? super ScrollEvent>> onScrollEndedProperty() {
return onScrollEnded;
}
public ScrollablePane() {
this.setOnScroll(e -> {
scrollCounter++;
Thread th = new Thread(() -> {
try {
Thread.sleep(1000);
if (scrollCounter == 1)
onScrollEnded.get().handle(e);
scrollCounter--;
} catch (Exception e1) {
e1.printStackTrace();
}
});
th.setDaemon(true);
th.start();
});
}
public void setOnScrollEnded(EventHandler<? super ScrollEvent> handler) {
onScrollEnded.setValue(handler);
}
}
To use it:
public class MyApplication extends Application {
#Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
Scene scene = new Scene(root, 400, 400);
ScrollablePane pane = new ScrollablePane();
pane.setOnScrollEnded(e -> System.out.println("Scroll just has been ended"));
root.setCenter(pane);
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}

Related

RichTextFX's undo is not working properly

Whenever, I type something in CodeArea and press CTRL + Z and then begin typing again, the cursor gets reset to the start of the text.
I looked into this issue https://github.com/FXMisc/RichTextFX/issues/761 and seems like this bug is fixed. However, I was able to replicate the bug in latest version (0.9.1).
Following code will replicate the behavior:
public class GuiTest extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
StyleClassedTextArea codeArea = new StyleClassedTextArea();
codeArea.replaceText("Text");
VirtualizedScrollPane<StyleClassedTextArea> scrollPane = new VirtualizedScrollPane<>(codeArea);
final Scene scene = new Scene(scrollPane, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
}
Am I missing something? If the bug exists any idea how to solve it?
After a brief debugging I found that RichTextFx is doing some weird behavior when selecting the new range (it is from this range you start typing the text, cursor position does not matter). When selecting the new range, the current range is set to 0,0 and then only moved to the cursor position. However, after performing the undo, the new range is selected to 0,0 and never to the current cursor position. It is because of that when we press undo, though the cursor is highlighted in another position, the text starts getting typed from the 0,0.
I was able to somewhat solve this using reflection. You need to extend the StyleClassedTextArea, override the undo() method, perform undo, and then set the position where you need your range in. You could set the range to the current cursor position but the cursor is not always in the correct position especially when the cursor needs to move to another style in another paragraph. I got the correct position range inside the ChangeQueue in the undoManager.
You need to make sure that you are casting properly so, do some debugging before casting.
#Override
public void undo() {
if (isUndoAvailable()) {
super.undo();
UndoManager undoManager = getUndoManager();
try {
Field queueField = UndoManagerImpl.class.getDeclaredField("queue");
queueField.setAccessible(true);
UnlimitedChangeQueue queue = (UnlimitedChangeQueue) queueField
.get(undoManager);
int newRange = ((PlainTextChange) ((List) queue.peekNext()).get(0))
.getRemovalEnd();
selectRange(newRange, newRange);
} catch (NoSuchFieldException | IllegalAccessException e) {
// Handle exception
}
}
}
#Override
public void redo() {
if (isRedoAvailable()) {
super.redo();
UndoManager undoManager = getUndoManager();
try {
Field queueField = UndoManagerImpl.class.getDeclaredField("queue");
queueField.setAccessible(true);
UnlimitedChangeQueue queue = (UnlimitedChangeQueue) queueField
.get(undoManager);
int newRange = ((PlainTextChange) ((List) queue.peekPrev()).get(0))
.getInsertionEnd();
selectRange(newRange, newRange);
} catch (NoSuchFieldException | IllegalAccessException e) {
// Handle
}
}
}

Repaint BorderPane (javaFx)

I have an application that can create a rectangle that decreases in size for example a lapse of time of 10 sec, but here is when I try to shrink the rectangle, the window bug (nothing is displayed in the scene) and wait until the countdown is finished to stop bugging (and then display the rectangle not diminished).
I tried to find on the Internet the equivalent of repaint in Swing but not average: /
this.requestLayout () -> I found this on the internet but it does not work.
Here is my code of my countdown:
public class Compteur {
DemoBorderPane p ;
public DemoBorderPane getPan() {
if(p==null) {
p = new DemoBorderPane();
}
return p;
}
public Compteur() {
}
public void lancerCompteur() throws InterruptedException {
int leTempsEnMillisecondes=1000;
for (int i=5;i>=0;i--) {
try {
Thread.sleep (leTempsEnMillisecondes);
}
catch (InterruptedException e) {
System.out.print("erreur");
}
System.out.println(i);
getPan().diminuerRect(35);
}
}
}
There is my Borderpane code :
public class DemoBorderPane extends BorderPane {
private Rectangle r;
public Rectangle getRect() {
if(r==null) {
r = new Rectangle();
r.setWidth(350);
r.setHeight(100);
r.setArcWidth(30);
r.setArcHeight(30);
r.setFill( //on remplie notre rectangle avec un dégradé
new LinearGradient(0f, 0f, 0f, 1f, true, CycleMethod.NO_CYCLE,
new Stop[] {
new Stop(0, Color.web("#333333")),
new Stop(1, Color.web("#000000"))
}
)
);
}
return r;
}
public void diminuerRect(int a) {
getRect().setWidth(getRect().getWidth()-a);
int c= (int) (getRect().getWidth()-a);
System.out.println(c);
this.requestLayout();
//this.requestFocus();
}
public DemoBorderPane() {
this.setBottom(getRect());
}
}
There is my Main code :
public class Main extends Application {
private DemoBorderPane p;
public DemoBorderPane getPan() {
if(p==null) {
p = new DemoBorderPane();
}
return p;
}
#Override
public void start(Stage primaryStage) {
Compteur c = new Compteur();
try {
//Group root = new Group();
Scene scene = new Scene(getPan(),800,600);
//scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
//root.getChildren().add(getPan());
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
try {
c.lancerCompteur();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
/*Son s = null;
try {
s = new Son();
} catch (LineUnavailableException | IOException | UnsupportedAudioFileException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
s.volume(0.1);
s.jouer();
c.lancerCompteur();
s.arreter();*/
}
}
Thank ;)
As long as you keep the JavaFX application thread busy it cannot perform layout/rendering. For this reason it's important to make sure any methods that run on the application thread, like e.g. Application.start or event handlers on input events return fast.
lancerCompteur however blocks the application thread for 5 seconds so the only result you see is the final one after the method completes.
In general you can run code like this on a different thread and use Platform.runLater to update the ui.
In this case you could take advantage of the Timeline class which allows you to trigger an event handler on the application thread after a given delay:
#Override
public void start(Stage primaryStage) {
Scene scene = new Scene(getPan(), 800, 600);
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(1), event -> {
getPan().diminuerRect(35);
}));
timeline.setCycleCount(5);
timeline.play();
primaryStage.setScene(scene);
primaryStage.show();
}
You also use different instances of DemoBorderPane in your Main class and the Compteur class; the Rectangle shown in the scene was never subject to an update.
there's no need to call requestLayout in diminuerRect. This happens automatically when the Rectangle's size is modified.
Lazy initialisation is pointless, if you know for sure the getter will be invoked during the object's creation. DemoBorderPane.getRect is invoked from it's constructor so moving the initialisation to the constructor would allow you to get rid of the if check without affecting functionality.

JavaFX: how to temporary block the GUI

I'm trying to figure out if blocking the GUI is possible. Basically, my application (which is using the NetBeans Platform and JavaFX) has a connection to the server.
Independently on which screen the user is seeing, if the application loses the connection to the server I'd like to block everything (the users cannot open any new windows or click anywhere) until the application is connected again (it doesn't matter if that needs 5 minutes or 5 hours). Nevertheless, on the top of everything should appear an alert message (always on the top).
The java class which is listening to the server connection doesn't have any reference to JavaFX containers. That's what I actually have:
public class StatusConnectionObserver implements ConnectionObserver {
private final Led led;
private final Label label;
public StatusConnectionObserver(Led led, Label label) {
this.led = led;
this.label = label;
}
#Override
public void setConnected(boolean connected) {
if (connected) {
Platform.runLater(() -> {
led.setLedColor(Color.rgb(59, 249, 53));
label.setText("Connected");
});
} else {
Platform.runLater(() -> {
led.setLedColor(Color.RED);
label.setText("Disconnected");
});
}
}
}
and:
public class ConnectionComponent {
private Led led;
private Label label;
private HBox container;
private VBox ledContainer;
public ConnectionComponent() {
initGraphics();
}
public Parent getView() {
return this.container;
}
public void initGraphics() {
//Here I set up the elements (label and Led) inside the container
}
Which is called here:
#ServiceProvider(service = StatusLineElementProvider.class)
public class ConnectionIndicator implements StatusLineElementProvider {
#Override
public Component getStatusLineElement() {
JFXPanel fxPanel = new JFXPanel();
Platform.setImplicitExit(false);
new JavaFXUIThread().runOnUiToolkitThread(() -> {
Scene scene = new Scene(new ConnectionComponent().getView());
scene.getStylesheets().add(FXTheme.getDefault().getStylesheet());
fxPanel.setScene(scene);
});
return fxPanel;
}
}
The idea is to showing something on the top (even a simple text message) and, in the meanwhile, make the application in background more opaque.
You need a modal Dialog. Create such a dialog and show it when your connection goes down. Then use a Thread which periodically checks if your connection is back up. The time the connection comes alive kill the dialog. Since the dialog is modal it means that you can do nothing to the UI until it is resolved. See this.
Use Alert or Dialog components. You can style them by CSS or add custom content. Try this simplest solution:
Alert a = new Alert(Alert.AlertType.ERROR, "Connection error");
public void createAlert() {
a.getDialogPane().getButtonTypes().clear();
a.initModality(Modality.APPLICATION_MODAL);
//*************** EDIT ***************
a.initStyle(StageStyle.UNDECORATED);
a.initOwner(label.getScene().getWindow());
//************************************
}
#Override
public void setConnected(boolean connected) {
if (connected) {
Platform.runLater(() -> {
label.setText("Connected");
a.show();
});
} else {
Platform.runLater(() -> {
label.setText("Disconnected");
a.close();
});
}
}
You can also add additional Pane on top of your entire Scene:
StackPane root = new StackPane();
root.getChildren().addAll(applicationContent);
Pane p = new Pane();
p.setStyle("-fx-background-color: rgba(31,31,31,0.6);");
//add Pane to root when disconnected
//root.getChildren().add(p);
Scene scene = new Scene(root, 300, 250);

java.lang.IllegalStateException: Application launch must not be called more than once - JavaFX (first once works, the 2nd non-) [duplicate]

How to call the launch() more than once in java i am given an exception as "ERROR IN MAIN:java.lang.IllegalStateException: Application launch must not be called more than once"
I have create rest cleint in my java application when request comes it call javafx and opening webview after completing webview operarion am closing javafx windows using Platform.exit() method. when second request comes am getting this error how to reslove this error.
JavaFx Application Code:
public class AppWebview extends Application {
public static Stage stage;
#Override
public void start(Stage _stage) throws Exception {
stage = _stage;
StackPane root = new StackPane();
WebView view = new WebView();
WebEngine engine = view.getEngine();
engine.load(PaymentServerRestAPI.BROWSER_URL);
root.getChildren().add(view);
engine.setJavaScriptEnabled(true);
Scene scene = new Scene(root, 800, 600);
stage.setScene(scene);
engine.setOnResized(new EventHandler<WebEvent<Rectangle2D>>() {
public void handle(WebEvent<Rectangle2D> ev) {
Rectangle2D r = ev.getData();
stage.setWidth(r.getWidth());
stage.setHeight(r.getHeight());
}
});
JSObject window = (JSObject) engine.executeScript("window");
window.setMember("app", new BrowserApp());
stage.show();
}
public static void main(String[] args) {
launch(args);
}
RestClient Method:
Calling to JavaFX application
// method 1 to lanch javafx
javafx.application.Application.launch(AppWebview.class);
// method 2 to lanch javafx
String[] arguments = new String[] {"123"};
AppWebview .main(arguments);
You can't call launch() on a JavaFX application more than once, it's not allowed.
From the javadoc:
It must not be called more than once or an exception will be thrown.
Suggestion for showing a window periodically
Just call Application.launch() once.
Keep the JavaFX runtime running in the background using Platform.setImplicitExit(false), so that JavaFX does not shutdown automatically when you hide the last application window.
The next time you need another window, wrap the window show() call in Platform.runLater(), so that the call gets executed on the JavaFX application thread.
For a short summary implementation of this approach:
See the answer by sergioFC
If you are mixing Swing you can use a JFXPanel instead of an Application, but the usage pattern will be similar to that outlined above.
For an example of the JFXPanel apprach, see Irshad Babar
s answer.
Wumpus Sample
This example is bit more complicated than it needs to be because it also involves timer tasks. However it does provide a complete stand-alone example, which might help sometimes.
import javafx.animation.PauseTransition;
import javafx.application.*;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.*;
// hunt the Wumpus....
public class Wumpus extends Application {
private static final Insets SAFETY_ZONE = new Insets(10);
private Label cowerInFear = new Label();
private Stage mainStage;
#Override
public void start(final Stage stage) {
// wumpus rulez
mainStage = stage;
mainStage.setAlwaysOnTop(true);
// the wumpus doesn't leave when the last stage is hidden.
Platform.setImplicitExit(false);
// the savage Wumpus will attack
// in the background when we least expect
// (at regular intervals ;-).
Timer timer = new Timer();
timer.schedule(new WumpusAttack(), 0, 5_000);
// every time we cower in fear
// from the last savage attack
// the wumpus will hide two seconds later.
cowerInFear.setPadding(SAFETY_ZONE);
cowerInFear.textProperty().addListener((observable, oldValue, newValue) -> {
PauseTransition pause = new PauseTransition(
Duration.seconds(2)
);
pause.setOnFinished(event -> stage.hide());
pause.play();
});
// when we just can't take it anymore,
// a simple click will quiet the Wumpus,
// but you have to be quick...
cowerInFear.setOnMouseClicked(event -> {
timer.cancel();
Platform.exit();
});
stage.setScene(new Scene(cowerInFear));
}
// it's so scary...
public class WumpusAttack extends TimerTask {
private String[] attacks = {
"hugs you",
"reads you a bedtime story",
"sings you a lullaby",
"puts you to sleep"
};
// the restaurant at the end of the universe.
private Random random = new Random(42);
#Override
public void run() {
// use runlater when we mess with the scene graph,
// so we don't cross the streams, as that would be bad.
Platform.runLater(() -> {
cowerInFear.setText("The Wumpus " + nextAttack() + "!");
mainStage.sizeToScene();
mainStage.show();
});
}
private String nextAttack() {
return attacks[random.nextInt(attacks.length)];
}
}
public static void main(String[] args) {
launch(args);
}
}
Update, Jan 2020
Java 9 added a new feature called Platform.startup(), which you can use to trigger startup of the JavaFX runtime without defining a class derived from Application and calling launch() on it. Platform.startup() has similar restrictions to the launch() method (you cannot call Platform.startup() more than once), so the elements of how it can be applied is similar to the launch() discussion and Wumpus example in this answer.
For a demonstration on how Platform.startup() can be used, see Fabian's answer to How to achieve JavaFX and non-JavaFX interaction?
I use something like this, similar to other answers.
private static volatile boolean javaFxLaunched = false;
public static void myLaunch(Class<? extends Application> applicationClass) {
if (!javaFxLaunched) { // First time
Platform.setImplicitExit(false);
new Thread(()->Application.launch(applicationClass)).start();
javaFxLaunched = true;
} else { // Next times
Platform.runLater(()->{
try {
Application application = applicationClass.newInstance();
Stage primaryStage = new Stage();
application.start(primaryStage);
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
try this, I tried this and found successful
#Override
public void start() {
super.start();
try {
// Because we need to init the JavaFX toolkit - which usually Application.launch does
// I'm not sure if this way of launching has any effect on anything
new JFXPanel();
Platform.runLater(new Runnable() {
#Override
public void run() {
// Your class that extends Application
new ArtisanArmourerInterface().start(new Stage());
}
});
} catch (Exception e) {
e.printStackTrace();
}
}

RequestFocus in TextField doesn't work

I use JavaFX 2.1 and I created GUI using FXML, in the controller of this GUI I added myTextField.requestFocus();.
But I always get the focus in the other control.
At the time of initialize() controls are not yet ready to handle focus.
You can try next trick:
#Override
public void initialize(URL url, ResourceBundle rb) {
Platform.runLater(new Runnable() {
#Override
public void run() {
tf.requestFocus();
}
});
}
For tricky complex applications (like Pavel_K has in the comments) you may want to repeat this routine several times and call method line next one:
private void requestFocusOrDieTrying(Node node) {
Platform.runLater(() -> {
if (!node.isFocused()) {
node.requestFocus();
requestFocusOrDieTrying(node);
}
});
}
Note this is the undocumented approach and it may be wise to add a limit for repetitions to avoid endless loop if something changed or broke in future Java releases. Better to lose focus than a whole app. :)
Example with the described threshold:
#Override
public void requestFocus() {
requestFocus( getNode(), 3 );
}
private void requestFocus( final Node node, final int max ) {
if( max > 0 ) {
runLater(
() -> {
if( !node.isFocused() ) {
node.requestFocus();
requestFocus( node, max - 1 );
}
}
);
}
}
The exact same answer as #Sergey Grinev. Make sure your version of java is up-to-date (JDK 1.8 or later).
Platform.runLater(()->myTextField.requestFocus());
If you requestFocus(); after initializing the scene, it will work!
Like this:
Stage stage = new Stage();
GridPane grid = new GridPane();
//... add buttons&stuff to pane
Scene scene = new Scene(grid, 800, 600);
TEXTFIELD.requestFocus();
stage.setScene(scene);
stage.show();
I hope this helps. :)
This can occur when the Scene property for the Node is not yet set.
Alas, the scene property can take a "long" time to be set.
The child node's scene property lags when a scene is first created, and also, when items are added to some parents, such as a TabPane (oddly some parents seem immune, I'm not sure why).
The correct strategy, which has always worked for me :
if (myNode.scene) == null {
// listen for the changes to the node's scene property,
// and request focus when it is set
} else {
myNode.requestFocus()
}
I have a handy Kotlin extension function which does this.
fun Node.requestFocusOnSceneAvailable() {
if (scene == null) {
val listener = object : ChangeListener<Scene> {
override fun changed(observable: ObservableValue<out Scene>?, oldValue: Scene?, newValue: Scene?) {
if (newValue != null) {
sceneProperty().removeListener(this)
requestFocus()
}
}
}
sceneProperty().addListener(listener)
} else {
requestFocus()
}
}
You can then call it from within you code like so :
myNode.requestFocusOnSceneAvailable()
Perhaps somebody would like to translate it to Java.
I ran into the same problem using JavaFX 11 and solved it in a similar way that nickthecoder proposed.
ChangeListener<Scene> sceneListener = new ChangeListener<Scene>() {
#Override
public void changed(ObservableValue<? extends Scene> observable, Scene oldValue, Scene newValue) {
if (newValue != null) {
editInput.requestFocus();
editInput.sceneProperty().removeListener(this);
}
}
};
editInput.sceneProperty().addListener(sceneListener);
Basicly just add a listener to the sceneProperty of the node and in that listener request focus once the scene is set. I also wrote it in such a way that the listener will be removed after it is invoked.
I would rather using timer to enforce focus to text field. The process of checking whether or not the text field has focus, is done in a separate (background) thread. While the process of requesting focus is done in the GUI thread, with the help of Platform.runLater().
//I'd rather using timer to enforce focus
Timer checkIfTFIsFocusedTimer = new Timer();
TimerTask checkIfTFIsFocusedTask = new TimerTask() {
#Override
public void run() {
if (!textField.isFocused()) {
Platform.runLater(() -> {
textField.requestFocus();
});
} else {
checkIfTFIsFocusedTimer.cancel();
}
}
};
checkIfTFIsFocusedTimer
.scheduleAtFixedRate(checkIfTFIsFocusedTask,
0, 100);
The older answers account for the case of Platform.runLater not working, but this answer covers also the case of multiple requests on multiple nodes.
Problem is: the order in which the scene property becomes non-null for the nodes, and thence the order in which the added listeners get called, is not necessarily the same as the order in which the two listeners were added. And so this order:
requestFocusOnSceneAvailable(node1)
requestFocusOnSceneAvailable(node2)
might unexpectedly result in this order:
node2.requestFocus()
node1.requestFocus()
A solution requires having the listeners call requestFocus() only on the most recent node, which can be tracked with a static variable:
private static Node nodeToRequestFocusOnOnceSceneAvailable;
public static void requestFocusOnceSceneAvailable(Node node) {
// Remember this node as the latest node requested to receive focus.
nodeToRequestFocusOnOnceSceneAvailable = node;
// Schedule the focus request to happen whenever
// JavaFX finally adds the node to the scene.
Listeners.addAndFire(node.sceneProperty(), new ChangeListener<Scene>() {
#Override
public void changed(ObservableValue<? extends Scene> observable, Scene oldScene, Scene newScene) {
if (newScene != null) {
if (node == nodeToRequestFocusOnOnceSceneAvailable) {
node.requestFocus();
// We no longer need to remember this node,
// since its focus has been requested.
nodeToRequestFocusOnOnceSceneAvailable = null;
}
// We no longer need the listener
// after it has run once.
observable.removeListener(this);
}
}
});
}
Note, this solution assumes there is only one scene.

Categories

Resources