Reputation: 740
Making progress with my board game, so on a mouse click, some tasks will be performed like playing some animations, receiving new panes for the board (I asked about this my last question, works great now), making changes to player data etc.
So, here is my EventHandler
grid.setOnMouseReleased(new EventHandler<MouseEvent> () {
public void handle(MouseEvent me) {
//make changes to player data
//receive new panes for the board
//make some gui changes
//play some animations
}
});
Here grid
is a GridPane object, which contains other panes for each cell of the board, panes for animation etc.
One mouse event would take 2-3 seconds to be handled. While this is going on, I saw that another mouse click would also start being handled parallel with the one already ongoing.
What I want is that another mouse event should not be processed until the first one has been completed. It would be better if, any clicks if received would be discarded.
I tried to use threading:
grid.setOnMouseReleased(new EventHandler<MouseEvent> () {
public void handle(MouseEvent me) {
Thread t = new Thread(new Runnable() {
public void run() {
//completing the necessary tasks here
}
});
t.start();
t.join();
}
});
But GUI changes would not occur if I use threading (don't know why, I tested it in many different ways, always failed to make GUI changes), so this would not work I guess.
So what could I do to not take in any mouse clicks while one mouse event is being handled? I think this question doesn't require more code, but if necessary, I would edit and add some code.
EDIT:
class Animations {
public Pane getAnimationPane() {
//returns Pane which would be used for transition
}
public void playAnimation() {
//called only when the timeline transition is to be played
}
}
So I made an array of the Animations class because I need a lot of such panes and need to keep a track of each one of them.
When I need to play the animation, I call the playAnimation()
method.
Now this playAnimation()
method is like an animation-inception.
After an animation on this pane is completed, changes are made to the board according to the player's progress, and if required... this playAnimation()
method would call the playAnimation()
method of several other Animation objects.
grid.setOnMouseReleased(new EventHandler<MouseEvent> () {
public void handle(MouseEvent me) {
//make changes to player data
//receive new panes for the board
//make some gui changes
//play some animations
someAnimationObject.playAnimation();
}
});
This could go as deep as 10-20 (more, if the grid size is large) other Animation objects being used for their playAnimation()
.
EDIT 2:
The playAnimation()
of Animations
could call playAnimation()
on 0-8 other Animations objects. And this could keep on going.
EDIT 3:
I finally sovled my problem, this is how I did it.
I added a SimpleBooleanProperty
isAnimating
to my Animations
class (instance variable, i.e. non-static), set it to false
by default.
When the timeline animation begins playing, I set it to true
, and when the timeline animation is completed, I set it to false
.
In the main class (say Game
), I added a BooleanBinding
anyAnimating
.
I binded anyAnimating
to isAnimating
of each Animations
object using the array I said I create.
anyAnimating = Bindings.or(anyAnimating, AnimationsObj[i][j].isAnimatingProperty());
And finally I used the value of anyAnimating
as a flag in the MouseEvent EventHandler as Brad suggested.
grid.setOnMouseReleased(new EventHandler<MouseEvent> () {
public void handle(MouseEvent me) {
if(!anyAnimating.get()){
//make changes to player data
//receive new panes for the board
//make some gui changes
//play some animations
}
}
});
Upvotes: 0
Views: 1128
Reputation: 209340
I'm not really sure on the details here because there aren't a lot of details in your question about how the animations are managed, but this should give you the general idea.
Add a flag to your Animations
class indicating if animations are in progress:
class Animations {
private ReadOnlyBooleanWrapper animating = new ReadOnlyBooleanWrapper() ;
public ReadOnlyBooleanProperty animatingProperty() {
return animating.getReadOnlyProperty();
}
public boolean isAnimating() {
return animatingProperty().get();
}
public Pane getAnimationPane() {
//returns Pane which would be used for transition
}
public void playAnimation() {
//called only when the timeline transition is to be played
Animation animation = ... ;
animation.statusProperty().addListener((obs, oldStatus, newStatus) ->
animating.set(newStatus == Animation.Status.RUNNING));
animation.play(); // etc
}
}
Now you can do
grid.setOnMouseReleased(new EventHandler<MouseEvent> () {
public void handle(MouseEvent me) {
if (! someAnimationObject.isAnimating()) {
//make changes to player data
//receive new panes for the board
//make some gui changes
//play some animations
someAnimationObject.playAnimation();
}
}
});
or perhaps
grid.setOnMouseReleased(new EventHandler<MouseEvent> () {
public void handle(MouseEvent me) {
grid.setDisable(true);
//make changes to player data
//receive new panes for the board
//make some gui changes
//play some animations
someAnimationObject.animatingProperty().addListener((obs, wasAnimating, isNowAnimating) -> {
if (! isNowAnimating) {
grid.setDisable(false);
}
});
someAnimationObject.playAnimation();
}
});
Another approach would be to let playAnimation()
accept a callback to execute when the animation is complete (or when all the animations are complete, if you are executing multiple animations).
This would look something like:
class Animations {
public Pane getAnimationPane(Runnable finishedCallback) {
//returns Pane which would be used for transition
}
public void playAnimation() {
//called only when the timeline transition is to be played
Animation animation = ... ;
animation.statusProperty().addListener((obs, oldStatus, newStatus) -> {
if (newStatus == Animation.Status.STOPPED) {
finishedCallback.run();
}
});
animation.play(); // etc
}
}
Then your event handling code could do:
// declared at instance level:
private boolean animating = false ;
grid.setOnMouseReleased(new EventHandler<MouseEvent> () {
public void handle(MouseEvent me) {
if (! animating) {
animating = true ;
//make changes to player data
//receive new panes for the board
//make some gui changes
//play some animations
someAnimationObject.playAnimation( () -> {animating = false ;});
}
}
});
or
grid.setOnMouseReleased(new EventHandler<MouseEvent> () {
public void handle(MouseEvent me) {
grid.setDisable(true);
//make changes to player data
//receive new panes for the board
//make some gui changes
//play some animations
someAnimationObject.playAnimation( () -> {grid.setDisable(false);});
}
});
Upvotes: 2
Reputation: 198
I think this could be handled a couple of different ways. One way that comes to mind is to use a simple semaphore. To do this, you would simply declare a boolean field at the top of your class called something like isUpdating.
private boolean isUpdating = false;
When you enter your mouse click, the first thing the method would do is check to see if isUpdating is false. If so, the next thing would be to set isUpdating to true, do the work, and then set it to false again.
grid.setOnMouseReleased(new EventHandler<MouseEvent> () {
public void handle(MouseEvent me) {
if(!isUpdating){
isUpdating = true;
//make changes to player data
//receive new panes for the board
//make some gui changes
//play some animations
isUpdating = false;
}
}
});
Regarding your comment on why the thread isn't updating your GUI, this is because your the changes in one thread while your UI repainting is handled in another thread. If your anonymous thread/mouselistener is in the same class as the UI that is being updated, you can create a refresh method and call it at the end of you worker thread.
UI Class:
private void refresh(){
this.revalidate();
this.repaint();
}
Worker Thread:
grid.setOnMouseReleased(new EventHandler<MouseEvent> () {
public void handle(MouseEvent me) {
Thread t = new Thread(new Runnable() {
public void run() {
//completing the necessary tasks here
refresh();
}
});
t.start();
t.join();
}
});
If your UI is in different class, you could create an external action listener and register your UI components to it.
public interface GridListener {
public void gridUpdated();
}
Then add your listener to an object that would need access:
public class MyUIClass() {
private List<GridListener> listeners;
}
public MyUIClass(){
listeners = new ArrayList<GridListener>();
}
public void addListener(GridListener listener) {
listeners.add(listener);
}
This may be more code then you're looking to write to remedy your current problem, these are just some suggestions I thought of while reading your question.
Upvotes: 0