After finishing and running a simple program that lets you change the color of a shape when it's clicked on:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
class MyPanel extends JPanel{
int x = 200,y = 200,r = 50;
static Color co = Color.BLUE;
static final JFrame frame = new JFrame();
public static void main(String[] args){
frame.setTitle("Color Change with Mouse Click");
frame.setSize(500,500);
MyPanel pane = new MyPanel();
frame.add(pane);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pane.setVisible(true);
}
public void Panel(){
addMouseListener(new MouseAdapter(){
public void mouseClicked(MouseEvent e){
if(((e.getX() >= (x-r) && e.getX() < (x+r) && e.getY() >= (y-r) && e.getY() < (y+r))) & (co == Color.GREEN)){
co = Color.BLUE;
repaint();
};
if(((e.getX() >= (x-r) && e.getX() < (x+r) && e.getY() >= (y-r) && e.getY() < (y+r))) & (co == Color.BLUE)){
co = Color.GREEN;
repaint();
};
}
});
}
Timer timer = new Timer(1, new ActionListener(){
#Override
public void actionPerformed(ActionEvent e){
Panel();
}
});
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
timer.start();
Panel();
g.setColor(co);
g.fillOval(x-r, y-r, 2*r, 2*r);
repaint();
}
}
I encountered a problem that I simply don't know how to fix. The JPanel never updates on the second mouse click, only on the first. I thought that adding the timer would take care of this, but apparently it didn't. Help is much appreciated.
edit:
I changed my code per Aqua's suggestions:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
class MyPanel extends JPanel{
int x = 200,y = 200,r = 50;
static Color co = Color.BLUE;
public static void main(String[] args){
final JFrame frame = new JFrame();
frame.setTitle("Color Change with Mouse Click");
MyPanel pane = new MyPanel();
frame.add(pane);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(500, 500);
}
public void panel(){
addMouseListener(new MouseAdapter(){
public void mouseClicked(MouseEvent e){
if(((e.getX() >= (x-r) && e.getX() < (x+r) && e.getY() >= (y-r) && e.getY() < (y+r))) && (co == Color.GREEN)){
co = Color.BLUE;
repaint();
}else if(((e.getX() >= (x-r) && e.getX() < (x+r) && e.getY() >= (y-r) && e.getY() < (y+r))) && (co == Color.BLUE)){
co = Color.GREEN;
repaint();
}else{
repaint();
};
}
});
}
public MyPanel(){
Timer timer = new Timer(20, new ActionListener(){
#Override
public void actionPerformed(ActionEvent e){
panel();
}
});
timer.start();
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
g.setColor(co);
g.fillOval(x-r, y-r, 2*r, 2*r);
repaint();
}
}
And the color change works now, however the color change is a bit unpredictable. sometimes it has a delay, sometimes it doesn't, and sometimes it just outright won't change back. Is this a problem with my timer?
What is the point of the Timer? A Timer is used to schedule an event. You are not scheduling any events, you are waiting for the user to click the mouse. There is no need for the Timer. If you ever do need a Timer then:
you would never schedule the Timer to fire every millisecond, that is too fast for the machine to respond to. Use a more realistic value depending on the requirement. Maybe 50 or 100ms.
you would never start the Timer in a painting method. You would probably start the Timer in the constructor of your class.
Your MouseListener code is wrong. You start the Timer and invoke the Panel() method which invokes the addMouseListener() method. So after 1 second you will have added 1000 MouseListeners added to your panel. This is obviously wrong. The MouseListener should be added in the constructor of your class.
There is no need for the "co" variable (if it was needed it would NOT be static). All Swing components support a setForeground() method to set the color of the painted text. In your case you are painting a shape, but you can still use the setForeground() method when you want to change the color. Then in your painting code you would just use:
//g.setColor(co);
g.setColor(getForeground());
Since you added the MouseListener to the panel there is no need to check the point of the mouse click. The event will only be generated when you click on the panel. So all you need to do is use setForeground(...) to the color you want. You don't even need to invoke repaint since Swing is smart enough to repaint itself when you change one of its properties.
Calling Panel() from paintComponent() triggers an endless loop that results in StackOverflowError exception. As repaint() eventually results in a call to paintComponent().
You should attach a mouse listener once in the initialization of a panel, not on every repaint. Same goes for the timer. But, it is not clear what is the intention of timer in this sample.
Some other problems with the posted code:
Call frame.setVisible(true); instead of pane.setVisible(true); As posted the code does not work. There is also no need to keep a static member JFrame frame in the panel. You can simply declare and use JFrame it in main().
paintComponent() is for painting only. Avoid complex logic in that method. Do not setup timers or call other initialization methods. You have little control when paintComponent is executed and it has to be fast for optimal drawing results. Go through Performing Custom Painting tutorials. Move out initialization of timer and mouse listener to panel's constructor.
Do not use frame.setSize(500,500);, instead override JPanel.getPreferredSize(). See Should I avoid the use of set(Preferred|Maximum|Minimum)Size methods in Java Swing? for details.
Stick to Java naming conventions. Method named Panel() is confusing. See Naming Conventions.
There is a missing else statement in Panel() method. On the second click, you first get into the first if statement, set the color BLUE, then, enter the second if statement and reset the color to GREEN.
As mentioned above in comments, & and && are different operators. See Operators.
Related
On Head First Java book we're seeing some little animation and I'm trying to draw an animation that traces out a diagonal line. I'm doing so by using the paintComponent() method and drawing an oval at x,y (values which are updated each time through the loop). Why do I lose the previously drawn ovals? According to the book I should be getting a smear on the screen where the previously drawn ovals are not lost. This should need fixing by adding to the paintComponent() method a white background every time repaint() is called but instead i'm not getting the 'mistake'. Why is this and how do I get to KEEP the previously drawn ovals on the panel? Using JDK 13.0.2 and Mac OSX Catalina
import javax.swing.*;
import java.awt.*;
public class SimpleAnimation {
int x = 70;
int y = 70;
public static void main(String[] args) {
SimpleAnimation gui = new SimpleAnimation();
gui.go();
}
public void go() {
JFrame frame = new JFrame();
MyDrawPanel drawPanel = new MyDrawPanel();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(drawPanel);
frame.setSize(300,300);
frame.setVisible(true);
for (int i = 0; i < 130; i++) {
x++;
y++;
drawPanel.repaint();
try {
Thread.sleep(25);
} catch (Exception ex){};
}
}
class MyDrawPanel extends JPanel {
public void paintComponent(Graphics g) {
g.setColor(Color.orange);
g.fillOval(x,y,50,50);
}
} // close inner class
} // close outer class
Is that what prevents the smear of ovals?
class MyDrawPanel extends JPanel {
public void paintComponent(Graphics g) {
g.setColor(Color.orange);
g.fillOval(x,y,50,50);
}
The code should be:
class MyDrawPanel extends JPanel {
public void paintComponent(Graphics g) {
super.paintComponent(g); // added
g.setColor(Color.orange);
g.fillOval(x,y,50,50);
}
You need the super.paintComponent(g) to clear the background of the panel before doing the custom painting.
Yeah, great book. The output i'm getting with the code as is, is the "corrected version"
Edit:
The book is correct. You need to understand how the EDT works. When you start an application the code invoked in the main() method executes on a separate Thread. Swing events and painting is done on the Event Dispatch Thread (EDT). So invoking sleep() in the go() method should NOT affect the painting of the circle and you should see the smear. If you only see a single oval painted after the loop is finished, then this imples your IDE or platform starts the code in the main() method on the EDT, which is not normal.
You can verify my above statement by adding:
System.out.println( SwingUtilities.isEventDispatchThread() );
to your loop to see if it is executing on the EDT or not.
I would like to draw some circles every second and delete them all (or at least one) from the panel.
Here is the exisiting code:
public class DrawShape {
ShapePanel panel;
public DrawShape(ShapePanel panel) {
this.panel = panel;
Timer t = new Timer();
t.schedule(new TimerTask() {
long startTime = System.currentTimeMillis();
int secondsToRun = 3;
#Override
public void run() {
if (System.currentTimeMillis() - startTime > secondsToRun * 1000) {
panel.deleteCircle();
System.out.println("\t" + panel.circles.size());
cancel();
} else {
panel.addCircle(new Circle((int) (Math.random() * 200), (int) (Math.random() * 200)));
System.out.println(panel.circles.size());
}
}
}, 0, 1000);
}
}
If the time is greater than 3 seconds, delete all circles, else continue to draw circles on screen.
Here is the ShapePanel class:
public class ShapePanel extends JPanel {
public List<Circle> circles = new LinkedList<Circle>();
public ShapePanel() {
// Setting up the frame
JFrame frame = new JFrame();
frame.setSize(500, 500);
frame.setVisible(true);
frame.setBackground(Color.black);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(this); // adding the panel to the frame
}
public void addCircle(Circle circle) {
circles.add(circle);
this.repaint();
}
public void deleteCircle() {
// circles.remove(circles.get(0));
circles.clear(); //remove them all
this.repaint();
}
#Override
public void paint(Graphics g) {
for (Circle c : circles) {
g.setColor(Color.GREEN);
g.drawOval(c.x, c.y, Circle.radius * 2, Circle.radius * 2);
}
}
}
When I call deleteCircle(), the circles should be removed from the list and repaint. I should end up with a blank screen with no circles in it. I think repaint doesn't work in this case.
P.S.: first time asking a question, so sorry if it's long one :D
So, two things jump out at me immediately.
Using java.util.Timer
Swing is NOT thread safe and since java.util.Timer runs in it's own thread, updating the UI (or in this case, something the UI relies on) could cause you random issues.
I'd consider using a javax.swing.Timer instead, as it is triggered inside the Event Dispatching Queue (and is generally simpler to use)
Not calling super.paint
You've failed to take into consideration what paint does and failed to take over it's responsibilities, which, in this case, would be to "prepare" the Graphics context for painting.
Graphics is a shared resource, which is passed to all the components been updated in the paint pass. This means it will contain what ever was previously painted to it.
Two recommendations:
As a general recommendation, prefer overriding paintComponent instead of paint (paint does a lot of important jobs, so unless you're willing to do them, it's generally to high up in the paint chain)
Call super.paintComponent first, before doing any custom painting.
I would, highly, recommend reading:
Performing Custom Painting
Painting in AWT and Swing
and
Concurrency in Swing
How to Use Swing Timers
I am making a Solitaire program as a side project and I am having problems with the paint window I have made.
In the program, I am having a line start at one point that ends at the position of my mouse click. When I click on the window, it successfully reads my clicks and changes the xcor and ycor variables to my mouse click position, but fails to repaint a line using the new coordinates.
public class Game_Play extends JFrame {
public int xcor = 0;
public int ycor = 0;
public void setup() { //sets up JFrame
JFrame frame = new JFrame();
frame.setSize(500, 500);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocation(0, 0);
frame.setTitle("Circles");
frame.add(new MouseHandler());
frame.addMouseListener(new MouseHandler());
frame.addMouseMotionListener(new MouseHandler());
frame.setVisible(true);
}
//listener and painting subclass
class MouseHandler extends JPanel implements MouseListener, MouseMotionListener {
//when mouse pressed, the Xcor and Ycor
//will be changed to the current mouse
//x and y cords, then it will call
//repaint() to repaint the line using the
//new Xcor and Ycor locations
public void mousePressed(MouseEvent me) {
System.out.println("mouse pressed");
xcor = me.getX();
ycor = me.getY();
//prints out new cords
System.out.println(xcor + " xcor");
System.out.println(ycor + " ycor");
repaint();
}
public void mouseReleased(MouseEvent me) { //tests to make sure listener is working
System.out.println("mouse released");
}
public void mouseClicked(MouseEvent me) {}
public void mouseEntered(MouseEvent me) {}
public void mouseMoved(MouseEvent me) {}
public void mouseExited(MouseEvent me) {}
public void mouseDragged(MouseEvent me) {}
//paints the line with the Xcor and Ycor values
public void paintComponent(Graphics g) {
super.paintComponent(g);
System.out.println("repaint check"); //test to see if repaint has been called
g.drawLine(100, 100, xcor, ycor);
}
}
}
Note: repaint() is being called from the MouseListener method mousePressed, I also have tried calling it from different MouseListener and MouseMotionListener methods to no avail.
Note: The paintComponent method notifies me if it has been called successfully, and when I click, the paintComponent method does not execute.
Note: I did notice that if I click on the screen to set the new cords then hit the maximize button on the window, it will successfully call the repaint method with a redrawn line using the new cords.
Note: the setup() method is being called from another class in another file, the code is as follows:
public static void main(String[] args) throws IOException {
deck_Create();
deck_Shuffle();
game_setup();
BufferedImage_array_Setup();
//being called here
Game_Play a = new Game_Play();
a.setup();
//
}
Last Note: I have searched high and low for the fix to this problem, only coming up with similar problems that didn't help me. Any Feedback given is greatly appreciated.
If there are any questions, let me know and I will address them for you in a few.
Thanks!
Some comment on your code:
public void setup() {
JFrame frame = new JFrame();
frame.setSize(500, 500);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocation(0, 0);
frame.setTitle("Circles");
frame.add(new MouseHandler());// your panel
frame.addMouseListener(new MouseHandler()); // your listener, also a panel, but not the one you added to your frame
frame.addMouseMotionListener(new MouseHandler()); // yet another listener, also not the panel you added to your frame
frame.setVisible(true);
}
You probably meant to write:
public void setup() {
JFrame frame = new JFrame();
frame.setSize(500, 500);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocation(0, 0);
frame.setTitle("Circles");
JPanel p = new MouseHandler();
frame.add(p);
frame.addMouseListener(p);
frame.addMouseMotionListener(p);
frame.setVisible(true);
}
Note that having your UI components implement listener interfaces is not a good idea. What if you want to have two mouse listeners for different components in the panel? You can't have both listeners on the panel.
A better way is to have the listener interfaces implemented by anonymous classes, following the seperation of concerns guideline.
Another thing is to add the listeners to the components that should handle them. You should be registering these listeners on the panel, not the frame containing the panel.
And finally, you should be setting the panel as the content pane using setContentPane. Usually it's best to have the panel dictate what its size should be by overriding setPreferredSize. In that case you don't need to set the size of the containing frame, rather you call pack to size the frame to the preferred size of its subcomponents.
I'm trying to make a pong game in Java but it doesn't work.
I've done some testing and it seems that the variables are updating but that when I do
repaint(); in the timers actionPerformed(ActionEvent e) doesn't call the paintComponent() method
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class PongGame extends JComponent implements ActionListener, MouseMotionListener{
public int state = 1;
public int paddleX;
public String buttonColor = "blue";
public int mouseX, mouseY;
private int ballX = 400;
private int ballY = 150;
public static void main(String[] args){
JFrame window = new JFrame("Pong");
PongGame game = new PongGame();
window.add(new PongGame());
window.pack();
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setLocationRelativeTo(null);
window.setResizable(false);
window.setVisible(true);
Timer t = new Timer(20, game);
t.start();
}
public Dimension getPreferredSize(){
return new Dimension(800, 600);
}
public void paintComponent(Graphics g){
paddleX = mouseX;
g.setColor(Color.WHITE);
g.fillRect(0,0, 800, 600);
g.setColor(Color.BLACK);
g.fillRect(paddleX, 550, 150, 15);
g.fillOval(ballX, ballY, 30, 30);
}
#Override
public void actionPerformed(ActionEvent e) {
ballX = ballX + 10;
ballY = ballY + 10;
System.out.println(ballX + " " + ballY);
}
#Override
public void mouseDragged(MouseEvent e) {
}
#Override
public void mouseMoved(MouseEvent e) {
mouseX = e.getX();
repaint();
}
}
you haven't registered the implemented MouseMotionListener to any component:
game.addMouseMotionListener(game);
you are not adding your first created instance of PongGame to the frame rather added a new one producing bug:
PongGame game = new PongGame();
window.add(new PongGame()); // <<--- why creating the new instance ?
// it should be window.add(game);
As a good programming practice: try putting the add listener code in the component's own creating context i.e., in their constructor to make your code better readable.
The problem is:
PongGame game = new PongGame();
window.add(new PongGame());
You have two instances of PongGame. One added to the frame (new PongGame()) and the other (game) that actually reacts to the timer. Change this row to:
window.add(game);
To correct the actual problem. Add a constructor (tested locally):
PongGame() {
addMouseMotionListener(this);
}
repaint() does not invoke paint() directly. It schedules a call to an intermediate method, update(). Finally, update() calls paint() (unless you override update).
The reason for this complexity is Java's support for concurrent programming. It does this using threads.
Using repaint() can be tricky for at least three reasons.
the interaction between repaint() and the spontaneous painting done by the GUI thread
the fact that repaint() just asks the thread system to schedule a call to update()/paint() and then exits. The repaint() method is asynchronous.
the problem of preventing your drawing from being erased when being updated.
I suggest you to try the same with update().
Useful link : http://www.scs.ryerson.ca/~mes/courses/cps530/programs/threads/Repaint/index.html
I have a question. I want to make a swing form that, when clicking in a button he slides a panel (with his content) to the left so the panel on the right replaces it with a smooth effect.
I Have tried to do a while how checks the size of the panel and then minimize it and shows the next one like this :
while (jpanelprincipal1.getWidth() < 439 || jpanelprincipal1.getHeight() > 250)
{
int panel1width = jpanelprincipal1.getWidth();
int panel2height = jpanelprincipal1.getHeight();
jpanelprincipal1.setSize(panel1width -- , panel2height --);
jpanelprincipal2.setSize(440,250);
}
I used this trick in C# but with the Application.DoEvent(); (how obviously it's not available on java).
Is there anyway i can make a slide effect of 2 or more panels?
BTW : Sorry for my very bad english !
Thanks In Advance,
Luis Da Costa
he slides a panel (with his content) to the left so the panel on the right replaces it with a smooth effect
You question mentions you want the panel to "slide", but the code looks like you are trying to get the panel to "shrink", so it is replaced by another panel.
Assuming you have two panels each with the same size, then you can "slide" one out of view while the other slides into view.
To do this you an use a panel with a GridLayout. This way each component will be the same size. Then you add the panel to a scrollpane without any scrollbars. The size of the scrollpane will need to be set to the size of the first compnoent. Then you can "slide" the two panels by changing the position of the viewport. So in your Timer you would have code something like:
JViewport viewport = scrollPane.getViewport();
Point position = viewport.getViewPosition();
position.x += 5;
viewport.setViewPosition( position );
You would then stop the Timer when the position is greater than the size of the component.
As suggested by #HFOE, javax.swing.Timer is a good choice for animation. The setDividerLocation() method of JSplitPane can be called from the ActionListener. See How to Use Split Panes for additional options.
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
/** #see http://stackoverflow.com/questions/5069152 */
public class SplitPaneTest {
double ratio = 0.5;
double delta = ratio / 10;
private void create() {
JFrame f = new JFrame("JSplitPane");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
MyPanel p1 = new MyPanel(Color.red);
MyPanel p2 = new MyPanel(Color.blue);
final JSplitPane jsp = new JSplitPane(
JSplitPane.HORIZONTAL_SPLIT, true, p1, p2);
Timer timer = new Timer(200, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
ratio += delta;
if (ratio >= 1.0) {
ratio = 1.0;
delta = -delta;
} else if (ratio <= 0) {
delta = -delta;
ratio = 0;
}
jsp.setDividerLocation(ratio);
}
});
f.add(jsp);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
timer.start();
}
private static class MyPanel extends JPanel {
Color color;
public MyPanel(Color color) {
this.color = color;
this.setPreferredSize(new Dimension(300, 300));
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(color);
g.drawLine(0, 0, getWidth(), getHeight());
g.drawLine(getWidth(), 0, 0, getHeight());
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new SplitPaneTest().create();
}
});
}
}
I would probably do this with a Swing Timer. Change a class field representing the x, y position of the sliding JPanel in the timer's ActionListener and then call repaint on the container holding the JPanels. A JLayeredPane could work well as the container for the sliding JPanels.
Edit 1: regarding your request for code, I think the best thing is for you to try to create a very small compilable runnable program that attempts to do this, and then post your code with an explanation of your program's behavior as an edit to your original post. Also send us a comment to notify us of your changes. Then we can inspect your code, test it, modify it, and help you mold it into a working program. This is called creating a "Short, Self Contained, Correct (Compilable), Example" or SSCCE (please check the link).