I have been trying to learn more on using JavaFX and in this program I am trying to display a 3 by 3 game of tic tac toe that has already been played. I have created my ImageViews and set the images I want to use but once I started plugging them into columns and rows I noticed I cannot use the same one twice. I have an image for an empty space, an X, and an O. Once I use one more than once I get an "Exception while running application". Might be a rookie mistake, but an explanation would be greatly appreciated.
package Fresh;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
public class Fresh extends Application {
#Override
public void start(Stage primaryStage) {
//Create a pane and set its properties
GridPane pane = new GridPane();
pane.setAlignment(Pos.CENTER);
pane.setPadding(new Insets(11.5, 12.5, 13.5, 14.5));
pane.setHgap(5.5);
pane.setVgap(5.5);
//imv0 = X image
final ImageView imv0 = new ImageView();
final Image image0 = new Image(Fresh.class.getResourceAsStream("images/x.gif"));
imv0.setImage(image0);
//imv1 = O image
final ImageView imv1 = new ImageView();
final Image image1 = new Image(Fresh.class.getResourceAsStream("images/o.gif"));
imv1.setImage(image1);
//imv2 = empty image
final ImageView imv2 = new ImageView();
final Image image2 = new Image(Fresh.class.getResourceAsStream("images/empty.gif"));
imv2.setImage(image2);
//Place nodes in the pane
pane.add((imv0),0,0);
pane.add((imv1), 1, 0);
//Once I try to use imv0 again "I get an exception while running".
pane.add((imv0),0,1);
//Create a scene and place it in the stage
Scene scene = new Scene(pane);
primaryStage.setTitle("ShowGridPane");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
You can use the same Image as many times as you like; however, you can only place a given ImageView in one place in the scene graph. From the Javadocs:
A node may occur at most once anywhere in the scene graph.
To see why this must be true, what would you expect
GridPane.getColumnIndex(imv0)
to return, given the code you have?
So you can do:
final Image image0 = new Image(Fresh.class.getResourceAsStream("images/x.gif"));
ImageView imv00 = new ImageView(image0);
ImageView imv01 = new ImageView(image0);
pane.add(imv00, 0, 0);
pane.add(imv01, 0, 1);
// etc
The overhead here is not too bad; you use the same image data for each ImageView.
Related
Ok so from my stand point my code is pretty decent enough to get a passing grade but I am having trouble adding a simple refresh/shuffle button. NOT USING the aids of JOptionPane.
Eclipse doesnt seem to recognize that I have created the button which doesnt make sense at all for me because its telling me something about a Node which the Button is in fact a node and it is created. But when I go into another class and add another button with the 3 line example it simply works. But when I move it to my homework program it simply gives me an error on the add method which breaks the whole program!
Says
"The method add(Node) in the type List is not applicable for the arguements (Button)"
Could anyone shed some light of where I could be going wrong in my code? It has to be something along the a node to string conversion or something I just cant seem to figure it out. Willing to take any hints given to me but please DO NOT SOLVE THE PROBLEM FOR ME.
Here is the question from the book basically.
"Write a program that lets the user click the refresh button to display four cards from a deck of 54 cards."
I just need some help on the button thats all. I literally have the rest.
Here is my code so far.
I Have left the imports out as there is just too many.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.stage.Stage;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import java.awt.Button;
import java.io.File;
import java.util.ArrayList;
public class Cards extends Application
{
public void start(Stage primaryStage)
{
ArrayList<String> cards = new ArrayList<>(); //Array list
Shuffle(cards); //Shuffles the Cards
String file1 = new File("cards" + "/" + cards.get(1) + ".png").toURI().toString();
String file2 = new File("cards" + "/" + cards.get(2) + ".png").toURI().toString();
String file3 = new File("cards" + "/" + cards.get(3) + ".png").toURI().toString();
String file4 = new File("cards" + "/" + cards.get(4) + ".png").toURI().toString();
Pane pane = new HBox(20); //Creates the Box for the Images
pane.setPadding(new Insets(5, 5, 5, 5)); //Spreads the Images out
Image image = new Image(file1); //Creates the String Image
Image image2 = new Image(file2);
Image image3 = new Image(file3);
Image image4 = new Image(file4);
pane.getChildren().add(new ImageView(image)); //Adds the First Image
ImageView view1 = new ImageView(image);
view1.setFitHeight(100);
view1.setFitWidth(100);
pane.getChildren().add(new ImageView(image2)); //Adds the Second Image
ImageView view2 = new ImageView(image2);
view2.setFitHeight(100);
view2.setFitWidth(100);
pane.getChildren().add(new ImageView(image3)); //Add the Third Image
ImageView view3 = new ImageView(image3);
view3.setFitHeight(100);
view3.setFitWidth(100);
pane.getChildren().add(new ImageView(image4)); //Add the Fourth Image
ImageView view4 = new ImageView(image4);
view4.setFitHeight(100);
view4.setFitWidth(100);
HBox hbox = new HBox(5); //Creates the Box for the Button
Button shuffle = new Button("Shuffle"); //Creates the Button
hbox.getChildren().add(shuffle); //Should add the button but doesn't
shuffle.addActionListener( e -> //Listener for the button
{
Shuffle(cards);
});
BorderPane pane2 = new BorderPane();/ /Creates the Pane for the Button
pane2.setCenter(pane); //Sets the cards in the Center
pane2.setBottom(hbox); //Sets the Button on the bottom
BorderPane.setAlignment(hbox, Pos.CENTER);
hbox.setAlignment(Pos.BOTTOM_CENTER);//Aligns the Button to BOT_CENTER
Scene scene = new Scene(pane2); //Creates the Scene
primaryStage.setTitle("Cards");
primaryStage.setScene(scene);
primaryStage.show();
}
public void Shuffle(ArrayList<String> cards)
//Allows the cards to Shuffle when called.
{
for (int i = 0; i <= 53; i++) //Sets the Number of Cards in Deck
cards.add(String.valueOf(i+1));
java.util.Collections.shuffle(cards);
}
public static void main(String[] args)
{
launch(args);
}
}
You're using the AWT-button with your import java.awt.Button;, that's why you can use the method public void addActionListener(ActionListener l).
Replace your import to import javafx.scene.control.Button;. Furthermore you could use (analogue to your code) the following lambda:
shuffle.setOnAction( (x) -> //Listener for the button
{
Shuffle(cards);
});
Give it a try :)
I have a question. I need to make a GridPane with a directory choose that will then lead me to a modal dialog showing photos. I cannot figure how to do the modal dialog that also has to be a GridPane or a HBox...so the question is , how do I get to show a Modal Dialog after selecting the Folder and pressing the "Show" Button... Thanks a lot!
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.stage.DirectoryChooser;
import javafx.stage.Stage;
public class FotoView extends Application {
#Override
public void start(Stage primaryStage) {
TextField tf = new TextField();
Button b1 = new Button("Search");
Button b2 = new Button("Show");
DirectoryChooser dc = new DirectoryChooser();
GridPane gp = new GridPane();
gp.add(tf, 0 , 0);
gp.add(b1, 1, 0);
gp.add(b2, 0, 1);
b1.setOnAction(e-> dc.showDialog(primaryStage));
primaryStage.setScene(new Scene(gp)) ;
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
} ```
Below is a quick example where a first window has a button that opens up a DirectoryChooser. Once a directory has been selected a second smaller window opens up with the Modality set to APPLICATION_MODAL. In this second window you could add the image(s) that you load and add them to the GridPane.
import java.io.File;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.stage.DirectoryChooser;
import javafx.stage.Modality;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(final String[] args) {
launch(args);
}
#Override
public void start(final Stage aStage) throws Exception {
final HBox root = new HBox();
final Button browseBtn = new Button("Click to open a Directory chooser");
root.getChildren().add(browseBtn);
browseBtn.setOnAction(e -> {
final DirectoryChooser chooser = new DirectoryChooser();
final File dir = chooser.showDialog(aStage);
openNewModalStage(aStage, dir);
});
final Scene scene = new Scene(root, 500, 500);
aStage.setScene(scene);
aStage.show();
}
private void openNewModalStage(final Stage aStage, final File aDirectory) {
final Stage stage = new Stage();
final GridPane grid = new GridPane();
final Scene scene = new Scene(grid);
grid.setStyle("-fx-background-color:black");
grid.setPrefWidth(400);
grid.setPrefHeight(400);
// get your images from 'aDirectory' and add them to your grid pane.
stage.setScene(scene);
// set the new windows Modality.
stage.initModality(Modality.APPLICATION_MODAL);
stage.show();
}
}
This way you would only need the one button and the dialog would show as soon as you've selected a directory. However, if you would still want a Search and Show button then just store the directory as a variable and add a listener on the 'show' button and move the openNewModalStage call to that one and remove the second argument.
Edit:
Also, depending on how many images and exactly what you want to display in the modal window, you might want to reconsider the GridPane and use a TilePane, or an hbox/vbox inside of a scroll pane. It's just a thought but I don't know what you will be doing with the GridPane.
I'm having a problem positioning JavaFX's HBox in a similar manner to Circle.
If using a circle shape it is possible to manually position it such that it is bound to a different node. This is what I've done until now, by having a Pane as the point of reference:
Pane node; //can be dragged around/resized
//...
Circle terminal = new Circle(10);
terminal.setStroke(Color.GREEN);
terminal.setFill(Color.GREEN);
terminal.centerXProperty().bind( node.layoutXProperty() );
terminal.centerYProperty().bind( node.layoutYProperty() );
The pane (node) functions as a graph node and can be dragged around and resized. The circle functions as a port/terminal for edge connections in the graph. Seeing that the node should have more than one the idea is to put the circles into an HBox that is attached/bound to the pane like the circle has until now. This makes it so that manual layout calculations are unnecessary when adding or removing ports, resizing the node, etc. So the code then used was:
Pane node; //can be dragged around/resized
//...
HBox terminalContainer = new HBox();
terminalContainer.layoutXProperty().bind( node.layoutXProperty() );
terminalContainer.layoutYProperty().bind( node.layoutYProperty() );
//... adding circles into HBox as scenegraph children
The only difference is swapping out the HBox for the Circle and using the layoutXProperty() as there is no centerXProperty(). But of course this fails, and the ports appear glued on to the top part of the containing frame, acting strangely. Is there a fix for this? I tried changing the parenting Pane to an anchorPane, this allowed to manually anchor down the HBox in the correct place, but caused issues with the resizing/dragging code.
Minimal example:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Main2 extends Application {
private AnchorPane component;
#Override
public void start(Stage primaryStage) {
component = new AnchorPane();
Scene scene = new Scene(component, 1024, 768);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
//This works, but is hard to maintain
Cell c1 = new Cell();
Cell c2 = new Cell();
Port p1 = new Port(c1);
Port p2 = new Port(c2);
component.getChildren().addAll(c1, c2, p1, p2);
c1.relocate(150, 150);
c2.relocate(550, 550);
//This does not work, even if unbinding circles, but is simpler
HBox pc1 = new HBox();
HBox pc2 = new HBox();
pc1.layoutXProperty().bind( c1.layoutXProperty() );
pc1.layoutYProperty().bind( c1.layoutYProperty() );
pc2.layoutXProperty().bind( c2.layoutXProperty() );
pc2.layoutYProperty().bind( c2.layoutYProperty() );
Port p3 = new Port(c1);
Port p4 = new Port(c2);
pc1.getChildren().add(p3);
pc2.getChildren().add(p4);
component.getChildren().addAll(pc1, pc2);
}
class Cell extends Pane {
public Cell() {
Rectangle view = new Rectangle(50,50);
view.setStroke(Color.DODGERBLUE);
view.setFill(Color.DODGERBLUE);
getChildren().add(view);
}
}
class Port extends Pane {
public Port(Cell owner) {
Circle view = new Circle(10);
view.setStroke(Color.GREEN);
view.setFill(Color.GREEN);
view.centerXProperty().bind( owner.layoutXProperty() );
view.centerYProperty().bind( owner.layoutYProperty() );
getChildren().add(view);
}
}
public static void main(String[] args) {
launch(args);
}
}
Got it to work, was a typo in the code binding the layoutXProperty twice instead of the layoutYProperty facepalm
I want to set Label to graphic. I tested this code:
private static final ImageView livePerformIcon;
static
{
livePerformIcon = new ImageView(MainApp.class.getResource("/images/Flex.jpg").toExternalForm());
}
final Label label = new Label();
label.setStyle("-fx-background-image: url(\"/images/Flex.jpg\");");
livePerformIcon.setFitHeight(20);
livePerformIcon.setFitWidth(20);
label.setGraphic(livePerformIcon);
But I don't see any image.
The only way that I found to make it work is this:
label.setStyle("-fx-background-image: url(\"/images/Flex.jpg\");");
Is there a way to solve this?
Not sure, but AFAIK controls should be created on the JavaFX Application thread, but you're creating ImageView in a static initializer, which I'm not sure if it's executed on the Application thread.
Besides: Do you really want livePerformIcon to be static???
This one made from the data used in the docs, works perfectly for me
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class LabelWithImages extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
Scene scene = new Scene(new Group());
stage.setTitle("Label With Image Sample");
stage.setWidth(400);
stage.setHeight(180);
HBox hbox = new HBox();
//Replace the image you want to put up
Image image = new Image(getClass().getResourceAsStream("a.png"));
Label label = new Label("Demo Label");
label.setGraphic(new ImageView(image));
hbox.setSpacing(10);
hbox.getChildren().add((label));
((Group) scene.getRoot()).getChildren().add(hbox);
stage.setScene(scene);
stage.show();
}
}
Code snippets below will set the value of the property graphic of a label. You can use any of the two. I prefer using javafx css, just to implement the model-view-controller design.
// programmatically, provided with image input stream
label.setGraphic(new ImageView(new Image(getClass().getResourceAsStream("path/to/image.png"))));
// javafx css, provided with image url
.label {
-fx-graphic: url("path/to/image.png");
}
I would like to add little space between rows every x row in the loop. I find this is better to add empty row to GridPane than setting particular constrains on rows. Problem is I don't know what node should I put into that row to fake empty element. I could do by putting let say Text node. But is this really correct? Can anyone provide more elegant solution?
gridPane.addRow(i, new Text(""));
Using a Text node with an empty string for creating the empty gridpane row is fine.
As an alternative, the sample below uses a Pane to create a "spring" node for the empty grid row which could have it's preferred height set to any required value to achieve whatever gap size you want. Additionally the spring node can also be styled via css if necessary.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
// GridPane with a blank row
// http://stackoverflow.com/questions/11934045/how-to-add-empty-row-in-gridpane-in-javafx
public class GridPaneWithEmptyRowSample extends Application {
public static void main(String[] args) { launch(args); }
#Override public void start(final Stage stage) throws Exception {
// create nodes for the grid.
final Label label1 = new Label("Label 1");
final Label label2 = new Label("Label 2");
final Label label3 = new Label("Label 3");
final Pane spring = new Pane();
spring.minHeightProperty().bind(label1.heightProperty());
// layout the scene.
final GridPane layout = new GridPane();
layout.add(label1, 0, 0);
layout.add(spring, 0, 1);
layout.add(label2, 0, 2);
layout.add(label3, 0, 3);
layout.setPrefHeight(100);
stage.setScene(new Scene(layout));
stage.show();
}
}
I think the best way to solve this is by adding RowConstraints, setting the height of each row in the Gridpane. Then you wont have to add "empty" rows, because each row will acquire the same space regardless of whether it contains anything or not.
Here's a minimal, complete and verifiable example:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.RowConstraints;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class SSCCE extends Application {
#Override
public void start(Stage stage) {
VBox root = new VBox();
GridPane gridPane = new GridPane();
gridPane.add(new Label("First"), 0, 0);
gridPane.add(new Label("Second"), 0, 2);
gridPane.add(new Label("Third"), 0, 3);
// Add one RowConstraint for each row. The problem here is that you
// have to know how many rows you have in you GridPane to set
// RowConstraints for all of them.
for (int i = 0; i <= 3; i++) {
RowConstraints con = new RowConstraints();
// Here we set the pref height of the row, but you could also use .setPercentHeight(double) if you don't know much space you will need for each label.
con.setPrefHeight(20);
gridPane.getRowConstraints().add(con);
}
root.getChildren().add(gridPane);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
The problem with this method is that there's - to my knowledge - no easy way to to get the amount rows in a GridPane, and there's no easy way to add the same RowConstraint to every row in the GridPane. This makes the code rather messy. But you could solve this by e.g. creating your own subclass to GridPane that keeps track of the size.
In the example above we set the pref height of the row, but you could also use .setPercentHeight(double) if you don't know much space you will need for each label.
GridPane gp = new GridPane();
gp.add(" ",2, 2);