JPanel paintComponent(...) doesnt work - java

My problem is easy to explain: i want a/some JPanels, added to a JFrame, to paint themself with an image. sadly the last thing does not work. for info: the image path is correct and the JPanel size is the same as the image size.
thx for help :P
package frames;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Image;
import javax.swing.ImageIcon;
import javax.swing.JPanel;
import world.Terrain;
public class PanelTerrain extends JPanel {
private Image img;
private int x;
private int y;
private Image imga;
public PanelTerrain(Terrain terra, int x, int y) {
imga = new ImageIcon(terra.getPath()).getImage();
this.x = x;
this.y = y;
this.setBounds(x, y, 8, 8);
//this.setBackground(terra.getColor());
}
public void changeTerrain(Terrain t)
{
this.setVisible(false);
this.setBackground(t.getColor());
this.setVisible(true);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(imga, x, y, this);
}
}

My first guess is that you're passing the wrong x and y.The x and y in g.drawImage are the coordinates of the top left corner, not the size of the image, so usually they are set directly at 0 (that means, g.draWimage(imga, x, y, this).

One very big problem is your possible misuse of bounds and x and y. You're calling setBounds on the JPanel (something to avoid) but then drawing the image at some x and y that in all likelihood is way beyond the bounded size of your JPanel. Don't do this. for more complete help, please create and post your Minimal, Complete, and Verifiable example.
Here's what I think that you should do instead -- assuming that you want a grid of images, some of say earth, some water, some grass, ...
Create ImageIcons for the base images, one Icon for grass, one for water, etc...
Create a JPanel that uses a GridLayout, and fill it with a grid of JLabels.
Place those same JLabels in a 2-dimensional array of JLabel.
Swap the label icons where and when you need to change the image by calling setIcon(newIcon) on the JLabel.
For example, please see this answer of mine to a similar question as well as the other answers to the same question.

Related

How to dynamically center a Graphic

Although in this example, the X-Y values are hard-coded, lets assume the user entered the values dynamically and clicked a button to view the results on the screen.
It wouldn't make sense to calculate the frame based on the largest size as the Frame would be too large for the monitor.
What is required to take the X-Y values entered (not matter how large or small) and have the image appear centered within the frame?
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Area;
import java.awt.geom.Path2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class ZoomToXY extends JPanel
{
int x = 0;
public void paint(Graphics g)
{
//Can't see this.
int[] xs2 = {5308, 5306, 4143, 4143, 4120, 4119, 4118, 4117, 4116, 4114, 4112};
int[] ys2 = {4474, 5329, 5306, 5171, 5171, 5173, 5175, 5177, 5179, 5181, 5182};
BasicStroke traceStroke = new BasicStroke (1); //Line thickness
Graphics2D gc = (Graphics2D) g.create();
gc.setColor(Color.RED);
gc.drawPolyline(xs2, ys2, 11);
gc.setStroke(traceStroke);
x++;
}
public static void main(String[] args)
{
JFrame frame = new JFrame();
frame.add(new ZoomToXY());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setBounds(20,20, 250,250);
frame.setVisible(true);
}
}
The reason we can't see the polygon or whatever you're making, is because it's outside the frame's bounds. Let's take a look.
frame.setBounds(20,20, 250,250);
This line indicates we will only see what's inside these bounds, though everything outside will also be drawn but not shown. Try drawing a rectangle inside the bounds and see.
g.fillRect(20, 20, 100, 100);
You will see a rectangle. But how can I solve this issue? Since having a frame being 5000px by 5000px isn't going to work on most monitors, either you work with smaller resolutions and therefore smaller coordinates, or you implement a camera. Having a camera you can have as big world as you want and being able to move around in it. But if your frame can only show 100 pixels and your polygon is 1000px, we will only see 10% of it, this problem can easily be solved with zooming. Here is a topic how to implement a gamecamera. With the gameCamera you can simply calculate the center of your image, then translate it, quite simple. If you need assistance just ask.
A frame that is 250x250 is quite small, consider it being a little bigger. Also why set the coordinates as (20, 20)? If you want to center the JFrame to the current monitor just call:
frame.setLocationRelativeTo(null);

Draw a car wheel

