I'm trying to make a Chess Game, I'm stuck with this:
I create a bunch of panels each one has a label that has an icon (piece icon) with a loop for every panel to represent a case in the game. How can I delete an icon from the last position after the user drags the piece to a new position?
import ma.jerroudi.cheesegame.bouard.Bouard;
import ma.jerroudi.cheesegame.bouard.Case;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
public class CheeseGameGui extends JFrame {
Point prevPt;
Point currentPt;
public CheeseGameGui() {
Bouard bouard = new Bouard();
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent eP) {
prevPt = eP.getPoint();
System.out.println(" x: " + eP.getX() + " y: " + eP.getY());
System.out.println("tab[" + (int) prevPt.getX() / 50 + "] [" + (int) prevPt.getY() / 50 + "]");
}
#Override
public void mouseReleased(MouseEvent eR) {
currentPt = eR.getPoint();
System.out.println(" x: " + eR.getX() + " y: " + eR.getY());
bouard.caseChange((int) prevPt.getY() / 50, (int) prevPt.getX() / 50, (int) currentPt.getY() / 50, (int) currentPt.getX() / 50);
System.out.println("piece name : " + bouard.getCase((int) currentPt.getY() / 50, (int) currentPt.getX() / 50).getSymbol());
}
});
this.setTitle("jeroudi cheese game");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
ImageIcon image = new ImageIcon("logo.jpg");
this.setIconImage(image.getImage());
for (int i = 0; i <= Bouard.MAX_SIZE; i++) {
for (int j = 0; j <= Bouard.MAX_SIZE; j++) {
JPanel mypanel = new JPanel();
this.add(mypanel);
JLabel labelImg = new JLabel();
int xIndice = j * 50;
int yIndice = i * 50;
mypanel.setBounds(xIndice, yIndice, 50, 50);
mypanel.setBorder(BorderFactory.createLineBorder(Color.green));
ImageIcon pieceImg = new ImageIcon(new ImageIcon(bouard.getCase(i, j).getPiecePhat()).getImage().getScaledInstance(50, 50, Image.SCALE_DEFAULT));
labelImg.setIcon(pieceImg);
mypanel.add(labelImg);
this.getContentPane().add(mypanel);
}
}
setSize(450, 450);
setLayout(null);
this.setUndecorated(true);
setVisible(true);
}
}
setLayout(null); Don't do this. Instead, set a GridLayout to the chessboard (a single JPanel).
Don't add labels to panels then add the panels to the chessboard, simply add the 'labels' directly to the one chessboard panel. But make them undecorated buttons (JButton).
Important
Keep a reference to the buttons in an array (JButton[8][8]).
Establish a ChessModel which encapsulates the state of a chess game and a method to configure the GUI (the button icons) to match the model.
When the user (or their opponent) moves a piece, update the model then refresh the chessboard.
Oh, and now the GUI is laid out, instead of the (wrong) guess setSize(450, 450);, simply call pack() for the right sized GUI. For more on laying out the chessboard, see Making a robust, resizable Swing Chess GUI.
Related
I'm trying to put a drag-n-drop operation into a program of mine; I found the following example that illustrates a lot of what I'm trying to do:
package sandbox;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.LayoutManager;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
/**
* Example showing the use of a JLayeredPane to implement dragging an object
* across a JPanel containing other objects.
* <P>
* Basic idea: Create a JLayeredPane as a container, then put the JPanel containing
* the application's components or whatever in the JLayeredPane.DEFAULT_LAYER layer of that layered pane.
* The code is going to drag a JComponent object by calling JComponent.setPosition(x,y)
* on the component. When a mouse is clicked on the panel to start the dragging, put the
* component on the drag layer of the layered pane; as it is dragged, continue to call
* setPosition to move it. When the mouse is released, use the x.y position of the release
* to decide what to do with it next.
*
*/
public class ChessBoard extends JFrame implements MouseListener, MouseMotionListener
{
private static final long serialVersionUID = 1L;
JLayeredPane layeredPane;
JPanel chessBoard;
JLabel chessPiece;
int xAdjustment;
int yAdjustment;
public ChessBoard()
{
Dimension boardSize = new Dimension(600, 600);
// Use a Layered Pane for this application
layeredPane = new JLayeredPane();
layeredPane.setPreferredSize( boardSize );
layeredPane.addMouseListener( this );
layeredPane.addMouseMotionListener( this );
getContentPane().add(layeredPane);
//debug
LayoutManager lm = layeredPane.getLayout();
System.out.println("Layered pane layout name is " + (lm == null? "<null>" : lm.getClass().getName()));
// Add a chess board to the Layered Pane on the DEFAULT layer
chessBoard = new JPanel();
chessBoard.setLayout( new GridLayout(8, 8) );
chessBoard.setPreferredSize( boardSize );
chessBoard.setBounds(0, 0, boardSize.width, boardSize.height);
layeredPane.add(chessBoard, JLayeredPane.DEFAULT_LAYER);
// Build the Chess Board squares
// We use an 8x8 grid, and put a JPanel with BorderLayout on each square.
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 8; j++)
{
JPanel square = new JPanel( new BorderLayout() );
square.setBackground( (i + j) % 2 == 0 ? Color.gray : Color.white );
chessBoard.add( square );
}
}
// Add a few pieces to the board
// we do this with an ImageIcon that gets added to the square's panel.
ImageIcon duke = new ImageIcon("granary.gif"); // this is the image to add to each space.
addDuke(duke, 0);
addDuke(duke, 6);
addDuke(duke, 15);
addDuke(duke, 20);
}
private void addDuke(ImageIcon duke, int boardPosition)
{
JLabel pieceLabel = new JLabel(duke);
JPanel piecePanel = (JPanel)chessBoard.getComponent(boardPosition);
piecePanel.add(pieceLabel);
}
/*
** Add the selected chess piece to the dragging layer so it can be moved
*/
public void mousePressed(MouseEvent e)
{
// get the component where the user pressed; iff that's not a panel,
// we'll put it on the dragging layer.
chessPiece = null; // change1 swap the change1 lines
// chessPiece = new JLabel(new ImageIcon("house1x1.gif")); // change1
Component c = chessBoard.findComponentAt(e.getX(), e.getY());
if (c instanceof JPanel) return;
// get the location of the panel containing the image panel, i.e.,
// the square's panel. we adjust the location to which we move the
// piece by this amount so the piece doesn't 'snap to' the cursor
// location.
Point parentLocation = c.getParent().getLocation();
xAdjustment = parentLocation.x - e.getX();
yAdjustment = parentLocation.y - e.getY();
chessPiece = (JLabel)c; // change2 - comment out
chessPiece.setLocation(e.getX() + xAdjustment, e.getY() + yAdjustment);
layeredPane.add(chessPiece, JLayeredPane.DRAG_LAYER); // evidently this removes it from the default layer also.
layeredPane.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
}
/*
** Move the chess piece around
*/
public void mouseDragged(MouseEvent me)
{
if (chessPiece == null) return;
// The drag location should be within the bounds of the chess board
int x = me.getX() + xAdjustment;
int xMax = layeredPane.getWidth() - chessPiece.getWidth();
x = Math.min(x, xMax);
x = Math.max(x, 0);
int y = me.getY() + yAdjustment;
int yMax = layeredPane.getHeight() - chessPiece.getHeight();
y = Math.min(y, yMax);
y = Math.max(y, 0);
chessPiece.setLocation(x, y); // evidently this works for whatever layer contains the piece.
// also, the layout manager of its new home is evidently not the same as lower layers.
}
/*
** Drop the chess piece back onto the chess board
*/
public void mouseReleased(MouseEvent e)
{
layeredPane.setCursor(null);
if (chessPiece == null) return;
// Make sure the chess piece is no longer painted on the layered pane
chessPiece.setVisible(false);
layeredPane.remove(chessPiece);
chessPiece.setVisible(true);
// The drop location should be within the bounds of the chess board
int xMax = layeredPane.getWidth() - chessPiece.getWidth();
int x = Math.min(e.getX(), xMax);
x = Math.max(x, 0);
int yMax = layeredPane.getHeight() - chessPiece.getHeight();
int y = Math.min(e.getY(), yMax);
y = Math.max(y, 0);
Component c = chessBoard.findComponentAt(x, y);
Container parent = null;
if (c instanceof JLabel)
{
parent = c.getParent(); // there's a piece on the square already; remove it from the panel.
parent.remove(0);
}
else
{
parent = (Container)c;
}
parent.add( chessPiece ); // this adds the piece back to the default layer
parent.validate();
}
public void mouseClicked(MouseEvent e) {}
public void mouseMoved(MouseEvent e) {}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
public static void main(String[] args)
{
JFrame frame = new ChessBoard();
frame.setDefaultCloseOperation( DISPOSE_ON_CLOSE );
frame.setResizable( false );
frame.pack();
frame.setLocationRelativeTo( null );
frame.setVisible(true);
}
}
This does the job for the case of a chessboard, namely allowing a user to drag any of the pieces on the chessboard to a different square.
In the application I'm writing, the item being dragged doesn't exist until the user clicks something that starts the dragging operation. I'm having trouble figuring out how to do that creation and have it show up.
My current attempt is on lines labelled 'change1' and 'change2'; you swap the two lines with 'change1', and comment out the one with 'change2'. In other words, create the JLabel when the mouse is pressed, and (hopefully) drag that around. But when I run that, the image does not show up on press or during dragging, but DOES show up on the square at the end of the drag.
What have I missed here? I am a little confused by JLayeredPane, the javadoc says that it will follow layout rules, but not whether the layout rules apply to all the components on all layers, or just the bottom, or to all layers but separately, what? I wouldn't think it was a layout issue, but I don't know what's wrong. Do I need some kind of UI update somewhere? I thought adding the component would invalidate the panel.
The original code was written assuming you were clicking on a chess piece.
Now you want to click on an empty cell which will require the following changes.
The chess board consists of JPanels in each cell. Some cells will contain a JLabel represent a chess piece. The current logic in the mousePressed event is expecting you to click on a JLabel otherwise some processing is skipped.
You need to remove:
//if (c instanceof JPanel) return;
By default a Swing component has a 0 size when it is created.
You need to give it a size:
chessPiece.setSize( chessPiece.getPreferredSize() );
The positioning logic for the label is based on finding the location of the clicked component relative to the parent. Since there is no label, this logic is now based on the panel relative to the layered pane.
You need to adjust this logic to make it relative to parent panel again:
//Point parentLocation = c.getParent().getLocation();
Point parentLocation = c.getLocation();
My updated mousePressed method looks like:
public void mousePressed(MouseEvent e)
{
// get the component where the user pressed; iff that's not a panel,
// we'll put it on the dragging layer.
//chessPiece = null; // change1 swap the change1 lines
chessPiece = new JLabel(new ImageIcon("dukewavered.gif")); // change1
chessPiece.setSize( chessPiece.getPreferredSize() );
Component c = chessBoard.findComponentAt(e.getX(), e.getY());
//if (c instanceof JPanel) return;
// get the location of the panel containing the image panel, i.e.,
// the square's panel. we adjust the location to which we move the
// piece by this amount so the piece doesn't 'snap to' the cursor
// location.
//Point parentLocation = c.getParent().getLocation();
Point parentLocation = c.getLocation();
xAdjustment = parentLocation.x - e.getX();
yAdjustment = parentLocation.y - e.getY();
//chessPiece = (JLabel)c; // change2 - comment out
chessPiece.setLocation(e.getX() + xAdjustment, e.getY() + yAdjustment);
layeredPane.add(chessPiece, JLayeredPane.DRAG_LAYER); // evidently this removes it from the default layer also.
layeredPane.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
}
Note the above changes will break the old functionality of being able to drag an existing label. If you need functionality for both then your logic will be determined on whether you click on a JLabel (in which case you use the old logic) or click on a JPanel (in which case you use the newer logic).
I am visually plotting 100 random points in a Java.awt panel (as far as i know) but it is not working so smoothly. The pane has to be maximized by the user before they show up. Im not sure which command I am missing to make this more fluid
The 100 x,y coordinates are generated randomly and sent to a JFrame in this file.
CC_simplePerceptron.Java
import java.awt.*; // Using AWT's Graphics and Color abstract window toolkit
import java.awt.event.*; // Using AWT event classes and listener interfaces
import javax.swing.*; // Using Swing's components and containers
import javax.swing.*;
import java.awt.geom.Ellipse2D;
import Components.Perceptron;
import Components.Point;
public class CC_SimplePerceptron extends JComponent {
public static final int maxD = 800;
public static Perceptron p = new Perceptron();
public static Point[] points = new Point[100]; //100 element array of type Point to hold data
public static void main(String[] args){
JFrame frame = new JFrame("Draw Ellipse Demo");
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.getContentPane().add(new CC_SimplePerceptron());
frame.pack();
frame.setSize(new Dimension(maxD, maxD));
frame.setVisible(true);
System.out.println("Point initialiations");
//initializing 100 random points
for(int i = 0; i < points.length; i++){
points[i] = new Point(); //random point
System.out.println("Point " + i + " =" + points[i].getX() + ", " + points[i].getY());
}
float[] inputs = {-1f,0.5f}; //0.5f to indicate its float not double
int guess = p.guess(inputs);
System.out.println(guess);
return;
}
// Constructor to set up the GUI components and event handlers
public CC_SimplePerceptron() {
System.out.println("Def constructor");
}
#Override
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setPaint(Color.RED);
g2.setStroke(new BasicStroke(5.0f));
for(int i = 0; i < points.length; i++){
g2.fill(new Ellipse2D.Double(points[i].getX(), points[i].getY(), 8, 8));
}
}
}
The imported files "Perceptron" & "Point" are not relevant for this question scope, but can be found here if one wants to run the code. Any thoughts on why the pane doesnt display all points right away? Im not exactly sure how my paint method works, and why it is called with a graphics obj, is this the best method to plot my x,y coordinates in a java program on the basis of convience?
Take frame.setVisible(true); and call it last, after you've built all the data you want to display...
public static void main(String[] args){
JFrame frame = new JFrame("Draw Ellipse Demo");
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.getContentPane().add(new CC_SimplePerceptron());
frame.pack();
frame.setSize(new Dimension(maxD, maxD));
System.out.println("Point initialiations");
//initializing 100 random points
for(int i = 0; i < points.length; i++){
points[i] = new Point(); //random point
System.out.println("Point " + i + " =" + points[i].getX() + ", " + points[i].getY());
}
float[] inputs = {-1f,0.5f}; //0.5f to indicate its float not double
int guess = p.guess(inputs);
System.out.println(guess);
frame.setVisible(true);
}
If you want to dynamically update the UI, then, in your case, calling repaint on the component you want updated should also work...
public static void main(String[] args){
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame("Draw Ellipse Demo");
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
CC_SimplePerceptron component = new CC_SimplePerceptron();
frame.getContentPane().add(component);
frame.pack();
frame.setSize(new Dimension(maxD, maxD));
frame.setVisible(true);
System.out.println("Point initialiations");
//initializing 100 random points
for(int i = 0; i < points.length; i++){
points[i] = new Point(); //random point
System.out.println("Point " + i + " =" + points[i].getX() + ", " + points[i].getY());
}
float[] inputs = {-1f,0.5f}; //0.5f to indicate its float not double
int guess = p.guess(inputs);
System.out.println(guess);
component.repaint();
}
})
}
Other considerations
As general recommendation, you should be overriding paintComponent and paint and you should be calling the super paint method before performing any custom painting to ensure that the paint chain remains intact.
You should also override getPreferredSize and return an appropriate size hint, this will provide pack with better information when it calculates the size of the window
First, override paintComponent and put your paint code in there. Don't forget to use super.paintComponent(g) at the beginning so that it clears the panel before painting. If you make your CC_SimplePerceptron extend JPanel, you can set it as the content pane:
frame.setContentPane(new CC_SimplePerceptron)
so it fills the frame. Finally, use setPreferredSize() on the panel before you call frame.pack()
I'm trying to make a level editor for my platformer game, I want my levels to be 100 by 100 squares.
So far the editor works, but I can't scroll through the JPanel. I've been playing around and I've made a small test class to fiddle with which I'll post. If you run it, all it does it show the grid. However if I swap out two variables (I'll comment where) it can show an image and scroll according to the size of that image.
I want that scrolling ability only for the JPanel, so that I can scroll through my 100 x 100 square level.
import java.awt.BorderLayout;
public class ScrollPaneJ extends JFrame {
// setting the panels
private JPanel contentPane;
private JScrollPane scrollPane;
// dimensions/ variables of the grid
int size = 16;
int startX = 112;
int startY = 48;
int width = 30;
int height = 30;
// this is the grid
String[][] grid = new String[width][height];
// this is from the full editor class
String currentImage = new String("platform");
ImageIcon currentBackIcon = new ImageIcon("Resources/backdirttile.jpg");
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
// adding the scrollpane
ScrollPaneJ frame = new ScrollPaneJ();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the frame.
*/
public ScrollPaneJ() {
setTitle("Scrolling Pane Application");
setSize(new Dimension(300, 200));
setBackground(Color.gray);
setDefaultCloseOperation(EXIT_ON_CLOSE);
// defining the top and bottom panels, bottom is what I think I'm
// drawing on, top is where the scrollpanel goes, I copied this code
// from the internet and I'm not too sure how it works
JPanel topPanel = new JPanel();
JPanel bottomPanel = new JPanel(new GridLayout());
bottomPanel.setLayout(new BorderLayout());
getContentPane().add(bottomPanel);
topPanel.setLayout(new BorderLayout());
getContentPane().add(topPanel);
// this is the label I was talking about
Icon image = new ImageIcon("src/MenuDesign.jpg");
JLabel label = new JLabel(image);
// Create a tabbed pane
// if you set it to say label instead of bottomPanel, you can scroll
// through the size of the label
scrollPane = new JScrollPane(bottomPanel);
scrollPane.setBounds(40, 40, 100, 100);
// set it label here as well.
scrollPane.getViewport().add(bottomPanel);
// I was hoping this would force the scrollbar in but it does nothing
scrollPane
.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
scrollPane
.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
scrollPane.setBounds(50, 30, 300, 50);
JPanel contentPane = new JPanel(null);
contentPane.setPreferredSize(new Dimension(500, 400));
contentPane.add(scrollPane);
topPanel.add(scrollPane, BorderLayout.CENTER);
init();
}
public void init() {
// this sets the grid to empty
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
grid[x][y] = "";
}
}
}
#Override
public void paint(Graphics g) {
// this paints the grid
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.black);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
g2d.drawRect(x * size + startX, y * size + startY, size, size);
if (grid[x][y].equals("")) {
g2d.drawImage(currentBackIcon.getImage(),
x * size + startX, y * size + startY, null);
}
g2d.setColor(Color.black);
g2d.drawRect((x * size) + 1 + startX, (y * size) + 1 + startY,
size, size);
}
}
}
public void drawTile() {
// this isn't enabled which is why you can't paint the grid, however it
// would change the tile of the square you're mouse is on, to the
// current tile, it works and isn't really important for what i need
// help with
PointerInfo a = MouseInfo.getPointerInfo();
Point b = a.getLocation();
int mouseX = (int) b.getX();
int mouseY = (int) b.getY();
int gMX = ((mouseX - 48) / 16) - 4;
int gMY = ((mouseY - 48) / 16) - 3;
grid[gMX][gMY] = currentImage;
repaint();
}
}
scrollPane.getViewport().add(bottomPanel); should be more like scrollPane.getViewportView(bottomPanel);
You shouldn't be painting directly to the frame, as child components can be painted without the notification to the parents, meaning that what ever you've painted could be partially wiped out. Instead, this kind of painting should be done within a custom component which acts as the JScrollPane's, JViewport's view.
A JScrollPane needs two things, first, the size that the component would like to be (the preferredSize) and the size of the viewport view. If the component doesn't implement the Scrollable interface, then the component's preferredSize is used to determine that as well. This is why a JLabel will work.
A JScrollPane has a JViewport as it's primary child component. The JViewport should only have a single component, typically assigned either via JScrollPane#setViewportView or JViewport#setView methods
See How to Use Scroll Panes for more details
Create a custom component that extends JPanel and override it's getPreferredSize method to return the size of the component you want. Override it's paintComponent method and perform you custom painting their.
Overlaying custom painting ontop of other components is more difficult
You can also add JScrollPane in your panel like this
JPanel p = new JPanel();
add(new JScrollPane(p));
In short, I want to set the text of a JLabel to be that of a JTextField in a JPanel (pnlUser) and then drag the JLabel across the screen from JPanel onto another JTextField in another JPanel (pnlGrid).
Here are the details.
I have written a "Solitaire Scrabble" program. The user can either position the text cursor in a grid cell (a JTextField in pnlGrid) and type a letter that is in the list of "User letters" (a JTextField in pnlUser) OR the user can simulate dragging a letter from "User letters" and dropping it into the destination grid cell in pnlGrid.
I say "simulate" because the selected letter is not actually dragged across the screen. I use the mouse pointer HAND_CURSOR to make the drag/drop as real as possible, but I haven't figured out how to make the HAND_CURSOR "grab" the letter and physically drag the letter across the board to its destination.
As it is, the letter is highlighted but left in the "User letters" area while the HAND_CURSOR moves along the grid during the drag operation. When it gets to the destination cell in pnlGrid and the mouse button is released, the letter is erased from "User letters" and suddenly appears in the grid cell.
So the letter is more or less "teleported" (beam me up, Scotty) from "User letters" to a grid cell. This is too abstract. I want the user letter to be at the tip of the HAND_CURSOR's pointing finger and be dragged along the grid into the grid cell where it will be dropped, as shown in the 3 pictures below.
I've successfully made it happen in a small test program (source below) using JLayeredPane, but I can't make it happen in the game. But I knew nothing about JLayeredPane until two days ago so I don't really know what I'm doing. (I adapted an Oracle tutorial program that demos JLayeredPane.)
I just read about the "glass pane" and thought it would maybe be easier to implement until I downloaded the source for that demo, which is quite long, so since it's totally new and will be even harder to adapt.
So I thought before I spend more hours in frustration I should ask:
Is a JLayeredPane or a setGlassPane approach appropriate? Is there an easier or better way to drag a JLabel from one JPanel onto another another JPanel?
(The approach in the program is to determine which "User letter" is being pointed at, store that letter in a JLabel, and then make sure that during mouseDragged the HAND_CURSOR fingertip is right at the bottom center of the letter.)
package mousemoveletter;
import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
import static java.awt.Color.*;
import java.awt.event.*;
import static javax.swing.SwingUtilities.invokeLater;
public class LayeredPaneDemo extends JPanel
{
private static final int USER7 = 7;
static Cursor HAND = new Cursor(Cursor.HAND_CURSOR);
static Cursor ARROW = new Cursor(Cursor.DEFAULT_CURSOR);
private static JLayeredPane layeredPane;
private static JLabel lblToMove;
private static JPanel pnlUser;
private static JPanel pnlGrid;
private static final JTextField[] txtUser = new JTextField[USER7];
public LayeredPaneDemo() // constructor
{
pnlGrid = new JPanel();
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
layeredPane = new JLayeredPane();
layeredPane.setPreferredSize(new Dimension(240, 240));
pnlGrid.setSize(140, 140);
pnlGrid.setBorder(new EtchedBorder(RED, GREEN));
pnlGrid.setBackground(YELLOW);
lblToMove = new JLabel("XXX");
lblToMove.setSize(new Dimension(40,40));
layeredPane.add(pnlGrid, 0,0);
layeredPane.add(lblToMove, new Integer(0), -1);
add(layeredPane);
}
private static void createAndShowGUI() {
JFrame frame = new JFrame("LayeredPaneDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JComponent newContentPane = new LayeredPaneDemo();
newContentPane.setOpaque(true); //content panes must be opaque
frame.setContentPane(newContentPane);
makeUser();
frame.add(pnlUser);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
invokeLater(new Runnable()
{
public void run() {
createAndShowGUI();
}
});
}
private static void makeUser(){
pnlUser = new JPanel(new GridLayout(1,USER7));
pnlUser.setPreferredSize(new Dimension(225, 50));
pnlUser.setBackground(Color.green);
pnlUser.setBorder(BorderFactory.createLineBorder(Color.BLUE));
for(int k = 0; k < USER7; k++)
{
txtUser[k] = new JTextField("" + (char)(Math.random()*26+65));
txtUser[k].setName("" + k);
txtUser[k].setEditable(false);
txtUser[k].addMouseMotionListener(new MouseMotionAdapter()
{
public void mouseDragged(MouseEvent e)
{
lblToMove.setCursor(HAND);
int w = Integer.parseInt(e.getComponent().getName());
lblToMove.setText(txtUser[w].getText());
layeredPane.setLayer(lblToMove, 0, 0);
lblToMove.setLocation(e.getX() + (e.getComponent().getWidth())*w,
e.getY() + layeredPane.getHeight() - e.getComponent().getHeight()/2);
};
});
txtUser[k].addMouseListener(new MouseAdapter()
{
public void mouseReleased(MouseEvent e)
{
lblToMove.setCursor(ARROW);
}
});
pnlUser.add(txtUser[k]);
}
}
}
Thanks to #trashgod, I figured it out by following his links to this example and variation; I adapted the drag/drop of the chessboard found there to my own particular needs for "Scrabble".
The code below is not final code for my Solitaire Scrabble program, but proof-of-concept, possibly usable by others wishing to drag a cell from a 1xN grid onto a MxM grid.
package components;
import java.awt.*;
import static java.awt.BorderLayout.NORTH;
import static java.awt.BorderLayout.SOUTH;
import java.awt.event.*;
import static java.lang.Integer.parseInt;
import javax.swing.*;
import static javax.swing.WindowConstants.DISPOSE_ON_CLOSE;
import javax.swing.event.MouseInputAdapter;
public class ChessBoard //implements MouseListener, MouseMotionListener
{
static Point parentLocation;
int homeRow, homeCol; // where to restore moved user letter if dropped on occupied cell
static int N = 11; // NxN 'chessboard' squares
static int S = 44; // square dimensions: SxS
static int W ; // chessboard dimensions: WxW
static int USER7 = 7;
static Font dragFont;
static JFrame frame;
JLayeredPane layeredPane;
static JPanel gamePanel, // encompasses both pnlGrid and pnlUser
pnlGrid,
pnlUser;
JLabel userDragLetter = new JLabel(); // main item to drag around or restore if needed
int xAdjustment, yAdjustment; // how to locate drops accurately
String userLetters[] ;
public ChessBoard() // constructor
{
W = S*N;
dragFont = new Font("Courier", Font.PLAIN, S);
userLetters = new String[USER7];
for (int i = 0; i < USER7; i++)
userLetters[i] = "" + (char)(65 + Math.random()*26);
Dimension gridSize = new Dimension(W, W);
Dimension userSize = new Dimension(W, S);
Dimension gameSize = new Dimension(W, (W + S));
frame = new JFrame();
frame.setSize(new Dimension(gameSize)); // DO NOT USE PREFERRED
layeredPane = new JLayeredPane();
layeredPane.setPreferredSize( gameSize ); // NO PREFERRED => NO GRID!
gamePanel = new JPanel();
// **EDIT** LOSE THIS LINE gamePanel.setLayout(new BorderLayout());
gamePanel.setPreferredSize(gameSize);
pnlGrid = new JPanel();
pnlGrid.setLayout(new GridLayout(N, N));
pnlGrid.setPreferredSize( gridSize );
pnlGrid.setBounds(0, 0, gridSize.width, gridSize.height);
pnlUser = new JPanel();
pnlUser.setLayout(new GridLayout(1, N));
pnlUser.setPreferredSize(userSize);
pnlUser.setBounds(0, gridSize.height, userSize.width, userSize.height);
layeredPane.add(pnlGrid, JLayeredPane.DEFAULT_LAYER); // panels to drag over
layeredPane.add(pnlUser, JLayeredPane.DEFAULT_LAYER); // " "
for (int i = 0; i < N; i++){
for (int j = 0; j < N; j++){
JPanel square = new JPanel();
square.setBackground( (i + j) % 2 == 0 ? Color.red : Color.white );
pnlGrid.add( square );
}
}
for (int i = 0; i < N; i++) {
JPanel square = new JPanel(new BorderLayout());
square.setBackground(Color.YELLOW);
pnlUser.add(square);
}
for (int i = 0; i < USER7; i++)
addPiece(i, 0, userLetters[i]);
gamePanel.addMouseListener(new MouseInputAdapter()
{
public void mousePressed (MouseEvent e){mousePressedActionPerformed (e);}
public void mouseReleased(MouseEvent e){mouseReleasedActionPerformed(e);}
});
gamePanel.addMouseMotionListener(new MouseMotionAdapter()
{
public void mouseDragged(MouseEvent me){mouseDraggedActionPerformed(me);}
});
// **EDIT: LOSE THE NEXT TWO LINES AND REPLACE BY THE LINE AFTER THEM**
// gamePanel.add(layeredPane, NORTH);
// gamePanel.add(pnlUser, SOUTH);
gamePanel.add(layeredPane);
}
private void addPiece(int col, int row, String glyph) {
JLabel piece = new JLabel(glyph, JLabel.CENTER);
piece.setFont(dragFont);
JPanel panel = (JPanel) pnlUser.getComponent(col + row * N);
piece.setName("piece " + glyph + " # " + row + " " + col);
panel.add(piece);
}
void mousePressedActionPerformed(MouseEvent e)
{
userDragLetter = null; // signal that we're not dragging if no piece is in the square
gamePanel.setCursor(new Cursor(Cursor.HAND_CURSOR));
Component c = pnlGrid.findComponentAt(e.getX(), e.getY());
if(c != null)
return; // Illegal to click pnlGrid
c = pnlUser.findComponentAt(e.getX(), e.getY() - pnlGrid.getHeight());
if(c == null | c instanceof JPanel)
return; // letter already played; can't drag empty cell
parentLocation = c.getParent().getLocation();
xAdjustment = parentLocation.x - e.getX();
yAdjustment = parentLocation.y - e.getY() + gamePanel.getHeight() - pnlUser.getHeight();
userDragLetter = (JLabel)c;
userDragLetter.setPreferredSize(new Dimension(S, S)); // prevent 2 letters in a square
userDragLetter.setLocation(e.getX() + xAdjustment, e.getY() + yAdjustment);
layeredPane.add(userDragLetter, JLayeredPane.DRAG_LAYER);
homeRow = parseInt(userDragLetter.getName().substring(10,11)); // save restore location
homeCol = parseInt(userDragLetter.getName().substring(12,13));
}
void mouseDraggedActionPerformed(MouseEvent me)
{
if (userDragLetter == null)
return; // nothing to drag
int x = me.getX() + xAdjustment; // make sure grid cell will be chosen in-bounds
int xMax = layeredPane.getWidth() - userDragLetter.getWidth();
x = Math.min(x, xMax);
x = Math.max(x, 0);
int y = me.getY() + yAdjustment;
int yMax = layeredPane.getHeight() - userDragLetter.getHeight();
y = Math.min(y, yMax);
y = Math.max(y, 0);
if(y >= pnlGrid.getHeight())
return; // can't drag to location off grid
userDragLetter.setLocation(x, y);
}
void mouseReleasedActionPerformed(MouseEvent e)
{
//**EDIT: CHANGED NEXT LINE**
gamePanel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
if (userDragLetter == null)
return; // nothing to drag; nothing to release
// Make sure the chess piece is no longer painted on the layered pane
userDragLetter.setVisible(false);
layeredPane.remove(userDragLetter);
userDragLetter.setVisible(true);
int xMax = layeredPane.getWidth() - userDragLetter.getWidth();
int x = Math.min(e.getX(), xMax);
x = Math.max(x, 0);
int yMax = layeredPane.getHeight()- userDragLetter.getHeight();
int y = Math.min(e.getY(), yMax);
y = Math.max(y, 0);
Component c = pnlGrid.findComponentAt(x, y); // find deepest nested child component
if(c == null) // then grid cell is unoccupied so ...
c = pnlUser.findComponentAt(x, y); // see if there's a letter there ...
if(c == null | (c instanceof JLabel)){ // and if illegal or there is one, put it back...
userDragLetter.setLocation(parentLocation.x + xAdjustment,
parentLocation.y + yAdjustment + gamePanel.getHeight());
userDragLetter.setVisible(true);
addPiece(homeCol, homeRow,userDragLetter.getName().substring(6,7));
layeredPane.remove(userDragLetter);
return;
}
else // but if NO letter ...
{
Container parent = (Container)c;
parent.add( userDragLetter ); // put one in the grid cell
parent.validate();
}
userDragLetter.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
public static void main(String[] args)
{
new ChessBoard();
frame.add(gamePanel);
frame.setDefaultCloseOperation( DISPOSE_ON_CLOSE );
// frame.setResizable( false );
frame.pack();
frame.setLocationRelativeTo( null );
frame.setVisible(true);
}
}
Here's the application I'm building https://github.com/chrisbramm/LastFM-History-Graph.
Below is part of the controller class in src/lastfmhistoryclasses. When OutputGUIView is created it creates three components, a JPanel graphPanel, this is added to a JScrollPane graphScrollPanel and another JPanel autocompletePanel. These are then all added to the JFrame OutputGUIView. The listeners below then change the preferred size of graphPanel and the first time you press a button the UI updates to show that graphPanel has increased in size and the scrollbars of graphScrollPanel have change.
However now, if you press another button, the preferred size changes but the UI doesn't update, it will do it if you change the window dimensions by lets say maximising it.
class Zoom2000 implements ActionListener{
public void actionPerformed(ActionEvent e){
outputGUIView.graphPanel.setPreferredSize(new Dimension(screenWidth, 2000));
outputGUIView.graphScrollPanel.updateUI();
}
}
class ZoomDefault implements ActionListener{
public void actionPerformed(ActionEvent e){
outputGUIView.graphPanel.setPreferredSize(new Dimension(screenWidth, screenHeight));
outputGUIView.graphScrollPanel.updateUI();
}
}
class Zoom6000 implements ActionListener{
public void actionPerformed(ActionEvent e){
outputGUIView.graphPanel.setPreferredSize(new Dimension(screenWidth, 6000));
outputGUIView.graphScrollPanel.updateUI();
}
}
I've tried different things as well such as invalidate/validate/revalidate (and repaint) on various components all the while graphPanel just sits there and only repaints when you change the window dimensions.
Any ideas what I'm doing wrong?
EDIT UPDATE:
Here is the GraphPanel class:
package lastfmhistoryguis;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import javax.swing.*;
import de.umass.lastfm.*;
import lastfmhistoryclasses.*;
SuppressWarnings("serial")
public class GraphPanel extends JPanel {
private LastFMHistory graphData;
private int graphHeight;
private int graphWidth;
private int zoom;
private final int PAD = 20;
public GraphPanel(LastFMHistory model, int zoom){
this.graphData = model;
if (zoom != 1){
this.zoom = zoom;
}else{
this.zoom = 1;
System.out.println("getHeight() returning:" + getHeight());
}
System.out.println("Width" + getWidth() + "Height" + getHeight());
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
System.out.println("Drawing");
Graphics2D graph = (Graphics2D) g;
if (graphData == null) {
System.err.println("No data found");
} else {
System.out.println("paintComponent Width" + getWidth() + "Height" + getHeight());
graphWidth = getWidth() - 5 * PAD;
//graphHeight = getHeight() - 2 * PAD;
graphHeight = 6000 - 2* PAD;
System.out.println(graphWidth + ", " + graphHeight);
int x0 = graphWidth + PAD;
graph.draw(new Rectangle2D.Double(PAD, PAD, graphWidth, graphHeight));
double xInc = (double) (graphWidth) / (graphData.dayMax);
double secondHeight = (double) (graphHeight) / 86400;
for (int i = 0; i <= 86400; i++) {
if (i % 3600 == 0) {
graph.draw(new Line2D.Double(x0, (i * secondHeight) + PAD,
x0 + 10, (i * secondHeight) + PAD));
String hour = Integer.toString(i / 3600);
graph.drawString(hour, x0 + 15,
(int) ((i * secondHeight) + PAD));
}
}
for (Track t : graphData.history) {
if (t.getPlayedWhen() != null) {
Color color = t.getColour();
int duration = t.getDuration();
int day = Math.abs(t.getDay());
double dayOrigin = x0 - ((day + 1) * xInc);
double timeOrigin = t.getGraphHeight() * secondHeight + PAD;
double trackHeight = duration * secondHeight;
graph.setColor(color);
// System.out.println("PLOTTING TRACK, " + day + ", " +
// dayOrigin + ", " + t.getGraphHeight() + ", " + timeOrigin
// + ", " + trackHeight);
// graph.draw(new Rectangle2D.Double(dayOrigin, timeOrigin,
// xInc, trackHeight));
graph.fillRect((int) dayOrigin, (int) timeOrigin,
(int) xInc, (int) trackHeight);
}
}
for (int i = 0; i < graphData.dayMax;){
graph.draw(new Line2D.Double(x0 - i * xInc, PAD, x0 - i * xInc, graphHeight + PAD));
i = i + 7;
}
}
}
public void zoom(int zoom){
this.zoom = zoom;
repaint();
}
}
It creates a Graphics2D object onto which a large outlining rectangle is drawn and each track rectangle is then drawn onto somewhere on this Graphics2D graph object. Now I've removed the use of setPreferredSize as recommended here but now I have the problem of when I manually set graphHeight in GraphPanel to a height say 6000, graphScrollPanel doesn't realise that graphPanel that it, containing is actually bigger as all graphHeight is doing is drawing a rectangle that is ~6000px high. So in this case should I be using setPreferredSize or is there another way to set the size of a Graphics2D object?
Don't call updateUI this is related to the UI Look and Feel API and has little to do with repainting.
What layout managers have you tried/are using?
Calls to invalidate(), repaint() should effect the parent containers layout managers and cause them to re-layout the components accordingly, but you need to remember, layout managers only use the min/max/pref size information as guides.
My best guest from your example is that you should be trying to call either
outputGUIView.graphPanel.invalidate();
outputGUIView.graphPanel.repaint();
and/or
outputGUIView.invalidate();
outputGUIView.repaint();
Calling invalidate in the scroll pane is probably not going to achieve a lot as the viewport is responsible for layout out the contents, not the scroll pane.
If you're really desperate you could try
outputGUIView.graphScrollPanel.getViewport().invalidate();
outputGUIView.graphScrollPanel.getViewport().repaint();