It's a problem that it's annoying me for 3 days now.
I have to rewrite the UI of a little tictactoe(Gomoku of n x n) game.
the problem is that when i created the swing GUI , i made a new class that inherits JButton properties and added an int for rows and an int for columns. I cannot do that with SWT(no inheritance). is there a way for me to add the values of i and j to the button.
Here is the example in Swing:
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
final MyJButton button = new MyJButton(i, j);
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
MoveResult move = game.move(button.getRow(), button.getCol());
switch (move) {
case ValidMove:
button.setBackground(game.getCurrentPlayer().getColor());
game.changePlayer();
break;
}
}
}
}
}
}
I give the i and j for the game class which give it to a table clas to check the move.
if (table.getElement(x, y) != PieceType.NONE) return MoveResult.InvalidMove;
private PieceType[][] table;
is there a way to do the same in SWT, any indication is welcomed .
this is what i made
buttonpanel = new Composite(shell, SWT.NONE);
buttonpanel.setLayout(new org.eclipse.swt.layout.GridLayout(cols, true));
buttonTable = new Button[rows][cols];
for (int i = 0; i < rows; ++i){
for (int j = 0; j < cols; ++j) {
gridData.heightHint = 45;
gridData.widthHint = 45;
Button button = new Button(buttonpanel, SWT.PUSH);
button.setLayoutData(gridData);
buttonTable[i][j] = button;
buttonTable[i][j].addSelectionListener(new buttSelectionListener());
// buttonpanel.pack();
}
}
I see two solutions :
use Button's setData method (defined in the Widget superclass) to associate an object containing your x and y (you'll found those data in the event object provided to your listener)
use different listeners for each button
In your case, the first solution seems the most natural one.
This means creating a class holding x and y (let's call it Cell), and doing
button.setData(new Cell(i, j));
and in you listener using
game.move(e.data.x, e.data.y);
Options include:
Subclass Button, and override its checkSubclass() method to indicate that you're taking responsibility for not subclassing harmfully.
Make each Button a Composite, which allows subclassing, and put a Button in the Composite.
Make a separate listener for each button.
In a single listener, search through the buttonTable for the button that calls the listener.
Related
I am trying to make a simple chess GUI, that consists only in a gridLayout where each square is made of a button, each button has an ActionListener and works independantly of the actual game, only "printing out" what is happening in the Board class.
private JButton[][] squares = new JButton[8][8];
private Color darkcolor = Color.decode("#D2B48C");
private Color lightcolor = Color.decode("#A0522D");
public ChessGUI(){
super("Chess");
contents = getContentPane();
contents.setLayout(new GridLayout(8,8));
ButtonHandler buttonHandler = new ButtonHandler();
for(int i = 0; i< 8; i++){
for(int j = 0;j < 8;j++){
squares[i][j] = new JButton();
if((i+j) % 2 != 0){
squares[i][j].setBackground(lightcolor);
}else{
squares[i][j].setBackground(darkcolor);
}
contents.add(squares[i][j]);
squares[i][j].addActionListener(buttonHandler);
squares[i][j].setSize(75, 75);
}
}
setSize(600, 600);
setResizable(true);
setLocationRelativeTo(null);
setVisible(true);
}
private class ButtonHandler implements ActionListener{
public void actionPerformed(ActionEvent e){
Object source = e.getSource();
for(int i = 0;i< 8; i++){
for(int j = 0;j < 8;j++){
if(source == squares[i][j]){
//Pass set of coordinates to game.Move(...)
return;
}
}
}
}
}
I need to pass 2 sets of coordinates, the "from" and the "to" to the game.Move(...) function(function that aplies game movement logic), where each set of coordinates is given by a click in a button.
How should i handle the fact that i need to wait for the user to make 2 clicks before calling the game.Move(...) function? It seems it can only pass 1 set of coordinates at a time.
Any help would be apricieated.
In game object you should probably have field variables that will store the from and to coordinates instead of passing them into game.move function. You can make a counter that starts at 0. When it is 0, it will process the "from" click (set the "from" variable in Game to the coordinate that was clicked), and when it is 1, it will process the "to" click (set "to" variable in Game), call move, and reset counter back to 0.
Okay, so when initializing the playboard for Minesweeper, my code iterates through all the buttons created in the pane and sets the text to either an X for bomb or a number (indicating how many bombs are neighbors). If it's neither it does nothing. But now I wonder how to hide that text when initializing the game so that it can be uncovered and later recovered by clicking the mouse?
Here's the iteration logic:
//iterate through rows and columns to fill board with random bombs
for (int y = 0; y < model.Y_FIELDS; y++) {
for (int x = 0; x < model.X_FIELDS; x++) {
Field field = new Field(x, y, Math.random() < 0.2, model);
model.array[x][y] = field;
root.getChildren().add(field);
}
}
for (int y = 0; y < model.Y_FIELDS; y++) {
for (int x = 0; x < model.X_FIELDS; x++) {
Field field = model.array[x][y];
if (field.isBomb())
continue;
long number = field.getSurrounding().stream().filter(f -> f.isBomb()).count();
if (number > 0)
field.board.setText(String.valueOf(number));
}
}
I would like them to be blank at first. Where do I put setText("")? In a left mouse click event I want to uncover them. That would look something like if(leftmouseclick) then set.Visible or something like that...
You can use for example the PseudoClass API to change the CSS pseudo state of your Buttons between "revealed" and "unrevealed".
You need to define a CSS pseudo class like:
.button:unrevealed { -fx-text-fill: transparent; }
which will represent the button when it was not pressed yet and makes the text of the Button invisible.
And you have to define the JavaFX PseudoClass like:
PseudoClass unrevealedPseudo = PseudoClass.getPseudoClass("unrevealed");
Then to use it:
Button button = new Button("X");
button.pseudoClassStateChanged(unrevealedPseudo, true);
button.setOnAction(e -> button.pseudoClassStateChanged(unrevealedPseudo, false));
In the snippet the Button is set to be "unrevealed" when it is created, then on press leaves that state, therefore the -fx-text-fill property will be changed back to the default one.
If you apply the same creation logic for all of your buttons, it does not matter what the initial text of them, as it is hidden until it is not revealed (by button press or by programatically changing it).
Note1: You can use the same API to define more pseudo-classes which will be handy if you for example want to set a "flag" on the button on right click, as you can simply use these CSS classes to define how the buttons should look like in the different states.
Note2: If you have a backend, that stores the state of each field (revealed, flagged, unrevealed) for example using a property, in the frontend while creating a separate Button for each element of the domain model, you can simply check for the update of the state property of the element in the model, and you can simply put the Button into the correct pseudo class. It is much more elegant then changing it on e.g. button click.
An example with the approach in Note2:
Model:
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
public class MineSweeperField {
public enum State {
UNREVEALED, REVEALED, FLAGGED
};
private ObjectProperty<State> state = new SimpleObjectProperty<State>(State.UNREVEALED);
public ObjectProperty<State> stateProperty() {
return state;
}
public State getState() {
return stateProperty().get();
}
public void setState(State state) {
stateProperty().set(state);
}
}
Button:
import application.MineSweeperField.State;
import javafx.css.PseudoClass;
import javafx.scene.control.Button;
public class MineSweepButton extends Button {
static PseudoClass unrevealedPseudo = PseudoClass.getPseudoClass("unrevealed");
static PseudoClass revealedPseudo = PseudoClass.getPseudoClass("revealed");
static PseudoClass flaggedPseudo = PseudoClass.getPseudoClass("flagged");
public MineSweepButton(MineSweeperField field) {
this.getStyleClass().add("minesweep-button");
this.pseudoClassStateChanged(unrevealedPseudo, true);
field.stateProperty().addListener((obs, oldVal, newVal) -> changePseudoClass(newVal));
changePseudoClass(field.getState());
}
private void changePseudoClass(State state) {
this.pseudoClassStateChanged(unrevealedPseudo, false);
this.pseudoClassStateChanged(revealedPseudo, false);
this.pseudoClassStateChanged(flaggedPseudo, false);
switch (state) {
case FLAGGED:
this.pseudoClassStateChanged(flaggedPseudo, true);
break;
case REVEALED:
this.pseudoClassStateChanged(revealedPseudo, true);
break;
case UNREVEALED:
this.pseudoClassStateChanged(unrevealedPseudo, true);
break;
}
}
}
CSS:
.minesweep-button:unrevealed { -fx-text-fill: transparent; }
.minesweep-button:revealed { -fx-text-fill: black; }
.minesweep-button:flagged { -fx-text-fill: orange; }
Usage:
BorderPane root = new BorderPane();
MineSweeperField field = new MineSweeperField();
MineSweepButton msButton = new MineSweepButton(field);
msButton.setText("5");
Button reveal = new Button("Reveal");
Button unreveal = new Button("Unreveal");
Button flag = new Button("Flag");
root.setTop(new VBox(msButton, new HBox(reveal, unreveal, flag)));
reveal.setOnAction(e -> field.setState(State.REVEALED));
unreveal.setOnAction(e -> field.setState(State.UNREVEALED));
flag.setOnAction(e -> field.setState(State.FLAGGED));
And the output:
Just don't set the text before the button is clicked. If you store the Buttons in a GridPane, the row and column indices are stored in the Buttons anyways. The mines could simply be stored in a boolean[][] array and looked up based on the indices. BTW: I recommend using ToggleButtons, since they already provide a selected and a unselected state, which could be used to represent nodes already uncovered.
private static boolean checkMine(boolean[][] mines, int row, int column) {
return row >= 0 && column >= 0 && row < mines.length && column < mines[row].length && mines[row][column];
}
#Override
public void start(Stage primaryStage) {
GridPane field = new GridPane();
boolean[][] mines = new boolean[][]{
new boolean[]{false, false, false},
new boolean[]{false, true, false},
new boolean[]{false, false, false}
};
EventHandler<ActionEvent> handler = event -> {
ToggleButton source = (ToggleButton) event.getSource();
// find column/row indices in GridPane
Integer row = GridPane.getRowIndex(source);
Integer column = GridPane.getColumnIndex(source);
int r = row == null ? 0 : row;
int c = column == null ? 0 : column;
boolean mine = mines[r][c];
if (mine) {
source.setText("X");
System.out.println("you loose");
// TODO: Represent lost state in GUI
} else {
int mineCount = 0;
// count surrounding mines
for (int i = -1; i < 2; i++) {
for (int j = -1; j < 2; j++) {
if (checkMine(mines, r + i, c + j)) {
mineCount++;
}
}
}
if (mineCount > 0) {
source.setText(Integer.toString(mineCount));
}
}
source.setDisable(true);
// keep activated look
source.setOpacity(1);
};
for (int i = 0; i < mines.length; i++) {
boolean[] row = mines[i];
for (int j = 0; j < row.length; j++) {
ToggleButton toggleButton = new ToggleButton();
toggleButton.setPrefSize(30, 30);
toggleButton.setOnAction(handler);
field.add(toggleButton, j, i);
}
}
Scene scene = new Scene(field);
primaryStage.setScene(scene);
primaryStage.show();
}
I am working on coding a MineSweeper game using JavaFx. I am having an issue with changing the Buttons to just include an image and not have text. A portion of my code is as follows:
ImageView bomb;
Image bombImage = new Image(MineSweeper.class.getResourceAsStream("images/bomb.png"));
bomb = new ImageView(bombImage);
boolean[][] mineField = new boolean[row][column];
for (int i = 0; i < numMines; i++) {
int indexRow = isMine.nextInt(row);
int indexCol = isMine.nextInt(column);
System.out.println("row: " + indexRow + ", column: " + indexCol);
mineField[indexRow][indexCol] = true;
}
for (int i = 0; i < row; i++) {
for (int j = 0; j < column; j++) {
System.out.println("" + mineField[i][j]);
if (mineField[i][j] == true) {
board[i][j].setText("");
board[i][j].setGraphic(bomb);
} else {
board[i][j].setText("Nope!");
}
}
}
This is not how the actual game will work. But I was wanting to check if I could add the image of the bomb to the buttons that contain a mine. When I run the code there is only one image of a mine that shows up and the other buttons just have empty text or say "Nope!." If I cannot figure out how to add an image to a button then I will not be able to actually continue with the programming of the game. I have decided that I want to build this game from scratch and not use Scene Builder. I appreciate any advice.
The reason for only one image is displayed that you cannot use the same ImageView as graphic for two Button objects.
An optional icon for the Labeled. This can be positioned relative to
the text by using
setContentDisplay(javafx.scene.control.ContentDisplay). The node
specified for this variable cannot appear elsewhere in the scene
graph, otherwise the IllegalArgumentException is thrown. See the class
description of Node for more detail.
Modify this line ...
board[i][j].setGraphic(bomb);
... to ...
board[i][j].setGraphic(new ImageView(bombImage ));
This will create a new ImageView object for all of your Buttons.
My program is consist of two classes(test and paintClass) in different files. In the paintClass class I draw a 5x5 square board by using paintComponent method. I want to add buttons in each small square in the big square. When I run the code I don't get any buttons. I want to have 25(5x5) buttons by using jpanel on a shape drawn by paintComponent. Is this possible? If it is, how I can do it?
EDIT : The problem was the loop. Number had a default value of 0 so the loop didn't work. I defined number at the beginning. It solved the problem. Also one of the invervals were wrong. I changed j = 0 with j = 1.
import javax.swing.*;
import java.awt.*;
public class test
{
public static void main(String[] args)
{
JFrame frame = new JFrame("buttons");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(250,250);
PaintClass paint = new PaintClass();
paint.repaint();
f1.getContentPane().add(paint);
frame.pack();
frame.setVisible(true);
}
}
import javax.swing.*;
import java.awt.*;
public class PaintClass extends JPanel
{
private Graphics g;
private int interval,side,number;
private JButton button;
public PaintClass()
{
number = 5;
button = new JButton();
setLayout(new GridLayout(5,5));
for(int i = 0; i <= number - 1; i++)
{
for(int j = 1; j <= number - 1; j++)
{
button = new JButton();//ADDED
button.setBounds(i * interval, 0, interval, interval);
add(button);
}
button = new JButton();//ADDED
button.setBounds(0, i * interval, interval, interval);
add(button);
}
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
this.repaint();
side = 250;
number = 5;
interval = side / number;
g.drawRect(0,0, side, side);
for(int i = 0; i <= number - 1; i++)
{
for(int j = 0; j <= number - 1; j++)
{
g.drawLine(i * interval, 0, i * interval, side);
}
g.drawLine(0, i * interval, side, i * interval);
}
}
}
Choose one or the other: either add the buttons using the GridLayout, or paint the buttons using paintComponent. If the former, you should a) define the loop constraint (right now it is 0) b) create a new JButton for every loop (your code currently reuses the instance) and c) register the appropriate ActionListener to respond to events. If the latter, you need to register the appropriate listener (like MouseListener) to respond to user generated events.
private int interval,side,number;
Number has a default value of 0.
for(int i = 0; i <= number - 1; i++)
Since number is 0, your loop will never execute.
Once you do this the buttons will be added to the panel but they will cover your custom painting. To see background lines you just need to set the background of the panel to Color.BLACK and then create your GridLayout with a gap between the components. Read the API for the method to use.
I am currently working on a Java class that produces a simple JFrame/JButton layout of Tic-Tac-Toe. Implementing ActionListener, I intended on having the selected JButton set its title to "X" or "O" (based on a boolean statement of whether or not it is X's turn to pick a JButton) and become disabled (so it cannot be played on top of in following turns). The current application I have created does this, but it will sometimes not change the JButton text or disable the button until I click another button. There does not seem to be any kind of cohesive order at which this happens when I click one of the JButtons. I have spent hours trying to fix this issue with no avail. Is there an issue with how I coded my actionPerformed method or how I added it to my JButtons?
Here is the code to my class:
import javax.swing.*;
import java.awt.event.*;
import javax.swing.*;
public class TTT extends JFrame implements ActionListener{
// private fields
private JButton[] buttonArray;
private JLabel prompt;
private boolean turnX;
private String letter;
public TTT() {
// Instantiates JFrame window and adds lines to board
super.setSize(235, 280);
super.setTitle("Tic-Tac-Toe");
// Instantiates JButton array
buttonArray = new JButton[9];
// Loop that creates the JButton squares
for(int y = 30; y <= 140; y += 55) {
for(int x = 30; x <= 140; x += 55) {
for(int index = 0; index < buttonArray.length; index++) {
buttonArray[index] = new JButton();
buttonArray[index].setSize(50, 50);
buttonArray[index].setLocation(x, y);
buttonArray[index].addActionListener(this);
super.add(buttonArray[index]);
}
}
}
prompt = new javax.swing.JLabel("X's TURN");
prompt.setVerticalAlignment(JLabel.BOTTOM);
super.add(prompt);
turnX = true;
super.setVisible(true);
}
public void actionPerformed(java.awt.event.ActionEvent a) {
// Calculate whose turn it is
if(turnX){
letter = "X";
prompt.setText("O's TURN");
turnX = false;
} else if(!turnX){
letter = "O";
prompt.setText("X's TURN");
turnX = true;
}
JButton pressedButton = (JButton)a.getSource();
pressedButton.setText(letter);
pressedButton.setEnabled(false);
super.repaint();
}
public static void main(String[] args) {
new TTT();
}
}
With this code:
for(int y = 30; y <= 140; y += 55) {
for(int x = 30; x <= 140; x += 55) {
for(int index = 0; index < buttonArray.length; index++) {
You are adding 82(!) JButtons (one on top of the other in groups of 9). So, what was actually happening was that one was changed (disabled etc), but on calling repaint, the order of painting them might change, so the one whose ActionListener was triggered was painted underneath one of the 8 that were still enabled and blank.
You can correct it like this:
...
int index = 0; // <-- Add this line
for(int y = 30; y <= 140; y += 55) {
for(int x = 30; x <= 140; x += 55) {
// <-- remove the third for-loop
buttonArray[index] = new JButton();
buttonArray[index].setSize(50, 50);
buttonArray[index].setLocation(x, y);
buttonArray[index].addActionListener(this);
super.add(buttonArray[index]);
index++; // <-- increment 'index'
}
}
...
You could also remove the super.repaint() line, since it is redundant.
Not directly related to your problem, but Swing related stuff should (for various reasons) be called from the Event Dispatching Thread (EDT). One way to achieve this, is by using SwingUtilities.invokeLater(), so it might be a good idea to replace new TTT(); with this:
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new TTT();
}
});