Object on Image does not move when Image moves - java

I have two classes. The first, JPanelImage, adds an Image to my JPanel. The second, myObjet, represents the object I want to add on my Image. The Image can move and can zoom.
The problem is that when I move the image, the object remains fixed.
Class JImagePanel:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.Panel;
import java.awt.Toolkit;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
class JImagePanel extends Panel{
private static final long serialVersionUID = 5375994938523354306L;
private MediaTracker tracker;
private Image img;
private Dimension imgSize,iniSize;
private int zoom = 0 ;
private int MouseX;
private int MouseY;
int transX=0;
int transY=0;
public JImagePanel(String file){
//setSize(100,200);
img=Toolkit.getDefaultToolkit().getImage(file);
setLayout(null);
tracker=new MediaTracker(this);
tracker.addImage(img,0);
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
MouseX = e.getX();
MouseY = e.getY();
}
});
addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
transX += e.getX()-MouseX;
transY += e.getY()-MouseY;
MouseX = e.getX();
MouseY = e.getY();
repaint();
}
});
try{
tracker.waitForAll();
}
catch(Exception ie){}
imgSize=iniSize=new Dimension(img.getWidth(this),img.getHeight(this));
}
public Dimension getPreferredSize(){
return new Dimension(imgSize);
}
public void paint(Graphics g){
super.paint(g);
if(imgSize.width<=iniSize.width) {
imgSize=iniSize;
}
g.drawImage(this.img, (getWidth()-imgSize.width)/2+transX, (getHeight()-imgSize.height)/2+transY, imgSize.width,imgSize.height,this);
}
public void zoomIn(){
int x=10*imgSize.width/100;
int y=10*imgSize.height/100;
imgSize=new Dimension(imgSize.width+x,imgSize.height+y);
if(imgSize.width>iniSize.width){
setSize(imgSize);
getParent().doLayout();
}
repaint();
}
public void zoomOut(){
int x=10*imgSize.width/100;
int y=10*imgSize.height/100;
imgSize=new Dimension(imgSize.width-x,imgSize.height-y);
if(getWidth()>iniSize.width)
{
setSize(imgSize);
getParent().doLayout();
}
repaint();
}
public int getZoom() {
return zoom;
}
Class myObjet:
import java.awt.BorderLayout;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
public class myObjet extends JPanel {
JLabel lblName,lblAct,lblSeuil ;
JPanel panelObjet;
public myObjet(String NameObjet ,double activite )
{
ImageIcon img = createImageIcon("images/Source.png");
lblName = new JLabel(img);
lblAct = new JLabel(String.valueOf(activite));
panelObjet = new JPanel();
panelObjet.setToolTipText(NameObjet);
panelObjet.setLayout(new BorderLayout());
panelObjet.add("North",lblName);
panelObjet.add("South",lblAct);
add(panelObjet);
isOpaque();
}
public ImageIcon createImageIcon(String path) {
java.net.URL imgURL = getClass().getResource(path);
if (imgURL != null) {
return new ImageIcon(imgURL);
} else {
JOptionPane.showMessageDialog(null, "Cette image n'existe pas : " + path, "Erreur", JOptionPane.ERROR_MESSAGE);
// System.err.println("L'image n'est pas dans : " + path);
return null;
}
}
public boolean isOpaque()
{
return true ;
}
}
MYObject is alone , i adds this Object in Panel of this Image .
Here is a concrete example of how I use these classes
public static void (String [] args )
{
imagePanel = new JImagePanel("/home/Image.png");
p = new JPanel();
p.setLayout(new FlowLayout());
// p.setBounds(0,0,0,0);
p.add(getImagePanel());
ple2.add("Center",p);
}
2/ in actionPerformed :
public void actionPerformed(ActionEvent ev) {
Object sourceEv = ev.getSource() ;
if(sourceEv == action.jpfI.btnFrame[4])
{
df = new DecimalFormat("0.00");
int x = Integer.valueOf(action.jpfI.lblTxt[4].getText());
int y =Integer.valueOf(action.jpfI.lblTxt[5].getText()) ;
x =(int)(x/0.26) ;
y =(int)(y/0.26):
objet = new myObjet("islem","0.002");
objet.setBounds(x,y , 50,50);
action.getImagePanel().addImage(objet);
action.repaint();
}

Okay, I've come across three issues.
Firstly: There's no layout manager on the image pane. No big deal, but if you're not going to use a layout manager, you become responsible for laying out any child components. I fixed this (and it's in the wrong place) by adding the following to the "myObjet" class.
Dimension size = getPreferredSize();
setBounds(0, 0, size.width, size.height);
This really should be taken care of by the JImagePanel - either add a layout manager or check the doLayout method.
Secondly: The JImagePanel is a heavy weight component. You should avoid mixing heavy and light weight components if you can (there are Z order issues amongst other things). I updated the JImagePanel to extend from a JPanel.
Thridly: You should only very rarely have to override the paint method. In your case I can understand why you did, but what you ended up doing was painting on the top of everything else (and mixed with the fact you were using a heavy weight component compounded the issue).
I changed the "paint" for "paintComponent" which paints the background and was able to get it to work nicely. I was able to move the image around and have the "myObjet" visible and static in place.
UPDATE
Okay...
public void mouseDragged(MouseEvent e) {
transX += e.getX() - MouseX;
transY += e.getY() - MouseY;
MouseX = e.getX();
MouseY = e.getY();
// Add this to your code
for (Component comp : getComponents()) {
comp.setLocation(transX, transY);
}
repaint();
}
In fact, a better solution would be to allow the parent container to handle the movement and set the image and objects statically with in the image pane (my pane was set to a static size). The basic idea you have running here just needs to be moved to the container.
The only other thing you would need to deal with is the Z order of the panes.

Related

How do I set a well functioning JFrame to be inside another JFrame as a internal JFrame?

Now I know how JInternalFrame works but what I am trying to do is give an already well-functioning JFrame with a space in a place where I have placed an empty Internal Frame.
I want it to grab what I have in the already existing JFrame in the package and place it in that Internal JFrame. Here is the JFrame I want to be placed into another JFrame as an internal frame.
Why I wanna do it this way because the inner frame has a lot of functionality and the container Jframe would be too big to do it all in itself.
What it does is not much of an interest to the thing I wanna do but here it is: It takes images and makes them pure b/w and takes 2 clicks on the screen and stores every pixel between them in a 2d array with their coordinates.
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
public class imageFilt {
public static void main(String[] args) {new imageFilt();}
//--basic initialization
int[] x= new int[3], y= new int[3];
static int[] black= new int[3]; //---------------- if black is 0, then the pixel is black
int clr;int flag=0;
//-----------------------------initialize the screen as runnable. dont disturb the fit
public imageFilt() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());} catch (Exception ex) {}
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private BufferedImage master;
private BufferedImage blackWhite;
public TestPane() {
//----------------------try/catch for (pure black || pure white)
try {
master = ImageIO.read(new File("D:\\colz\\java\\1Aakansh thapa\\1_1_2.jpg"));
blackWhite = new BufferedImage(master.getWidth(), master.getHeight(), BufferedImage.TYPE_BYTE_BINARY);
Graphics2D g2d = blackWhite.createGraphics();
g2d.drawImage(master, 0, 0, this);
g2d.dispose();
}catch (IOException ex) {ex.printStackTrace();}
//--------------------------1st and 2nd click point data and color
this.addMouseListener(new MouseListener() {
int[] isFristEmpty;
#Override
public void mouseClicked(MouseEvent e1) {
int[] temp =new int[3]; //external container so i can get 1st and 2nd separately
temp[0] = (int) e1.getX();
temp[1] = (int) e1.getY();
clr = blackWhite.getRGB(temp[0], temp[1]);
temp[2] = (clr & 0x00ff0000) >> 16;//--------------------bit map to find if red is there or not.
//-------------------------------------------------------since its pure b/w, if red 0, its white.
if(isFristEmpty==null) {
isFristEmpty=temp;
x[0] = temp[0]; y[0] = temp[1]; black[0]=temp[2];//------1st click
}else {
x[1] = temp[0]; y[1] = temp[1]; black[1]=temp[2];//-----2nd click
isFristEmpty=null; //so the 3rd click is considered 1st click again
flag=1;
}
if (flag==1) {
System.out.print("X1: "+x[0]+" & "+"Y1: "+y[0]+" "+"(225 if white): "+black[0]+"\t");
System.out.println("X2: "+x[1]+" & "+"Y2: "+y[1]+" "+"(225 if white): "+black[1]);
counter(x,y);
}
}
#Override public void mousePressed(MouseEvent e) {}
#Override public void mouseReleased(MouseEvent e) {}
#Override public void mouseEntered(MouseEvent e) {}
#Override public void mouseExited(MouseEvent e) {}
});
}
//--------------------------------------------DDA block
private void counter(int x[],int y[]) {
if(flag!=1) return;//-------------------to only go to counter method after it takes that 2nd click
int dx = (x[1] - x[0]);
int dy = (y[1] - y[0]);//--------------makes it applicable for both inclinations (we do not have math.abs implies-> -ve goes as -ve)
int step = Math.abs(dx) > Math.abs(dy) ? Math.abs(dx) : Math.abs(dy);
System.out.println("Steps: "+step);
float Xinc = dx / (float) step;
float Yinc = dy / (float) step;
int[][] tog= new int[step][3];
tog[0][0]=x[0]; tog[0][1]=y[0];
//---------------------------------------------------------------send value of x1 and y1 to listOfCoordinates
float xt=x[0],yt=y[0]; int i=0, j=1; int a=0 ,b=0;
while (a!=x[1] && b!=y[1]){
xt += Xinc;
yt += Yinc;
a=(int) xt; b=(int) yt;
tog[j][i] = a;
tog[j][i+1] = b;
//System.out.println(tog[j][i]+" "+tog[j][i+1]); //*------------to print all that is saved
if(i==1) i=0;
}
}
//------------image size and such stuff. don't touch it
#Override
public Dimension getPreferredSize() {
Dimension size = super.getPreferredSize();
if (master != null) {
size = new Dimension(master.getWidth(), master.getHeight());
}
return size;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (master != null) {
int x = (getWidth() - (master.getWidth())) / 2;
int y = (getHeight() - master.getHeight()) / 2;
g.drawImage(blackWhite, x, y, this);
}
}
}
}
Hope what I wanna do makes sense.
Just imported the frame that I want into the JFrame I want it to be in and did this.
JInternalFrame printImg = new JInternalFrame(inTitle);
JPanel inLabel= new JPanel();
inLabel.add(new TestPane());
printImg.add(inLabel);
printImg.setVisible(true);
just for people who want to know when they stumble upon the post. Wouldnt want them to stay unanswered.
might also work if u copy-paste the Testpane (as the example) part and do the same thing for the rest instead of importing.