I have a very simple problem. I am learning Java, and was given an assignment to draw a car. I did this all in one class that extends JPanel, and did the drawing within paintComponent().
I realize this is poor object-oriented programming, and decided to try to subclass some of the parts to rectify this situation.
I tried to create a class that draws wheels, but was unsuccessful.
Essentially, I wanted to be able to do this:
Main Class extends JPanel
paintComponent{
Wheel leftWheel = new Wheel(0, 50, 100);
this.add(leftWheel);
}
This should draw a wheel at the point (0, 50) within the JPanel, and have a diameter of 100.
However, i'm unsure how i'm supposed to control the positioning in the JPanel. When I do this, the wheel in drawn at the top center of my window. This is what my wheel class looks like:
public class Wheel extends JComponent {
private int x, y, diameter;
private boolean clockwise;
Wheel(int x, int y, int size, boolean cw)
{
this.setPreferredSize(new Dimension(size, size));
this.x = x;
this.y = y;
diameter = size;
clockwise = cw;
repaint();
}
public void paintComponent(Graphics canvas)
{
super.paintComponent(canvas);
canvas.setColor(Color.gray);
canvas.fillOval(x,y,diameter,diameter);
}
}
The x and y should be where it appears on the parent window, however, this is not the case in the following code (located in the parent class that extends JFrame):
Wheel leftWheel = new Wheel(0,0,WHEEL_DIAMETER,true);
this.add(leftWheel);
The wheel doesn't draw at the top left of my window, it draws in the center of my window at the top. Am I doing something incorrectly? Sorry if I don't follow some Java conventions, I don't have any experience yet. Is this how I should be handling the drawing of the wheel, or is there a more accepted practice for doing this type of drawing?
For example, in my JPanel class, if I add the following code:
Wheel x = new Wheel(50,60,75,true);
this.add(x);
I get a frame sized 75x75 in which a wheel (sized 75x75) is drawn at the point (50,60) within that frame, not within the parent JPanel's coordinate system. The result is a circle that gets clipped and I only see the top left of the circle. That image is displayed at the top center of my JPanel
I understand how to draw the wheel, and move it within itself, but how do I position the wheel on the JPanel??
Your constructor has a small bug,
Wheel(int x, int y, int size, boolean cw) {
this.setPreferredSize(new Dimension(size, size));
diameter = size;
clockwise = cw;
repaint();
}
You forgot to store x and y. I think you wanted,
Wheel(int x, int y, int size, boolean cw) {
this.x = x;
this.y = y;
this.setPreferredSize(new Dimension(size, size));
diameter = size;
clockwise = cw;
repaint();
}
Because your x and y are 0 if you don't set them.
Could you explain how to control it's location within the JPanel, not within itself please?
The default LayoutManager for a JPanel is a FlowLayout so the component will always be positioned based on the rules of the layout manager.
If you want to add components to a random location then you need to use a null layout. But when you use a null layout you are then responsible for setting the size and location of the component. So, in reality the custom painting should always be done at (0, 0) in your custom component.
Instead of adding multiple JPanels to create the vehicle I would simply use one class that extends JPanel and create multiple methods to create things such as wheels etc. to be called from within the overridden paintComponent method. You can pass the new method a reference of your graphics object, create a copy of your graphics object using g.create(), or use getGraphics() from inside the method itself. Inside the method to create a wheel you then are able to calculate it's position by using the panel's dimensions and place it properly.
An alternative would be to define and return shapes in other methods and simply draw them using the graphics object in paintComponent().

Moving Java Swing rectangle leaves rectangles behind

