I'm attempting to write a GUI that presents five rectangles in a FlowPane, so that the user may click on them to select a character.
I use the following method to test whether the user has clicked inside a rectangle or not:
#FXML
private Rectangle characterA, characterB, characterC, characterD, character E;
private List<Rectangle> rects;
#FXML
private void initialize() {
rects = Arrays.asList(characterA, characterB, characterC, characterD, character E);
}
#FXML
private void onMousePressed(MouseEvent e) {
Rectangle rect;
for (int i = 0; i < rects.size(); i++) {
rect = rects.get(i);
if (rect.contains(e.getSceneX(), e.getSceneY())) {
System.out.println("Clicked in rect " + i);
}
}
}
My problem is this: although on screen, the rectangles are laid out alongside each other, their coordinates are all apparently the same. Clicking on the first rectangle results in five copies of "Clicked in rect x", whilst clicking elsewhere gives no output at all.
How can I correctly determine which rectangle the user clicked in?
Related
I am trying to design bus seat structure in javafx application using sceneBuilder. Where drawBus is a GridPane which is under an AnchorPane(Bus Structure Anchorpane). This anchorpane is drawn using scenebuilder. and DrawBus GridPane is drawn in the controller class using code. When Loadinfo Button is pressed it draws bus seat structure.
#FXML
private void Loadinfo(ActionEvent event) throws IOException, ParseException {
String co=(String) coachBox.getSelectionModel().getSelectedItem();
getbusinfo(getBusID(co));
coachLbl.setText(co);
SubRouteLbl.setText(source+"-"+destination);
BusNameLbl.setText(busname);
//Adding Gridpane to the anchorpane of Bus Seat structure
int row=Integer.parseInt(totalrows);
int col=Integer.parseInt(totalcolumns);
System.out.println("row: "+row);
System.out.println("col: "+col);
BusStructure.getChildren().add(drawBus(row,col));
AnchorPane.setRightAnchor(drawBus(row,col),0.0);
AnchorPane.setTopAnchor(drawBus(row,col),0.0);
AnchorPane.setBottomAnchor(drawBus(row,col),0.0);
AnchorPane.setLeftAnchor(drawBus(row,col),0.0);
}
Here is my code where drawBus is called. totalrows & totalcolumns are data taken from json file.
public static String totalcolumns;
public static String totalrows;
public static GridPane drawBus(int rows, int col){
GridPane table = new GridPane();
for(int i=0; i<rows; i++){
for(int j=0;j<col; j++)
{
Button seat=new Button();
seat.setAlignment(Pos.CENTER);
seat.setPrefSize(80, 31);
seat.setText(i+","+j);
seat.setStyle("-fx-background-color: MediumSeaGreen");
//add them to the GridPane
table.add(seat, j, i); // (child, columnIndex, rowIndex)
// margins
GridPane.setMargin(seat, new Insets(2));
GridPane.setMargin(seat, new Insets(2));
GridPane.setMargin(seat, new Insets(2));
}
table.setAlignment(Pos.CENTER);
}
return table;
}
Each time coachNumber changes from the choice box The BusInfo is updated and totalrows and totalcolumns changes. To check the changes I have printed the values in Loadinfo function. The row and col changes properly. But the BusStructure(Gridpane) remains as it is updated first time Load Info Button is clicked.
For more detail I am attaching my UI image.
first image when bus seat structure is drawn first time
second image where coach number changes from choicebox but bus structure remains same
You never remove old GridPanes from the AnchorPane. This means you overlay all the results of the drawBus method. Probably the first bus you load is the biggest one and the scene looks the same after adding smaller buses on top of it.
To fix this issue replace GridPane.
Furthermore you create new GridPanes when setting the anchors. This has no effect on the GridPane added to the scene. Also setting the margin 3 times for the same seat does not provide benefits. To assign different values to different sides use the Insets constructor taking 4 parameters. You could also use hgap and vgap (adding a padding to the GridPane if needed) to set the horizontal/vertical distance of children for the GridPane:
public static GridPane drawBus(int rows, int col){
GridPane table = new GridPane();
table.setHgap(4);
table.setVgap(4);
table.setAlignment(Pos.CENTER);
for(int i=0; i<rows; i++){
for(int j=0;j<col; j++) {
Button seat=new Button();
seat.setAlignment(Pos.CENTER);
seat.setPrefSize(80, 31);
seat.setText(i+","+j);
seat.setStyle("-fx-background-color: MediumSeaGreen");
//add them to the GridPane
table.add(seat, j, i); // (child, columnIndex, rowIndex)
}
}
return table;
}
private GridPane currentTable;
#FXML
private void Loadinfo(ActionEvent event) throws IOException, ParseException {
String co=(String) coachBox.getSelectionModel().getSelectedItem();
getbusinfo(getBusID(co));
coachLbl.setText(co);
SubRouteLbl.setText(source+"-"+destination);
BusNameLbl.setText(busname);
//Adding Gridpane to the anchorpane of Bus Seat structure
int row=Integer.parseInt(totalrows);
int col=Integer.parseInt(totalcolumns);
System.out.println("row: "+row);
System.out.println("col: "+col);
GridPane newTable = drawBus(row,col);
if (currentTable == null) {
BusStructure.getChildren().add(newTable);
} else {
// replace table
BusStructure.getChildren().set(BusStructure.getChildren().indexOf(currentTable), newTable);
}
AnchorPane.setRightAnchor(newTable, 0.0);
AnchorPane.setTopAnchor(newTable, 0.0);
AnchorPane.setBottomAnchor(newTable, 0.0);
AnchorPane.setLeftAnchor(newTable, 0.0);
// save reference to table to determine which node to replace
currentTable = newTable;
}
if(!BusStructure.getChildren().isEmpty())
{
BusStructure.getChildren().remove(0);
}
BusStructure.getChildren().add(drawBus(row,col));
AnchorPane.setRightAnchor(drawBus(row,col),0.0);
AnchorPane.setTopAnchor(drawBus(row,col),0.0);
AnchorPane.setBottomAnchor(drawBus(row,col),0.0);
AnchorPane.setLeftAnchor(drawBus(row,col),0.0);
adding this if condition solved my problem.
I'm trying to make a battleship game.
Here's a link to the code: Battleship
Basically, when you click on one of the ship buttons(battleship, cruiser, submarine, etc.) a Rectangle pops up on the screen. You can move this rectangle around the screen by clicking on it and dragging the mouse around. The class that adds all of the event handlers to the Rectangle can be found in class MouseGestures.java.
Event Handlers are added to the GridPane in the GameGUI.java class starting at line 82.
Basically, I'm trying to find the index of whatever node you drag this object over in the GridPane. I'm able to accomplish this without dragging, by applying setOnMouseEntered to each node in the gridpane, but when I try to utilize setOnMouseDragEntered, I get nothing.
Would anyone have an idea for how to get this to work?
Just to reiterate, I want to print in the console the index of each gridpane when I drag and move a node over the nodes contained in the gridpane.
These are the event handlers I have applied to the nodes in the gridpane.
for (int i=0; i<=11; i++) {
for (int j=0; j<=11; j++) {
int colIndex = i;
int rowIndex = j;
grid2[i][j] = new Rectangle();
grid2[i][j].setStroke(Color.BLACK);
grid2[i][j].setFill(null);
grid2[i][j].setStrokeWidth(1);
grid2[i][j].setWidth(30);
grid2[i][j].setHeight(30);
//Prints index of each grid2[][] upon mouse entry.
grid2[i][j].setOnMouseEntered( e -> {
System.out.printf("Mouse entered cell [%d, %d]%n", colIndex, rowIndex);
});
//Does not work! Want to print index of each grid2[][] upon mouse drag enter.
grid2[i][j].setOnMouseDragEntered( e -> {
System.out.printf("Mouse entered cell [%d, %d]%n", colIndex, rowIndex);
});
//Does not work! Want to print index of each grid2[][] upon mouse drag over.
grid2[i][j].setOnMouseDragOver( e -> {
System.out.printf("Mouse entered cell [%d, %d]%n", colIndex, rowIndex);
});
These are the event handlers for the object you create when you push one of the buttons.
private EventHandler<MouseEvent> onMousePressedEventHandler = event -> {
if (event.getSource() instanceof Rectangle) {
shipSelected = (Rectangle) (event.getSource());
dragContext.x = shipSelected.getTranslateX() - shipSelected.getWidth()/2;
dragContext.y = shipSelected.getTranslateY() - shipSelected.getHeight()/2;
}
};
private EventHandler<MouseEvent> onMouseDragDetectedEventHandler = event -> {
shipSelected.startFullDrag();
System.out.println("startfulldrag");
};
private EventHandler<MouseEvent> onMouseDraggedEventHandler = event -> {
if (event.getSource() instanceof Rectangle) {
shipSelected = (Rectangle) (event.getSource());
shipSelected.setX(dragContext.x + event.getX());
shipSelected.setY(dragContext.y + event.getY());
}
};
You need to make the Rectangle you move around mouse transparent with setMouseTransparent(true) while you are dragging it.
private EventHandler<MouseEvent> onMouseDragDetectedEventHandler = event -> {
shipSelected.startFullDrag();
shipSelected.setMouseTransparent(true)
System.out.println("startfulldrag");
};
Otherwise, the Rectangle is always the top-most element under your mouse pointer, and you will not get dragEntered events from the elements below (i.e. the grid).
Of course, do not forget to set mouse transparency back to false, when you ended dragging.
I am writing a board game which has a 20x20 grid.
This is in my board class:
private final Position[][] grid = new Position[GRID_SIZE][GRID_SIZE];
each position has :
public class Position {
private final Coordinates pos;
private Player player;
private final static double RECTANGLE_SIZE = 40.0;
private final Rectangle rect = new Rectangle(RECTANGLE_SIZE, RECTANGLE_SIZE);
}
so basically I have 20x20 Positions and each positions has a Rectangle
This is what I do to display the grid
for (int cols = 0; cols < GRID_SIZE; ++cols) {
for (int rows = 0; rows < GRID_SIZE; ++rows) {
grid.add(gameEngine.getBoard().getGrid()[cols][rows].getRect(), cols, rows);
}
}
Anyway, the grid is initialized and works properly. What I want to do is to make the rectangle objects clickable and to be able to return their Coordinates when they are clicked.
This is how I handle the mouse click
private void setUpRectangle() {
rect.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
rect.setFill(Color.BLACK);
}
});
}
What this code does is to change the color of the rectangle to black, but how could I return the Coordinates.
Basically, I can edit the onclick function to return the coordinates of this position, but how can I acquire them later?
This is not a JavaFX question as much as it is a design question. You have a container (Position) of 2 objects (Coordinates and Rectangle) and you want one of them to know about the other. That is, the rectangle should know the coordinates of its position.
There are a few approaches here, and depending on the bigger picture, one might be better than the others. James_D mentioned a couple in a comment.
Keep a reference of the position object in the rectangle object. This is useful if rectangle needs to access various datum in the container from various places. You would do something like rectangle.getPosition().getCoordinates() or .getPlayer().
Keep a reference of the coordinates object in the rectangle object. This is a more specific approach of 1 useful if you only need that object. You would do something like rectangle.getCoordinates().
Pass the coordinates to your setUpRectangle method. This is useful if you rectangle doesn't need access to this data from various places, it's a local solution. Then in the handle method you would return the coordinates you passed to setUpRectangle, though we can't see what class this method is in.
Use external help. You can keep something like Map<Rectangle, Coordinates> and then call map.get(rectangle). You can hide this map in a method Coordinates getCoordinatesForRectangle(Rectangle rectangle) instead of calling it directly.
You could store this data as userData (or use properties in case userData is preserved for something else in your program):
private final Rectangle rect;
public Position() {
rect = new Rectangle(RECTANGLE_SIZE, RECTANGLE_SIZE);
rect.setUserData(this);
}
rect.setOnMouseClicked((MouseEvent event) -> {
Position pos = (Position) ((Node) event.getSource()).getUserData();
...
});
You could also use a listener that knows about the position:
class CoordinateAwareListener implements EventHandler<MouseEvent> {
private final int coordinateX;
private final int coordinateY;
public CoordinateAwareListener(int coordinateX, int coordinateY) {
this.coordinateX = coordinateX;
this.coordinateY = coordinateY;
}
#Override
public void handle(MouseEvent event) {
// do something with the coordinates
}
}
I am programming a small space-themed dungeon crawler game with a GUI made with Swing in Java. I am using the MVC programming paradigm. The game is represented as a grid on which buttons can be clicked to move the player over the board or attack enemies.
In the model/logic of the game I generate an abstract representation of the game environment using a 2D array by randomly assigning objects to x (column) and y (row) coordinates using the following method:
//methods
public void setGalaxy() {
galaxyArray = new GalacticObject[this.row][this.col];
//construct the array
Random rand = new Random();
int random;
for (int i = 0; i < this.row; i++) {
for (int j = 0; j < this.col; j++) {
GalacticObject obj = new GalacticObject(i,j);
random = rand.nextInt(100);
if (random < 5) {
//asteroid (5 percent chance)
obj.makeAsteroid();
} else if (random < 9) {
//blackhole (4 percent chance)
obj.makeBlackHole();
} else {
//rest is open (= empty)
obj.makeOpen();
}
galaxyArray[i][j] = obj;
}
}
}
Now I want to draw the actual GUI game board backed by this 2D array of objects in a JPanel container using a grid of JButtons. I want to basically call all the objects in the galaxyArray and draw a JButton with an corresponding image overlay at the corresponding coordinates in a ButtonGrid. What would be the best way to achieve this using Swing?
Currently, I also have written the following draft for a method that draws a JButtonGrid but I still fail to see what is the best strategy of having it backed by a 2D array of objects.
Furthermore, adding the icon for the buttons is not working for reasons unclear to me. (I commented that part out.)
public JPanel makeGameBoard() {
/** This method makes a first version of the game board when the object is first called */
//create a new JPanel
JPanel boardPanel = new JPanel();
// create a gridButton of JButtons defined by the width and length
JButton[][] gridButton = new JButton[this.boardWidth][this.boardLength];
// set layout
boardPanel.setLayout(new GridLayout(this.boardWidth,this.boardLength));
//for-loops to place buttons in gridButton
for(int y = 0; y < this.boardLength; y++) {
for(int x = 0; x < this.boardWidth; x++){
// creates new button (with coordinates as string); gridButton[x][y] needs to be reused as coordinate system
gridButton[x][y]=new JButton(x+":"+y);
gridButton[x][y].setActionCommand(x+":"+y);
gridButton[x][y].setText("");
gridButton[x][y].addActionListener(new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
String com = e.getActionCommand();
System.out.println(com);
}
});
// add icon to every button
// try {
// Image img = ImageIO.read(getClass().getResource("resources/invisible.png"));
// gridButton[x][y].setIcon(new ImageIcon(img));
// }
// catch (IOException ex) {
// System.out.println("Image file for gridButton not found!");
// }
// add the gridButton to the panel
boardPanel.add(gridButton[x][y]);
}
}
//return boardPanel
return boardPanel;
}
Essentially I am wondering what is the best strategy to access the objects stored in the galaxyArray from my makeGameBoard method so that the variables of the objects in the array can be used to draw an image on the button with corresponding coordinates in the grid.
/*EDIT Thanks to the suggestion and linked tutorials from the friendly people below, I have made it work! Here is the code for the board View for anyone having similar questions:
public void updateGameBoard(Galaxy board) {
/** This method initializes the board when the galaxy is first created in the game logic and updates it throughout the game */
Galaxy galaxyArray = board;
for (int r = 0; r < this.boardWidth; r++) {
for (int c = 0; c < this.boardLength; c++) {
//make galactic object and extract info from Galaxy
GalacticObject temporary = new GalacticObject(r,c);
temporary = galaxyArray.getGalacticObject(r,c);
//check the object and set the corresponding tile image
if (temporary.isAsteroid()) {
gridButton[r][c].setText("A");
} else if (temporary.isAsteroidField()) {
gridButton[r][c].setText("AF");
} else if (temporary.isBlackHole()) {
gridButton[r][c].setText("BH");
} else if (temporary.isOpen()) {
gridButton[r][c].setText("");
}
}
}
}
The exact details depend on how you implement the observer pattern. Following this outline, the example game cited here simply lets the main view, RCView, keep a private reference to the array named board, which is maintained by the game model, RCModel. Because RCView implements the Observer interface, the view's update() implementation simply iterates through the board and updates its array of tiles in response. Note that the update() method signature includes a reference to the Observable requesting the update. In your example,
public void update(Observable model, Object arg) {
GalacticObject galaxyArray = ((GameModel) model).getGalaxyArray();
//loop though model, updating view components
this.repaint();
}
I am busying writing a simple application using JavaFX2. The goal is just to plot 2 nodes (the nodes are movable by dragging them) and then have a function to draw lines between these nodes.
I finished the functions to add and move nodes (at the moment I am just using Ellipse shapes but I am going to replace it later with my own node class) but now I am struggling with the connecting lines. The actions to add a node or a line is from a dropdown menu and I have the following code on the line function:
private void drawLine(MenuItem line) {
final BooleanProperty lineActive = new SimpleBooleanProperty(false);
final BooleanProperty clickOne = new SimpleBooleanProperty(false);
final BooleanProperty clickTwo = new SimpleBooleanProperty(false);
line.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent t) {
lineActive.set(true);
}
});
nodeGroup.setOnMousePressed(new EventHandler<MouseEvent>() {
public void handle(final MouseEvent t1) {
clickOne.set(true);
if (lineActive.get()) {
if (clickOne.get()) {
//get x and y of first node
x1 = ((Ellipse) t1.getTarget()).getCenterX();
y1 = ((Ellipse) t1.getTarget()).getCenterY();
clickOne.set(false);
clickTwo.set(true);
}
if (clickTwo.get()) {
nodeGroup.setOnMouseClicked(new EventHandler<MouseEvent>() {
public void handle(MouseEvent t2) {
//get x and y of second node
x2 = ((Ellipse) t2.getTarget()).getCenterX();
y2 = ((Ellipse) t2.getTarget()).getCenterY();
//draw line between nodes
final Line line = new Line();
line.setStartX(x1);
line.setStartY(y1);
line.setEndX(x2);
line.setEndY(y2);
canvas.getChildren().add(line);
clickTwo.set(false);
lineActive.set(false);
}
});
}
}
}
});
}
I just have the booleans to check for the first and second click to get the center of each node.
My first question is when I click on the line function and add a line between 2 nodes, it doesn't seem to end the function, and any other nodes I click on gets a line to it. How can I prevent it from executing more than once.
And my second question is how can I "connect" the line to the nodes that if the node moves, the line stays in the center of the node?
Thanks.
I think there is a couple of things which could make this simpler...
Do not use booleans when you have more than two states (no click, click one, click two) use an enum instead. Then you only need one variable to look after.
Only ever set one mouse listener on the nodeGroup and check which state you're in and have the appropriate code there instead of separate mouse listeners.
I imagine that the program is setting the listener for the second click and not resetting it to the listener for the first click when it completes.
Since i am new to Stack Overflow , I tried something on your problem
First add line between two labels
final Line line = new Line();
#Override
public void initialize(URL arg0, ResourceBundle arg1)
{
// TODO Auto-generated method stub
line.setStartX(lblDragTest.getLayoutX());
line.setStartY(lblDragTest.getLayoutY());
line.setEndX(lblNew.getLayoutX());
line.setEndY(lblNew.getLayoutY());
rootAnchorPane.getChildren().add(line);
}
And then add this methods...
// This code handles label move
//set lblDragMousePressed method to Mouse Pressed event for lblDrag
#FXML
public void lblDragMousePressed(MouseEvent m)
{
System.out.println("Mouse is pressed");
prevLblCordX= (int) lblDragTest.getLayoutX();
prevLblCordY= (int) lblDragTest.getLayoutY();
prevMouseCordX= (int) m.getX();
prevMouseCordY= (int) m.getY();
}
//set this method on mouse released event for lblDrag
#FXML
public void lblDragMouseReleased(MouseEvent m)
{
System.out.println("Label Dragged");
}
// set this method on Mouse Drag event for lblDrag
#FXML
public void lblDragMouseDragged(MouseEvent m)
{
diffX= (int) (m.getX()- prevMouseCordX);
diffY= (int) (m.getY()-prevMouseCordY );
int x = (int) (diffX+lblDragTest.getLayoutX()-rootAnchorPane.getLayoutX());
int y = (int) (diffY+lblDragTest.getLayoutY()-rootAnchorPane.getLayoutY());
if (y > 0 && x > 0 && y < rootAnchorPane.getHeight() && x < rootAnchorPane.getWidth())
{
lblDragTest.setLayoutX(x);
lblDragTest.setLayoutY(y);
}
line.setStartX(lblDragTest.getLayoutX());
line.setStartY(lblDragTest.getLayoutY());
line.setEndX(lblNew.getLayoutX());
line.setEndY(lblNew.getLayoutY());
// rootAnchorPane.getChildren().add(line);
}