Ball doesn't move; Thread?

This is an UI that makes a ball go down in a diagonal way, but the ball stays static; it seems something is not working adecuatedly with the threads. Could you please, tell me how to make the ball move?
Please download a ball and change the directory so the program can find where your ball is allocated. It's not necessary to download the soccer pitch but if you want, it's OK. Finally, I have to thank you for spending time in search of this malfunctioning.
import java.awt.BorderLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JButton;
import java.awt.event.ActionListener;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import javax.imageio.ImageIO;
import java.io.File;
class Animation extends JFrame implements ActionListener { //Frame and listener
Rectangle2D dimensions = new Rectangle2D.Double(0,0,850,595); //Not implemented limits
JButton animate, stop;
Runnable runnable;
Thread move;
public Animation() {
setLayout(new BorderLayout()); //BorderLayout disposition
setTitle("Pelota en acción");
animate = new JButton("Animate it!"); //Button to create balls
animate.setBounds(0,0,120,30);
animate.addActionListener(new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
Image ball = null;
new Layout().createEllipse(ball);
runnable = new Layout();
move = new Thread(runnable);
move.start();
}
});
stop = new JButton("Freeze"); //Button to interrupt thread (not implemented)
stop.setBounds(0,0,120,30);
stop.addActionListener(new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
move.interrupt();
Layout.running = false;
}
});
JPanel subPanel = new JPanel(); //Layout with its buttons situated to the south
subPanel.add(animate);
subPanel.add(stop);
add(subPanel,BorderLayout.SOUTH);
add(new Layout());
}
public static void main(String[] args) {
Animation ventana = new Animation();
ventana.setSize(850,625);
ventana.setLocationRelativeTo(null);
ventana.setVisible(true);
ventana.setDefaultCloseOperation(EXIT_ON_CLOSE);
}
#Override
public void actionPerformed(ActionEvent e) {} //Tag
} //Class close
class Layout extends JPanel implements Runnable { //Layout and thread
int X,Y; //Coordenadas
static boolean running = true; //"To interrupt the thread" momentaneously.
static ArrayList<Image> balls = new ArrayList<>(); //Balls collection
#Override
public void run () { //Just moves ball towards Narnia xd
while(running) {
X++; Y++;
System.out.println(X+" "+Y);
repaint();
updateUI();
try {
Thread.sleep(4);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
g2.addRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));
repaint();
updateUI();
try {
URL url = new URL("https://www.freejpg.com.ar/image-900/9c/9ca2/F100004898-textura_pasto_verde_linea_de_cal.jpg");
Image picture = ImageIO.read(url);
g.drawImage(picture,0,0,null);
} catch(IOException e){
System.out.println("URL image was not found");
}
finally {
try {
//----------------------------------------------------------------------------
Image picture = ImageIO.read(new File("C:\\Users\\Home\\Desktop\\Cancha.jpg")); //Pitch
//----------------------------------------------------------------------------
g.drawImage(picture, 0, 0, null);
} catch (IOException ex) {
System.out.println("Pitch image was not found");
}
}
for (Image ball : balls) { //I add balls to the Layout
g2.drawImage(ball,X,Y,100,100,null);
}
}
public void createEllipse (Image ball) { //Method that adds balls to the collection
try {
//-------------------------------------------------------------------- Ball
ball = ImageIO.read(new File("C:\\Users\\Home\\Desktop\\Pelota.png")); //Change this
//-------------------------------------------------------------------- Ball
} catch(IOException ex) {
System.out.println("Any balls were found");
}
balls.add(ball);
}
}
So to break your code down:
When the button is pressed, you execute the following code:
Image ball = null;
new Layout().createEllipse(ball);
runnable = new Layout();
move = new Thread(runnable);
move.start();
This will create a new layout. The run() method of this will increase the X and Y variables. They are declared here:
int X,Y; //Coordenadas
Those are instance variables, this means they belong to your newly created Layout.
Then you call repaint() on the new Layout, which will do nothing, because this new Layout has not been added to some window.
So, how do you fix this?
First, you have to keep the original Layout around:
class Animation extends JFrame { // no need to implement ActionListener
Rectangle2D dimensions = new Rectangle2D.Double(0,0,850,595); //Not implemented limits
JButton animate, stop;
Thread move;
Layout layout;
Then remember the Layout when you create it:
// before: add(new Layout());
layout = new Layout();
add(layout);
Then use the layout in your ActionListener:
layout.createEllipse(ball);
move = new Thread(layout);
move.start();
This might have some problems with concurrency (Swing is not thread-safe), so for good measure, you should call repaint() in the AWTEventThread:
// in run(), was repaint():
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
repaint();
}
});
Now, there are some cleanup tasks left:
Delete this code:
#Override
public void actionPerformed(ActionEvent e) {} //Tag
It's no longer needed, because you don't implement ActionListener.
Drop the static modifiers from some fields, and add volatile:
volatile int X,Y; //Coordenadas
volatile boolean running = true; //"To interrupt the thread" momentaneously.
ArrayList<Image> balls = new ArrayList<>(); //Balls collection
volatile is needed for variables that are accessed from more than one thread.
Also remove repaint() and resetUI() from the paint method. You don't need them.
For the pictures in paint: you should cache them. Store them in a field, so you don't have to load the picture every time.
When all this is done, your code is much cleaner, but there are still some warts that should be addressed. But at least you have something working.
Johannes has already spoken about many of the things which are wrong with your original example, so I won't go over many of them again.
This example makes use of a Swing Timer instead of a Thread as the main "animation" loop. It also focuses on demonstrating encapsulation and responsibility.
For example, the AnimtionPane is responsible for managing the balls, managing the animation loop and paint. It isn't, however, responsible for determining "how" the balls are updated or paint, it only provides the timing and functionality to make those things happen.
A couple of the glaring issues I can see are:
Trying to load resources from within the paintComponent method. This is a bad ideas, as it could slow you paint pass down, causing your UI to lag
Calling repaint and updateUI from within the paintComponent method. You should avoid causing any new updates to the UI from occurring during a paint process. This could cause your program to run wide and consume all the CPU cycles, not only making your app non-responsive, but also the whole system.
Some very quick points
Swing is not thread safe. You should never update the UI (or anything the UI relies on) from outside the context of the Event Dispatching Thread. This example uses a Swing Timer as it allows the delay to occur of the EDT (and not block the UI), but it's updates are triggered within the EDT, allowing us to safely update the UI from within
You create multiple instances of Layout, meaning that the one on the screen isn't the one which is been updated
Your "freeze" logic is broken. It will never "freeze" anything
Runnable 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.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private AnimationPane animationPane;
public TestPane() {
setLayout(new BorderLayout());
animationPane = new AnimationPane();
JButton actionButton = new JButton("Start");
actionButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
if (animationPane.isAnimating()) {
animationPane.stop();
actionButton.setText("Start");
} else {
animationPane.start();
actionButton.setText("Stop");
}
}
});
add(animationPane);
add(actionButton, BorderLayout.SOUTH);
}
}
// This is just makes it seem more random ;)
private static Random RANDOM = new Random();
public class Ball {
private int x;
private int y;
private int xDelta;
private int yDelta;
private Color color;
private Shape shape;
public Ball(Color color) {
shape = new Ellipse2D.Double(0, 0, 10, 10);
this.color = color;
// Get some random motion
do {
xDelta = RANDOM.nextInt(6) + 2;
yDelta = RANDOM.nextInt(6) + 2;
} while (xDelta == yDelta);
}
public void update(Rectangle bounds) {
x += xDelta;
y += yDelta;
if (x + 10 > bounds.x + bounds.width) {
x = bounds.x + bounds.width - 10;
xDelta *= -1;
} else if (x < bounds.x) {
x = bounds.x;
xDelta *= -1;
}
if (y + 10 > bounds.y + bounds.height) {
y = bounds.y + bounds.height - 10;
yDelta *= -1;
} else if (y < bounds.y) {
y = bounds.y;
yDelta *= -1;
}
}
public void paint(Graphics2D g2d) {
// This makes it easier to restore the graphics context
// back to it's original state
Graphics2D copy = (Graphics2D) g2d.create();
copy.setColor(color);
copy.translate(x, y);
copy.fill(shape);
// Don't need the copy any more, get rid of it
copy.dispose();
}
}
public class AnimationPane extends JPanel {
// This does not need to be static
private List<Ball> balls = new ArrayList<>(); //Balls collection
private Timer timer;
private List<Color> colors;
public AnimationPane() {
colors = new ArrayList<>(8);
colors.add(Color.RED);
colors.add(Color.GREEN);
colors.add(Color.BLUE);
colors.add(Color.CYAN);
colors.add(Color.MAGENTA);
colors.add(Color.ORANGE);
colors.add(Color.PINK);
colors.add(Color.YELLOW);
timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
if (RANDOM.nextBoolean()) {
makeBall();
}
Rectangle bounds = new Rectangle(new Point(0, 0), getSize());
for (Ball ball : balls) {
ball.update(bounds);
}
repaint();
}
});
makeBall();
}
protected void makeBall() {
Collections.shuffle(colors);
balls.add(new Ball(colors.get(0)));
}
public boolean isAnimating() {
return timer.isRunning();
}
public void start() {
timer.start();
}
public void stop() {
timer.stop();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
g2.addRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));
// Bad ideas. Repaint will cause a new paint event to be posted, causing your
// UI to run away - consuming all your CPU cycles in a singulator forms
// and destorys the known universe
//repaint();
// This doesn't do what you think it does and there shouldn't be
// reason for you to call it
//updateUI();
// This is a bad idea as it could cause the paint cycles to slow down
// destorying the responsiveness of your app
// Besids, you should be passing this as the ImageObserver
// try {
// URL url = new URL("https://www.freejpg.com.ar/image-900/9c/9ca2/F100004898-textura_pasto_verde_linea_de_cal.jpg");
// Image picture = ImageIO.read(url);
// g.drawImage(picture, 0, 0, null);
// } catch (IOException e) {
// System.out.println("URL image was not found");
// } finally {
// try {
// //----------------------------------------------------------------------------
// Image picture = ImageIO.read(new File("C:\\Users\\Home\\Desktop\\Cancha.jpg")); //Pitch
// //----------------------------------------------------------------------------
// g.drawImage(picture, 0, 0, null);
// } catch (IOException ex) {
// System.out.println("Pitch image was not found");
// }
// }
// This is "bad" per say, but each ball should have it's own
// concept of location
// for (Image ball : balls) { //I add balls to the Layout
// g2.drawImage(ball, X, Y, 100, 100, null);
// }
for (Ball ball : balls) {
ball.paint(g2);
}
// I made a copy of the graphics context, as this is shared
// with all the other components been painted, changing the
// render hints could cause issues
g2.dispose();
}
}
}

