Why is the text in the JPanel Clipping after a certain point? - java

If we drag the scroll bar past a certain point the text displayed becomes "mushed." I manually placed the string at the far end to see if it can be displayed and it worked.
It draws fine from those coordinates when I manually set it (as in the example) but clips when I change the x-coordinate with the scroll bar).
This is the code:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
public class ScrollBarDemo2 extends JFrame {
private MessagePanel messagePanel = new MessagePanel();
private JScrollBar jscbHorizontal = new JScrollBar(JScrollBar.HORIZONTAL);
private JScrollBar jscbVertical = new JScrollBar(JScrollBar.VERTICAL);
private JTextField jtfMessage = new JTextField("Example String");
public ScrollBarDemo2() {
// Add components to the frame
add(messagePanel, BorderLayout.CENTER);
add(jscbHorizontal, BorderLayout.SOUTH);
add(jscbVertical, BorderLayout.EAST);
add(jtfMessage, BorderLayout.NORTH);
// Register listener to scroll bars
ScrollBarListener jscbListener = new ScrollBarListener();
jscbHorizontal.addAdjustmentListener(jscbListener);
jscbVertical.addAdjustmentListener(jscbListener);
// Register a listener in text field
jtfMessage.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// Set the text in messagePanel to the given text
messagePanel.setText(e.getActionCommand());
}
});
}
private class ScrollBarListener implements AdjustmentListener {
#Override
public void adjustmentValueChanged(AdjustmentEvent e) {
// Determine the orientation of the event source
JScrollBar scrollBar = (JScrollBar)e.getAdjustable();
if (scrollBar.getOrientation() == JScrollBar.HORIZONTAL) {
// Obtain the horizontal space remaining
double spaceAvailable = (double)messagePanel.getHorizontalEmptySpace();
// Find how much to scale each value of the scroll bars (since we're using the default 100 total values)
double scaledValue = (scrollBar.getValue() * spaceAvailable / (double)scrollBar.getMaximum());
// Set new x coordinate
messagePanel.setX((int)scaledValue);
}
else if (scrollBar.getOrientation() == JScrollBar.VERTICAL) {
// Obtain the vertical space remaining
double spaceAvailable = (double)messagePanel.getVerticalEmptySpace();
// Find how much to scale each value of the scroll bars (since we're using the default 100 total values)
double scaledValue = (scrollBar.getValue() / (double)scrollBar.getMaximum()) * spaceAvailable;
// Set new x coordinate
messagePanel.setY((int)scaledValue);
}
}
}
/** main method **/
public static void main(String[] args) {
ScrollBarDemo2 frame = new ScrollBarDemo2();
frame.setSize(500, 200);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
class MessagePanel extends JPanel {
private FontMetrics fm;
private String message = "";
private int messageX = -1;
private int messageY = -1;
public MessagePanel() {
this("Welcome to Java");
}
public MessagePanel(String message) {
this.message = message;
}
public void moveRight() {
messageX += getWidth() / 50;
repaint();
}
public void moveLeft() {
messageX -= getWidth() / 50;
repaint();
}
public void moveUp() {
messageY -= getHeight() / 100;
repaint();
}
public void moveDown() {
messageY += getHeight() / 100;
repaint();
}
public int getX() {
return messageX;
}
public int getY() {
return messageY;
}
public void setX(int x) {
messageX = x;
repaint();
}
public void setY(int y) {
messageY = y;
repaint();
}
public void setText(String newMessage) {
message = newMessage;
repaint();
}
public int getVerticalEmptySpace() {
return getHeight() - fm.getAscent();
}
public int getHorizontalEmptySpace() {
return getWidth() - fm.stringWidth(message);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (messageX < 0 && messageY < 0) { // Check to initialize centered position
fm = g.getFontMetrics();
messageX = getWidth() - fm.stringWidth(message); // Manually setting it to the very end coordinate
messageY = getHeight() / 2 - fm.getAscent() / 2;
}
g.drawString(message, messageX, messageY);
}
}

I made a few changes to your code. Here's the GUI I created.
I formatted your code.
I enclosed your Swing code in a Runnable, so I could start your Swing application on the Event Dispatch thread using the SwingUtilities invokeLater method. Oracle and I insist that all Swing applications start on the Event Dispatch thread.
As camickr said in his answer, you accidentally overrode the getX, getY, setX, and setY methods of JPanel. I renamed your methods.
I used the action listener of the underlying JTextField Document so that whatever you type in the JTextField gets drawn on the JPanel.
There is still a problem with your messageX being set to less than zero. I'm leaving this problem for you to solve.
Here's the corrected code.
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
public class ScrollBarDemo2 extends JFrame {
private static final long serialVersionUID = -3189856074534869132L;
private JScrollBar jscbHorizontal = new JScrollBar(JScrollBar.HORIZONTAL);
private JScrollBar jscbVertical = new JScrollBar(JScrollBar.VERTICAL);
private JTextField jtfMessage = new JTextField("Example String");
private MessagePanel messagePanel = new MessagePanel(jtfMessage.getText());
public ScrollBarDemo2() {
// Add components to the frame
add(messagePanel, BorderLayout.CENTER);
add(jscbHorizontal, BorderLayout.SOUTH);
add(jscbVertical, BorderLayout.EAST);
add(jtfMessage, BorderLayout.NORTH);
// Register listener to scroll bars
ScrollBarListener jscbListener = new ScrollBarListener();
jscbHorizontal.addAdjustmentListener(jscbListener);
jscbVertical.addAdjustmentListener(jscbListener);
// Register a listener in text field
jtfMessage.getDocument().addDocumentListener(new DocumentListener() {
#Override
public void insertUpdate(DocumentEvent e) {
messagePanel.setText(jtfMessage.getText());
}
#Override
public void removeUpdate(DocumentEvent e) {
messagePanel.setText(jtfMessage.getText());
}
#Override
public void changedUpdate(DocumentEvent e) {
messagePanel.setText(jtfMessage.getText());
}
});
}
private class ScrollBarListener implements AdjustmentListener {
#Override
public void adjustmentValueChanged(AdjustmentEvent e) {
// Determine the orientation of the event source
JScrollBar scrollBar = (JScrollBar) e.getAdjustable();
if (scrollBar.getOrientation() == JScrollBar.HORIZONTAL) {
// Obtain the horizontal space remaining
double spaceAvailable = (double) messagePanel
.getHorizontalEmptySpace();
// Find how much to scale each value of the scroll bars (since
// we're using the default 100 total values)
double scaledValue = (scrollBar.getValue() * spaceAvailable / (double) scrollBar
.getMaximum());
// Set new x coordinate
messagePanel.setMessageX((int) scaledValue);
} else if (scrollBar.getOrientation() == JScrollBar.VERTICAL) {
// Obtain the vertical space remaining
double spaceAvailable = (double) messagePanel
.getVerticalEmptySpace();
// Find how much to scale each value of the scroll bars (since
// we're using the default 100 total values)
double scaledValue = (scrollBar.getValue() / (double) scrollBar
.getMaximum()) * spaceAvailable;
// Set new x coordinate
messagePanel.setMessageY((int) scaledValue);
}
}
}
/** main method **/
public static void main(String[] args) {
Runnable runnable = new Runnable() {
#Override
public void run() {
ScrollBarDemo2 frame = new ScrollBarDemo2();
frame.setTitle("Scroll Bar Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
};
SwingUtilities.invokeLater(runnable);
}
}
class MessagePanel extends JPanel {
private static final long serialVersionUID = -2743160276473942475L;
private FontMetrics fm;
private String message = "";
private int messageX = -1;
private int messageY = -1;
public MessagePanel() {
this("Welcome to Java");
}
public MessagePanel(String message) {
this.message = message;
this.setPreferredSize(new Dimension(500, 200));
}
public void moveRight() {
messageX += getWidth() / 50;
repaint();
}
public void moveLeft() {
messageX -= getWidth() / 50;
repaint();
}
public void moveUp() {
messageY -= getHeight() / 100;
repaint();
}
public void moveDown() {
messageY += getHeight() / 100;
repaint();
}
public int getMessageX() {
return messageX;
}
public int getMessageY() {
return messageY;
}
public void setMessageX(int x) {
messageX = x;
repaint();
}
public void setMessageY(int y) {
messageY = y;
repaint();
}
public void setText(String newMessage) {
message = newMessage;
repaint();
}
public int getVerticalEmptySpace() {
return getHeight() - fm.getAscent();
}
public int getHorizontalEmptySpace() {
return getWidth() - fm.stringWidth(message);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (messageX < 0 && messageY < 0) { // Check to initialize centered
// position
fm = g.getFontMetrics();
messageX = getWidth() - fm.stringWidth(message); // Manually setting
// it to the
// very end
// coordinate
messageY = getHeight() / 2 - fm.getAscent() / 2;
}
g.drawString(message, messageX, messageY);
}
}

getX() and getY are methods of the JComponent class and you should not be overriding them.
Rename the methods, maybe something like getMessageX() and getMessageY(). You should also rename the setX() and setY() methods to be consistent with whatever getter names you choose.

Related

Swing threading vs timers in my program Java

I'm looking for some guidance in finishing my program for my project. It is a simple simulation where multiple graphical objects are drawn at runtime and using a button to add another ball in this case to the jpanel, I had to rewrite my whole code to take in multiple objects at once just 2 days ago and last night I did some research when I got an exception...changed some code then got another exception in thread AWT EventQueue 0, after doing some more research many people suggested that dynamically drawing objects on a jpanel at runtime with a thread is a pain in the.... and I should just use a timer instead so this is where I am at right now. The Grid_Bag_Constraints can be ignored for the time being as its just to lay it out at the moment.
My question is how could i make it so a new ball is added at runtime on the press of a button...sorry forgot to mention
Ps yeh i tried playing round with validating the jpanels already :/
All guidance is appreciated :)
Sim
=============
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.*;
public class Sim extends JPanel
private static final int UPDATE_RATE = 60; // Number of refresh per second
public static int X = 640;
public static int Y = 480;
public static int numberOfBall = 3;
public static boolean isRunning = true;
public Sim() {
// have some code here all commented out
}
public static void main(String[] args) {
// Run GUI in the Event Dispatcher Thread (EDT) instead of main thread.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// Set up main window (using Swing's Frame)
JFrame frame = new JFrame("Simulation");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.gridwidth = 3;
c.gridheight = 3;
balls balls = new balls();
frame.add(balls);
gui GUI = new gui();
frame.add(GUI);
frame.setPreferredSize(new Dimension(1280,720));
frame.setResizable(false);
//frame.setContentPane(new Sim());
frame.pack();
frame.setVisible(true);
new Thread(new bounceEngine(balls)).start();
}
});
}
public static int random(int maxRange) {
return (int) Math.round((Math.random() * maxRange));
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
invalidate();
repaint();
}
gui
---------
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class gui extends JPanel {
boolean shouldFill;
public gui(){
setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
if(shouldFill){
c.fill = GridBagConstraints.HORIZONTAL;
}
AbstractButton addBallButton = new JButton("Add Ball");
addBallButton.setMultiClickThreshhold(1500);
addBallButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent actionEvent) {
balls.listBalls.add(new Ball((new Color(Sim.random(255),Sim.random(255), Sim.random(255))),20));
for(Ball ball : balls.listBalls){
System.out.print("I work ");
}
invalidate();
//repaint();
}
});
c.fill = GridBagConstraints.HORIZONTAL;
c.weightx = 1;
c.weighty = 0.5;
add(addBallButton);
bounceEngine
------------------
import java.awt.*;
import javax.swing.*;
public class bounceEngine implements Runnable {
private balls parent;
private int UPDATE_RATE = 144;
public bounceEngine(balls parent) {
this.parent = parent;
}
public void run() {
int width = Sim.X;
int height = Sim.Y;
// Randomize the starting position...
for (Ball ball : getParent().getBalls()) {
int x = Sim.random(width);
int y = Sim.random(height);
int size = ball.getRadius();
if (x + size > width) {
x = width - size;
}
if (y + size > height) {
y = height - size;
}
ball.setLocation(new Point(x, y));
}
while (getParent().isVisible()) {
// Repaint the balls pen...
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
getParent().repaint();
}
});
//I know this is dangerous to update it as the repaint method is called
for (Ball ball : getParent().getBalls()) {
move(ball);
}
try{
Thread.sleep(1000/UPDATE_RATE);
}catch (InterruptedException e){
}
}
}
public balls getParent() {
return parent;
}
public void move(Ball ball) {
Point p = ball.getLocation();
Point speed = ball.getSpeed();
int size = ball.getRadius();
int vx = speed.x;
int vy = speed.y;
int x = p.x;
int y = p.y;
if (x + vx < 0 || x + (size*2) + vx > getParent().getWidth()) {
vx *= -1;
}
if (y + vy < 0 || y + (size*2) + vy > getParent().getHeight()) {
vy *= -1;
}
x += vx;
y += vy;
ball.setSpeed(new Point(vx, vy));
ball.setLocation(new Point(x, y));
}
balls(for all the balls)
---------------------------------
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.*;
public class balls extends JPanel {
public static ArrayList<Ball> listBalls;
public balls() {
setPreferredSize(new Dimension(640,480));
setBackground(Color.white);
listBalls = new ArrayList<Ball>(10);
for(int i = 0; i < Sim.numberOfBall; i++){
listBalls.add(new Ball((new Color(Sim.random(255),Sim.random(255), Sim.random(255))),20));
}
}
public ArrayList<Ball> getBall(){
return listBalls;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
for (Ball ball : listBalls) {
ball.paint(g2d);
}
g2d.dispose();
}
public ArrayList<Ball> getBalls(){
return listBalls;
}
Ball(for 1 ball object blank class with basic constructor)
----------------------------------------------------------------------
import java.awt.*;
public class Ball {
private Color color;
private Point location;
private int radius;
private Point speed;
public Ball(Color color, int radius) {
setColor(color);
speed = new Point(5 - Sim.random(20), 5 - Sim.random(20));
this.radius = radius;
}
public int getRadius() {
return radius;
}
public void setColor(Color color) {
this.color = color;
}
public void setLocation(Point location) {
this.location = location;
}
public Color getColor() {
return color;
}
public Point getLocation() {
return location;
}
public Point getSpeed() {
return speed;
}
public void setSpeed(Point speed) {
this.speed = speed;
}
protected void paint(Graphics2D g2d) {
Point p = getLocation();
if (p != null) {
g2d.setColor(getColor());
g2d.fillOval(p.x, p.y, radius*2, radius*2);
}
}
}

Cannot use MouseEvents in main

What I'm trying to do
Making a Pong game where the Y axis gets the value from my cursor according to the application
What did I tried
private void pallet() {
ycur=(int)MouseInfo.getPointerInfo().getLocation().getY();
}
This way I get the Y value according to my monitor instead of the application.
I also tried to use the MouseEvent.getY(), but I get the error when trying to call this method from the main.
private void pallet() {
ycur=(int)MouseInfo.getPointerInfo().getLocation().getY();
}
This is how my code looks like, I think the problem lies in how I'm using my main and methods but I'm not sure.
public class MyFirst extends JPanel {
public int x = 500, y = 300, border = 30;
public boolean goingDown = true;
public int ycur, cursor;
public void moveBall() {
x++;
if (goingDown == true) {
y++;
} else if (goingDown == false) {
y--;
}
if (y == getHeight() - border) {
goingDown = false;
} else if (y == 0) {
goingDown = true;
}
}
#Override
public void paint(Graphics g) {
super.paint(g);
g.fillOval(x, y, 30, 30);
g.fillRect(30, ycur, 15, 100);
}
public static void main(String[] args) throws InterruptedException {
JFrame frame = new JFrame("Pong");
frame.pack();
frame.setSize(1000, 600);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
frame.setLocationRelativeTo(null);
MyFirst game = new MyFirst();
frame.add(game);
while (true) {
game.pallet(e);
game.moveBall();
game.repaint();
Thread.sleep(10);
}
}
public void pallet(MouseEvent e) {
ycur=e.getY();
}
}
Problems with your code:
As already mentioned, you're fighting against Swing's event-driven architecture. Instead of a while true loop, use listeners, including a MouseMotionListener ot track the changes in the mouse location, and an ActionListener tied to a Swing Timer to move the ball.
Avoid using Thread.sleep(...) in Swing GUI's except with great care as this can put the entire application to sleep.
Avoid putting too much logic within the main method. This method should be short, should create the key objects, connect them, set the program in motion and that's it.
Paint with the paintComponent method, not the paint method. It results in smoother animation with its double buffering.
For example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.*;
#SuppressWarnings("serial")
public class MoveBallTest extends JPanel{
private static final int PREF_W = 1000;
private static final int PREF_H = 600;
private static final int TIMER_DELAY = 12;
private static final int SPRITE_WIDTH = 30;
private static final Color OVAL_SPRITE_COLOR = Color.RED;
private static final Color RECT_SPRITE_COLOR = Color.BLUE;
private static final int DELTAY_Y = 1;
private boolean goingDown = true;
private Timer timer = new Timer(TIMER_DELAY, this::timerActionPerformed);
private int ovalSpriteY;
private int rectSpriteY;
public MoveBallTest() {
timer.start();
MyMouse myMouse = new MyMouse();
addMouseMotionListener(myMouse);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(OVAL_SPRITE_COLOR);
g.fillOval(SPRITE_WIDTH, ovalSpriteY, SPRITE_WIDTH, SPRITE_WIDTH);
g.setColor(RECT_SPRITE_COLOR);
g.fillRect(SPRITE_WIDTH, rectSpriteY, SPRITE_WIDTH / 2, SPRITE_WIDTH * 3);
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
public void timerActionPerformed(ActionEvent e) {
if (ovalSpriteY <= 0) {
goingDown = true;
} else if (ovalSpriteY >= getHeight() - SPRITE_WIDTH) {
goingDown = false;
}
ovalSpriteY += goingDown ? DELTAY_Y : -DELTAY_Y;
repaint();
}
private class MyMouse extends MouseAdapter {
#Override
public void mouseMoved(MouseEvent e) {
rectSpriteY = e.getY();
}
}
private static void createAndShowGui() {
JFrame frame = new JFrame("MoveBallTest");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new MoveBallTest());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}

Placement of a JPanel within a JPanel

I'm trying to code a simple Pong and I have the background panel which contains a Bar panel. So of course I need to be able to place the bar on the size and move it vertically at request. Now I'm just trying to put it in a starting position. If I don't disable the layout the bar gets placed in the top center regardless of location setting, but if I disable the layout and set location it just doesn't show up. I'm not sure what I missing. Here is a code snippet if it can be relevant:
public PongPanel() {
setLayout(null);
setPreferredSize(SIZE);
setBackground(Color.BLACK);
player_one_bar = new Bar();
add(player_one_bar);
player_one_bar.setLocation(10, getSize().height/2-3);
}
If you set the layout manager as null you'll have to specify the exact coordinates of the panel, meaning something like -
setBounds(10, 10, 20, 100);
Will put the panel at location (10,10) with Width of 20 and Height of 100.
If by "bar" you mean Pong game paddle, then it shouldn't be a component at all but rather a logical entity that represents a position, which is visually represented by a sprite that gets drawn in the JPanel's paintComponent method.
For example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class PongPaddle extends JPanel {
private static final int PREF_W = 800;
private static final int PREF_H = 500;
private static final int RECT_X = 20;
private static final int RECT_W = 10;
private static final int RECT_H = 60;
private static final int STARTING_Y = (PREF_H - RECT_H) / 2;
private static final int TIMER_DELAY = 15;
private static final int DELTA_PADDLE = 3;
private boolean paddle1GoingDown = true;
private boolean paddle2GoingDown = false;
private Rectangle paddle1 = new Rectangle(RECT_X, STARTING_Y, RECT_W, RECT_H);
private Rectangle paddle2 = new Rectangle(PREF_W - RECT_X - RECT_W,
STARTING_Y, RECT_W, RECT_H);
public PongPaddle() {
setBackground(Color.black);
new Timer(TIMER_DELAY, new TimerListener()).start();
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
private class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
int deltaPaddle1 = paddle1GoingDown ? 1 : -1;
deltaPaddle1 *= DELTA_PADDLE;
int x = paddle1.getLocation().x;
int y = paddle1.getLocation().y + deltaPaddle1;
if (y + RECT_H >= PREF_H) {
paddle1GoingDown = false;
}
if (y <= 0) {
paddle1GoingDown = true;
}
paddle1.setLocation(x, y);
int deltaPaddle2 = paddle2GoingDown ? 1 : -1;
deltaPaddle2 *= DELTA_PADDLE;
x = paddle2.getLocation().x;
y = paddle2.getLocation().y + deltaPaddle2;
if (y + RECT_H >= PREF_H) {
paddle2GoingDown = false;
}
if (y <= 0) {
paddle2GoingDown = true;
}
paddle2.setLocation(x, y);
repaint();
if (!PongPaddle.this.isShowing()) {
((Timer) e.getSource()).stop();
}
}
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.white);
if (paddle1 != null) {
g2.fill(paddle1);
}
if (paddle2 != null) {
g2.fill(paddle2);
}
}
private static void createAndShowGui() {
PongPaddle mainPanel = new PongPaddle();
JFrame frame = new JFrame("PongPaddle");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}

How to animate Rectangle in JPanel?

I want to learn some tricks about JAVA for my project.
I want to animate my Rectangle leftoright and righttoleft but I can't apply the same functions for ball animation.
In addition,how can I start my ball in different x-direction with a border of y-coordinate ?
Thanks a lot for your advices and helping.
My codes:
import javax.swing.Timer;
import java.util.ArrayList;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
public class MultipleBall extends JApplet {
public MultipleBall() {
add(new BallControl());
}
class BallControl extends JPanel {
private BallPanel ballPanel = new BallPanel();
private JButton Suspend = new JButton("Suspend");
private JButton Resume = new JButton("Resume");
private JButton Add = new JButton("+1");
private JButton Subtract = new JButton("-1");
private JScrollBar Delay = new JScrollBar();
public BallControl() {
// Group buttons in a panel
JPanel panel = new JPanel();
panel.add(Suspend);
panel.add(Resume);
panel.add(Add);
panel.add(Subtract);
// Add ball and buttons to the panel
ballPanel.setBorder(new javax.swing.border.LineBorder(Color.red));
Delay.setOrientation(JScrollBar.HORIZONTAL);
ballPanel.setDelay(Delay.getMaximum());
setLayout(new BorderLayout());
add(Delay, BorderLayout.NORTH);
add(ballPanel, BorderLayout.CENTER);
add(panel, BorderLayout.SOUTH);
// Register listeners
Suspend.addActionListener(new Listener());
Resume.addActionListener(new Listener());
Add.addActionListener(new Listener());
Subtract.addActionListener(new Listener());
Delay.addAdjustmentListener(new AdjustmentListener() {
public void adjustmentValueChanged(AdjustmentEvent e) {
ballPanel.setDelay(Delay.getMaximum() - e.getValue());
}
});
}
class Listener implements ActionListener {
public void actionPerformed(ActionEvent e) {
if (e.getSource() == Suspend)
ballPanel.suspend();
else if (e.getSource() == Resume)
ballPanel.resume();
else if (e.getSource() == Add)
ballPanel.add();
else if (e.getSource() == Subtract)
ballPanel.subtract();
}
}
}
class BallPanel extends JPanel {
private int delay = 30;
private ArrayList<Ball> list = new ArrayList<Ball>();
// Create a timer with the initial delay
protected Timer timer = new Timer(delay, new ActionListener() {
/** Handle the action event */
public void actionPerformed(ActionEvent e) {
repaint();
}
});
public BallPanel() {
timer.start();
}
public void add() {
list.add(new Ball());
}
public void subtract() {
if (list.size() > 0)
list.remove(list.size() - 1); // Remove the last ball
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawRect(185, 279, 50, 15);
g.setColor(Color.RED);
g.fillRect(185, 279, 50, 15);
for (int i = 0; i < list.size(); i++) {
Ball ball = (Ball) list.get(i); // Get a ball
g.setColor(ball.color); // Set ball color
// Check boundaries
if (ball.x < 0 || ball.x > getWidth())
ball.dx = -ball.dx;
if (ball.y < 0 || ball.y > getHeight())
ball.dy = -ball.dy;
// Adjust ball position
ball.x += ball.dx;
// ball.y += ball.dy;
g.fillOval(ball.x - ball.radius, ball.y - ball.radius,
ball.radius * 2, ball.radius * 2);
}
}
public void suspend() {
timer.stop();
}
public void resume() {
timer.start();
}
public void setDelay(int delay) {
this.delay = delay;
timer.setDelay(delay);
}
}
class Ball {
int x = 20;
int y = 20; // Current ball position
int dx = 2; // Increment on ball's x-coordinate
int dy = 2; // Increment on ball's y-coordinate
int radius = 15; // Ball radius
Color color = new Color((int) (Math.random() * 256),
(int) (Math.random() * 256), (int) (Math.random() * 256));
}
/** Main method */
public static void main(String[] args) {
JFrame frame = new JFrame();
JApplet applet = new MultipleBallApp();
frame.add(applet);
frame.setTitle("MultipleBallApp");
frame.setLocationRelativeTo(null); // Center the frame
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 400);
frame.setLocationRelativeTo(null); // Center the frame
frame.setVisible(true);
}
}
can't apply the same functions for ball animation
This is probably your first mistake. In fact, this is exactly what you should be trying to do. The idea is, you should be trying to devise a means by what is painted/animated is abstract so it doesn't matter what the shape is you want to paint, you can apply it to the sam basic animation process...
For example, you could start with some kind interface which describes the basic properties of an animated entity...
public interface AnimatedShape {
public void update(Rectangle bounds);
public void paint(JComponent parent, Graphics2D g2d);
}
This says that an animated entity can be updated (moved) and painted. By convention (and because I'm lazy), I like to create an abstract implementation which implements the most common aspects...
public abstract class AbstractAnimatedShape implements AnimatedShape {
private Rectangle bounds;
private int dx, dy;
public AbstractAnimatedShape() {
}
public void setBounds(Rectangle bounds) {
this.bounds = bounds;
}
public Rectangle getBounds() {
return bounds;
}
public int getDx() {
return dx;
}
public int getDy() {
return dy;
}
public void setDx(int dx) {
this.dx = dx;
}
public void setDy(int dy) {
this.dy = dy;
}
#Override
public void update(Rectangle parentBounds) {
Rectangle bounds = getBounds();
int dx = getDx();
int dy = getDy();
bounds.x += dx;
bounds.y += dy;
if (bounds.x < parentBounds.x) {
bounds.x = parentBounds.x;
setDx(dx *= -1);
} else if (bounds.x + bounds.width > parentBounds.x + parentBounds.width) {
bounds.x = parentBounds.x + (parentBounds.width - bounds.width);
setDx(dx *= -1);
}
if (bounds.y < parentBounds.y) {
bounds.y = parentBounds.y;
setDy(dy *= -1);
} else if (bounds.y + bounds.height > parentBounds.y + parentBounds.height) {
bounds.y = parentBounds.y + (parentBounds.height - bounds.height);
setDy(dy *= -1);
}
}
}
And then start creating implementations...
public class AnimatedBall extends AbstractAnimatedShape {
private Color color;
public AnimatedBall(int x, int y, int radius, Color color) {
setBounds(new Rectangle(x, y, radius * 2, radius * 2));
this.color = color;
setDx(Math.random() > 0.5 ? 2 : -2);
setDy(Math.random() > 0.5 ? 2 : -2);
}
public Color getColor() {
return color;
}
#Override
public void paint(JComponent parent, Graphics2D g2d) {
Rectangle bounds = getBounds();
g2d.setColor(getColor());
g2d.fillOval(bounds.x, bounds.y, bounds.width, bounds.height);
}
}
In this manner, you can customise the way that the entity is animated and painted, but the basic logic for each instance of the entity is the same...
But what's all the point of this...
Basically, what it allows us to do is produce a "virtual" concept of all the animated objects and simplify there management, for example...
Instead of using a "tightly" coupled List, we can use a loosely couple List instead...
private ArrayList<AnimatedShape> list = new ArrayList<AnimatedShape>();
Then when we want the entities to be updated, we simply need to iterate the List and ask the entities to update...
protected Timer timer = new Timer(delay, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for (AnimatedShape ball : list) {
ball.update(getBounds());
}
repaint();
}
});
And when they need to be painted...
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
for (AnimatedShape ball : list) {
ball.paint(this, g2d);
}
}
Because the BallPane doesn't care what actually type of entity it is, but only that it's a type of AnimatedShape...makes life easier...
Now, my implementation of the AnimatedBall already randomise the direction of each instance of the ball, but you can also randomise the starting position when the ball is added using something like...
public void add() {
int radius = 15;
// Randomised position
int x = (int)(Math.random() * (getWidth() - (radius * 2))) + radius;
int y = (int)(Math.random() * (getHeight() - (radius * 2))) + radius;
Color color = new Color((int) (Math.random() * 256),
(int) (Math.random() * 256), (int) (Math.random() * 256));
AnimatedBall ball = new AnimatedBall(x, y, radius, color);
list.add(ball);
}
But how does this help you with adding a rectangle?
You now need to create an AnimatedRectangle that extends from AbstractAnimatedShape and implemented the required methods and add instances of this to the List of AnimatedShapes in the BallPane.
If you don't want the rectangle to be managed within the same list, you could create another list and manage it sepearatly (it create two additional methods, update(List<AnimatedShape>) and paint(List<AnimatedShape>, Graphics2D) passing in each individual list so as to reduce the duplicate code, but that's me)...
You can restrict the rectangles vertical movement by overriding the setDy method and ignoring any changes, for example
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.util.ArrayList;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class MultipleBall {
public MultipleBall() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("MultipleBallApp");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new BallControl());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class BallControl extends JPanel {
private BallPanel ballPanel = new BallPanel();
private JButton Suspend = new JButton("Suspend");
private JButton Resume = new JButton("Resume");
private JButton Add = new JButton("+1");
private JButton Subtract = new JButton("-1");
private JScrollBar Delay = new JScrollBar();
public BallControl() {
// Group buttons in a panel
JPanel panel = new JPanel();
panel.add(Suspend);
panel.add(Resume);
panel.add(Add);
panel.add(Subtract);
// Add ball and buttons to the panel
ballPanel.setBorder(new javax.swing.border.LineBorder(Color.red));
Delay.setOrientation(JScrollBar.HORIZONTAL);
ballPanel.setDelay(Delay.getMaximum());
setLayout(new BorderLayout());
add(Delay, BorderLayout.NORTH);
add(ballPanel, BorderLayout.CENTER);
add(panel, BorderLayout.SOUTH);
// Register listeners
Suspend.addActionListener(new Listener());
Resume.addActionListener(new Listener());
Add.addActionListener(new Listener());
Subtract.addActionListener(new Listener());
Delay.addAdjustmentListener(new AdjustmentListener() {
public void adjustmentValueChanged(AdjustmentEvent e) {
ballPanel.setDelay(Delay.getMaximum() - e.getValue());
}
});
}
class Listener implements ActionListener {
public void actionPerformed(ActionEvent e) {
if (e.getSource() == Suspend) {
ballPanel.suspend();
} else if (e.getSource() == Resume) {
ballPanel.resume();
} else if (e.getSource() == Add) {
ballPanel.add();
} else if (e.getSource() == Subtract) {
ballPanel.subtract();
}
}
}
}
class BallPanel extends JPanel {
private int delay = 30;
private ArrayList<AnimatedShape> list = new ArrayList<AnimatedShape>();
private AnimatedRectange rectangle;
public BallPanel() {
this.rectangle = new AnimatedRectange(-25, 200, 50, 25, Color.RED);
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
// Create a timer with the initial delay
protected Timer timer = new Timer(delay, new ActionListener() {
/**
* Handle the action event
*/
#Override
public void actionPerformed(ActionEvent e) {
for (AnimatedShape ball : list) {
ball.update(getBounds());
}
rectangle.update(getBounds());
repaint();
}
});
public void add() {
int radius = 15;
// Randomised position
int x = (int) (Math.random() * (getWidth() - (radius * 2))) + radius;
int y = (int) (Math.random() * (getHeight() - (radius * 2))) + radius;
Color color = new Color((int) (Math.random() * 256),
(int) (Math.random() * 256), (int) (Math.random() * 256));
AnimatedBall ball = new AnimatedBall(x, y, radius, color);
list.add(ball);
}
public void subtract() {
if (list.size() > 0) {
list.remove(list.size() - 1); // Remove the last ball
}
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
for (AnimatedShape ball : list) {
ball.paint(this, g2d);
}
rectangle.paint(this, g2d);
}
public void suspend() {
timer.stop();
}
public void resume() {
timer.start();
}
public void setDelay(int delay) {
this.delay = delay;
timer.setDelay(delay);
}
}
public interface AnimatedShape {
public void update(Rectangle bounds);
public void paint(JComponent parent, Graphics2D g2d);
}
public abstract class AbstractAnimatedShape implements AnimatedShape {
private Rectangle bounds;
private int dx, dy;
public AbstractAnimatedShape() {
}
public void setBounds(Rectangle bounds) {
this.bounds = bounds;
}
public Rectangle getBounds() {
return bounds;
}
public int getDx() {
return dx;
}
public int getDy() {
return dy;
}
public void setDx(int dx) {
this.dx = dx;
}
public void setDy(int dy) {
this.dy = dy;
}
#Override
public void update(Rectangle parentBounds) {
Rectangle bounds = getBounds();
int dx = getDx();
int dy = getDy();
bounds.x += dx;
bounds.y += dy;
if (bounds.x < parentBounds.x) {
bounds.x = parentBounds.x;
setDx(dx *= -1);
} else if (bounds.x + bounds.width > parentBounds.x + parentBounds.width) {
bounds.x = parentBounds.x + (parentBounds.width - bounds.width);
setDx(dx *= -1);
}
if (bounds.y < parentBounds.y) {
bounds.y = parentBounds.y;
setDy(dy *= -1);
} else if (bounds.y + bounds.height > parentBounds.y + parentBounds.height) {
bounds.y = parentBounds.y + (parentBounds.height - bounds.height);
setDy(dy *= -1);
}
}
}
public class AnimatedBall extends AbstractAnimatedShape {
private Color color;
public AnimatedBall(int x, int y, int radius, Color color) {
setBounds(new Rectangle(x, y, radius * 2, radius * 2));
this.color = color;
setDx(Math.random() > 0.5 ? 2 : -2);
setDy(Math.random() > 0.5 ? 2 : -2);
}
public Color getColor() {
return color;
}
#Override
public void paint(JComponent parent, Graphics2D g2d) {
Rectangle bounds = getBounds();
g2d.setColor(getColor());
g2d.fillOval(bounds.x, bounds.y, bounds.width, bounds.height);
}
}
public class AnimatedRectange extends AbstractAnimatedShape {
private Color color;
public AnimatedRectange(int x, int y, int width, int height, Color color) {
setBounds(new Rectangle(x, y, width, height));
this.color = color;
setDx(2);
}
// Don't want to adjust the vertical speed
#Override
public void setDy(int dy) {
}
#Override
public void paint(JComponent parent, Graphics2D g2d) {
Rectangle bounds = getBounds();
g2d.setColor(color);
g2d.fill(bounds);
}
}
/**
* Main method
*/
public static void main(String[] args) {
new MultipleBall();
}
}
Amendment
You really should avoid adding JApplet to a JFrame, an applet has a prescribed life cycle and management process which you are ignoring. Better to focus on just using the BallControl panel as the core UI element and then add this to what ever top level container you want
You may find a JSlider more piratical then a JScrollBar, not to mention, it will look better on different platforms, most uses understand what a slider is used for...
Add a static variable like ballCount and add 1 to it every time you make a ball. In the Ball class, change the definition of y to something likey = 20 + ballcount*(radius*2+distanceInBalls)
public class RandomTests extends JApplet {
public RandomTests() {
add(new BallControl());
}
static int ballCount = 0;
class BallControl extends JPanel {
private BallPanel ballPanel = new BallPanel();
private JButton Suspend = new JButton("Suspend");
private JButton Resume = new JButton("Resume");
private JButton Add = new JButton("+1");
private JButton Subtract = new JButton("-1");
private JScrollBar Delay = new JScrollBar();
public BallControl() {
// Group buttons in a panel
JPanel panel = new JPanel();
panel.add(Suspend);
panel.add(Resume);
panel.add(Add);
panel.add(Subtract);
// Add ball and buttons to the panel
ballPanel.setBorder(new javax.swing.border.LineBorder(Color.red));
Delay.setOrientation(JScrollBar.HORIZONTAL);
ballPanel.setDelay(Delay.getMaximum());
setLayout(new BorderLayout());
add(Delay, BorderLayout.NORTH);
add(ballPanel, BorderLayout.CENTER);
add(panel, BorderLayout.SOUTH);
// Register listeners
Suspend.addActionListener(new Listener());
Resume.addActionListener(new Listener());
Add.addActionListener(new Listener());
Subtract.addActionListener(new Listener());
Delay.addAdjustmentListener(new AdjustmentListener() {
public void adjustmentValueChanged(AdjustmentEvent e) {
ballPanel.setDelay(Delay.getMaximum() - e.getValue());
}
});
}
class Listener implements ActionListener {
public void actionPerformed(ActionEvent e) {
if (e.getSource() == Suspend) ballPanel.suspend();
else if (e.getSource() == Resume) ballPanel.resume();
else if (e.getSource() == Add) ballPanel.add();
else if (e.getSource() == Subtract) ballPanel.subtract();
}
}
}
class BallPanel extends JPanel {
private int delay = 30;
private ArrayList<Ball> list = new ArrayList<Ball>();
// Create a timer with the initial delay
protected Timer timer = new Timer(delay, new ActionListener() {
/** Handle the action event */
public void actionPerformed(ActionEvent e) {
repaint();
}
});
public BallPanel() {
timer.start();
}
public void add() {
list.add(new Ball());
ballCount++;
}
public void subtract() {
if (list.size() > 0) list.remove(list.size() - 1); // Remove the last ball
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawRect(185, 279, 50, 15);
g.setColor(Color.RED);
g.fillRect(185, 279, 50, 15);
for (int i = 0; i < list.size(); i++) {
Ball ball = (Ball) list.get(i); // Get a ball
g.setColor(ball.color); // Set ball color
// Check boundaries
if (ball.x < 0 || ball.x > getWidth()) ball.dx = -ball.dx;
if (ball.y < 0 || ball.y > getHeight()) ball.dy = -ball.dy;
// Adjust ball position
ball.x += ball.dx;
// ball.y += ball.dy;
g.fillOval(ball.x - ball.radius, ball.y - ball.radius, ball.radius * 2, ball.radius * 2);
}
}
public void suspend() {
timer.stop();
}
public void resume() {
timer.start();
}
public void setDelay(int delay) {
this.delay = delay;
timer.setDelay(delay);
}
}
class Ball {
int radius = 15; // Ball radius
int x = radius;
int y = 20 + (radius * ballCount * 2 + 15); // Current ball position
int dx = 2; // Increment on ball's x-coordinate
int dy = 2; // Increment on ball's y-coordinate
Color color = new Color((int) (Math.random() * 256), (int) (Math.random() * 256), (int) (Math.random() * 256));
}
public static void main(String[] args) {
JFrame frame = new JFrame();
JApplet applet = new RandomTests();
frame.add(applet);
frame.setTitle("MultipleBallApp");
frame.setLocationRelativeTo(null); // Center the frame
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 400);
frame.setLocationRelativeTo(null); // Center the frame
frame.setVisible(true);
}
}

