I am trying to display a background image in a JFrame, but the image isn't showing up and I can't find what the error in the code is.
public class GraphicsComponent extends JComponent
{
int xPosition = 200;//initializes and declares the x coordinate
int yPosition = 400;//initializes and declares the y coordinate
Image i;
public GraphicsComponent()
{
try{
File sky = new File("lib/background.png");
i = ImageIO.read(sky);
}
catch (Exception e)
{
e.printStackTrace();
}
}
public void paintComponent(Graphics g) //overrides paintComponent method in JComponent
{
if (i == null) return;
g.drawImage(i, 0, 0, this);
}
Why are you creating a custom component? If you create a custom component then you also need to override the getPreferredSize() method of the component to return the size of the image so the layout manager can paint the component at its proper size.
So the size of the component is probably zero, so there is nothing to paint.
The easiest solution is to just use a JLabel with an ImageIcon and let the layout manager deal with the size issue.
Related
I'm building a game and I'm painting the road sprites with the JPanel's draw function. The roads (Building class) can be built by dragging the mouse and on each field a new road sprite appeares. But after I've drawn like 20 road sprites, the drawing gets really slow.
I have a frame and there is this JPanel on it.
Here is the code of the JPanel on which my game drawing is:
private class GamePanel extends JPanel implements ActionListener{
Field[][] map = gameEngine.getMap().getFields();
ArrayList<Building> buildings = gameEngine.getBuildings();
Timer timer;
ArrayList<Field> fields = new ArrayList<>();
GameFrame frame; //REFERENCE FOR THE CONTAINER OF THIS PANEL
private int mousePosX;
private int mousePosY;
GamePanel(GameFrame frame){
/*...*/
Mouse mouseListener = new Mouse();
addMouseListener(mouseListener);
addMouseMotionListener(mouseListener);
timer = new Timer(1000/30,this);
timer.start();
/*...*/
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
draw(g);
}
public void draw(Graphics g){
for(Building b : buildings){
int drawPosX = b.getLocation().getPos().x*40;
int drawPosY = b.getLocation().getPos().y*40;
try {
BufferedImage img = ImageIO.read(new File("src/GFX/" + b.getType() + ".png"));
g.drawImage(img, drawPosX, drawPosY, null);
} catch (IOException e) {
}
}
}
#Override
public void actionPerformed(ActionEvent e) {
// I thought this is not needed because I call "repaint()" only at mouse events (like
// building road by dragging)
}
public class Mouse extends MouseAdapter{
#Override
public void mouseDragged(MouseEvent evt){
repaint(); // THIS IS AN EXAMPLE TO WHERE I CALL THE REPAINT()
fieldPosX = evt.getX() - (evt.getX() % 40);
fieldPosY = evt.getY() - (evt.getY() % 40);
gameEngine.placeRoad(new SimpleRoad(new Field(fieldPosX/40,fieldPosY/40)));
}
/*... OTHER MOUSE EVENTS ...*/
}
I thought that calling repaint() only at mouse events will optimise the speed but it really isn't. I attached a GIF on which it can be seen that after 2 line of roads, it gets really slow.
I heard about invokeLater and people advised me to use it but I don't know how to implement that in this project. Why is my game getting slower after several buildings, where am I making a mistake? Would invokeLater solve the problem? How do I place it in my project?
Thanks for helping!
Why is my game getting slower after several buildings,
try
{
BufferedImage img = ImageIO.read(new File("src/GFX/" + b.getType() + ".png"));
g.drawImage(img, drawPosX, drawPosY, null);
}
Don't do I/O in a painting method. As you add more building you are doing more I/O.
The images should be read in the constructor of your class.
You can save them in a HashMap for easy access in the painting method.
Or, the image can be saved as part of the Building class itself.
I am doing the initial section of a simple platform game in Java. I have created a class called entity which extends JPanel and successfully added it to the window.
import javax.swing.*;
import java.awt.*;
/**
* Created by bw12954 on 27/05/16.
*/
public abstract class Entity extends JPanel {
private final SpriteSheet sprites;
private Point location;
private Dimension dimensions;
public Entity(int x, int y, int w, int h, SpriteSheet sprites)
{
location = new Point(x, y);
dimensions = new Dimension(w, h);
this.sprites = sprites;
}
public Entity(int x, int y, int w, int h)
{
this(x, y, w, h, null);
}
#Override
public Dimension getPreferredSize()
{
return dimensions;
}
public void setLocation(int x, int y)
{
location.setLocation(x, y);
}
/* Some code removed here for brevity */
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
g.drawImage(sprites.get(),
(int)location.getX(),
(int)location.getY(),
null);
}
}
If I add this directly to the JFrame as below, then the graphic shows up on the window as I would expect it to (note that Player is a very simple subclass of Entity)
public class Window {
private JFrame window;
public Window()
{
SwingUtilities.invokeLater(this::run);
}
private void run()
{
try {
window = new JFrame();
window.setDefaultCloseOperation(window.EXIT_ON_CLOSE);
window.setLocationByPlatform(true);
window.setUndecorated(true);
Player p = new Player(0,0);
window.add(p);
window.setExtendedState(JFrame.MAXIMIZED_BOTH);
window.setVisible(true);
} catch (IOException e) {
// TODO handle exception
e.printStackTrace();
}
}
}
However - when I create a class called World which also extends JPanel, add that to the window instead, then use the add() method in its constructor to add a new Player to it, it doesn't appear. Interestingly, if I add setBackground() to the constructor for Player/Entity, I can see a coloured square where the entity SHOULD be. Its just that drawImage doesn't appear to work.
If anyone else has any idea what is going on here, it will be greatfully appreciated!
If I add this directly to the JFrame as below, then the graphic shows up on the window as I would expect it to
The default layout manager of the content pane of the frame is a BorderLayout. Any component added to the frame without a constraint will be added to the "CENTER" which means the component is automatically resized to fill the entire space.
However - when I create a class called World which also extends JPanel, add that to the window instead, then use the add() method in its constructor to add a new Player to it, it doesn't appear.
The default layout manager of a JPanel is a FlowLayout, which respects the preferred size of any component added to it and will reset the location of the component based on the rules of the layout manager.
Interestingly, if I add setBackground() to the constructor for Player/Entity, I can see a coloured square where the entity SHOULD be. Its just that drawImage doesn't appear to work
Probably because your preferred size calculation is incorrect and the image is truncated.
location = new Point(x, y);
dimensions = new Dimension(w, h);
The above code will only work if the Point is (0, 0). The more general case code should be:
location = new Point(x, y);
dimensions = new Dimension(x + w, y + h);
because you need to consider where you actually paint the image realative to the component.
So when you do this you should see the image, however you will not see the image in the proper location because the layout manager will override the location.
So it you want to continue with this approach of using components you will need to use a null layout, which means you will manually need to use the setSize(...) and setLocation(...) of each component. In this case the size of the component will be the (w, h) and you will draw the image using:
g.drawImage(sprites.get(), 0, 0, this);
Note however if you use the component approach there is no need to even create a custom component. You could just use a JLabel with an ImageIcon. You would assign the Icon when you create the label.
I have JPanel inside JFrame and there are more than one BufferedImages in the JPanel like this.
Now my question is how can i move the BufferedImages inside the JPanel? More specifically How to add MouseEventhandler to the Buffered Images?. Though I can drag JPanel from the code below but can't figure out how to drag Buffered Images inside JPanel. Thanks for the help.
I have three classes like this
MainWindow.Java
public class MainWindow extends JFrame {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
MainWindow window = new MainWindow();
window.frame.setVisible(true);
} catch (Exception e) {
}
}
});
}
public MainWindow() {
JFrame frame = new JFrame();
.... // Other code of JFrame
JPanel panel_3 = new JPanel();
.... // other code of JPanel
frame.getContentPane().add(panel_3);
panel_3.setLayout(new BorderLayout(0, 0));
Drawing drawingObj = new Drawing();
panel_3.removeAll();
panel_3.add(drawingObj );
drawingObj .revalidate();
drawingObj .repaint();
BasicDragging drag= new BasicDragging();
panel_3.addMouseListener(drag);
panel_3.addMouseMotionListener(drag);
}
}
Drawing.java
public class Drawing extends JPanel {
private BufferedImage Image1 ;
private BufferedImage Image2;
private BufferedImage Image3;
public Drawing() {
try {
Image1 = ImageIO.read(new File("path of file"));
Image2 = ImageIO.read(new File("path of file"));
Image3 = ImageIO.read(new File("path of file"));
}
catch (IOException ex) {
// handle exception...
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(Image1 , 150, 10, null);
g.drawImage(Image2 , 150, 70,null);
// Other code of drawing images
}
}
}
BasicDragging.java
public class BasicDragging extends MouseInputAdapter{
Point location;
MouseEvent pressed;
public void mousePressed(MouseEvent me)
{
pressed = me;
}
public void mouseDragged(MouseEvent me)
{
Component component = me.getComponent();
location = component.getLocation(location);
int x = location.x - pressed.getX() + me.getX();
int y = location.y - pressed.getY() + me.getY();
component.setLocation(x, y);
}
}
Instead of using images and then writing code to determine which image has "been clicked" you can just use a JLabel with an ImageIcon to display the image. Then you can add your MouseListener to the JLabel and drag the label around the panel.
Using this approach the basic code for dragging components would be:
public class DragListener extends MouseInputAdapter
{
Point location;
MouseEvent pressed;
public void mousePressed(MouseEvent me)
{
pressed = me;
}
public void mouseDragged(MouseEvent me)
{
Component component = me.getComponent();
location = component.getLocation(location);
int x = location.x - pressed.getX() + me.getX();
int y = location.y - pressed.getY() + me.getY();
component.setLocation(x, y);
}
}
The code to use this class would be:
DragListener drag = new DragListener();
component.addMouseListener( drag );
component.addMouseMotionListener( drag );
You can also check out the Component Mover which adds some more advanced dragging features.
You will need to use a null layout on the panel and set the size/location of the label. So you may want to use the Drag Layout which simplifies this process for you.
My first suggestion would be a simple one...
As you can see you draw these images by coordinates, x and y.
You could add the MouseListener to JPanel. That's where the magic already happens and it would be rather confusing if you create more objects.
Now you only have to check if the mouse hover above a image and is pressed.
You only need to calculate the size and if the point is inside the image.
If it is inside the image moves in the direction of the mouse.
And you shouldn't reset the position rather move it along.
That's how it is done.
Just set the x and y for the images when the are clicked and pressed on.
I'd create an object DragImage which contains the core BufferedImage + coordinates that represent the x and y and it's size. Additionally i'd add a function that calculates public boolean isHovering(int x, int y).
It should work out. And the x and y coordinates are used in your draw function.
I hope you understand what i was trying to say
Here is what I am trying to do. I have extended JButton and overwritten the paintComponent method creating my desired effect of a rounded edge button, and a color fading effect when the button is rolled over by a mouse. All that works great. My problem is that the JButton is still painting a white rectangle area as the images show.
I would like 1) the white corners to be gone and 2) the cetner of the button to show the panel behind it. Here is what I have tried:
1- when painting the button use getParent().getBackground() and paint the button first. This works great for opaque panels. However I would love this button to work on a partially or fully transparent panel. With transparent panels it paints the color, but on the white background hiding anything behind the panel (like an image).
2- I have tried many combinations of setOpaque(false) or setContentAreaFilled(false). I have tried this while calling super.paintComponent(g) and not calling it. None of those seem to work.
3- The button looks right when I don't use the method g2.clearRect(0,0,width,height) (clearing the graphics area before painting), but since the graphics object is never covered up the fade effect stops working after one rollover of the button.
4- I use a JLabel for the text and have tried setting it opaque or just not using it and the issue still remains. so I don't think that is the issue.
Since I only want an affect for the JButton and not other swing components I'm really hoping to avoid making my own ButtonUI.
Thanks and I hope this makes sense. Below is the code for my button.
import javax.swing.*;
import javax.swing.Timer;
import java.awt.*;
import java.awt.event.*;
/**
* WButton.java
*
* An extension of JButton but with custom graphics
*
*/
public class WButton extends JButton{
private Timer timer;
private float[] background = {.3f,.6f,.8f,0f};
private boolean fadeUp = true;
private boolean fadeDown = false;
private JLabel label;
/**
* Default Constructor
*/
public WButton(){
super();
label = new JLabel();
setupButton();
}
/**
* Text constructor
*/
public WButton(String text){
super(text);
label = new JLabel(text);
setupButton();
}
/**
* common setup functions
*/
private void setupButton(){
timer = new Timer(24,new TimerAction(this));
label.setLabelFor(this);
add(label);
}
/**
* Set the background color
*/
#Override
public void setBackground(Color bg){
background = bg.getRGBComponents(background);
background[3] = 0f;
super.setBackground(new Color(background[0],background[1],
background[2],background[3]));
repaint();
}
/**
* get background
*/
#Override
public Color getBackground(){
if(background!=null)
return new Color(background[0],background[1],background[2],background[3]);
return new Color(.5f,.5f,.5f);
}
/**
* Set the font of the button
*/
#Override
public void setFont(Font font){
super.setFont(font);
if(label!=null)
label.setFont(font);
}
/**
* Override the set text method
*/
#Override
public void setText(String t){
super.setText(t);
if(label!=null)
label.setText(t);
}
/**
* Paint the button
*/
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
int width = getWidth();
int height = getHeight();
Graphics2D g2 = (Graphics2D)g;
g2.clearRect(0,0,width,height);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
//Check Button Model state
if(model.isPressed())
paintPressedButton(g2,width,height);
else{
if(model.isRollover()){
if(fadeUp){
fadeUp = false;
timer.start();
}
}
else{
if(fadeDown){
fadeDown = false;
timer.start();
}
}
g2.setPaint(new Color(background[0],background[1],background[2],background[3]));
g2.fillRoundRect(0,0,width-1,height-1,height,height);
}
}
/**
* Draw a pressed button
*/
private void paintPressedButton(Graphics2D g2,int width,int height){
float[] temp = new float[4];
for(int i=0;i<background.length;i++)
temp[i] = background[i]-.4f < 0f ? 0f : background[i]-.4f;
g2.setPaint(new Color(temp[0],temp[1],temp[2],temp[3]));
g2.fillRoundRect(0,0,width-1,height-1,height,height);
}
/**
* paint the border
*/
public void paintBorder(Graphics g){
int width = getWidth();
int height = getHeight();
g.setColor(Color.BLACK);
g.drawRoundRect(0,0,width-1,height-1,height,height);
}
/**
* Inner action listener class
*/
private class TimerAction implements ActionListener{
private float alphaInc = .2f;
WButton button;
public TimerAction(WButton b){
button = b;
}
public void actionPerformed(ActionEvent e){
if(model.isRollover()){
background[3] += alphaInc;
if(background[3] > 1.0f){
timer.stop();
background[3] = 1.0f;
fadeDown = true;
}
}
else{
background[3] -= alphaInc;
if(background[3] < 0f){
timer.stop();
background[3] = 0f;
fadeUp = true;
}
}
button.repaint();
}
}
}
EDIT 1
What aly suggested got me closer, but not quite there. Instead of g2.clearRect() I painted the object with a transparent color as suggested. The white box is gone, but a different color is there. Upon investigation is is the color of the parent panel but with no transparency. Here are pictures for an example (the panel has 70% transparency). The first pictures is when the program starts. The second picture is after 1 mouse rollover.
What you could do is instead of using clearRect(), clear the background with a completely transparent color.
g2.setColor(new Color(0,0,0,0));
g2.drawRect(0,0,width,height);
You still need to setOpaque(false) on the JButton so that it doesn't use the blue rollover color as the background once you hover over it once.
Edit: After seeing what you just posted, I think the problem is that the main frame isn't repainted.
Try:
SwingUtilities.getWindowAncestor(this).repaint();
in the paint method to repaint the frame, that might fix the problem.
The problem here is, swing uses paintImmediately(x, y, width, height) on JButton to repaint the changed area. If you look inside that method, it iterates through the parent hierarchy (if parent is not opaque) until it finds an opaque component. Then call repaint on it. As you may notice, this approach don't take the alpha component in background color into account because those components are opaque (isOpaque = true).
To address this issue, you can override the paintImmediately() method in JButton as follows:
#Override
public void paintImmediately(int x, int y, int w, int h) {
super.paintImmediately(x, y, w, h);
Component component = this;
boolean repaint = false;
while (!component.isOpaque() || (component.getBackground() != null && component.getBackground().getAlpha() < 255)) {
if (component.getBackground() != null && component.getBackground().getAlpha() < 255) {
repaint = true;
}
if (component.getParent() == null) {
break;
}
component = component.getParent();
if (!(component instanceof JComponent)) {
break;
}
}
// There is no need to repaint if no component with an alpha component in
// background is found and no parent component is found (implied by component != this)
// since super.paintImmediately() should have handled the general case.
if (repaint && component != this) {
component.repaint();
}
}
This method will check for the parent of the topmost component which is either not opaque or has a transparent background and repaints it. Performance point of view, this is much better than the currently accepted answer (which is redrawing the entire window, everytime when a button is hovered/pressed).
I have a Java application which draws a drawing. I want to give the user the possibility to mark an area with the mouse (in order to, for example, zoom into it).
For that I use the MouseMotionListener class, and when the mouse is (clicked and then) moved, I save the location of the currently selected (it isn't final since the user haven't released the mouse) rectangle, and use the repaint() function. I wish to display that rectangle over the original drawing, making it similar to the Selection tool in MSPaint.
The problem is that when I call the repaint() function, the method paintComponent (Graphics page) is invoked, in which I use the method super.paintComponent(page) which erases my drawing. However, if I don't use that method when I know the user is selecting a rectangle, I get that all the selected rectangles are "packed" one above the other, and this is an undesirable result - I wish to display the currently selected rectangle only.
I thought I should be able to save a copy of the Graphics page of the drawing and somehow restore it every time the user moves the mouse, but I could not find any documentation for helpful methods.
Thank you very much,
Ron.
Edit: Here are the relevant pieces of my code:
public class DrawingPanel extends JPanel
{
public FractalPanel()
{
addMouseListener (new MyListener());
addMouseMotionListener (new MyListener());
setBackground (Color.black);
setPreferredSize (new Dimension(200,200));
setFocusable(true);
}
public void paintComponent (Graphics page)
{
super.paintComponent(page);
//that's where the drawing takes place: page.setColor(Color.red), page.drawOval(..) etc
}
private class MyListener implements MouseListener, MouseMotionListener
{
...
public void mouseDragged (MouseEvent event)
{
//saving the location of the rectangle
isHoldingRectangle = true;
repaint();
}
}
}
I'm betting that you are getting your Graphics object via a getGraphics() call on a component, and are disatisfied since this obtains a Graphics object which does not persist. It is for this reason that you shouldn't do this but instead just do your drawing inside of the JPanel's paintComponent. If you do this all will be happy.
As an aside -- we'll be able to help you better if you tell us more of the pertinent details of your problem such as how you're getting your Graphics object and how you're trying to draw with it, key issues here. Otherwise we're limited to taking wild guesses about what you're trying to do.
e.g.,
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.*;
public class MandelDraw extends JPanel {
private static final String IMAGE_ADDR = "http://upload.wikimedia.org/" +
"wikipedia/commons/thumb/b/b3/Mandel_zoom_07_satellite.jpg/" +
"800px-Mandel_zoom_07_satellite.jpg";
private static final Color DRAWING_RECT_COLOR = new Color(200, 200, 255);
private static final Color DRAWN_RECT_COLOR = Color.blue;
private BufferedImage image;
private Rectangle rect = null;
private boolean drawing = false;
public MandelDraw() {
try {
image = ImageIO.read(new URL(IMAGE_ADDR));
MyMouseAdapter mouseAdapter = new MyMouseAdapter();
addMouseListener(mouseAdapter);
addMouseMotionListener(mouseAdapter);
} catch (MalformedURLException e) {
e.printStackTrace();
System.exit(-1);
} catch (IOException e) {
e.printStackTrace();
System.exit(-1);
}
}
#Override
public Dimension getPreferredSize() {
if (image != null) {
return new Dimension(image.getWidth(), image.getHeight());
}
return super.getPreferredSize();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
if (image != null) {
g.drawImage(image, 0, 0, null);
}
if (rect == null) {
return;
} else if (drawing) {
g2.setColor(DRAWING_RECT_COLOR);
g2.draw(rect);
} else {
g2.setColor(DRAWN_RECT_COLOR);
g2.draw(rect);
}
}
private class MyMouseAdapter extends MouseAdapter {
private Point mousePress = null;
#Override
public void mousePressed(MouseEvent e) {
mousePress = e.getPoint();
}
#Override
public void mouseDragged(MouseEvent e) {
drawing = true;
int x = Math.min(mousePress.x, e.getPoint().x);
int y = Math.min(mousePress.y, e.getPoint().y);
int width = Math.abs(mousePress.x - e.getPoint().x);
int height = Math.abs(mousePress.y - e.getPoint().y);
rect = new Rectangle(x, y, width, height);
repaint();
}
#Override
public void mouseReleased(MouseEvent e) {
drawing = false;
repaint();
}
}
private static void createAndShowGui() {
JFrame frame = new JFrame("MandelDraw");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new MandelDraw());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
You need to repaint on every mouse movement:
public void mouseDragged(MouseEvent e){
int x = e.getX();
int y = e.getY();
//Update the rectangle holder object with that point coordinates
repaint();
}
You'll probably have a holder rectangle object to hold the initial and final rectangle points. The initials are set on mouse click, the final are modified on mouse dragged and on mouse released.
In paint method, clear the graphics and draw a rectangle with the coordinates in the holder. This is the basic idea.
UPDATE: How to draw a new shape on top of the existing image:
I'm thinking of two options:
If you are only drawing shapes (such as lines, rectangles and other Java2D stuff) you could have a Collection holding these shapes coordinates, and draw all of them on each paint. Pros: good when there are few shapes, allows undoing. Cons: When the number of shapes increase, the paint method will take more and more time in each pass.
Have a "background image". On each paint call, draw first the image and then the currently active shape on top. when an active shape is made persistent (onMouseReleased), it is saved to the background image. Pros: efficient, constant time. Cons: drawing a big background image on every mouse movement could be "expensive".