How do I fire a custom event when a new Rectangle is drawn?

I created a graphical component that allows you to view an image and allows you to make a selection of a part of the image: the selection of a portion of the image is accomplished by drawing a rectangle on this image (using drag-and-drop).
To this purpose, I used this example, which created a subclass of JLabel in order to draw the image and in order to deal with the drawing of the rectangle. Then I put an instance of this subclass within a JPanel, in order to have the image always positioned at the center of the panel.
FigurePanel.java
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.event.MouseInputAdapter;
public class FigurePanel extends JPanel
{
private SelectionLabel imageLabel = null;
public FigurePanel()
{
this.setLayout(new GridBagLayout());
imageLabel = new SelectionLabel();
this.add(imageLabel, null);
}
public void setImage(Image image)
{
imageLabel.setImage(image);
}
private class SelectionLabel extends JLabel
{
private Rectangle currentRect = null;
private Rectangle rectToDraw = null;
private final Rectangle previousRectDrawn = new Rectangle();
public SelectionLabel()
{
super();
setOpaque(true);
SelectionListener listener = new SelectionListener();
addMouseListener(listener);
addMouseMotionListener(listener);
}
public void setImage(Image image)
{
currentRect = null;
rectToDraw = null;
previousRectDrawn.setBounds(0, 0, 0, 0);
setIcon(new ImageIcon(image));
}
private class SelectionListener extends MouseInputAdapter
{
#Override
public void mousePressed(MouseEvent e)
{
int x = e.getX();
int y = e.getY();
currentRect = new Rectangle(x, y, 0, 0);
updateDrawableRect(getWidth(), getHeight());
repaint();
}
#Override
public void mouseDragged(MouseEvent e)
{
updateSize(e);
}
#Override
public void mouseReleased(MouseEvent e)
{
updateSize(e);
}
/*
* Update the size of the current rectangle
* and call repaint. Because currentRect
* always has the same origin, translate it
* if the width or height is negative.
*
* For efficiency (though
* that isn't an issue for this program),
* specify the painting region using arguments
* to the repaint() call.
*
*/
void updateSize(MouseEvent e)
{
int x = e.getX();
int y = e.getY();
currentRect.setSize(x - currentRect.x,
y - currentRect.y);
updateDrawableRect(getWidth(), getHeight());
Rectangle totalRepaint = rectToDraw.union(previousRectDrawn);
repaint(totalRepaint.x, totalRepaint.y,
totalRepaint.width, totalRepaint.height);
}
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g); //paints the background and image
//If currentRect exists, paint a box on top.
if (currentRect != null) {
//Draw a rectangle on top of the image.
g.setXORMode(Color.white); //Color of line varies
//depending on image colors
g.drawRect(rectToDraw.x, rectToDraw.y,
rectToDraw.width - 1, rectToDraw.height - 1);
System.out.println("rectToDraw: " + rectToDraw);
}
}
private void updateDrawableRect(int compWidth, int compHeight)
{
int x = currentRect.x;
int y = currentRect.y;
int width = currentRect.width;
int height = currentRect.height;
//Make the width and height positive, if necessary.
if (width < 0) {
width = 0 - width;
x = x - width + 1;
if (x < 0) {
width += x;
x = 0;
}
}
if (height < 0) {
height = 0 - height;
y = y - height + 1;
if (y < 0) {
height += y;
y = 0;
}
}
//The rectangle shouldn't extend past the drawing area.
if ((x + width) > compWidth) {
width = compWidth - x;
}
if ((y + height) > compHeight) {
height = compHeight - y;
}
//Update rectToDraw after saving old value.
if (rectToDraw != null) {
previousRectDrawn.setBounds(
rectToDraw.x, rectToDraw.y,
rectToDraw.width, rectToDraw.height);
rectToDraw.setBounds(x, y, width, height);
} else {
rectToDraw = new Rectangle(x, y, width, height);
}
}
}
}
FigurePanelTest.java
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
public class FigurePanelTest extends JFrame
{
public FigurePanelTest()
{
FigurePanel imagePanel = new FigurePanel();
JScrollPane imageScrollPane = new JScrollPane();
imageScrollPane.setPreferredSize(new Dimension(420, 250));
imageScrollPane.setViewportView(imagePanel);
JButton imageButton = new JButton("Load Image");
imageButton.addActionListener(
new ActionListener()
{
#Override
public void actionPerformed(ActionEvent evt)
{
JFileChooser fc = new JFileChooser();
int returnValue = fc.showOpenDialog(null);
if (returnValue == JFileChooser.APPROVE_OPTION) {
File selectedFile = fc.getSelectedFile();
System.out.println(selectedFile.getName());
try
{
Image image = ImageIO.read(selectedFile.getAbsoluteFile());
imagePanel.setImage(image);
imageScrollPane.getViewport().setViewPosition(new Point(0, 0));
}
catch(IOException e)
{
e.printStackTrace();
}
}
}
}
);
Container container = getContentPane();
container.setLayout(new BorderLayout());
container.add(imageScrollPane, BorderLayout.CENTER);
container.add(imageButton, BorderLayout.NORTH);
setSize(600, 400);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
/**
* #param args the command line arguments
*/
public static void main(String args[]) {
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new FigurePanelTest().setVisible(true);
}
});
}
}
The private class SelectionLabel is the class SelectionArea from this example.
When a new rectangle is drawn, a message is printed on the console. Now I would replace the printing of the message with the firing of a custom event, so that the position and size of the rectangle are accessible to the application business logic.
I read how to create a custom event in Java. Moreover, this article identifies two super types for creating events: EventObject and AWTEvent. This articles states:
Normally you extend AWTEvent for events generated by a graphical
component and EventObject any other time.
Since the event concerning the selection of a part of the image is generated by a graphical component (that is the FigurePanel panel), I could implement the ImageSelectionEvent class by extending AWTEvent, as the following code snippet.
public class ImageSelectionEvent extends AWTEvent
{
public ImageSelectionEvent(Object source, int id) {
super(source, id);
}
}
The documentation identifies the id as the event type. So, what value should be assigned to this parameter?
Moreover, why does the constructor of EventObject class be devoid of the id parameter?
When creating an event class, you must guarantee that the event is
immutable. The event generator will share the same event instance
among the listeners; so ensure any one listener cannot change the
event's state.
What about this?
I don't know what is needed to create a custom event.
However, since you are extending JLabel maybe you can just create a PropertyChangeEvent.
To generated the event you would just use something like:
firePropertyChange("selectionRectangle", oldRectangle, newRectangle);
Then you can use a PropertyChangeListener to listen for "selectionRectangle" changes.
The Javadoc for AWTEvent says:
Subclasses of this root AWTEvent class defined outside of the java.awt.event package should define event ID values greater than the value defined by RESERVED_ID_MAX.
This value is 1999. You can set it to whatever you want that's higher than that. This value is specified by all the different types of Swing events, and Swing uses values that are less than that. For example, the MouseEvent event types use values from 500-507.
The main thing is to use a consistent value for your events.
Finally, I would consider subclassing ComponentEvent over AWTEvent as the source of your event is a Component, not an Object.

