I need to compare different image values after they've been clicked - java

I have not been able to find any good questions regarding my predicament. I am trying to count/check how many times a specific item is clicked via an actionListener but am unsure of how to use an actionListener/EventHandler to document the click. This is being done in JavaFX.Pardon the potentially noobish question.
correctUrl = getCorrectUrl(); //Correct image for set
wrongUrl = getWrongUrl();
initialPos(); //everything is placed into default.
imageOne.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
imageOne.setOnMouseClicked((MouseEvent e) -> {
imageOne = flipToCard(imageOne, 1);
});
returnCardOne();
System.out.println("Tile pressed ");
event.consume();
});
The ultimate goal of the code is to notice two images have been clicked and then check to see if their URL == each-other.

If the URLs are equal, the Images should be equal. So why not compare Images? This little app demos what you are trying to achieve. Comments in the code.
Main:
import java.util.ArrayList;
import java.util.List;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
import javafx.util.Duration;
/**
*
* #author Sedrick
*/
public class JavaFXApplication51 extends Application {
#Override
public void start(Stage primaryStage) {
List<ImageViewTile> imageViews = new ArrayList();
List<ImageViewTile> cardsBeingMatched = new ArrayList();//Used to keep up with the current two cards that are pressed inorder to deteremine if they are a match
Image initialImage = new Image(getClass().getResourceAsStream("cardBack_blue1.png"));
Image ace = new Image(getClass().getResourceAsStream("cardSpadesA.png"));
Image king = new Image(getClass().getResourceAsStream("cardSpadesK.png"));
imageViews.add(new ImageViewTile(initialImage, ace));
imageViews.add(new ImageViewTile(initialImage, king));
imageViews.add(new ImageViewTile(initialImage, ace));
imageViews.add(new ImageViewTile(initialImage, king));
for(ImageViewTile view : imageViews)
{
//Set ImageViews' onMouseClicked handler
view.setOnMouseClicked(event->{
if(!view.isShowing())//If card face value is not showing
{
view.showFrontImage();//show it.
cardsBeingMatched.add(view);//Add card being clicked to list so it can be compared against the next card
if(cardsBeingMatched.size() == 2)//Once two cards are in the list, see if they are equal.
{
if(cardsBeingMatched.get(0).getImage().equals(cardsBeingMatched.get(1).getImage()))//If cards are equal a match is found
{
System.out.println("Match");
cardsBeingMatched.clear();//clear the list to prepare to compare the next two cards that are clicked
}
else//If cards are not equal
{
System.out.println("No match");
//wait half a second and flip cards back over
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(.5), (event1) -> {
System.out.println(".5 seconds need to pass before another mouse click!");
}));
timeline.setCycleCount(1);
timeline.play();
timeline.setOnFinished(event1->{
cardsBeingMatched.get(0).showBackImage();
cardsBeingMatched.get(1).showBackImage();
cardsBeingMatched.clear();//clear the list to prepare to compare the next two cards that are clicked
});
}
}
}
});
}
GridPane root = new GridPane();
for(int i = 0; i < imageViews.size(); i++)
{
root.add(imageViews.get(i), i % 2, i / 2);//Add ImageViews to the GridPane
}
Scene scene = new Scene(root, 280, 380);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
ImageViewTitle Class:
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
/**
*
* #author Sedrick
*/
public final class ImageViewTile extends ImageView{
private boolean showing;
private final Image backImage;
private final Image frontImage;
public ImageViewTile(Image backImage, Image frontImage) {
this.backImage = backImage;
this.frontImage = frontImage;
this.setImage(backImage);
showing = false;
}
public void showBackImage()
{
this.setImage(backImage);
showing = false;
}
public void showFrontImage()
{
this.setImage(frontImage);
showing = true;
}
public boolean isShowing()
{
return showing;
}
}
I created this class to help me keep up with the ImageView's different states.

Related

How to properly implement the Action Listener in a bullseye animation

