How to add zooming and panning functions to a JPanel? - java

So I have a program in Java which uses a grid bag layout to create cells(like graph paper), and if a mouse is over one of the cell said cell will change its color. I would like to implement a zoom function, using mouse scroll wheel. Also I would like to add a function that, when your zoomed in, you can pan around by clicking and dragging the zoomed in canvas. To specify panning around, think of clicking and dragging to move around a zoomed in map, like google maps.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.Border;
import javax.swing.border.MatteBorder;
public class Testing {
public int RowI = 10;
public int ColI = 10;
public static void main(String[] args) {
new Testing();
}
public Testing() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
for (int row = 0; row < RowI; row++) {
for (int col = 0; col < ColI; col++) {
gbc.gridx = col;
gbc.gridy = row;
CellPane cellPane = new CellPane();
Border border = null;
if (row < 4) {
if (col < 4) {
border = new MatteBorder(1, 1, 0, 0, Color.GRAY);
} else {
border = new MatteBorder(1, 1, 0, 1, Color.GRAY);
}
} else {
if (col < 4) {
border = new MatteBorder(1, 1, 1, 0, Color.GRAY);
} else {
border = new MatteBorder(1, 1, 1, 1, Color.GRAY);
}
}
cellPane.setBorder(border);
add(cellPane, gbc);
}
}
}
}
public class CellPane extends JPanel { //The CellPane class changes the color of an individual cell based on whether or no the mouse I on a cell.
private Color defaultBackground; //This is a private color that is only used by the mouseListener.
public CellPane() {
addMouseListener(new MouseAdapter() {
#Override
public void mouseEntered(MouseEvent e) { //If mouse is on cell turn cell the given color(in this case green).
defaultBackground = getBackground();
setBackground(Color.GREEN);
}
#Override
public void mouseExited(MouseEvent e) { //If mouse is not on cell revert to default background.
setBackground(defaultBackground);
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(50, 50); //Cell size on x and y axis.
}
}
}

Maybe this question will answer what you're looking for.

Related

How to make all images in Grid Overlay flush?

I am trying to implement a GUI for a maze-based game I created that meets the following specific conditions:
The GUI itself has a set size and is not resizable (line 41) .
The master panel (line 57) that contains all the maze images is scrollable. All maze image components are flush with each other.
If maze is small enough, then entire maze will be visible in master panel.
If maze is very large, then user would need to scroll.
The master panel needs to be accessed by a mouse listener (line 130) that returns the component that is being clicked.
The following code seems to meet criteria 1 and 3, but fails criteria 2:
public class MazeGui extends JFrame implements DungeonView {
private final Board board;
public MazeGui(ReadOnlyModel m) {
//this.setSize(m.getNumRows()*100, m.getNumCols()*100);
this.setSize(600, 600);
this.setLocation(200, 200);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setResizable(false);
this.board = new Board(m);
JScrollPane scroller = new JScrollPane(board);
this.add(scroller, BorderLayout.CENTER);
setTitle("Dungeon Escape");
}
private class Board extends JPanel {
private ReadOnlyModel m;
public Board(ReadOnlyModel m) {
this.m = m;
GridLayout layout = new GridLayout(m.getNumRows(),m.getNumCols(), 0, 0);
// layout.setHgap(-100);
// layout.setVgap(-100);
this.setLayout(layout);
this.setSize(m.getNumRows()*64,m.getNumCols()*64);
for (int i = 0; i < m.getNumRows() * m.getNumCols(); i++) {
try {
// load resource from the classpath instead of a specific file location
InputStream imageStream = getClass().getResourceAsStream(String.format("/images/%s.png", m.getRoomDirections(i + 1)));
// convert the input stream into an image
Image image = ImageIO.read(imageStream);
// add the image to a label
JLabel label = new JLabel(new ImageIcon(image));
label.setPreferredSize(new Dimension(64, 64));
JPanel panel = new JPanel();
panel.setSize(64, 64);
String name = String.format("%d", i);
panel.setName(name);
panel.add(label);
// add the label to the JFrame
//this.layout.addLayoutComponent(TOOL_TIP_TEXT_KEY, label);
this.add(panel);
} catch (IOException e) {
JOptionPane.showMessageDialog(this, e.getMessage());
e.printStackTrace();
}
}
}
}
#Override
public void addClickListener(DungeonController listener) {
Board board = this.board;
MouseListener mouseListener = new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
System.out.println(String.format("(%d,%d)", e.getX(), e.getY()));
JPanel panel = (JPanel) board.getComponentAt(e.getPoint());
System.out.println(panel.getName());
}
};
board.addMouseListener(mouseListener);
}
#Override
public void refresh() {
this.repaint();
}
#Override
public void makeVisible() {
this.setVisible(true);
}
}
Here is an image of what it produces:
First, I'd make use of a different layout manager, one which would try and expand to fit the size of the underlying container.
Then, I would let the components do their jobs. I don't know why you're adding the label to another panel, the panel doesn't seem to be adding additional functionality/features and is just adding to the complexity.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
List<Maze.Direction> directions = new ArrayList<>(32);
directions.add(Maze.Direction.EAST_SOUTH);
directions.add(Maze.Direction.EAST_SOUTH_WEST);
directions.add(Maze.Direction.EAST_SOUTH_WEST);
directions.add(Maze.Direction.EAST_SOUTH_WEST);
directions.add(Maze.Direction.EAST_SOUTH_WEST);
directions.add(Maze.Direction.SOUTH_WEST);
directions.add(Maze.Direction.NORTH_EAST_SOUTH);
directions.add(Maze.Direction.NORTH_EAST_SOUTH_WEST);
directions.add(Maze.Direction.NORTH_EAST_SOUTH_WEST);
directions.add(Maze.Direction.NORTH_EAST_SOUTH_WEST);
directions.add(Maze.Direction.NORTH_EAST_SOUTH_WEST);
directions.add(Maze.Direction.NORTH_SOUTH_WEST);
directions.add(Maze.Direction.NORTH_SOUTH);
directions.add(Maze.Direction.NORTH_SOUTH);
directions.add(Maze.Direction.NORTH_SOUTH);
directions.add(Maze.Direction.NORTH_SOUTH);
directions.add(Maze.Direction.NORTH_SOUTH);
directions.add(Maze.Direction.NORTH_SOUTH);
directions.add(Maze.Direction.NORTH_SOUTH);
directions.add(Maze.Direction.NORTH_SOUTH);
directions.add(Maze.Direction.NORTH_SOUTH);
directions.add(Maze.Direction.NORTH_SOUTH);
directions.add(Maze.Direction.NORTH_SOUTH);
directions.add(Maze.Direction.NORTH_SOUTH);
directions.add(Maze.Direction.NORTH);
directions.add(Maze.Direction.NORTH);
directions.add(Maze.Direction.NORTH);
directions.add(Maze.Direction.NORTH);
directions.add(Maze.Direction.NORTH);
directions.add(Maze.Direction.NORTH);
System.out.println(directions.size());
Maze maze = new DefaultMaze(5, 6, directions);
MazeGui frame = new MazeGui(maze);
frame.addClickListener(null);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public interface Maze {
enum Direction {
EAST_SOUTH("EastSouth.png"), EAST_SOUTH_WEST("EastSouthWest.png"), SOUTH_WEST("SouthWest.png"),
NORTH_EAST_SOUTH("NorthEastSouth.png"), NORTH_EAST_SOUTH_WEST("NorthEastSouthWest.png"),
NORTH_SOUTH_WEST("NorthSouthWest.png"), NORTH_SOUTH("NorthSouth.png"), NORTH("North.png");
private BufferedImage image;
private Direction(String name) {
try {
image = ImageIO.read(getClass().getResource("/images/" + name));
} catch (IOException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
}
}
public BufferedImage getImage() {
return image;
}
}
public int getRows();
public int getColumns();
public Direction getRoomDirections(int index);
}
public class DefaultMaze implements Maze {
int rows;
int columns;
private List<Direction> directions;
public DefaultMaze(int rows, int columns, List<Direction> directions) {
this.rows = rows;
this.columns = columns;
this.directions = directions;
}
public int getRows() {
return rows;
}
public int getColumns() {
return columns;
}
#Override
public Direction getRoomDirections(int index) {
return directions.get(index);
}
}
public class MazeGui extends JFrame {
// Missing code
public interface DungeonController {
}
private final Board board;
public MazeGui(Maze m) {
this.setSize(600, 600);
this.setResizable(false);
this.board = new Board(m);
JScrollPane scroller = new JScrollPane(board);
this.add(scroller, BorderLayout.CENTER);
setTitle("Dungeon Escape");
}
public Board getBoard() {
return board;
}
public void addClickListener(DungeonController listener) {
Board board = getBoard();
board.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
Component cell = board.getComponentAt(e.getPoint());
System.out.println(cell.getName());
board.highlight(cell.getBounds());
}
});
}
private class Board extends JPanel {
private Rectangle selectedCell;
private Maze maze;
public Board(Maze maze) {
this.maze = maze;
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
for (int index = 0; index < maze.getRows() * maze.getColumns(); index++) {
Maze.Direction direction = maze.getRoomDirections(index);
JLabel label = new JLabel(new ImageIcon(direction.getImage()));
label.setName(direction.name());
add(label, gbc);
gbc.gridx++;
if (gbc.gridx >= maze.getColumns()) {
gbc.gridx = 0;
gbc.gridy++;
}
}
// addMouseListener(new MouseAdapter() {
// #Override
// public void mouseClicked(MouseEvent e) {
// Component component = getComponentAt(e.getPoint());
// selectedCell = null;
// if (component != null) {
// selectedCell = component.getBounds();
// }
// repaint();
// }
// });
}
public void highlight(Rectangle bounds) {
selectedCell = bounds;
repaint();
}
#Override
public void paint(Graphics g) {
super.paint(g);
if (selectedCell != null) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(new Color(0, 0, 255, 128));
g2d.fill(selectedCell);
g2d.dispose();
}
}
}
}
}
The GUI itself has a set size and is not resizable
So the issue here is that you are forcing the "board" panel to have an arbitrary size.
this.setSize(600, 600);
The actual size of the panel should be 8 * 64 = 512. So extra space is being added to each grid.
Don't hardcode size values.
It is the job of the layout manager to determine the preferred size of each component.
So instead of using setSize(...) you should pack() the frame before making it visible:
this.pack();
this.setVisible(true);
When you do this you will see that the maze fits completely in the frame.
If you want extra space around the maze then you need to add a "border" to your board:
setBorder( new EmptyBorder(88, 88, 88, 88) );
GridLayout layout = new GridLayout(m.getNumRows(),m.getNumCols(), 0, 0);
Turns out I should have been using GridBagLayout!
There is no need to change layout managers, only use the layout managers more effectively.
If you really for some reason need to specify a fixed frame size then you can make the following change:
//this.add(scroller, BorderLayout.CENTER);
JPanel wrapper = new JPanel( new GridBagLayout() );
wrapper.add(scroller, new GridBagConstraints());
this.add(wrapper, BorderLayout.CENTER);
This will allow the "board" panel to be displayed at its preferred size and the "board" panel will be centered in its parent container.
Using these tips will help you effectively create more complicated layouts.