Components disappear after resizing JPanel

I am trying to create JPanel with draggable crosses which appear after mouse clicking. Everything works fine but when I resize the JPanel the crosses disappear. I tried to override the paintComponent method in my JPanel but then all crosses are at coordinates (0,0). How can I fix it?
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionAdapter;
import java.util.ArrayList;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class CrossPanel extends JPanel implements MouseListener {
private int orderOfCross = 0;
private ArrayList<Cross> crosses;
private int defaultSizeOfCrosses = 10;
CrossPanel() {
setOpaque(false);
addMouseListener(this);
crosses = new ArrayList<Cross>();
}
#Override
public void mouseClicked(MouseEvent e) {
int x = e.getX();
int y = e.getY();
Cross cross = new Cross(orderOfCross++, defaultSizeOfCrosses);
crosses.add(cross);
cross.setLocation(x - defaultSizeOfCrosses, y - defaultSizeOfCrosses);
add(cross);
repaint();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
// for (int i = 0; i < crosses.size(); i++) {
// crosses.get(i).paint(g);
// }
}
#Override
public void mousePressed(MouseEvent e) {}
#Override
public void mouseReleased(MouseEvent e) {}
#Override
public void mouseEntered(MouseEvent e) {}
#Override
public void mouseExited(MouseEvent e) {}
public static void main(String[] args) {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
CrossPanel crossPane = new CrossPanel();
f.getContentPane().add(crossPane);
f.setSize(600, 500);
f.setLocation(200, 200);
f.setVisible(true);
}
}
class Cross extends JComponent {
private int order;
protected Cursor draggingCursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
private volatile int draggedAtX, draggedAtY;
int size;
public Cross(int order, int size) {
this.order = order;
this.size = size;
this.setBounds(0, 0, 4 * size, 3 * size + 10);
addDragListeners();
setCursor(draggingCursor);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.red);
g2.setStroke(new BasicStroke(3));
g2.drawLine(0, size, size + size, size);
g2.drawLine(size, 0, size, size + size);
Font f = new Font("Monospaced", Font.BOLD, size + 10);
g2.setFont(f);
g2.drawString(String.valueOf(order), size - size / 2, 3 * size + 10);
}
private void addDragListeners() {
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
draggedAtX = e.getX();
draggedAtY = e.getY();
}
});
addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
Point newLocation = new Point(e.getX() - draggedAtX + getLocation().x, e.getY() - draggedAtY + getLocation().y);
setLocation(newLocation);
}
});
}
}
I very rarely see a use for a null layout, for all the perceived benefits, there are simply to many draw backs.
The entire Swing API has been designed around the use of layout managers so you'd be crazy (IMHO) to simply throw all that work away.
If you find yourself in a position where the available layout managers don't seem to do what you want, it might be more worth while to write you own.
Here, I've presented a PropertionalLayoutManager whose intention is to provide layout capabilities that will place components on a container based an percentage of the width/height of the parent component. This means, as the parent component is resized, the child components will reposition themselves at a percentage of the parent size.
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.LayoutManager2;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionAdapter;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class CrossPanel extends JPanel implements MouseListener {
private int orderOfCross = 0;
private ArrayList<Cross> crosses;
private int defaultSizeOfCrosses = 10;
CrossPanel() {
setOpaque(false);
addMouseListener(this);
crosses = new ArrayList<Cross>();
setLayout(new PropertionalLayoutManager());
}
#Override
public void mouseClicked(MouseEvent e) {
int x = e.getX();
int y = e.getY();
Cross cross = new Cross(orderOfCross++, defaultSizeOfCrosses);
float xPos = (float)x / (float)getWidth();
float yPos = (float)y / (float)getHeight();
crosses.add(cross);
add(cross, new PropertionalConstraints(xPos, yPos));
revalidate();
}
public static String format(float value) {
return NumberFormat.getNumberInstance().format(value);
}
#Override
public void mousePressed(MouseEvent e) {
}
#Override
public void mouseReleased(MouseEvent e) {
}
#Override
public void mouseEntered(MouseEvent e) {
}
#Override
public void mouseExited(MouseEvent e) {
}
public static void main(String[] args) {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
CrossPanel crossPane = new CrossPanel();
f.getContentPane().add(crossPane);
f.setSize(600, 500);
f.setLocation(200, 200);
f.setVisible(true);
}
public class Cross extends JComponent {
private int order;
protected Cursor draggingCursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
private volatile int draggedAtX, draggedAtY;
int size;
public Cross(int order, int size) {
this.order = order;
this.size = size;
// this.setBounds(0, 0, 4 * size, 3 * size + 10);
addDragListeners();
setCursor(draggingCursor);
Font f = new Font("Monospaced", Font.BOLD, size + 10);
setFont(f);
}
#Override
public Dimension getPreferredSize() {
// This is dangrous, you are making assumptions about platforms
// that you have no eviednce to support.
FontMetrics fm = getFontMetrics(getFont());
return new Dimension(Math.max(fm.stringWidth(String.valueOf(order)), size), size + fm.getHeight());
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.red);
g2.setStroke(new BasicStroke(3));
FontMetrics fm = g2.getFontMetrics();
int width = getWidth() - 1;
int height = getHeight() - 1;
int x = (width - fm.stringWidth(String.valueOf(order))) / 2;
int y = fm.getAscent();
g2.drawString(String.valueOf(order), x, y);
int crossSize = Math.min(width, height - fm.getHeight());
x = (width - crossSize) / 2;
y = fm.getHeight();
g2.drawLine(x, y, x + crossSize, y + crossSize);
g2.drawLine(x + crossSize, y, x, y + crossSize);
}
private void addDragListeners() {
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
draggedAtX = e.getX();
draggedAtY = e.getY();
}
});
addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
Point newLocation = new Point(e.getX() - draggedAtX + getLocation().x, e.getY() - draggedAtY + getLocation().y);
setLocation(newLocation);
}
});
}
}
public class PropertionalConstraints {
private float x;
private float y;
public PropertionalConstraints(float x, float y) {
this.x = x;
this.y = y;
}
public float getX() {
return x;
}
public float getY() {
return y;
}
public void setX(float x) {
if (x > 1f) {
x = 1f;
} else if (x < -0f) {
x = 0f;
}
this.x = x;
}
public void setY(float y) {
if (y > 1f) {
y = 1f;
} else if (y < -0f) {
y = 0f;
}
this.y = y;
}
}
public class PropertionalLayoutManager implements LayoutManager2 {
private Map<Component, PropertionalConstraints> mapConstraints;
public PropertionalLayoutManager() {
mapConstraints = new HashMap<>(25);
}
public PropertionalConstraints getConstraintsFor(Component comp) {
return mapConstraints.get(comp);
}
public void setConstraintsFor(Component comp, PropertionalConstraints pc) {
mapConstraints.put(comp, pc);
}
#Override
public void addLayoutComponent(Component comp, Object constraints) {
if (constraints instanceof PropertionalConstraints) {
mapConstraints.put(comp, (PropertionalConstraints) constraints);
} else {
throw new IllegalArgumentException("Constraints must be PropertionalConstraints");
}
}
#Override
public Dimension maximumLayoutSize(Container target) {
return preferredLayoutSize(target);
}
#Override
public float getLayoutAlignmentX(Container target) {
return 0.5f;
}
#Override
public float getLayoutAlignmentY(Container target) {
return 0.5f;
}
#Override
public void invalidateLayout(Container target) {
}
#Override
public void addLayoutComponent(String name, Component comp) {
}
#Override
public void removeLayoutComponent(Component comp) {
mapConstraints.remove(comp);
}
#Override
public Dimension preferredLayoutSize(Container parent) {
return parent.getSize();
}
#Override
public Dimension minimumLayoutSize(Container parent) {
return preferredLayoutSize(parent);
}
#Override
public void layoutContainer(Container parent) {
int width = parent.getWidth();
int height = parent.getHeight();
for (Component comp : parent.getComponents()) {
PropertionalConstraints con = mapConstraints.get(comp);
if (con != null) {
int x = (int)(width * con.getX());
int y = (int)(height * con.getY());
comp.setSize(comp.getPreferredSize());
comp.setLocation(x, y);
} else {
comp.setBounds(0, 0, 0, 0);
}
}
}
}
}
On some side notes, you are using "magic" numbers to determine the size and rendering position of certain elements. This is a very bad idea. You should, especially when painting or printing, base all these values on empirical values.
In this example, I've reverted to using FontMertrics to provide the required information to more accurately calculate the size and positions of various elements. This will allow for better cross platform support, as not all fonts are rendered the same across all platforms ;)

Categories

Resources