When I finally figured out the repaint method, I came to a problem. I want to move a rectangle across the screen, rather than re-drawing it again. Redrawing is fine, but it leaves the older rectangle behind it! This is my code:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Tutorial3{
public static void main(String[] agrs){
createAndStartGui();
}
static void createAndStartGui(){
JFrame f = new JFrame("tutorial 3");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().setPreferredSize(new Dimension(500, 300));
MyPanel panel = new MyPanel();
f.add(panel);
f.pack();
f.setVisible(true);
for (int i = 0; i < 10; i++){
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
Logger.getLogger(Tutorial3.class.getName()).log(Level.SEVERE, null, ex);
}
panel.user.move("right");
panel.repaint();
}
}
}
class MyRectangle{
int x;
int y;
public MyRectangle(int x, int y){
this.x = x;
this.y = y;
}
void move(String direction){
switch (direction){
case "up":
this.y -= 10;
break;
case "down":
this.y += 10;
break;
case "left":
this.x -= 10;
break;
case "right":
this.x += 10;
break;
default:
break;
}
}
}
class MyPanel extends JPanel{
MyRectangle user = new MyRectangle(10, 10);
public MyPanel(){
}
public void paintComponent(Graphics g){
Graphics2D g2d = (Graphics2D) g;
g.drawRect(user.x, user.y, 10, 10);
}
}
How do I get the rectangle that is left behind disappear (I DO NOT WANT TO CLEAR THE FULL WINDOW)? Or even better yet, how do I get the rectangle to 'move' (if it is possible)?
My end result:
What I want in the end:
Note: simply drawing the rectangle in that point isn't what I want. I want to see it getting dragged across.
Your problem is that you are only painting the rectangle, rather than the whole panel, so the panel ends up full of rectangles as you call the method. You need to draw the background of the panel too. This will "erase" the previous rectangles so the panel only has whatever you paint in that particular call and not what you did previously. To accomplish this you need to call:
super.paintComponent(g);
at the beginning of your paintComponent method (before drawing anything else). This works because the only thing that paintComponent needs to do in an empty JPanel is painting the background.
So:
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g.drawRect(user.x, user.y, 10, 10);
}
EDIT:
To answer some of your comments:
I want to move a rectangle across the screen, rather than re-drawing it again.
There's no such thing as "moving" a rectangle. You can have things painted on the screen. If you want to see other things you have to paint those other things. There's no inherent "move the color of the pixels to the pixels...", that's not how it works. Do you want things? Draw them. Do you want them to move? Draw them repeatedly in different positions.
(I DO NOT WANT TO CLEAR THE FULL WINDOW)
But you do. You want to repaint the whole panel each time something has to change. If there are other things in the panel that you don't want "erased" then repaint them.
To be clear, it would be possible to only clear the "old" rectangle position and paint a new one, without affecting the rest of the panel. But that is unnecesarily tricky. When you override paintComponent calling super.paintComponent(g) in the first line is the standard procedure. Not putting it has to be a very conscious decision and you better are sure of what are you doing.
If your program is done in a way that part of your code misbehaves when you repaint the background of your panel, I can tell you with confidence that is those parts that aren't well designed and not that calling super.paintComponent(g) is a bad idea.
paintComponent has the responsibility of painting the whole component. The background is part of the component. It's natural, and good design within Swing, to do it when you override it.

Draw an image of growing size in Java

I have an application where I want to draw an image that grows in width over time. Specifically, the application listens to the microphone and computes a spectrogram every 6 milliseconds, and I want to draw the updated spectrogram when it comes in. I've been using java.awt.image.BufferedImage to draw the spectrograms, but only for pre-recorded files, so I have a fixed width for the image. What's the best way to do this for a streaming application, where I don't know a priori the width of the image?
One possibility is to just create a new BufferedImage with one extra pixel on the right and copy the data over, but that seems inefficient to do hundreds of times per second. Or I could start with a relatively large width and keep the right side blank until it fills up, and double the width when it does, similar to how an ArrayList amortizes its size - I would only have to copy the data a few times per second, or once every few seconds, or so. Is there a better option?
I would use a combination of what you suggest and an overriding component that only paints a subimage of the total image and returns an appropriate preferred size.
Here is a demo code which shows what I mean:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class TestRecording {
public static class MyPanel extends JPanel {
private BufferedImage buffer = new BufferedImage(3000, 100, BufferedImage.TYPE_INT_RGB);
private int width = 0;
private int lastY = 50;
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (width > 0) {
BufferedImage sub = buffer.getSubimage(0, 0, width, buffer.getHeight());
g.drawImage(sub, 0, Math.max(0, (getHeight() - buffer.getHeight()) / 2), this);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(width, 100);
}
protected void drawSomething() {
// Here need to handle growing image
Graphics g = buffer.getGraphics();
g.setColor(Color.GREEN);
int y = new Random().nextInt(buffer.getHeight());
g.drawLine(width, lastY, width + 1, y);
lastY = y;
width += 1;
Rectangle r = new Rectangle();
r.x = getWidth();
// Lame hack to auto-scroll to the end
scrollRectToVisible(r);
revalidate();
repaint();
}
}
protected void initUI() {
JFrame frame = new JFrame(TestRecording.class.getSimpleName());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final MyPanel p = new MyPanel();
JScrollPane scrollpane = new JScrollPane(p);
frame.add(scrollpane);
frame.setSize(400, 200);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
Timer t = new Timer(20, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
p.drawSomething();
}
});
t.start();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TestRecording().initUI();
}
});
}
}
And here is the result:
May be just scale the original image to get desired size.
Image has getScaledInstance(int width, int height, int hints) method
Do you have 1) gui where you draw it, or 2) do you need to realy output image file (or stream)?
if 1)
Draw only what is seen. Extend JComponent, I usually use JPanel and override paintComponent and paint what is visible.
To make it more efficient, create "tiles" - list of eg. BufferedImage of constant width and create them from incoming data and draw to gui only them.
2) Something similar, but you can use one image with relatively low width and draw new data to it. When full, "append" it to so far created "left" image (initially empty). This way you frequently modify small image and big not so frequently.
If end comes before filling whole right image, join only filled part with left.
You could try to gradually increase size of right image (*X, eg *1.5 or *2) to reduce number of joining images at cost of using more memory.
You could store those "images" as byte(or int) array (1D array representing 2D, but store it by columns, not by rows for more efficient modifications), this way you can store it bit more efficiently if you know that you need some unusual ammount of bits per pixels, because some colors will never be in result image.
If image gets too big, save it to disk and clear your left image and later join them together or use them separately.