Get click position in grid. Java Swing

I got this code witch creates a clickable grid that shows the mouse position, altough i am not able to get the position in the grid in where the mouse is clicked, trying to be both X and Y position. Any ideas? This is how the grid looks:
Code:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.Border;
import javax.swing.border.MatteBorder;
public class TestGrid02 {
public TestGrid02() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private static final int ROWS = 20;
private static final int COLUMNS = 20;
private static GridBagConstraints gbc;
public TestPane() {
setLayout(new GridBagLayout());
gbc = new GridBagConstraints();
for (int row = 0; row < ROWS; row++) {
for (int col = 0; col < COLUMNS; col++) {
gbc.gridx = col;
gbc.gridy = row;
CellPane cellPane = new CellPane();
Border border = null;
if (row < ROWS-1) {
if (col < COLUMNS-1) {
border = new MatteBorder(1, 1, 0, 0, Color.GRAY);
} else {
border = new MatteBorder(1, 1, 0, 1, Color.GRAY);
}
} else {
border = new MatteBorder(1, 1, 1, 0, Color.GRAY);
}
cellPane.setBorder(border);
add(cellPane, gbc);
}
}
}
}
public class CellPane extends JPanel {
private Color defaultBackground;
public CellPane() {
addMouseListener(new MouseAdapter() {
#Override
public void mouseEntered(MouseEvent e) {
defaultBackground = getBackground();
setBackground(Color.RED);
}
#Override
public void mouseExited(MouseEvent e) {
setBackground(defaultBackground);
}
#Override
public void mouseClicked(MouseEvent e){
//Here is where it is supposed to be
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(30, 30);
}
}
}
In the CellPane class, witch is intended to be the one that listens to the mouse it is supposed to be the function that i need, at the mouseClicked listener, however i have tried with e.getX() or e.getLocationOnScreen() and these values were changing everytime i click in the same grid.
Well, that looks familiar 🤣
So, the basic idea would be to pass in the cell it's coordinates (ie, row/column) value via the constructor, for example...
public class CellPane extends JPanel {
private Color defaultBackground;
private Point cellCoordinate;
public CellPane(Point cellCoordinate) {
this.cellCoordinate = cellCoordinate;
addMouseListener(new MouseAdapter() {
#Override
public void mouseEntered(MouseEvent e) {
defaultBackground = getBackground();
setBackground(Color.RED);
}
#Override
public void mouseExited(MouseEvent e) {
setBackground(defaultBackground);
}
#Override
public void mouseClicked(MouseEvent e) {
//Here is where it is supposed to be
System.out.println("Did click cell # " + getCellCoordinate().x + "x" + getCellCoordinate().y);
}
});
}
public Point getCellCoordinate() {
return cellCoordinate;
}
#Override
public Dimension getPreferredSize() {
return new Dimension(30, 30);
}
}
The cell itself doesn't really care, not does it have any reasonable information available to it to determine how it's been laid out, so your best bet is to "tell" it the information you want it to represent.
For me, I'd just pass in the GridBagLayout row/col information, for example...
gbc.gridx = col;
gbc.gridy = row;
CellPane cellPane = new CellPane(new Point(col, row));
This way you remove all concept (and the issues associated with it) of how the cell is laid out
This approach (using buttons and action listeners) is better IMO. It uses the getButtonRowCol method to return a string indicating the button's location in an array (the buttonArray).
Use b.setBorderPainted(false); (uncomment that code line) to get rid of the borders around each button. Look to the values passed to the constructor of the GridLayout to remove the space between buttons.
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
public class GameGridLayout {
int size = 40;
int iconSize = 10;
JButton[][] buttonArray = new JButton[size][size];
ActionListener actionListener;
JLabel output = new JLabel("Click somewhere on the GUI");
GameGridLayout() {
JPanel gui = new JPanel(new BorderLayout(2,2));
gui.setBorder(new EmptyBorder(4,4,4,4));
gui.add(output,BorderLayout.PAGE_END);
JPanel gameContainer = new JPanel(new GridLayout(0,size,2,2));
gui.add(gameContainer);
actionListener = e -> output.setText(getButtonRowCol((JButton)e.getSource()));
for (int ii=0; ii<size*size; ii++) {
JButton b = getButton();
gameContainer.add(b);
buttonArray[ii%size][ii/size] = b;
}
JFrame f = new JFrame("GameGridLayout");
f.add(gui);
f.pack();
f.setMinimumSize(f.getSize());
f.setLocationByPlatform(true);
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
f.setVisible(true);
}
private String getButtonRowCol(JButton button) {
StringBuilder sb = new StringBuilder();
for (int xx=0; xx<size; xx++) {
for (int yy=0; yy<size; yy++) {
if (button.equals(buttonArray[xx][yy])) {
sb.append("User selected button at: ");
sb.append(xx+1);
sb.append(",");
sb.append(yy+1);
break;
}
}
}
return sb.toString();
}
private JButton getButton() {
JButton b = new JButton();
b.setIcon(new ImageIcon(
new BufferedImage(iconSize,iconSize,BufferedImage.TYPE_INT_ARGB)));
b.setRolloverIcon(new ImageIcon(
new BufferedImage(iconSize,iconSize,BufferedImage.TYPE_INT_RGB)));
b.setMargin(new Insets(0,0,0,0));
//b.setBorderPainted(false);
b.setContentAreaFilled(false);
b.addActionListener(actionListener);
return b;
}
public static void main(String[] args) {
Runnable r = () -> new GameGridLayout();
SwingUtilities.invokeLater(r);
}
}

Drawing a line at a specific point in JFrame

So here is my code, which displays a 9x9 grid:
import javax.swing.*;
import java.awt.*;
public class SudokuGrid extends JFrame {
private static final int ROWS = 9;
private static final int COLUMNS = 9;
int fontSize = 30;
public static void main(String[] args) {
SudokuGrid makeSudokuGrid = new SudokuGrid();
} // end of main
// constructor SudokuGrid
public SudokuGrid() {
JTextField[][] inputBoxes = new JTextField[ROWS][COLUMNS];
Font font = new Font("Helvetica", Font.BOLD, fontSize);
setLayout(new GridLayout(ROWS, COLUMNS));
// set frame size
setSize(400, 400);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// outer loop to create the rows
for (int rows = 0 ; rows < ROWS ; rows++) {
// inner loop to create the columns
for (int columns = 0 ; columns < COLUMNS ; columns++) {
// make text fields empty
inputBoxes[rows][columns] = new JTextField("");
// add text fields to the frame
add(inputBoxes[rows][columns]);
// center text in each text box
inputBoxes[rows][columns].setHorizontalAlignment(JTextField.CENTER);
// apply font to each text box
inputBoxes[rows][columns].setFont(font);
} // end of columns loop
} // end of rows loop
// make frame visible
getContentPane().setBackground(Color.RED);
setVisible(true);
} // end of constructor SudokuGrid
} // end of class SudokuGrid
What I am trying to do is to draw a line every third row. So every third text box, there should be a thick line spanning all columns. Hope that makes sense.
Any help is greatly apprecaited. Thank you!
The simple answer is, GridLayout isn't going to do what you want, it's simply not flexible enough, instead...
You could...
Change the layout manager and make use of a JSeparator
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class SudokuGrid {
public static void main(String[] args) {
new SudokuGrid();
}
public SudokuGrid() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public static class TestPane extends JPanel {
private static final int ROWS = 9;
private static final int COLUMNS = 9;
int fontSize = 30;
public TestPane() {
JTextField[][] inputBoxes = new JTextField[ROWS][COLUMNS];
Font font = new Font("Helvetica", Font.BOLD, fontSize);
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.fill = GridBagConstraints.BOTH;
gbc.weightx = 1;
gbc.weighty = 1;
gbc.gridy = 0;
GridBagConstraints split = new GridBagConstraints();
split.fill = GridBagConstraints.BOTH;
split.weightx = 1;
split.gridx = 0;
split.gridwidth = GridBagConstraints.REMAINDER;
// outer loop to create the rows
for (int rows = 0; rows < ROWS; rows++) {
gbc.gridy++;
// inner loop to create the columns
for (int columns = 0; columns < COLUMNS; columns++) {
gbc.gridx = columns;
// make text fields empty
inputBoxes[rows][columns] = new JTextField(1);
// add text fields to the frame
add(inputBoxes[rows][columns], gbc);
// center text in each text box
inputBoxes[rows][columns].setHorizontalAlignment(JTextField.CENTER);
// apply font to each text box
inputBoxes[rows][columns].setFont(font);
} // end of columns loop
if ((rows + 1) % 3 == 0) {
System.out.println("Split");
split.gridy = gbc.gridy + 1;
gbc.gridy += 2;
JSeparator sep = new JSeparator(JSeparator.HORIZONTAL);
add(sep, split);
}
} // end of rows loop
}
}
}
You could...
Make your own "split" component through the use of custom painting
public static class HorizontalSplit extends JPanel {
public HorizontalSplit() {
setOpaque(false);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(0, 3);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int y = (getHeight() - 3) / 2;
BasicStroke stroke = new BasicStroke(3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
g2d.setStroke(stroke);
g2d.drawLine(0, y, getWidth(), y);
g2d.dispose();
}
}
Which would simply replace the JSeparator...
if ((rows + 1) % 3 == 0) {
System.out.println("Split");
split.gridy = gbc.gridy + 1;
gbc.gridy += 2;
JPanel sep = new HorizontalSplit();
add(sep, split);
}
You could...
Use a compound layout and a MatteLayout...
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.MatteBorder;
public class SudokuGrid {
public static void main(String[] args) {
new SudokuGrid();
}
public SudokuGrid() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public static class TestPane extends JPanel {
private static final int ROWS = 9;
private static final int COLUMNS = 9;
int fontSize = 30;
public TestPane() {
JTextField[][] inputBoxes = new JTextField[ROWS][COLUMNS];
Font font = new Font("Helvetica", Font.BOLD, fontSize);
setLayout(new GridBagLayout());
GridBagConstraints groupContraint = new GridBagConstraints();
groupContraint.fill = GridBagConstraints.BOTH;
groupContraint.weightx = 1;
groupContraint.weighty = 1;
groupContraint.gridwidth = GridBagConstraints.REMAINDER;
JPanel group = new JPanel(new GridLayout(3, COLUMNS));
group.setBorder(new MatteBorder(0, 0, 1, 0, Color.BLACK));
// outer loop to create the rows
for (int rows = 0; rows < ROWS; rows++) {
// inner loop to create the columns
for (int columns = 0; columns < COLUMNS; columns++) {
// make text fields empty
inputBoxes[rows][columns] = new JTextField(1);
// add text fields to the frame
group.add(inputBoxes[rows][columns]);
// center text in each text box
inputBoxes[rows][columns].setHorizontalAlignment(JTextField.CENTER);
// apply font to each text box
inputBoxes[rows][columns].setFont(font);
} // end of columns loop
if ((rows + 1) % 3 == 0) {
add(group, groupContraint);
group = new JPanel(new GridLayout(3, COLUMNS));
group.setBorder(new MatteBorder(0, 0, 1, 0, Color.BLACK));
}
} // end of rows loop
}
}
}

Draw Clickable Lines Between Buttons Swing

I have a gridbag of objects laid out in a specific pattern. Clicking on an object will trigger certain actions such as displaying information about that object or navigating to another screen.
Each object also has connections to other objects. These connections need to be clickable as clicking on them will display more information about that connection.
The following example shows what I am aiming for. Each blue circle in the grid is a clickable object and each red line between them is a clickable connection.
I have tried storing both the objects and connections as Jbuttons in a single grid bag layout and have had some success in that the connections are clickable.
However the connections span multiple cells in a single direction and the Jbuttons expand to fill the layout cells. This causes the ends of the connections to pass through the center of the objects and finish at the border of the grid bag cell.
Here is a drawing of what I mean:
Is there some simpler way to make this kind of interface / has anyone done this before?
Okay, very basic, but the idea is to paint the lines manually between the objects.
In this case, I created a simple Connection object which maintained the relationships between buttons. I then simply used the paintComponent method of the JPanel to paint the actual lines.
I'll leave you to devise a better path generation method ;)
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JButton[] buttons = new JButton[]{
new JButton("1"),
new JButton("2"),
new JButton("3"),
new JButton("4"),};
private List<Connection> connections;
public TestPane() {
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.insets = new Insets(10, 10, 10, 10);
add(buttons[0], gbc);
gbc.gridx = 2;
gbc.gridy = 0;
add(buttons[1], gbc);
gbc.gridx = 1;
gbc.gridy = 1;
add(buttons[2], gbc);
gbc.gridx = 2;
gbc.gridy = 2;
add(buttons[3], gbc);
connections = new ArrayList<Connection>(25);
connections.add(new Connection(buttons[0], buttons[1]));
connections.add(new Connection(buttons[0], buttons[2]));
connections.add(new Connection(buttons[0], buttons[3]));
connections.add(new Connection(buttons[1], buttons[3]));
connections.add(new Connection(buttons[2], buttons[3]));
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.RED);
for (Connection connection : connections) {
JButton source = connection.getSource();
JButton dest = connection.getDestination();
if (source.getX() == dest.getX()) {
// Same column...
g2d.drawLine(source.getX() + source.getWidth() / 2, source.getY(),
dest.getX() + source.getWidth() / 2, dest.getY());
} else if (source.getY() == dest.getY()) {
// Same row...
g2d.drawLine(source.getX(), source.getY() + source.getHeight() / 2,
dest.getX(), dest.getY() + dest.getHeight() / 2);
} else {
Path2D path = new Path2D.Double();
path.moveTo(horizontalCenter(source), verticalCenter(source));
path.curveTo(horizontalCenter(source), verticalCenter(dest),
horizontalCenter(source), verticalCenter(dest),
horizontalCenter(dest), verticalCenter(dest));
g2d.draw(path);
}
}
g2d.dispose();
}
protected double horizontalCenter(JComponent bounds) {
return bounds.getX() + bounds.getWidth() / 2d;
}
protected double verticalCenter(JComponent bounds) {
return bounds.getY() + bounds.getHeight() / 2d;
}
protected boolean hasIntersection(Line2D line, JComponent... exclude) {
List<JComponent> toExclude = Arrays.asList(exclude);
boolean intersects = false;
for (Component comp : getComponents()) {
if (!toExclude.contains(comp)) {
if (line.intersects(comp.getBounds())) {
System.out.println(line.getP1() + "-" + line.getP2() + " intersets with " + ((JButton)comp).getText() + "; " + comp.getBounds());
intersects = true;
break;
}
}
}
return intersects;
}
protected Line2D lineDownTo(JComponent from, JComponent to) {
return new Line2D.Double(horizontalCenter(from), from.getY(), horizontalCenter(from), verticalCenter(to));
}
protected Line2D lineAcrossTo(JComponent from, JComponent to) {
return new Line2D.Double(from.getX(), verticalCenter(from), horizontalCenter(to), verticalCenter(from));
}
protected Point2D centerOf(Rectangle bounds) {
return new Point2D.Double(bounds.getX() + bounds.getWidth() / 2, bounds.getY() + bounds.getHeight() / 2);
}
protected boolean canGoDownTo(Point2D startPoint, Point2D endPoint, JComponent to, JComponent from) {
Point2D targetPoint = new Point2D.Double(startPoint.getX(), endPoint.getY());
return !hasIntersection(new Line2D.Double(startPoint, targetPoint), to, from);
}
public class Connection {
private final JButton source;
private final JButton destination;
public Connection(JButton source, JButton destination) {
this.source = source;
this.destination = destination;
}
public JButton getSource() {
return source;
}
public JButton getDestination() {
return destination;
}
}
}
}