This program first displays a bullseye created by three different sized circles.
Once the animate me button is clicked, the function animation() will make the existing circles shrink inwards until the size of the circles is zero.
Once the user presses the button named "Press to stop", the animation will then stop. If the user presses the button again, it will then keep going from the state it was stopped from, so on so forth.
Currently, this is not working as intended. It only creates about 9 circles (including the nine circles that the program began with). I know I will need to use the action listener in order to make the program run, but I'm having a hard time in terms of the documentation of the action listener. What am I supposed to put in the parameters of the listener? If you see any other ways around this, please feel free to let me know.
package target;
import javafx.animation.ScaleTransition;
import javafx.animation.Timeline;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Target extends Application
{
Circle[] cir = new Circle[7];
Button btn = new Button("Animate me!");
StackPane root = new StackPane();
public static void main(String[] args)
{
launch(args);
}
/**
* start method will create the target and the start button first
* displayed on-screen to the user
*/
#Override
public void start(Stage primaryStage)
{
root.setStyle("-fx-border-color:black;");
cir[0] = new Circle(400, 250, 200);
cir[0].setFill(Color.RED);
cir[0].setStyle("-fx-border-color:black;");
cir[1] = new Circle(315, 165, 115);
cir[1].setFill(Color.WHITE);
cir[1].setStyle("-fx-border-color:black;");
cir[2] = new Circle(230, 80, 30);
cir[2].setFill(Color.RED);
cir[2].setStyle("-fx-border-color:black;");
root.getChildren().addAll(cir[0], cir[1], cir[2]);
root.getChildren().add(btn);
primaryStage.setScene(new Scene(root));
primaryStage.show();
btn.setOnAction(e ->
{
animation();
btn.setText("Press to Stop");
});
}
public void animation()
{
//Timeline animation = new Timeline(
//)
ScaleTransition[] st = new ScaleTransition[7];
boolean recycleCircles = false;
st[0]= new ScaleTransition(Duration.seconds(7), cir[0]);
st[0].setToX(0.0f);
st[0].setToY(0.0f);
st[0].play();
st[1] = new ScaleTransition(Duration.seconds(5.5), cir[1]);
st[1].setToX(0.0f);
st[1].setToY(0.0f);
st[1].play();
st[2] = new ScaleTransition(Duration.seconds(4), cir[2]);
st[2].setToX(0.0f);
st[2].setToY(0.0f);
st[2].play();
// int delayInc = 1;
int delay = 1;
//will create circles (will rotate between white and red) and then add
//to scaleTransitions
//while(btn.isPressed() == false)
{
for(int i = 3; i<st.length; i++)
{
if(recycleCircles == true)
{
i = 0;
recycleCircles = false;
}
if(i % 2 == 1)
{
cir[i] = new Circle(400,250,200);
cir[i].setFill(Color.WHITE);
cir[i].setStyle("-fx-border-color:black;");
root.getChildren().add(cir[i]);
cir[i].toBack();
st[i] = new ScaleTransition(Duration.seconds(7), cir[i]);
st[i].setDelay(Duration.seconds(delay));
delay++;
st[i].setToX(0.0f);
st[i].setToY(0.0f);
st[i].play();
}
else if(i%2==0)
{
cir[i] = new Circle(400, 250, 200);
cir[i].setFill(Color.RED);
cir[i].setStyle("-fx-border-color:black;");
root.getChildren().add(cir[i]);
cir[i].toBack();
st[i] = new ScaleTransition(Duration.seconds(7), cir[i]);
st[i].setDelay(Duration.seconds(delay));
delay++;
st[i].setToX(0.0f);
st[i].setToY(0.0f);
st[i].play();
}
if(i == 6)
recycleCircles = true;
}
}
//btn.pressedProperty().addListener(listener);
btn.setOnMousePressed(event ->
{
});
btn.setOnMouseReleased(event ->
{
for(int y = 0; y<st.length;y++)
{
}
});
}
}
Not sure whether you have any specific use case with each circle. If your are using the circles only for the purpose of alternating row colors, then you can get similar effect with radial gradient's repeat option.
To the extent I understand the question, below program is what I can think of. May be this can help you.
Just to let you know, the overall effect is slightly different from your program. The main difference in effects is, your program gives an effect/impression that each circle are shrinking towards center, as the distance between each circle is always same till it shrinked completely.
My program gives the effect/.impression like the entire board is moving away from your sight till it vanishes. In my program the distance between each circle decreases proportianally till it shrinks.
import javafx.animation.ScaleTransition;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class TargetAnimation extends Application {
Button btn = new Button("Animate me!");
StackPane root = new StackPane();
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
root.setPrefSize(400, 400);
root.setStyle("-fx-border-color:black;");
Circle board = new Circle();
board.setRadius(200);
board.setStyle("-fx-fill:radial-gradient(focus-angle 0deg , focus-distance 0% , center 50% 50% , radius 21% , repeat, red 44% , white 46% );-fx-stroke-width:1px;-fx-stroke:black;");
root.getChildren().addAll(board, btn);
primaryStage.setScene(new Scene(root));
primaryStage.show();
ScaleTransition transition = new ScaleTransition(Duration.seconds(7), board);
transition.setToX(0);
transition.setToY(0);
btn.setOnAction(e -> {
switch (transition.getStatus()) {
case RUNNING:
transition.pause();
break;
case PAUSED:
transition.play();
break;
default:
board.setScaleX(1);
board.setScaleY(1);
transition.playFromStart();
}
});
}
}
The code given to setOnAction is an EventHandler, which is a #FunctionalInterface with the single method handle. That means that you can give it a lambda expression instead. The method takes an argument, which is the ActionEvent of clicking the button (created for you by JavaFX), and runs the code you give it.
If you want to pause the animation, call Animation#pause, and if you want to resume it, call Animation#play. I suggest that you create a ParallelTransition with all of your ScaleTransitions as its children. Then call the above methods on the ParallelTransition in the event handler.
That means that the setup code, like naming the button and creates the animations, goes outside of the event handler.

javafx full screen on SECOND screen

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);
}
}

Get single stage resize event when user releases left mouse button

