Moving a ball on the screen with buttons. Can't program the initial position.

雨燕双飞 提交于 2019-12-08 08:14:41

问题


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);
        }

    }
}

回答1:


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);
        }
    }
}



回答2:


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 ;)




回答3:


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.



来源:https://stackoverflow.com/questions/19020702/moving-a-ball-on-the-screen-with-buttons-cant-program-the-initial-position

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!