Creating animated text that loops on itself

I'm trying to make a news ticker type thing where a string is entered and inside of a JPanel the text is looped.
I know it moves currently at about 90 pixels a second and that there is about 16 pixels per char in this font. So what I am asking is how I can use this information to make it so that after a timer runs for a certain time, a new animated text is spawned, and how after the 1st animated text leaves the screen completely, how to delete it from the memory.
This is what I got so far, the code is borrowed heavily from here : Java Animate JLabel, So also if you see any unneeded code in there, let me know.
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.util.EnumMap;
import javax.swing.*;
#SuppressWarnings("serial")
public class AnimateExample extends JPanel {
private static final int TIMER_DELAY = 20;
private static final String KEY_DOWN = "key down";
public static final int TRANSLATE_SCALE =2;
private static final Font BG_STRING_FONT = new Font(Font.SANS_SERIF,
Font.BOLD, 32);
private EnumMap<Direction, Boolean> dirMap =
new EnumMap<AnimateExample.Direction, Boolean>(Direction.class);
private BufferedImage image = null;
private int posX = 100;
private int posY = 50;
Timer t;
public AnimateExample() {
for (Direction dir : Direction.values()) {
dirMap.put(dir, Boolean.TRUE);
}
t = new Timer(TIMER_DELAY, new TimerListener());
t.start();
ActionMap actionMap = getActionMap();
for (final Direction dir : Direction.values()) {
actionMap.put(dir.Left + KEY_DOWN, new AbstractAction() {
#Override
public void actionPerformed(ActionEvent arg0) {
dirMap.put(dir, true);
}
});
}
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g.setFont(BG_STRING_FONT);
g.setColor(Color.LIGHT_GRAY);
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
String s = "Hi, I'm trying to make a news ticker type thing where a string is entered and inside of a JPanel the text is looped.I know it moves currently at about 90 pixels a second and that there is about 16 pixels per char in this font. So what I am asking is how I can use this information to make it so that after a timer runs for a certain time, a new animated text is spawned, and how after the 1st animated text leaves the screen completely, how to delete it from the memory.";
g.drawString(s, posX, posY);
}
private class TimerListener implements ActionListener {
public void actionPerformed(java.awt.event.ActionEvent e) {
for (Direction dir : Direction.values()) {
if (dirMap.get(dir)) {
posX += dir.getX() * TRANSLATE_SCALE;
posY += dir.getY() * TRANSLATE_SCALE;
}
}
repaint();
if(posX<-500)
{
t.stop();
}
};
}
enum Direction {
Left( KeyEvent.VK_LEFT, -1, 0);
private int keyCode;
private int x;
private int y;
private Direction(int keyCode, int x, int y) {
this.keyCode = keyCode;
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
}
private static void createAndShowGui() {
AnimateExample mainPanel = new AnimateExample();
JFrame frame = new JFrame("Animate Example");
frame.setUndecorated(true);
frame.setSize(1600, 900);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(null);
frame.add(mainPanel);
mainPanel.setBounds(new Rectangle(1600,400));
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
EDIT:
I was also thinking that perhaps resetting the X position after a certain amount of time or distance, but the problem with that would be that it would require that the area would be blank for one screen length. If you have a work around for that, it would also be great.
You could check out the Marquee Panel. It uses a different approach than most scrollers. You add actual components to the panel and the components scroll.
This allows you to scroll text or images. You can use labels with HTML so you can have colored text etc. The message will continue to scroll until you manually stop the scrolling.
Currently there is no automatic mechanism for replacing a scrolling message. Although you could easily create a List or Queue of messages. Then when a message has finished scrolling completely you just remove the current message and add a new one.
Following is the section code from the MarqueePanel class you would need to change:
public void actionPerformed(ActionEvent ae)
{
scrollOffset = scrollOffset + scrollAmount;
int width = super.getPreferredSize().width;
if (scrollOffset > width)
{
scrollOffset = isWrap() ? wrapOffset + scrollAmount : - getSize().width;
// add code here to swap component from the List or Queue
}
repaint();
}
Of course you would also need to add a method to the class to add a component to the List or Queue.
Don't worry about the character width, as different fonts can produce variable character widths. Instead use FontMetrics to measure the String width and determine it the xPos <= -stringWidth, this is when the text would be fully off the left hand side of the screen.
You could use a Queue of some kind to manage text, popping of the next one as you need it. This example simply pops the last message onto the end of the queue, so it will keep repeating
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.LinkedList;
import java.util.Queue;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
Queue<String> queue = new LinkedList<>();
queue.add("I have something to say, it's better to burn out then fade away");
queue.add("Banana peels");
queue.add("Don't worry if plan A fails, there are 25 more letters in the alphabet");
queue.add("When the past comes knocking, don't answer. It has nothing new to tell you");
queue.add("I know the voices in my head aren't real..... but sometimes their ideas are just absolutely awesome!");
TickerTapPane pane = new TickerTapPane();
pane.setMessages(queue);
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(pane);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TickerTapPane extends JPanel {
private Queue<String> queue;
private String message;
private int xPos;
public TickerTapPane() {
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (message == null) {
message = queue.remove();
xPos = getWidth();
}
xPos -= 4;
FontMetrics fm = getFontMetrics(getFont());
int stringWidth = fm.stringWidth(message);
if (xPos <= -stringWidth) {
queue.add(message);
xPos = getWidth();
message = queue.remove();
}
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (message != null) {
Graphics2D g2d = (Graphics2D) g.create();
FontMetrics fm = g2d.getFontMetrics();
int yPos = ((getHeight() - fm.getHeight()) / 2) + fm.getAscent();
g2d.drawString(message, xPos, yPos);
g2d.dispose();
}
}
protected void setMessages(Queue<String> queue) {
this.queue = queue;
}
}
}

JScrollPane setViewPosition After "Zoom"

The code that I have here is using a MouseAdapter to listen for the user to "draw" a box around the area of an image that they would like to zoom in on and calculate the ratio of the box to the image. It then resizes the image to the calculated ratio. This part works.
The issue that I am having is making the JScrollPane view appear as if it is still at the same top left position after the image has been resized. I have tried several methods that seem to have gotten close to the result I want but not exactly.
This is the Listener that finds the scale ratio and sets the position:
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.Graphics;
import java.awt.Point;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
import javax.swing.JComponent;
public class DynamicZoom extends MouseAdapter {
private Point start;
private Point end;
private double zoom = 1.0;
private JScrollPane pane;
private JViewport port;
public void mousePressed(MouseEvent e) {
if(e.getButton() == MouseEvent.BUTTON1) {
this.pane = (JScrollPane)e.getSource();
this.port = pane.getViewport();
start = e.getPoint();
}
}
public void mouseReleased(MouseEvent e) {
if(this.pane != null) {
Point curr = this.port.getViewPosition();
end = e.getPoint();
ImageComponent canvas = (ImageComponent)this.port.getView();
zoom = canvas.getScale();
double factor = 0.0;
double selectedWidth = Math.abs(end.getX() - start.getX());
double selectedHeight = Math.abs(end.getY() - start.getY());
if(selectedWidth > selectedHeight)
factor = this.port.getWidth() / selectedWidth;
else
factor = this.port.getHeight() / selectedHeight;
zoom *= factor;
int x = (int)((start.x+curr.x)*zoom);
int y = (int)((start.y+curr.y)*zoom);
Point point = new Point(x, y);
((ImageComponent)(this.port.getView())).setScale(zoom);
ResizeViewport.resize(pane);
this.port.setViewPosition(point);
}
}
public void mouseDragged(MouseEvent e) {
if(this.pane != null) {
Graphics g = this.port.getGraphics();
int width = this.start.x - e.getX();
int height = this.start.y - e.getY();
int w = Math.abs( width );
int h = Math.abs( height );
int x = width < 0 ? this.start.x : e.getX();
int y = height < 0 ? this.start.y : e.getY();
g.drawRect(x, y, w, h);
this.port.repaint();
}
}
}
This is the ImageComponent it resizes and displays the image:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import javax.swing.JComponent;
public class ImageComponent extends JComponent {
private static final long serialVersionUID = 1975488835382044371L;
private BufferedImage img = null;
private double scale = 0.0;
public ImageComponent() {}
public ImageComponent(BufferedImage img) {
this.displayPage(img);
}
#Override
public void paint(Graphics g) {
Graphics2D g2 = ((Graphics2D)g);
if(this.img != null) {
int width = (int)(this.img.getWidth() * this.scale);
int height = (int)(this.img.getHeight() * this.scale);
this.setPreferredSize(new Dimension(width, height));
g2.drawImage(this.img, 0, 0, width, height, null, null);
g2.dispose();
}
}
public void displayPage(BufferedImage pic) {
if(img != null) {
this.img = pic;
}
}
public BufferedImage getPage() {
return this.img;
}
public void setScale(double ratio) {
if(ratio > .04) {
this.scale = ratio;
this.repaint();
}
}
public double getScale() {
return this.scale;
}
}
The ResizeViewport forces the viewport to show the entire ImageComponent when it is scaled up because otherwise it will clip the image at the size that it previously was:
import java.awt.Dimension;
import javax.swing.JScrollPane;
public class ResizeViewport {
public static void resize(JScrollPane scroll) {
int vw = scroll.getWidth()-scroll.getVerticalScrollBar().getWidth();
int vh = scroll.getHeight()-scroll.getHorizontalScrollBar().getHeight();
scroll.getViewport().setViewSize(new Dimension(vw, vh));
}
}
It turns out there is nothing wrong with the math used to calculate the position or the way I designed the code. The problem was that the ImageComponent was still painting in another Thread when the position was being calculated; therefore returning "false" values for the getWidth() and getHeight() methods. I solved the issue using the following code:
EventQueue.invokeLater(new Runnable() {
public void run() {
port.setViewPosition(new Point(x,y));
}
});
This allows the painting to finish before trying to calculate the size of the image and setting the position in the JScrollPane. Problem solved. I would still like to throw a thanks out to the people that took the time to at least review this issue.
I had a similar issue but sometimes the panel got painted wrong the first time after resize, after hours of trying to find a workaround I found a solution!
After a resize of the panel in the scrollpane it is best is to destroy viewport by setting to null, and then adding panel back to scrollpane that will recreate a new viewport and everything is working!
// Old viewport has to be replaced
scrollPane.setViewport(null);
scrollPane.setViewportView(zoomablePanel);
/Anders

Categories

Resources