I don't know what I did, or what went wrong, but a change I made at some point in the last while has made my JPanel completely invisible. The JFrame it's nested in still changes in size to house it, and I can still toggle the content in the combobox.
In my desperation, I tried replacing the content of the SnakeSettingsPanel class with a single button, but the same thing happened - completely invisible, yet I can still interact with it. I figured it might be a computer error, so I tried restarting, but still nothing. When I tried adding a button to the frame outside of the JPanel, it worked just fine. What am I doing wrong?
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class SnakeSettingsPanel extends JPanel {
public boolean quit = false;
public boolean play = false;
public int width = 20;
public int height = 15;
public Speed speed = Speed.SLOW;
public JTextField wField;
public JTextField hField;
public JComboBox<Speed> sField;
public static void main(String[] args) {
JFrame jf = new JFrame();
jf.setTitle("Snake");
SnakeSettingsPanel settings = new SnakeSettingsPanel();
jf.add(settings);
jf.pack();
jf.setVisible(true);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public SnakeSettingsPanel() {
setLayout(new GridBagLayout());
// #author Create our labels.
JLabel wLabel = new JLabel("Width:");
JLabel hLabel = new JLabel("Height:");
JLabel sLabel = new JLabel("Speed:");
GridBagConstraints p = new GridBagConstraints();
p.gridx = 0;
p.gridy = 0;
p.insets = new Insets(10, 10, 10, 10);
// #author Create the buttons, and add listeners
JButton y = new JButton("Play");
JButton n = new JButton("Quit");
y.addActionListener(new PlayListener());
n.addActionListener(new QuitListener());
// #author Create text fields for height/width
wField = new JTextField(15);
wField.setText("20");
hField = new JTextField(15);
hField.setText("15");
// #author Creates a combobox for selecting speed.
Speed[] speeds = {Speed.SLOW, Speed.MEDIUM, Speed.FAST};
sField = new JComboBox<Speed>(speeds);
// #author Stitch everything into the panel.
add(wLabel, p);
p.gridx = 1;
add(wField, p);
p.gridx = 0;
p.gridy = 1;
add(hLabel, p);
p.gridx = 1;
add(hField, p);
p.gridx = 0;
p.gridy = 2;
add(sLabel, p);
p.gridx = 1;
add(sField, p);
p.gridx = 0;
p.gridy = 3;
add(y, p);
p.gridx = 1;
add(n, p);
setVisible(true);
}
public boolean getPlay() {
return play;
}
public boolean getQuit() {
return quit;
}
// #author Returns all settings as a SnakeSettings object
public SnakeSettings getSettings() {
return new SnakeSettings(width, height, speed);
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public Speed getSpeed() {
return speed;
}
// #author Sends out the word to start a new game.
public class PlayListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
quit = false;
play = true;
width = Integer.parseInt(wField.getText());
height = Integer.parseInt(hField.getText());
speed = (Speed) sField.getSelectedItem();
}
}
// #author Sends out the word to shut down the program.
public class QuitListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
quit = true;
play = false;
}
}
}
Let this be a lesson on why you should avoid mixing Model (your application's data) with View (how it is displayed). Your SnakeSettingsPanel is currently both.
As Model, it contains 3 important fields: width, height, and speed.
As View, it is a full JPanel. JPanels have a lot of fields which you should avoid touching directly. Including width and height, usually accessed via getHeight and getWidth -- which you are overwriting with a version that always returns the same built-in values of 20 and 15 (until the user changes those values through a UI that they cannot see).
The fast fix is to rename your current getWidth() and getHeight() to avoid clashing with the built-in getWidth() and getHeight() methods of the parent JPanel class. Call them getMyWidth(), getMyHeight(), and suddenly everything works.
The better fix is to remove those fields and methods entirely, and store your own model attributes in a SnakeSettings attribute. Update it when the user clicks on play, and return it when it is requested via getSettings(). Less code for you, less chance of accidental name clashes with your parent JPanel class. This would look like:
// SnakeSettingsPanel, before
public int width = 20;
public int height = 15;
public Speed speed = Speed.SLOW;
public int getWidth() { // <-- clashes with superclass
return width;
}
public int getHeight() { // <-- clashes with superclass
return height;
}
public Speed getSpeed() {
return speed;
}
public SnakeSettings getSettings() {
return new SnakeSettings(width, height, speed);
}
// SnakeSettingsPanel, after
SnakeSettings settings = new SnakeSettings();
public SnakeSettings getSettings() {
return settings;
}
Related
I am trying to add a thing like this in my music player application in swing.
I tried to add a rectangle to BorderLayout.SOUTH, but it never appeared on screen. Here is what I did:
public class MyDrawPanel extends JPanel {
#Override
public void paintComponent(Graphics g) {
g.setColor(Color.GREEN);
g.fillRect(200,200,200,200);
}
public static void main(String[] args) {
JFrame frame = new JFrame();
MyDrawPanel a = new MyDrawPanel();
frame.getContentPane().add(BorderLayout.SOUTH,a);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(1000,1000);
frame.setVisible(true);
}
}
I just did not try 200,200,200,200, but I tried a lot of values, even with the help of a for loop, but it never appeared on screen. If I used CENTER instead of SOUTH it appeared. I read the documentation to check how fillRect works, but it simply said it added x+width and y+height. The point (0,0) is the top left corner. I checked that by adding a rectangle to CENTER layout. How cam I do it?
I did not share the output, because it was just a blank screen.
The values you give to fillRect are wrong. The first two are the top left corner's coordinates, relative to the component you're painting in; in your case the MyDrawPanel. With the code you posted, this drawing area is outside of the container the panel is placed in. You want to do
g.fillRect(0,0,200,200);
A note: You usually want to call frame.pack() after you've finished adding all components, so it can layout itself. In your case, this results in a tiny window because the system doesn't know how large it should be. You probably want to add a method
public Dimension getPreferredSize() {
System.out.println("getting pref size");
return new Dimension(200, 200);
}
to ensure it's always large enough to draw the full rectangle.
Also, you should call frame.getContentPane().setLayout(new BorderLayout()) before. You can print it out without setting it to see it is not the default. EDIT: As VGR points out, the documentation says that it is in fact a BorderLayout. I cannot confirm that is the case - it is in fact a RootLayout. That seems to behave like a BorderLayout though.
I thought this might make a quick little project. Here's the level meter I came up with.
The important parts are the DrawingPanel and the LevelMeterModel. The DrawingPanel takes the information from the LevelMeterModel and paints the bars on a JPanel.
The LevelMeterModel is an int array of levels, a minimum level, and a maximum level. The maximum level could be calculated, but I assumed music has a certain volume and frequency range.
The JFrame holds the DrawingPanel. A Swing Timer varies the levels somewhat randomly. The random numbers are in a small range so the bar heights don't change abruptly.
Here's the complete runnable code.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class LevelMeterGUI implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new LevelMeterGUI());
}
private final DrawingPanel drawingPanel;
private final LevelMeterModel model;
public LevelMeterGUI() {
this.model = new LevelMeterModel();
this.drawingPanel = new DrawingPanel(model);
}
#Override
public void run() {
JFrame frame = new JFrame("Level Meter GUI");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(drawingPanel, BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
System.out.println("Frame size: " + frame.getSize());
Timer timer = new Timer(250, new ActionListener() {
#Override
public void actionPerformed(ActionEvent event) {
model.setRandomLevels();
drawingPanel.repaint();
}
});
timer.start();
}
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = 1L;
private final int drawingWidth, drawingHeight, margin, rows;
private final Dimension barDimension;
private final LevelMeterModel model;
public DrawingPanel(LevelMeterModel model) {
this.model = model;
this.margin = 10;
this.rows = 20;
this.barDimension = new Dimension(50, 10);
int columns = model.getLevels().length;
drawingWidth = columns * barDimension.width + (columns + 1) * margin;
drawingHeight = rows * barDimension.height + (rows + 1) * margin;
this.setBackground(Color.WHITE);
this.setPreferredSize(new Dimension(drawingWidth, drawingHeight));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int maximum = model.getMaximumLevel();
double increment = (double) maximum / rows;
int peak = rows * 75 / 100;
int x = margin;
for (int level : model.getLevels()) {
int steps = (int) Math.round((double) level / increment);
int y = drawingHeight - margin - barDimension.height;
for (int index = 0; index < steps; index++) {
if (index < peak) {
g.setColor(Color.GREEN);
} else {
g.setColor(Color.RED);
}
g.fillRect(x, y, barDimension.width, barDimension.height);
y = y - margin - barDimension.height;
}
x += margin + barDimension.width;
}
}
}
public class LevelMeterModel {
private final int minimumLevel, maximumLevel;
private int[] levels;
private final Random random;
public LevelMeterModel() {
this.minimumLevel = 100;
this.maximumLevel = 999;
this.levels = new int[8];
this.random = new Random();
setRandomLevels();
}
public void setRandomLevels() {
for (int index = 0; index < levels.length; index++) {
levels[index] = getRandomLevel(levels[index]);
}
}
private int getRandomLevel(int level) {
if (level == 0) {
return random.nextInt(maximumLevel - minimumLevel) + minimumLevel;
} else {
int minimum = Math.max(level * 90 / 100, minimumLevel);
int maximum = Math.min(level * 110 / 100, maximumLevel);
return random.nextInt(maximum - minimum) + minimum;
}
}
public int[] getLevels() {
return levels;
}
public int getMinimumLevel() {
return minimumLevel;
}
public int getMaximumLevel() {
return maximumLevel;
}
}
}
I am expanding Image editing application in Java. I want to make a class that would adjust contrast of the image. Main class calls apply method and passes image that has to be modified. I managed to create JFrame and algorithm for calculation, but I have problems with Action Listener because I don't know how to make my apply method wait for the users input and only then calculate and edit image. Here is the code for contrast class:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
lic class ContrastFilter extends Filter implements ActionListener {
private JFrame contFr;
private JButton ok;
private JTextField textF;
private String s;
private int contV;
private int factor;
public ContrastFilter(String name){
super(name);
}
public void makeFrame(){
contFr = new JFrame("contrast window");
Container contentPane = contFr.getContentPane();
contentPane.setLayout(new FlowLayout());
JLabel label = new JLabel("Enter contrast");
contentPane.add(label, BorderLayout.PAGE_START);
textF = new JTextField(5);
contentPane.add(textF, BorderLayout.PAGE_START);
ok = new JButton("OK");
ok.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event)
{
if (event.getSource() == ok){
s = textF.getText();
contV = Integer.parseInt(s);
factor = Math.round((259*(contV+255))/(255*(259 - contV)));
}}
});
contentPane.add(ok, BorderLayout.PAGE_START);
contFr.pack();
contFr.setVisible(true);
}
public void apply(OFImage image) {
makeFrame();
int height = image.getHeight();
int width = image.getWidth();
for(int y = 0; y < height; y++) {
for(int x = 0; x < width; x++) {
Color pix = image.getPixel(x, y);
image.setPixel(x, y, new Color(trunC(factor*(pix.getRed()-128)+128),
trunC(factor*(pix.getGreen()-128)+128),
trunC(factor*(pix.getBlue()-128)+128)));
} } }
public int trunC(int a){
if (a>255){
return 255;
}
return a;
}
}
If you want your apply() method to work after the button "OK" was clicked (user inputs something, then clicks OK), then you need to put an apply() invocation into you ActionListener's actionPerformed() method body.
I am developing a basic game in Java that is going to be similar to 2d games where you dodge asteroids in a space ship.
My issue is that for some reason some of my asteroids are not correctly being added to my JFrame.
Here is how I add my JLabel's (asteroids):
NOTE: SpriteSheet.java is a valid and functioning class that does successfully return the image.
#SuppressWarnings("serial")
class Entity extends JLabel {
private int asteroidSizeW = 72;
private int asteroidSizeH = 72;
private int entities = 5;
public Entity() {
List<Integer> asteroidLocations = new ArrayList<Integer>();
Random ran = new Random();
for (int i = 0; i < entities; i++) {
if (asteroidLocations.size() > 0) {
for (int j = 0; j < asteroidLocations.size(); j++) {
int chosenSpawn = ran.nextInt(Frame.WIDTH - 72);
if (chosenSpawn > (asteroidLocations.get(j) + 72) || chosenSpawn < (asteroidLocations.get(j) - 72)) {
System.out.println("Made it!");
System.out.println(asteroidLocations);
asteroidLocations.add(chosenSpawn);
setIcon(new SpriteSheet().load("asteroids.png", 0, 0, asteroidSizeW, asteroidSizeH));
setLocation(chosenSpawn, 0);
setSize(asteroidSizeW, asteroidSizeH);
break;
}
}
} else {
int x = ran.nextInt(Frame.WIDTH - 72);
asteroidLocations.add(x);
setIcon(new SpriteSheet().load("asteroids.png", 0, 0, asteroidSizeW, asteroidSizeH));
setLocation(x, 0);
setSize(asteroidSizeW, asteroidSizeH);
}
}
}
}
After testing I have found out that the first JLabel is being added (the else in):
if (asteroidLocations.size() > 0) {
} else {
// this is being executed once (like it should)
}
In other words, the first image (JLabel) does display in the game.
Everything else does work as intended as seen here by the output:
Made it!
[181]
Made it!
[181, 273]
Made it!
[181, 273, 452]
Made it!
[181, 273, 452, 627]
It seems like it has an issue returning the JLabel inside the nested loop.
Could anybody please help me out? Thanks.
FULL CLASS:
package main;
import java.awt.*;
import java.util.ArrayList;
import java.util.Random;
import java.util.List;
import javax.swing.*;
#SuppressWarnings("serial")
class Frame extends JFrame {
public static final int WIDTH = 800;
public static final int HEIGHT = 800;
private String TITLE = "Asteroid Killer";
public static void main(String args[]) {
Frame f = new Frame();
f.createFrame();
f.showFrame();
}
private void createFrame() {
setTitle(TITLE);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setResizable(false);
add(new MainPanel());
setSize(WIDTH, HEIGHT);
}
private void showFrame() {
this.setVisible(true);
}
}
#SuppressWarnings("serial")
class MainPanel extends JPanel {
public MainPanel() {
setLayout(null);
setBackground(Color.GRAY);
//add(new Minimap());
add(new Entity());
}
}
/*#SuppressWarnings("serial")
class Minimap extends JPanel {
public Minimap() {
setBounds(284, 0, 100, 100); // x,y,width,height
setBackground(Color.RED);
}
}*/
#SuppressWarnings("serial")
class Entity extends JLabel {
private int asteroidSizeW = 72;
private int asteroidSizeH = 72;
private int entities = 5;
public Entity() {
List<Integer> asteroidLocations = new ArrayList<Integer>();
Random ran = new Random();
for (int i = 0; i < entities; i++) {
if (asteroidLocations.size() > 0) {
for (int j = 0; j < asteroidLocations.size(); j++) {
int chosenSpawn = ran.nextInt(Frame.WIDTH - 72);
if (chosenSpawn > (asteroidLocations.get(j) + 72) || chosenSpawn < (asteroidLocations.get(j) - 72)) {
System.out.println("Made it!");
System.out.println(asteroidLocations);
asteroidLocations.add(chosenSpawn);
setIcon(new SpriteSheet().load("asteroids.png", 0, 0, asteroidSizeW, asteroidSizeH));
setLocation(chosenSpawn, 0);
setSize(asteroidSizeW, asteroidSizeH);
break;
}
}
} else {
int x = ran.nextInt(Frame.WIDTH - 72);
asteroidLocations.add(x);
setIcon(new SpriteSheet().load("asteroids.png", 0, 0, asteroidSizeW, asteroidSizeH));
setLocation(x, 0);
setSize(asteroidSizeW, asteroidSizeH);
}
}
}
}
You logic is around backwards. You are only ever adding a single JLabel to your container...
add(new Entity());
You actually Entity class has no means to put anything onto the screen other than a single representation of itself...which is typically the last thing it created within it's for loop...
Instead of trying to create multiple entities within your Entity class (I know, you're only setting the properties, but the intention is the same), move the creation process out to the the MainPanel and physically create a new Entity on each loop
Now, having said that, screwing with components in this manner is probably not the best solution. You should consider using a custom painting approach, you will find it easier to manage all the entities and will probably also consume less overhead...
Consider having a look at
Painting in AWT and Swing
Performing Custom Painting
for more details
So I'm doing this exercise, where I need to create a program that will be moving a little ball on the screen by pressing one of four buttons. I've completed it, but then I wanted to make the initial position to be in the center of the screen, so I assigned the values getWidth()/2 to xCoord and getHeight()/2 to yCoord (first I did it without the constructor, then when it didn't work I added the constructor and added repaint(), so the paintComponent() would be called) but the ball is still in the top left corner when I start the program. How can I fix this?
P.S. I will appreciate any comments on the code in general, as well. Thank you.
package movingaball;
import java.awt.BorderLayout;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class MovingABall extends JFrame {
private JButton jbtLeft = new JButton("Left");
private JButton jbtRight = new JButton("Right");
private JButton jbtUp = new JButton("Up");
private JButton jbtDown = new JButton("Down");
private BallPanel ballPanel = new BallPanel();
public MovingABall () {
JPanel buttonPanel = new JPanel();
buttonPanel.add(jbtLeft);
buttonPanel.add(jbtRight);
buttonPanel.add(jbtUp);
buttonPanel.add(jbtDown);
this.add(ballPanel);
this.add(buttonPanel, BorderLayout.SOUTH);
jbtLeft.addActionListener(new ButtonListener());
jbtRight.addActionListener(new ButtonListener());
jbtUp.addActionListener(new ButtonListener());
jbtDown.addActionListener(new ButtonListener());
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
MovingABall mainWondow = new MovingABall();
mainWondow.setTitle("Moving a ball");
mainWondow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainWondow.setSize(300, 200);
mainWondow.setVisible(true);
}
class ButtonListener implements ActionListener {
public void actionPerformed(ActionEvent buttonPressed) {
if (buttonPressed.getSource() == jbtLeft)
ballPanel.left();
else if (buttonPressed.getSource() == jbtRight)
ballPanel.right();
else if (buttonPressed.getSource() == jbtUp)
ballPanel.up();
else if (buttonPressed.getSource() == jbtDown)
ballPanel.down();
}
}
class BallPanel extends JPanel {
private int xCoord = 10;
private int yCoord = 10;
public BallPanel() {
xCoord = getWidth()/2;
yCoord = getHeight()/2;
repaint();
}
#Override
public void setBackground(Color bg) {
super.setBackground(bg); //To change body of generated methods, choose Tools | Templates.
}
public void left() {
xCoord-=5;
repaint();
}
public void right() {
xCoord+=5;
repaint();
}
public void up() {
yCoord-=5;
repaint();
}
public void down() {
yCoord+=5;
repaint();
}
protected void paintComponent(Graphics aBall) {
super.paintComponent(aBall);
System.out.println("X" + getWidth());
aBall.drawOval(xCoord, yCoord, 10, 10);
}
}
}
You Could
Use a ComponentListener and listener for ComponentResized events, but this could be called multiple times before the component reaches it's final screen size....
You Could
Use a AncestorListener listening for ancestorAdded, but it suffers from the same problem as the ComponentListener
You Could
Use a HierarchyListener listening for hierarchyChanged, but it suffers from the same problem as the ComponentListener and AncestorListener
You Could
Override doLayout, but this suffers from the same problem as the ComponentListener and AncestorListener, HierarchyListener...
So what to do?
What we need is to know when the component is resized for the last time when it is first shown. From my testing I found doLayout and hierarchyChanged of HierarchyListener to be good candidates.
Now the problem arises because we only want to use them until we get onto the screen, then after that, we don't care...
So, the first thing we need to is initialise the x/yCoords to some "invalid" value...
private int xCoord = -1;
private int yCoord = -1;
This gives us some clue that we still need to "set" the coordinates...
Next, we need some way we can set up an interruptible call back. Some way to way inject a short delay between our chosen "listener" and the time we actually update the coordinates, but which can be reset if the "listener" is triggered....
javax.swing.Timer is an excellent choice for this. I can wait in the background for a specified period of time and can be restarted should we need it...
private Timer resizeTimer;
public BallPanel() {
resizeTimer = new Timer(125, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// Only update the coorinates if they are invalid...
if (xCoord < 0 && yCoord < 0) {
xCoord = getWidth() / 2;
yCoord = getHeight() / 2;
repaint();
}
}
});
resizeTimer.setRepeats(false);
Finally, need to "restart" the timer when our chosen "listener" is triggered.
For simplicity, I went for doLayout....
#Override
public void doLayout() {
super.doLayout();
if (xCoord < 0 && yCoord < 0) {
resizeTimer.restart();
}
}
Now, you might need to play around with the delay, I found 250 milliseconds to slow, but that's just me ;)
See comments in code.
import java.awt.BorderLayout;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class MovingABall extends JFrame {
private JButton jbtLeft = new JButton("Left");
private JButton jbtRight = new JButton("Right");
private JButton jbtUp = new JButton("Up");
private JButton jbtDown = new JButton("Down");
private BallPanel ballPanel = new BallPanel();
public MovingABall () {
JPanel buttonPanel = new JPanel();
buttonPanel.add(jbtLeft);
buttonPanel.add(jbtRight);
buttonPanel.add(jbtUp);
buttonPanel.add(jbtDown);
ballPanel.setBackground(Color.RED);
this.add(ballPanel);
this.add(buttonPanel, BorderLayout.SOUTH);
jbtLeft.addActionListener(new ButtonListener());
jbtRight.addActionListener(new ButtonListener());
jbtUp.addActionListener(new ButtonListener());
jbtDown.addActionListener(new ButtonListener());
}
public static void main(String[] args) {
// Should be called on the EDT!
MovingABall mainWondow = new MovingABall();
mainWondow.setTitle("Moving a ball");
mainWondow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Don't pack here. Instead return a preferred size for the
// custom comonent end..
//mainWondow.setSize(300, 200);
// ..pack() the window.
mainWondow.pack();
mainWondow.setVisible(true);
}
class ButtonListener implements ActionListener {
public void actionPerformed(ActionEvent buttonPressed) {
if (buttonPressed.getSource() == jbtLeft)
ballPanel.left();
else if (buttonPressed.getSource() == jbtRight)
ballPanel.right();
else if (buttonPressed.getSource() == jbtUp)
ballPanel.up();
else if (buttonPressed.getSource() == jbtDown)
ballPanel.down();
}
}
class BallPanel extends JPanel {
private int xCoord = -1;
private int yCoord = -1;
private Dimension preferredSize = new Dimension(300,200);
/* Harmful to our logic..
public BallPanel() {
xCoord = getWidth()/2;
yCoord = getHeight()/2;
repaint();
}
*/
/* A good compiler would remove this..
#Override
public void setBackground(Color bg) {
super.setBackground(bg);
} */
public void left() {
xCoord-=5;
repaint();
}
public void right() {
xCoord+=5;
repaint();
}
public void up() {
yCoord-=5;
repaint();
}
public void down() {
yCoord+=5;
repaint();
}
/** Suggest a size to the layout manager. */
#Override
public Dimension getPreferredSize() {
return preferredSize;
}
protected void paintComponent(Graphics aBall) {
super.paintComponent(aBall);
// This will center the ball if it is the first time painted
// OR if the x or y co-ord goes off the left/top edge.
// Further logic left to user..
if (xCoord<0 || yCoord<0) {
xCoord = getWidth()/2;
yCoord = getHeight()/2;
}
System.out.println("X" + getWidth());
aBall.drawOval(xCoord, yCoord, 10, 10);
}
}
}
You're on the right track, but calling the getWidth() and getHeight() methods too soon. They'll still return zero cause the size has not yet been set:
GetWidth() and getHeight() get called here, in the constructor:
MovingABall mainWondow = new MovingABall();
mainWondow.setTitle("Moving a ball");
mainWondow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
But size gets set here:
mainWondow.setSize(300, 200);
mainWondow.setVisible(true);
So when to act then? You could override setSize() or setVisible() in your MovingABall class or listen for componentShown()
Listen for when a Component is Shown for the First Time suggests a HierarchyListener, which may be the most reliable approach, but a bit overkill here.
EDIT:
The simplest thing that could possibly work is simpler still, I just realized: Initialize the BallPanel not to 10,10 but to 150,100 :)
Somewhat more seriously:
Add the dimension to both MainWindow and BallPanel's constructors instead of calling setSize on the MainWindow and you'll reliably get a consistent initial state without any hassle.
In Java, what is the best way to perform drag and drop when the item being dragged is the source control itself? I know a control is nothing but data too, but the difference does have UI impacts.
I'm creating a solitaire-style game where I have card objects of class Card derived from JLabel. I want to drag that card to another location by dropping it onto a yet-to-be named Destination control. During the drag, I want the card to visually move with the mouse and when dropped I want it to either move to this destination object or return to its previous location.
I've done various D-n-D tests and haven't found anything that works under the proper rules of Java's D-D.
For example, if I drag the Card object using true D-n-D I can only create a ghosted image of the card and not a solid image. Also, the cursor changes and I'd rather it did not (I think I can fix that), and the source control remains visible (though it should be easy to make it transparent during the drag)
On the other hand, I can drag the Card beautifully by listening for MouseMotionListener.mouseDragged() events and manually moving the Card to the new location. This works great, but it is not following proper D-n-D because this will not inform other controls of the drag. I figured I could either create my own system to notify the other controls, but that would not be using Java's real D-n-D. Also, if I mix the real Java d-n-d stuff with this method of literally moving the Card during mouseDragged then I assume the real D-n-D stuff will never work because the mouse will never technically be directly over any other control than the card being dragged. This direction just seems like a crude hack.
I hope this makes sense. I've been having problems following samples because they all seem very different, and one that I spent a great deal of time studying looks to be dated a couple years before D-n-D had its major overhaul in version 1.4.
One way to drag a component around a single application and not between applications is to use a JLayeredPane. For example please see my code here: dragging a jlabel around the screen
An example with playing cards could look like this (as long as the playing card image remains valid!):
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.*;
public class PlayingCardTest {
public static void main(String[] args) {
String pathToDeck = "http://www.jfitz.com/cards/classic-playing-cards.png";
try {
final List<ImageIcon> cardImgList = CreateCards.createCardIconList(pathToDeck);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame("Moving Cards");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new CardGameTable(cardImgList, frame));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
} catch (MalformedURLException e) {
e.printStackTrace();
System.exit(-1);
} catch (IOException e) {
e.printStackTrace();
System.exit(-1);
}
}
}
#SuppressWarnings("serial")
class CardGameTable extends JLayeredPane {
private static final int PREF_W = 600;
private static final int PREF_H = 400;
private static final Color BASE_COLOR = new Color(0, 80, 0);
private static final int CARD_COUNT = 20;
private static final int WIDTH_SHOWING = 20;
private JPanel basePane = new JPanel(null);
public CardGameTable(List<ImageIcon> cardImgList, final JFrame frame) {
basePane.setSize(getPreferredSize());
basePane.setBackground(BASE_COLOR);
add(basePane, JLayeredPane.DEFAULT_LAYER);
final MyMouseAdapter myMouseAdapter = new MyMouseAdapter(this, basePane);
addMouseListener(myMouseAdapter);
addMouseMotionListener(myMouseAdapter);
for (int i = 0; i < CARD_COUNT; i++) {
JLabel card = new JLabel(cardImgList.remove(0));
card.setSize(card.getPreferredSize());
int x = (PREF_W / 2) + WIDTH_SHOWING * (CARD_COUNT - 2 * i) / 2 -
card.getPreferredSize().width / 2;
int y = PREF_H - card.getPreferredSize().height - WIDTH_SHOWING * 2;
card.setLocation(x, y);
basePane.add(card);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
}
class MyMouseAdapter extends MouseAdapter {
private JLabel selectedCard = null;
private JLayeredPane cardGameTable = null;
private JPanel basePane = null;
private int deltaX = 0;
private int deltaY = 0;
public MyMouseAdapter(JLayeredPane gameTable, JPanel basePane) {
this.cardGameTable = gameTable;
this.basePane = basePane;
}
#Override
public void mousePressed(MouseEvent mEvt) {
Component comp = basePane.getComponentAt(mEvt.getPoint());
if (comp != null && comp instanceof JLabel) {
selectedCard = (JLabel) comp;
basePane.remove(selectedCard);
basePane.revalidate();
basePane.repaint();
cardGameTable.add(selectedCard, JLayeredPane.DRAG_LAYER);
cardGameTable.revalidate();
cardGameTable.repaint();
deltaX = mEvt.getX() - selectedCard.getX();
deltaY = mEvt.getY() - selectedCard.getY();
}
}
#Override
public void mouseReleased(MouseEvent mEvt) {
if (selectedCard != null) {
cardGameTable.remove(selectedCard);
cardGameTable.revalidate();
cardGameTable.repaint();
basePane.add(selectedCard, 0);
basePane.revalidate();
basePane.repaint();
selectedCard = null;
}
}
#Override
public void mouseDragged(MouseEvent mEvt) {
if (selectedCard != null) {
int x = mEvt.getX() - deltaX;
int y = mEvt.getY() - deltaY;
selectedCard.setLocation(x, y);
cardGameTable.revalidate();
cardGameTable.repaint();
}
}
}
class CreateCards {
private static final int SUIT_COUNT = 4;
private static final int RANK_COUNT = 13;
public static List<ImageIcon> createCardIconList(String pathToDeck)
throws MalformedURLException, IOException {
BufferedImage fullDeckImg = ImageIO.read(new URL(pathToDeck));
int width = fullDeckImg.getWidth();
int height = fullDeckImg.getHeight();
List<ImageIcon> iconList = new ArrayList<ImageIcon>();
for (int suit = 0; suit < SUIT_COUNT; suit++) {
for (int rank = 0; rank < RANK_COUNT; rank++) {
int x = (rank * width) / RANK_COUNT;
int y = (suit * height) / SUIT_COUNT;
int w = width / RANK_COUNT;
int h = height / SUIT_COUNT;
BufferedImage cardImg = fullDeckImg.getSubimage(x, y, w, h);
iconList.add(new ImageIcon(cardImg));
}
}
Collections.shuffle(iconList);
return iconList;
}
}