I've googled enough but still can find solution to get only single resize event when user releases left mouse button. For example the following solution from here
stage.titleProperty().bind(
scene.widthProperty().asString().
concat(" : ").
concat(scene.heightProperty().asString()));
When user clicks mouse left button and starts resizing the stage we will get very many events (using property listeners) while he does resizing. However, I want to get only one event - when the user completes resizing and releases mouse left button.
Another solution is here This solution significantly decreases amount of events but still doesn't let to get only one.
How to get only one resize event after user releases mouse button?
As far as I know, the mouse event handlers that resize the stage are managed natively, and so there is no way to access those purely in JavaFX - to do this the way you describe would require writing native libraries and hooking into them.
If you are doing some heavy computation (or other work that takes a long time) in response to the change in size of the stage, your best bet is probably to write code that only processes one change at a time, and just processes the last known change when it can.
An example of this is:
import java.util.Random;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class StageResizeThrottling extends Application {
private Random rng = new Random();
#Override
public void start(Stage primaryStage) {
BlockingQueue<Point2D> dimensionChangeQueue = new ArrayBlockingQueue<>(1);
ChangeListener<Number> dimensionChangeListener = (obs, oldValue, newValue) -> {
dimensionChangeQueue.clear();
dimensionChangeQueue.add(new Point2D(primaryStage.getWidth(), primaryStage.getHeight()));
};
primaryStage.widthProperty().addListener(dimensionChangeListener);
primaryStage.heightProperty().addListener(dimensionChangeListener);
Thread processDimensionChangeThread = new Thread(() -> {
try {
while (true) {
System.out.println("Waiting for change in size");
Point2D size = dimensionChangeQueue.take();
System.out.printf("Detected change in size to [%.1f, %.1f]: processing%n", size.getX(), size.getY());
process(size, primaryStage);
System.out.println("Done processing");
}
} catch (InterruptedException letThreadExit) { }
});
processDimensionChangeThread.setDaemon(true);
processDimensionChangeThread.start();
Scene scene = new Scene(new StackPane(), 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
private void process(Point2D stageDimension, Stage stage) throws InterruptedException {
// simulate slow process:
Thread.sleep(500 + rng.nextInt(1000));
final String title = String.format("Width: %.0f Height: %.0f", stageDimension.getX(), stageDimension.getY());
Platform.runLater(() -> stage.setTitle(title));
}
public static void main(String[] args) {
launch(args);
}
}
Note that this will always process the very first change immediately, and then process the latest change when each previously-processed change has finished processing. If no further changes have occurred, it will wait until one does occur and then process it immediately. If you like, you can combine this with the timer-based technique you linked for coalescing the changes in the listener, which will typically remove the very first change that is processed (which is usually redundant as it is almost always followed by subsequent changes). The following changes will wait until no resizes have occurred for 300ms before submitting one to the queue for processing (the thread still behaves the same way - it will process the latest change, and when that processing is complete, wait for another one):
BlockingQueue<Point2D> dimensionChangeQueue = new ArrayBlockingQueue<>(1);
PauseTransition coalesceChanges = new PauseTransition(Duration.millis(300));
coalesceChanges.setOnFinished(e -> {
dimensionChangeQueue.clear();
dimensionChangeQueue.add(new Point2D(primaryStage.getWidth(), primaryStage.getHeight()));
});
ChangeListener<Number> dimensionChangeListener = (obs, oldValue, newValue) ->
coalesceChanges.playFromStart();
primaryStage.widthProperty().addListener(dimensionChangeListener);
primaryStage.heightProperty().addListener(dimensionChangeListener);
There's some tuning here, which is a tradeoff between latency and over-eagerness in processing changes. You probably want the pause transition to last something shorter than the average processing time of the change in screen size, but not an order of magnitude shorter.
The code guarantees that no more than one change will be processed at a time and that the latest change will eventually be processed if no more changes occur. This is probably about as good as you can get without accessing native user events. (And it would also handle programmatic changes in the stage size, which a mouse handler would not handle.)
I tried to create an example to achieve what you are looking for, I ended up with this, it is not perfect but when I tested it, it looked like it could help:
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class OneEventJavaFX extends Application{
double originalWidth = 400; // the initial width of Scene when the program starts
double originalHeight = 400; // the initial height of Scene when the program starts
// boolean property to be observed in order to know the completion of stage resize
BooleanProperty completedProperty = new SimpleBooleanProperty(false);
Timeline timeline;
#Override
public void start(Stage stage) throws Exception {
Pane root = new Pane(); // simple root as example just for test purpose
Scene scene = new Scene(root, 400,400);
stage.setScene(scene);
stage.setTitle("OneEventJavaFX");
stage.show();
// because I could not find a way to implement MouseEvent.MOUSE_RELEASED
// on the stage to notify the completion on resizing, I had to use a TimeLine
// the duration should consider the time the user usually take to finish every resize
// duration is tricky, Very Slow Resizing V.S Very Fast Resizing!
timeline = new Timeline(new KeyFrame(Duration.seconds(1), e ->{
System.out.println("Resizing Should Be Completed By Now!");
originalWidth = scene.getWidth(); // record the new scene size
originalHeight = scene.getHeight();
completedProperty.setValue(false);
}));
// change listener, to be added to and removed from the scene
ChangeListener<Number> changeListener= (observable, oldValue, newValue) ->{
System.out.println("I am Detecting an Event!"); // test
// once the size changed
if(originalWidth-scene.getWidth()>1 || scene.getWidth()-originalWidth>1 ||
originalHeight-scene.getHeight()>1 || scene.getHeight()-originalHeight>1){
completedProperty.set(true); // notify that completion should be considered
System.out.println("I Stopped! No More Events!");
timeline.play(); // and start counting the time
}};
// add the change listener when the program starts up
scene.widthProperty().addListener(changeListener);
scene.heightProperty().addListener(changeListener);
System.out.println("ChangeListener Added At Startup!");
// now listen to the change of the boolean property value
// instead of the size changes, it should NOT take a lot of work
// then accordingly add and remove change listener!
completedProperty.addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable,
Boolean notComplete, Boolean complete) {
if (complete) {
scene.widthProperty().removeListener(changeListener);
scene.heightProperty().removeListener(changeListener);
System.out.println("ChangeListener Removed!");
}
else{
scene.widthProperty().addListener(changeListener);
scene.heightProperty().addListener(changeListener);
System.out.println("ChangeListener Added Back!");
}
}
});
}
public static void main(String[] args) {
launch();
}
}
Test While Resizing
ChangeListener Added At Startup!
I am Detecting an Event!
I am Detecting an Event!
ChangeListener Removed!
I Stopped! No More Events!
Resizing Should Be Completed By Now!
ChangeListener Added Back!
UPDATE:
I have been working on solving this question, I believe this approach can achieve what you want.
The idea is as follows:
Create UNDECORATED Stage and Make it Resizable.
Create a Title Bar and add it to the Stage.
Now the Mouse Events can be detected on the Border of the Stage (because basically it happens on the Scene).
Create Double Property for both the Width and Height of Stage and add Change Listener to listen to the Changes.
The changes in the Stage Width & Height will only be recorded at the beginning of the drag and when user RELEASES the Mouse.
Explanations in Comments.
The whole solution can be found here as an archive file (Why? Because I tried to post it here fully but the Body Limit is 30000 Character!) .
OneEventStage Class:
import javafx.application.Platform;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.value.ChangeListener;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
/**
* This class customize a given Stage to record the changes
* of its size only when user starts and finishes resizing (recording one event)
* #author Yahya Almardeny
* #version 28/05/2017
*/
public class OneEventStage{
private double originalWidth; // the initial width of Scene when the program starts
private double originalHeight; // the initial height of Scene when the program starts
private TitleBar titleBar; // can be customized by the setter method (by default I made it for Windows 10 style)
private boolean started, alreadyFullScreen;
private DoubleProperty widthChange, heightChange; // record the changes in size
public Scene s;
public BorderPane scene; // this will be considered as a Scene when used in the program
public OneEventStage(Stage stage, double width, double height){
originalWidth = width; originalHeight = height;
widthChange = new SimpleDoubleProperty(originalWidth);
heightChange = new SimpleDoubleProperty(originalHeight);
started = false;
titleBar = new TitleBar("");
scene = new BorderPane();
scene.setTop(titleBar.getTitleBar());
s = new Scene(scene, originalWidth,originalHeight);
stage.initStyle(StageStyle.UNDECORATED);
stage.setScene(s);
ResizeHelper.addResizeListener(stage);
Task<Void> task = new Task<Void>(){
#Override
protected Void call() throws Exception {
Platform.runLater(new Runnable(){
#Override
public void run() {
// change listener, to be added to and removed from the scene
ChangeListener<Number> changeListener= (observable, oldValue, newValue) ->{
if(isFullScreen()){
widthChange.setValue(stage.getWidth());
heightChange.setValue(stage.getHeight());
alreadyFullScreen=true;
}
else if (alreadyFullScreen){ // coming from full screen mode
widthChange.setValue(Screen.getPrimary().getVisualBounds().getWidth());
heightChange.setValue(Screen.getPrimary().getVisualBounds().getHeight());
widthChange.setValue(originalWidth);
heightChange.setValue(originalHeight);
alreadyFullScreen = false;
}
else if(!alreadyFullScreen && !started){
started = true; // to inform the detecting Mouse Release Event is required
}
};
s.setOnMouseReleased(e->{
if(started){ // if this happens particularly after changing the size/dragging
originalWidth = stage.getWidth(); // record the new scene size
originalHeight = stage.getHeight();
widthChange.setValue(originalWidth); // add it
heightChange.setValue(originalHeight);
started = false;
}
});
// add the change listener when the program starts up
s.widthProperty().addListener(changeListener);
s.heightProperty().addListener(changeListener);
}
});
return null;
}};
new Thread(task).start();
}
/*
* to detected if user clicked on maximize button or double click on the title bar
*/
private boolean isFullScreen(){
return this.s.getWindow().getWidth()==Screen.getPrimary().getVisualBounds().getWidth() &&
this.s.getWindow().getHeight()==Screen.getPrimary().getVisualBounds().getHeight();
}
public DoubleProperty getWidthChange() {
return widthChange;
}
public DoubleProperty getHeightChange() {
return heightChange;
}
public TitleBar getTitleBar() {
return titleBar;
}
public void setTitleBar(TitleBar titleBar) {
this.titleBar = titleBar;
}
public void setTitle(String title){
titleBar.getTitle().setText(title);
}
}
OneEventStageTest Class:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.DoubleProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
* Implementing an Example of OneEventStage to test it
* #author Yahya Almardeny
* #version 28/05/2017
*/
public class OneEventStageTest extends Application{
#Override
public void start(Stage primaryStage) throws Exception {
// create stage
OneEventStage stage = new OneEventStage(primaryStage, 400,400);
stage.setTitle("One Event Stage");
// simple containers and its components for testing purpose
VBox container = new VBox();
container.setAlignment(Pos.CENTER);
HBox widthInfoContainer = new HBox();
widthInfoContainer.setAlignment(Pos.CENTER);
Label widthChangeL = new Label("Width Changes");
TextField widthChangeV = new TextField();
widthChangeV.setEditable(false);
widthInfoContainer.getChildren().addAll(widthChangeL, widthChangeV);
HBox.setMargin(widthChangeL, new Insets(10));
HBox.setMargin(widthChangeV, new Insets(10));
HBox heightInfoContainer = new HBox();
heightInfoContainer.setAlignment(Pos.CENTER);
Label heightChangeL = new Label("Height Changes");
TextField heightChangeV = new TextField();
heightChangeV.setEditable(false);
heightInfoContainer.getChildren().addAll(heightChangeL, heightChangeV);
HBox.setMargin(heightChangeL, new Insets(10));
HBox.setMargin(heightChangeV, new Insets(10));
container.getChildren().addAll(widthInfoContainer, heightInfoContainer);
//////////////////////////////////////////////////////////////////////////
DoubleProperty widthChange = stage.getWidthChange();
DoubleProperty heightChange = stage.getHeightChange();
// listen to the changes (Testing)
widthChange.addListener((obs, old, newV)->{
Platform.runLater(new Runnable(){
#Override
public void run() {
widthChangeV.setText("From(" + old.doubleValue() + ") To(" + newV.doubleValue() + ")");
}
});
});
heightChange.addListener((obs, old, newV)->{
Platform.runLater(new Runnable(){
#Override
public void run() {
heightChangeV.setText("From(" + old.doubleValue() + ") To(" + newV.doubleValue() + ")");
}
});
});
//////////////////////////////////////////////////////////////////////////////////////
// represent a root but in fact it's inside the real root (BorderPane in the OneEventStage Class!).
StackPane root = new StackPane();
root.setAlignment(Pos.CENTER);
root.getChildren().add(container);
stage.scene.setCenter(root);
primaryStage.show();
}
public static void main(String[] args) {
launch();
}
}
TitleBar Class:
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Cursor;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.stage.Screen;
import javafx.stage.Stage;
/**
* This class to create a default/customized Title Bar
* to be added to Undecorated Stage in JavaFX Application
* #author Yahya Almardeny
* #version 27/05/2017
*/
public class TitleBar {
private HBox titleBar;
private ImageView icon;
private StackPane close, minimize, maximize; // represent customized components for the title bar (by using the second constructor)
private Image maximizeBefore, maximizeAfter; // for changing maximize icon when it's full screen
private Label title;
private double height, stageWidth, stageHeight, x,y, offsetX, offsetY;
private double screenWidth = Screen.getPrimary().getVisualBounds().getWidth(),
screenHeight = Screen.getPrimary().getVisualBounds().getHeight();
private Color backgroundColor;
private StackPane maximizeButton; // for default title bar
private Label minimizeButton, closeButton; // for default title bar
private Stage stage;
private boolean intialized = false, fromMax = false;
public static enum Components {ICON,TITLE,MINIMIZE,MAXIMIZE,CLOSE;}
/**
* the default constructor, appearance of Windows 10
* #param title
*/
public TitleBar(String title){
titleBar = new HBox();
icon = new ImageView(new Image(TitleBar.class.getResourceAsStream("/icon/icon.png")));
icon.setFitWidth(15); this.icon.setFitHeight(13);
closeButton = new Label("×");
closeButton.setFont(Font.font("Times New Roman", 25));
closeButton.setPrefWidth(46);
closeButton.setAlignment(Pos.CENTER);
minimizeButton = new Label("—");
minimizeButton.setFont(Font.font(10));
minimizeButton.setPrefWidth(46);
minimizeButton.setPrefHeight(29);
minimizeButton.setAlignment(Pos.CENTER);
maximizeButton = maximiazeButton();
this.title = new Label(title);
final Pane space = new Pane();
HBox.setHgrow(space,Priority.ALWAYS);
titleBar.getChildren().addAll(this.icon, this.title,space,this.minimizeButton, this.maximizeButton, this.closeButton);
titleBar.setAlignment(Pos.CENTER_RIGHT);
HBox.setMargin(this.icon, new Insets(0,5,0,10)); // top,right, bottom, left
initalize(); // private method to get the Stage for first time
setDefaultControlsFunctionality(); // private method to add the default controls functionality
}
/**
* This is constructor to create a custom title bar
* #param icon
* #param minimize
* #param maximize
* #param close
* #param title
*/
public TitleBar(Image icon, Image minimize, Image maximizeBefore, Image maximizeAfter, Image close, String title){
titleBar = new HBox();
this.icon = new ImageView(icon);
this.icon.setFitWidth(15); this.icon.setFitHeight(14); // values can be changed via setters
this.close = new StackPane();
this.close.setPrefSize(25, 20);
this.close.getChildren().add(new ImageView(close));
((ImageView) this.close.getChildren().get(0)).setFitWidth(20);
((ImageView) this.close.getChildren().get(0)).setFitHeight(20);
this.minimize = new StackPane();
this.minimize.setPrefSize(25, 20);
this.minimize.getChildren().add(new ImageView(minimize));
((ImageView) this.minimize.getChildren().get(0)).setFitWidth(20);
((ImageView) this.minimize.getChildren().get(0)).setFitHeight(20);
this.maximizeBefore = maximizeBefore;
this.maximize = new StackPane();
this.maximize.setPrefSize(25, 20);
this.maximize.getChildren().add(new ImageView(maximizeBefore));
((ImageView) this.maximize.getChildren().get(0)).setFitWidth(20);
((ImageView) this.maximize.getChildren().get(0)).setFitHeight(20);
this.maximizeAfter = maximizeAfter;
this.title = new Label(title);
final Pane space = new Pane();
HBox.setHgrow(space,Priority.ALWAYS);
titleBar.getChildren().addAll(this.icon, this.title,space,this.minimize, this.maximize, this.close);
titleBar.setAlignment(Pos.CENTER_RIGHT);
HBox.setMargin(this.icon, new Insets(0,5,0,10)); // top,right, bottom, left
HBox.setMargin(this.close, new Insets(0,5,0,0));
initalize();
setCustomizedControlsFunctionality();
}
/**
* create the default maximize button
* #return container
*/
private StackPane maximiazeButton(){
StackPane container = new StackPane();
Rectangle rect = new Rectangle(8,8);
rect.setFill(Color.TRANSPARENT);
rect.setStroke(Color.BLACK);
container.setPrefWidth(46);
container.getChildren().add(rect);
return container;
}
/**
* To get the Stage of the application for one time only
* as well as adding listener to iconifiedProperty()
*/
private void initalize(){
titleBar.setOnMouseEntered(e->{ // the entire block will be executed only once
if(!intialized){
// get the stage and assign it to the Stage field
stage = ((Stage)titleBar.getScene().getWindow());
// add listener toiconifiedProperty()
stage.iconifiedProperty().addListener(ee->{
if(!stage.isIconified()){
stage.setMaximized(true);
if(fromMax){ // if already maximized
stage.setWidth(screenWidth);
stage.setHeight(screenHeight);
stage.setX(0);
stage.setY(0);
}
else{
stage.setWidth(stageWidth);
stage.setHeight(stageHeight);
stage.setX(x);
stage.setY(y);
}
try { // to remove the flash
Thread.sleep(10);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
stage.setOpacity(1.0);
}
});
intialized=true;
}
});
}
/**
* To add functionality to title bar controls
* via event listeners
*/
private void setDefaultControlsFunctionality(){
// Double-Click on Title Bar
titleBar.setOnMouseClicked(e->{
if(e.getClickCount()==2){
maximizefunctonality();
}
});
//Maximize Control
maximizeButton.setOnMouseEntered(e->{// highlight when hover
maximizeButton.setBackground(
new Background(new BackgroundFill(Color.LIGHTGRAY,null,null)));
((Rectangle)maximizeButton.getChildren().get(0)).setFill(Color.LIGHTGRAY);
if(maximizeButton.getChildren().size()==2){
((Rectangle)maximizeButton.getChildren().get(1)).setFill(Color.LIGHTGRAY);
}
});
maximizeButton.setOnMouseExited(e->{ // remove highlight
maximizeButton.setBackground(
new Background(new BackgroundFill(Color.TRANSPARENT,null,null)));
((Rectangle)maximizeButton.getChildren().get(0)).setFill(Color.TRANSPARENT);
if(maximizeButton.getChildren().size()==2){
((Rectangle)maximizeButton.getChildren().get(1)).setFill(Color.WHITE);
}
});
maximizeButton.setOnMouseClicked(e->{
maximizefunctonality();
});
//Close Control
closeButton.setOnMouseEntered(e->{
closeButton.setBackground(
new Background(new BackgroundFill(Color.CRIMSON,null,null)));
closeButton.setTextFill(Color.WHITE);
});
closeButton.setOnMouseExited(e->{
closeButton.setBackground(
new Background(new BackgroundFill(Color.TRANSPARENT,null,null)));
closeButton.setTextFill(Color.BLACK);
});
closeButton.setOnMouseClicked(e->{
stage.close();
});
//Minimize Control
minimizeButton.setOnMouseEntered(e->{
minimizeButton.setBackground(
new Background(new BackgroundFill(Color.LIGHTGRAY,null,null)));
});
minimizeButton.setOnMouseExited(e->{
minimizeButton.setBackground(
new Background(new BackgroundFill(Color.TRANSPARENT,null,null)));
});
minimizeButton.setOnMouseClicked(e->{
if(!stage.isIconified()){ // if it's not minimized
if(fromMax){ // check if it's already full screen(maximized)
stage.setOpacity(0.0);
stage.setIconified(true); // minimize it
}
else{ // if it's not -> record the size and position
stageWidth = stage.getWidth();
stageHeight = stage.getHeight();
x = stage.getX();
y = stage.getY();
stage.setOpacity(0.0);
stage.setIconified(true); // minimize it
}
}
});
// to make title bar movable
titleBar.setOnMousePressed(e->{
if(stage.getWidth()<screenWidth || stage.getHeight()<screenHeight){
offsetX = e.getScreenX() - stage.getX();
offsetY = e.getScreenY() - stage.getY();
}
});
titleBar.setOnMouseDragged(e->{
if(stage.getWidth()<screenWidth || stage.getHeight()<screenHeight){
stage.setX(e.getScreenX() - offsetX);
stage.setY(e.getScreenY() - offsetY);
}
});
}
private void maximizefunctonality(){
Rectangle rect = (Rectangle) maximizeButton.getChildren().get(0);
if(stage.getWidth()<screenWidth||stage.getHeight()<screenHeight){
// get the previous size + position
stageWidth = stage.getWidth();
stageHeight = stage.getHeight();
x = stage.getX();
y = stage.getY();
// maximize it
stage.setWidth(screenWidth);
stage.setHeight(screenHeight);
stage.centerOnScreen();
// change the maximize button appearance
rect.setTranslateX(2);
rect.setTranslateY(-2);
Rectangle rect1 = new Rectangle(8,8);
rect1.setFill(Color.WHITE);
rect1.setStroke(Color.BLACK);
maximizeButton.getChildren().add(rect1);
fromMax = true;
}
else{ // if already maximized -> return to previous size + position
stage.setWidth(stageWidth);
stage.setHeight(stageHeight);
stage.setX(x);
stage.setY(y);
fromMax = false;
// change the maximize button appearance
rect.setTranslateX(0);
rect.setTranslateY(0);
maximizeButton.getChildren().remove(1);
}
}
private void setCustomizedControlsFunctionality(){
//Maximize Control
maximize.setOnMouseClicked(e->{
if(stage.getWidth()<screenWidth||stage.getHeight()<screenHeight){
// get the previous size + position
stageWidth = stage.getWidth();
stageHeight = stage.getHeight();
x = stage.getX();
y = stage.getY();
// maximize it
stage.setWidth(screenWidth);
stage.setHeight(screenHeight);
stage.centerOnScreen();
// change the maximize button appearance
((ImageView) maximize.getChildren().get(0)).setImage(maximizeAfter);
fromMax = true;
}
else{ // if already maximized -> return to previous size + position
stage.setWidth(stageWidth);
stage.setHeight(stageHeight);
stage.setX(x);
stage.setY(y);
fromMax = false;
// change the maximize button appearance
((ImageView) maximize.getChildren().get(0)).setImage(maximizeBefore);
}
});
close.setOnMouseClicked(e->{
stage.close();
});
//Minimize Control
minimize.setOnMouseClicked(e->{
if(!stage.isIconified()){ // if it's not minimized
if(fromMax){ // check if it's already full screen(maximized)
stage.setOpacity(0.0);
stage.setIconified(true); // minimize it
}
else{ // if it's not -> record the size and position
stageWidth = stage.getWidth();
stageHeight = stage.getHeight();
x = stage.getX();
y = stage.getY();
stage.setOpacity(0.0);
stage.setIconified(true); // minimize it
}
}
});
// to make title bar movable
titleBar.setOnMousePressed(e->{
if(stage.getWidth()<screenWidth || stage.getHeight()<screenHeight){
offsetX = e.getScreenX() - stage.getX();
offsetY = e.getScreenY() - stage.getY();
}
});
titleBar.setOnMouseDragged(e->{
if(stage.getWidth()<screenWidth || stage.getHeight()<screenHeight){
stage.setX(e.getScreenX() - offsetX);
stage.setY(e.getScreenY() - offsetY);
}
});
}
/**
* To change margins/insets to the Title Bar components
* #param component
* #param top
* #param right
* #param bottom
* #param left
*/
public void setInsets(Components component, double top, double right, double bottom, double left){
switch(component){
case TITLE:
HBox.setMargin(title, new Insets(top, right, bottom ,left));
break;
case ICON:
HBox.setMargin(icon, new Insets(top, right, bottom ,left));
break;
case CLOSE:
HBox.setMargin(close, new Insets(top, right, bottom ,left));
break;
case MAXIMIZE:
HBox.setMargin(maximize, new Insets(top, right, bottom ,left));
break;
case MINIMIZE:
HBox.setMargin(minimize, new Insets(top, right, bottom ,left));
break;
}
}
public void setControlsSpace(Components component, double width, double height){
switch(component){
case CLOSE:
close.setPrefSize(width, height);
break;
case MAXIMIZE:
maximize.setPrefSize(width, height);
break;
case MINIMIZE:
minimize.setPrefSize(width, height);
break;
case TITLE:
//do nothing
break;
case ICON:
// do nothing
break;
}
}
public void addHoverEffect(Components component, Color defaultColor, Color onHover, Cursor cursor){
}
//reset of the class
{...}
}
ResizeHelper Class:
{....}
Test

Issue with selecting items in ListView - JavaFX

I wrote the following method that is used to react when I press a button:
private void handlePlayButton(ActionEvent e){
for (int i = 0; i < commands.size(); i++) {
list.getSelectionModel().select(i);
try {
Thread.sleep(1000);
} catch (InterruptedException ie){
System.out.println("Error at handlPlayButton: interruption");
}
}
}
In this code I try to select each element starting at the first line and than wait 1 second to select the next one, but it seems like it waits n-1 seconds (where n is the size of the items) and than selects the last item. Is there any way to fix this?
The field list is a ListView<String> by the way.
This question is relatively obscure, so I don't think the solution will be generally applicable to anybody else. The basic solution is to use a Timeline to automate updating the selection in the ListView when the user presses a "Cycle" button in the UI.
There is a bit of additional logic to handle edge cases such as what to do if the user modifies the selection while the cycling is ongoing or if the user restarts the cycling process. If the user clicks on the currently selected item, the automated cycling does not stop, so the original asker might wish to add some of his own code to do that if he adopts a similar solution.
Also there is some logic for placing ImageViews in the ListView, but that isn't central to the application and can be ignored for more common types stored used in a ListView such as Strings. The ImageView related stuff is just there to make the app look a bit better.
import javafx.animation.*;
import javafx.application.Application;
import javafx.collections.*;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.image.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.Arrays;
import java.util.stream.Collectors;
public class RotatingSushiMenu extends Application {
private static final Duration AUTO_CHANGE_PAUSE = Duration.seconds(2);
private boolean autoChange;
#Override
public void start(Stage stage) {
ObservableList<Image> images = FXCollections.observableList(
Arrays.stream(IMAGE_LOCS)
.map(Image::new)
.collect(Collectors.toList())
);
ListView<Image> list = new ListView<>(FXCollections.observableList(images));
list.setCellFactory(param -> new ImageListCell());
Timeline timeline = new Timeline(
new KeyFrame(Duration.ZERO),
new KeyFrame(
AUTO_CHANGE_PAUSE,
e -> {
int curIdx = list.getSelectionModel().getSelectedIndex();
if (curIdx < list.getItems().size() - 1) {
autoChange = true;
list.scrollTo(curIdx + 1);
list.getSelectionModel().select(curIdx + 1);
autoChange = false;
}
}
)
);
timeline.setCycleCount(list.getItems().size());
Button cycle = new Button("Cycle");
cycle.setOnAction(event -> {
if (list.getItems().size() > 0) {
list.scrollTo(0);
list.getSelectionModel().select(0);
timeline.playFromStart();
}
});
list.getSelectionModel().getSelectedItems().addListener((ListChangeListener<Image>) c -> {
if (!autoChange) {
timeline.stop();
}
});
VBox layout = new VBox(10, cycle, list);
layout.setPadding(new Insets(10));
stage.setScene(new Scene(layout));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
private class ImageListCell extends ListCell<Image> {
final ImageView imageView = new ImageView();
ImageListCell() {
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
#Override
protected void updateItem(Image item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
imageView.setImage(null);
setText(null);
setGraphic(null);
}
imageView.setImage(item);
setGraphic(imageView);
}
}
// image license: linkware - backlink to http://www.fasticon.com
private static final String[] IMAGE_LOCS = {
"http://icons.iconarchive.com/icons/fasticon/fish-toys/128/Blue-Fish-icon.png",
"http://icons.iconarchive.com/icons/fasticon/fish-toys/128/Red-Fish-icon.png",
"http://icons.iconarchive.com/icons/fasticon/fish-toys/128/Yellow-Fish-icon.png",
"http://icons.iconarchive.com/icons/fasticon/fish-toys/128/Green-Fish-icon.png"
};
}

JavaFX : Button never sets a graphic

EDIT :
A MCVE version of my code has been made to help debug it. It reproduces my bug. The purpose of my code is doing a Memory game. Which means that when it is your turn, you "open" a card, then another one. If they form a pair, they don't get turned over, they stay open. Otherwise, you turn them back over and try to find a pair on the next turn.
Simply put, the bug is : when you are opening the second card of your turn and both cards don't form a pair, the second one never gets opened!
Hopefully, this version of my code will help you to find the bug, which will help me a lot!
I have put the code on Github : https://gist.github.com/anonymous/e866671d80384ae53b53
(And you will find it attached at the end of the question)
Explanation of the issue
I am having fun on doing a little Memory game in JavaFX and I came across this strange behavior where the card I click on (represented by a custom class that extends the Button class) never changes the image displayed.
Normally, when I click on the card, it "opens" itself by changing the graphic it displays.
The strange and annoying thing is that it only happens in a specific case.
The behavior of my card is correct when I "open" the first card of the turn of the player. It also works when I "open" a second one and both cards are a pair. Sadly, it doesn't work only in the case where I want to open a second card and it doesn't match as a pair with the first one.
I modified the Button class by adding openCard() and closeCard() methods. Those methods will set a specific graphic on the button-card.
I will now show some code but it is hard to tell what might be the part that is making this behavior happen. Even more so that I am using Eclipse but can't possibly figure out how to debug a JavaFX app with breakpoints (I am using console prints) because the app will eventually crash when I reach my breakpoints and start crawling through the lines of code.
The code
Firstly, the modified Button class :
public class Card extends Button{
private String cardDesign;
public Card(int row, int column){
this.setGraphic(new ImageView("/resources/card_back.png"));
this.setBackground(new Background(new BackgroundFill(Color.SLATEGRAY,
new CornerRadii(6), null)));
}
public void setOpenCardDesign(String design){ cardDesign = design; }
public void openCard(){ this.setGraphic(new ImageView(cardDesign)); }
public void closeCard(){
this.setGraphic(new ImageView("/resources/card_back.png"));
}
}
Now the controller class, the event is set on a MouseEvent. There is more code in this controller (like checking if there is a pair), but this isn't an issue here I think as the problem is already at the line where I call the method to open the card.
I use the getSource() method here because my cards are arranged in a gridPane and I need to know which one has been clicked on.
#Override
public void handle(MouseEvent event) {
//Get the card that was clicked on
Card card = (Card) event.getSource();
//Open the card
card.openCard();
//Do some more after this...
}
That's pretty much it as from what I could figure out.
As already stated, I tried to check if the method openCard() is being called. It is as some comment printed in my console showed up. I even added some console printing just before and just after the line where I set the graphic and they both are showing up. I can't know for sure what happens when my app reaches the setGraphic() line as nothing is showing up in my app (the card remains closed).
Any hint would help because I am slowly sinking in madness right now.
Thank you in advance.
The MCVE version of my code
The card object : Card.java
package memory;
import javafx.scene.control.Button;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.paint.Color;
public class Card extends Button{
//-------------------------------------------------
//Store the position of the card
private int row;
private int column;
//-------------------------------------------------
//Constructor
public Card(int row, int column){
//Give the cards a specific color at init
this.setBackground(new Background(new BackgroundFill(Color.DEEPSKYBLUE,
new CornerRadii(6), null)));
this.setText("CLOSED");
this.row = row;
this.column = column;
}
//-------------------------------------------------
//Open the card
public void openCard(){
System.out.println("OPEN");
//Cards are red when open
this.setBackground(new Background(new BackgroundFill(Color.RED,
new CornerRadii(6), null)));
this.setText("OPEN");
}
//-------------------------------------------------
//Close the card
public void closeCard(){
System.out.println("CLOSE");
//Cards are blue when closed
this.setBackground(new Background(new BackgroundFill(Color.DEEPSKYBLUE,
new CornerRadii(6), null)));
this.setText("CLOSED");
}
//-------------------------------------------------
//Getters for row and column info
public int getRow() { return row; }
public int getColumn() { return column; }
}
The main (includes the view and start point of the app) : Main.java
package memory;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class Main extends Application {
//-------------------------------------------------
//The layout and the cards
GridPane gridCard = new GridPane();
static Card [][] cardArray;
//The event handler
private static EventHandler<MouseEvent> handler;
//The array which remembers the pairs and the reminder of last open card
static int[][] indexArray;
static int index;
//Boolean array to check if the card is already open
static boolean[][] isOpen;
//Number of pairs to find
static int pairs = 5;
//-------------------------------------------------
//Cheap main
public static void main(String[] args) {
Application.launch(args);
}
//-------------------------------------------------
#Override
public void start(Stage primaryStage) throws Exception {
//Init the event handler
handler = new Controller();
//Some formatting for the grid pane
gridCard.setHgap(10);
gridCard.setVgap(10);
gridCard.setPadding(new Insets(0, 10, 0, 10));
gridCard.setAlignment(Pos.CENTER);
//Creating our card board, index array and bool array
cardArray = new Card [2][5];
indexArray = new int [2][5];
isOpen = new boolean [2][5];
//Adding the cards to our card array
for(int i = 0; i < 2; i++){
for(int j = 0; j < 5; j++){
cardArray[i][j] = new Card(i, j);
//Make those buttons look like cards
cardArray[i][j].setPrefHeight(100);
cardArray[i][j].setPrefWidth(70);
//Register the event
cardArray[i][j].addEventHandler(MouseEvent.MOUSE_CLICKED, gameController());
//Add those cards
gridCard.add(cardArray[i][j], j, i);
//Set the pairs (no randomness here)
indexArray[i][j] = j+1;
}
}
//Print out the indexes of all the cards
System.out.println("----------------");
System.out.println("Card indexes :");
for (int i = 0; i < indexArray.length; i++) {
System.out.println();
for (int j = 0; j < indexArray[0].length; j++) {
System.out.print(indexArray[i][j]+ " | ");
}
System.out.println();
}
System.out.println("----------------");
//Set BorderPane
BorderPane root = new BorderPane();
root.setBackground(new Background(new BackgroundFill(Color.BLACK,
CornerRadii.EMPTY, null)));
root.setCenter(gridCard);
//Set the stage
primaryStage.setScene(new Scene(root));
primaryStage.setTitle("Memory Test");
primaryStage.show();
}
//-------------------------------------------------
//Getter for the event handler
public static EventHandler<MouseEvent> gameController() {
return handler;
}
//-------------------------------------------------
//Getter, Setter and "resetter" for the index
public static void resetIndex() { index = 0; }
public static int getIndex() { return index; }
public static void setIndex(int i) {
index = i;
}
//-------------------------------------------------
}
The controller : Controller.java
package memory;
import javafx.event.EventHandler;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.input.MouseEvent;
public class Controller implements EventHandler<MouseEvent>{
//-------------------------------------------------
#Override
public void handle(MouseEvent event) {
//Get the card which cas clicked on
Card card = (Card) event.getSource();
//If the card was already open, don't do anything
if (!Main.isOpen[card.getRow()][card.getColumn()]) {
//Open the card
card.openCard();
//We opened the first card of the turn
if (Main.getIndex() == 0) {
//Set the card as open
Main.isOpen[card.getRow()][card.getColumn()] = true;
//Remember the index
Main.setIndex(Main.indexArray[card.getRow()][card.getColumn()]);
System.out.println("index: "+Main.getIndex());
//We opened the second card
}else if (Main.getIndex() != 0) {
//Check if it is a pair
if (Main.getIndex() == Main.indexArray[card.getRow()][card.getColumn()]) {
//Decrement the number of pairs
Main.pairs--;
//Open the second card
Main.isOpen[card.getRow()][card.getColumn()] = true;
//Reset the index
Main.resetIndex();
}else{ //Close both cards if it isn't a pair
//Wait 0.7 second to let the player remember the cards
try {
Thread.sleep(700);
} catch (InterruptedException e) {
e.printStackTrace();
}
//Close the current card
card.closeCard();
System.out.println("index : " + Main.indexArray[card.getRow()][card.getColumn()]);
Main.isOpen[card.getRow()][card.getColumn()] = false;
//Close the first opened card by looking at the index
//It closes both cards with the same index, but it doesn't matter
//as the pair hasn't been found anyway
for (int i = 0; i < Main.indexArray.length; i++) {
for (int j = 0; j < Main.indexArray[0].length; j++) {
if (Main.getIndex() == Main.indexArray[i][j]) {
Main.cardArray[i][j].closeCard();
System.out.println("index: " + Main.indexArray[i][j]);
Main.isOpen[i][j] = false;
}
}
}
//Reset the index of last opened card
Main.resetIndex();
}
}
}
//Check endgame
if (Main.pairs == 0) {
//Show a dialog box
Alert incorrectPairs = new Alert(AlertType.INFORMATION);
incorrectPairs.setTitle("GAME OVER");
incorrectPairs.setHeaderText("The game is over");
incorrectPairs.setContentText("You found all the pairs, congrats!");
incorrectPairs.showAndWait();
}
}
//-------------------------------------------------
}
You're blocking the UI thread with Thread.sleep(...). That prevents any pending changes from being repainted, so you don't see the first update at all; you only see the subsequent updates after the pause is complete.
The simplest way to implement a pause on the UI thread is to use a PauseTransition from the JavaFX animation API. Basically, you do
PauseTransition pause = new PauseTransition(Duration.millis(700));
pause.setOnFinished(e -> {
// code to execute after pause...
});
pause.play();
In your case, you probably want to disable the user interface, so the user cannot click on anything during the pause, so you could do something like
PauseTransition pause = new PauseTransition(Duration.millis(700));
pause.setOnFinished(e -> {
card.closeCard();
// ... etc...
card.getParent().setDisable(false);
});
card.getParent().setDisable(true);
pause.play();

Categories

Resources