Moving jLabel to a different place in the jPanel (Pacman like game)

I'm making a game like pacman and so far I am just starting with the grid. I got the grid started but I need to figure out how to move something to a different place in the grid so that when the user clicks or my ghosts move, it will display on the screen. How do I make it move? I have tried a bunch of different ways but none worked for me.
import java.awt.*;
import javax.swing.*;
import javax.swing.border.BevelBorder;
public class GUI {
public static void main(String[] args) {
final JFrame f = new JFrame("Frame Test");
GridLayout Layout = new GridLayout(50,50);
JPanel panel = new JPanel(new GridLayout(50, 50, 1, 1));
//Not sure if I need this or not?
//panel.setLayout(new GridBagLayout());
//first set of black
for (int i = 0; i < 1000; i++) {
JLabel a = new JLabel(new ImageIcon("black-square.jpg"), JLabel.CENTER);
panel.add(a);
}
//adds pacman
JLabel b = new JLabel(new ImageIcon("pacman.png"), JLabel.CENTER);
panel.add(b);
//next set of black
for (int i = 0; i < 1000; i++) {
JLabel c = new JLabel(new ImageIcon("black-square.jpg"), JLabel.CENTER);
panel.add(c);
}
//do the thing
f.setContentPane(panel);
f.setSize(1000, 1000);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
}
}
Start by taking a look at Concurrency in Swing and How to Use Swing Timers
The next problem you're going to have is the fact that the container is under the control of a LayoutManager. While it's possible to achieve movement using this, it will be blocky, as each component will jump cells.
If you want smooth movement, you're going to have to devise your own layout logic, this can be very complicated.
None the less, what you should be aiming for is maintaining the a "virtual" view of the game. This allows you to know the shape of the maze and the position of the characters without need to do a lot of comparisons with the UI. You should then simply render the state of this "virtual" view or model
Updated with VERY BASIC example
This is a basic example, which uses a GridLayout and setComponentZOrder to move to components about the panel...There is no collision detection and the "AI" is pathetic...
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class PacMan101 {
public static void main(String[] args) {
new PacMan101();
}
public PacMan101() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new MazePane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class MazePane extends JPanel {
private JLabel pacMan;
private JLabel ghost;
public MazePane() {
pacMan = new JLabel(new ImageIcon(getClass().getResource("/PacMan.png")));
ghost = new JLabel(new ImageIcon(getClass().getResource("/Ghost.png")));
setLayout(new GridLayout(8, 8));
add(pacMan);
for (int index = 1; index < (8 * 8) - 1; index++) {
add(new JPanel() {
#Override
public Dimension getPreferredSize() {
return new Dimension(32, 32);
}
});
}
add(ghost);
Timer timer = new Timer(500, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
move(pacMan);
move(ghost);
revalidate();
repaint();
}
protected void move(Component obj) {
int order = getComponentZOrder(obj);
int row = order / 8;
int col = order - (row * 8);
boolean moved = false;
while (!moved) {
int direction = (int) (Math.round(Math.random() * 3));
int nextRow = row;
int nextCol = col;
switch (direction) {
case 0:
nextRow--;
break;
case 1:
nextCol++;
break;
case 2:
nextRow++;
break;
case 3:
nextCol--;
break;
}
if (nextRow >= 0 && nextRow < 8 && nextCol >= 0 && nextCol < 8) {
row = nextRow;
col = nextCol;
moved = true;
}
}
order = (row * 8) + col;
setComponentZOrder(obj, order);
}
});
timer.start();
}
}
}
It might be simpler to put labels at each spot in the grid, and then just change the icons associated with each label as your creatures move. See Add and remove an icon on a JLabel

Categories

Resources