java swing: Incorrect dimensions on rotated JComponent after resizing

Is there any way to correct the dimensions for a rotated component?
In my swing application I’d like to be able to rotate a single panel, and have it respond correctly to resize events. The rotation is straight-forward, but on resizing, the height and width dimensions are reversed. It could almost would work if a call to setSize was called from an overridden paint() call to swap the dimensions, but that doesn’t work, because setSize causes another call to paint to occur, and recursion ensues... I’ve also tried adding a ComponentAdapter to handle the swapping on resize events, but got the same result.
So, here’s a simplified example of what I’m working with. The components here are buttons, but the logic applies to a JComponent like a JPanel too. Button c is rotated with a JXTransformer, but this doesn’t resize (it’s commented out in the code, but you can add the JXTransformer class to the classpath if you wish). If you compile the sample, try resizing the window and see how the rotated button behaves. Screenshot:
(It said I can't post screenshots, but these links appear to be live..)
http://i.stack.imgur.com/S3qmb.png
If I add in a scale transformation, the resizing is correct, but the component is distorted beyond usability. Screenshot:
http://i.stack.imgur.com/K4l9e.png
I’ve seen lots of questions on here that discuss the rotating part, but nothing about the resizing issue. For instance, A rotated square panel in Java GUI
Thanks!
Code:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class RotatingTest {
public static void main(String[] args) {
JFrame frame = new JFrame();
JPanel panel = new JPanel(new BorderLayout());
// The button rotates, but the height/width dimensions are incorrect
RotatedButton a = new RotatedButton("ROTATED CONTENTS!");
JButton b = new JButton("Normal Contents");
JButton c = new JButton("Transformer Contents");
// JXTransformer t = new JXTransformer(c);
// t.rotate(Math.toRadians(90));
panel.add(a, BorderLayout.CENTER);
panel.add(b, BorderLayout.WEST);
// panel.add(t, BorderLayout.EAST);
panel.setBorder(BorderFactory.createEmptyBorder(30, 30, 30, 30));
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
}
class RotatedButton extends JButton{
private static final long serialVersionUID = -3355833063753337573L;
RotatedButton(String string){
super(string);
}
#Override
protected void paintComponent(Graphics g) {
int width = getWidth();
int height = getHeight();
Graphics2D graphics = (Graphics2D) g;
AffineTransform txOrig = graphics.getTransform();
AffineTransform transformer = new AffineTransform(txOrig);
transformer.translate(width/2, height/2);
transformer.rotate(Math.toRadians(90));
transformer.translate(-height/2, -width/2);
// this scaling fits the button to the window, but distorts the contents
// double coef1 = (double)width / (double)height;
// double coef2 = (double)height / (double)width;
// transformer.scale(coef2, coef1);
// this line sets the rotation, comment out to disable
graphics.setTransform(transformer);
super.paintComponent(graphics);
graphics.setTransform(txOrig);
graphics.dispose();
System.out.println("Parent size: "+getRootPane().getParent().getSize());
System.out.println("this size: "+getSize());
}
}
So, the problem here is that JComponents do not care what is actually on them when setting their size. When you modify something in paintComponent() it only affects superficial portions of the component, and not anything that will actually be returned with from the component. What you are going to want to do, is remodel your paintComponent() method, or change how you are resizing the component.
Think of it like when you place an image on a button that is too large. The button will simply display what it can, and the rest does not matter as far as the button is concerned. Rotating the graphics on a JComponent is the exact same way. You can create the graphics ahead of time, and set your size to that (this is the complicated way), or if you are only rotating 90 degrees, simply change what you put int for setSize(). I have always found the second to be very simple, as you can constantly change the size of it with no extra code, you just have to remember to switch all your dimensions.
If, however, you wish to put it at an angle that is not a multiple of 90 degrees, you will have to make a larger square for the component to sit in. (using the Pythagorean theory) This may seem silly, but think about how all coordinates are made, from the top left corner. that corner defines that object's location, but if it is not at an absolute (the highest leftmost point) then anything working with that component would have to manually look at every part of the component instead of just calculating it.
Hope this helps, late as it may be.

Categories

Resources