Without going into grave detail, I'm working toward creating a desktop-like program in Swing, with icons drawn on top of a background image. Usually I subclass JPanel or another JComponent and draw on that, but wanted to try something new just for kicks, and tried drawing on an instance of JFrame, without making my program a subclass of it.
I am aware that this is not the accepted way of doing this, but discovering that the image was not drawn has exposed a missing link (one of the many, I suppose) in my understanding of Swing and how it paints components.
What confuses me is that if my program subclasses JFrame and I override the paint() method (the accepted way, in other words), it will draw the image into the JFrame, but it will not do this for an instance of JFrame in my non-subclassed program.
Hopefully the code showing essentially what I want to do will help:
public class ImageLoader
{
BufferedImage img = null;
JFrame window = null;
public ImageLoader()
{
try
{
img = ImageIO.read(new File("src/strawberry.jpg"));
}catch(IOException e)
{
e.printStackTrace();
}
window = new JFrame("Strawberry Viewer");
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.pack();
window.setVisible(true);
//Why can't I do something akin to the following to draw on an instance of JFrame?
Graphics g = window.getGraphics();
paint(g);
}
public void paint(Graphics g)
{
g.drawImage(img, 0, 0, null);
}
public static void main(String[] args)
{
new ImageLoader();
}
}
I have read Oracle's page "Painting in AWT and Swing" but I'm still not understanding why I can't draw on an instance of JFrame. Is there any situation where I could draw on an instance of a component, or do they all have to be subclassed if I want to draw on them?
Finally, if the problem is based largely on my gross misunderstanding of how Swing works, what are some recommended books or other resources for understanding Swing?
Thanks for the help in advance. I appreciate it.
Don't use getGraphics() to do painting.
Anything you do with that Graphics object will only be temporary. Then next time Swing determines the frame needs to be repainted you will lose the painting.
In your case you use pack() so the frame is minimized. When you resize the frame, the normal frame painting will paint over your image. So try using setSize(500, 500);.
However even this won't work because your image will be painting before the normal painting has completed. Not all code is executed sequentially.
Try the following to delay the painting of the image:
try
{
Thread.sleep(1000);
Graphics g = window.getGraphics();
paint(g);
}
catch(Exception e) { e.printStackTrace(); }
When the image shows, then try resizing the frame and you will lose the image.
if my program subclasses JFrame and I override the paint() method
Don't override paint() of a JFrame (yes, it will work, but it is NOT the way painting was designed to be done in Swing). Custom painting is done by overriding paintComponent() of a JPanel and then you add the panel to the frame.
if the problem is based largely on my gross misunderstanding of how Swing works
The Swing tutorial is the best place to start for Swing basics. See the section on Custom Painting to get your started.
For a more technical article see Painting in AWT and Swing.
Related
I have a JFrame which contains a single panel.
In the panel I use the paintComponent method to resize its elements according the size of Jframe. The elements of the JPanel are an image as a background and 4 JLabel that cointains 4 ImageIcon and work like buttons. The method paintComponent of Jpanel is like below
public class MyPanel extends JPanel
{
//Declarations
private BufferedImage backGround;
public MyPanel()
{
//Some code here
}
public void paintComponent(Graphics graphics)
{
super.paintComponent(graphics);
Graphics2D graphics2d = (Graphics2D) graphics;
if(backGround != null)
{
graphics2d.drawImage(backGround, 0, 0, getWidth(), getHeight(), this);
}
/* This code is repeated 4 times because I have 4 labels */
label1.setSize(getWidth()/7 , getHeight()/10);
label1.setLocation(getWidth()/2 - getWidth()/14 , getHeight()/3 );
image1 = button1.getScaledInstance(label1.getWidth(), label1.getHeight(),
Image.SCALE_SMOOTH);
label1.setIcon(new ImageIcon(image1));
}
}
The frame has just a simple method , add(myPanel) so I did not write it here.
When I run the application , it takes me around 300 MB of ram and around 30% of CPU (Inter core i5-6200U) , which is quite unsual for me , expecially the amount of CPU. What is causing my application to take so much resources and is there any way I can reduce it ?
Whenever you repaint your component you change your labels' dimensions and create resources (the Image and the ImageIcon derived from it) and assign them as a new icon. These are changes to visible parts of your application and hence must cause repainting the components in question. Basically your paintComponent method
causes a repaint every time it is called effectively creating an endless loop and
is very heavyweight because it allocates expensive resources.
Both of these points are pretty bad ideas. Your paintComponent method should do just what the name suggests, i.e. painting the component. All actions that cause a repaint (changing icons or text, adding or removing components from the tree etc.) must not occur in it.
See also:
The API documentation on paintComponent(Graphics)
Painting in AWT and Swing
EDIT: When you want to resize components dependent on the size of other components create a ComponentListener and add it to the component you want to depend on by calling addComponentListener(ComponentListener). The ComponentListener instance will then have its componentResized(ComponentEvent) method called whenever the size changes.
Hello I i'm having random flicker every couple of seconds when running my program, it has a single image moving across screen. I'm using Graphics paint() method and repaint() in the thread's run() method. Here are the relevant parts of the code, i'll post entire code if necessary. Btw, pawns is an arraylist loaded with pawn objects, originally I had 5 threads for 5 images moving across but I tried with only 1 image and it still flickers so it's not that.
private BufferedImage helicopter;
helicopter = ImageIO.read(new File("white.png"));
public void paint(Graphics g) {
g.setColor(Color.WHITE);
g.fillRect(0, 0, this.getWidth(), this.getHeight());
for(count=0; count<pawns.size(); count++){
g.drawImage(helicopter, pawns.get(count).getX(), pawns.get(count).getY(), null);
}
g.setColor(Color.BLACK);
g.drawLine(350, 0, 350, 600);
}
public void run() {
while(true) {
randSleep = (int)(Math.random()*100);
randMove = (int)(Math.random()*2);
pawn.setX(pawn.getX()+randMove);
try{
Thread.sleep(40);
}
catch(Exception e) {
e.printStackTrace();
}
repaint();
}
}
On components with complex output, repaint() should be invoked with
arguments which define only the rectangle that needs updating, rather
than the no-arg version, which causes the entire component to be
repainted.
Swing's implementation of paint() factors the call into 3 separate
callbacks: paintComponent() paintBorder() paintChildren() Extensions
of Swing components which wish to implement their own paint code
should place this code within the scope of the paintComponent() method
( not within paint()).
source: Painting in AWT and Swing: Good Painting Code Is the Key to App Performance
You should notice in the source quoted and linked that repaint() (no arguments) will call update(), which by default clears the background before drawing. I suspect that this is the source of the flicker, when the component is cleared before calling paint().
If you are using Swing components you should not implement your own double buffering but instead use the functionality provided by Swing.
First try, calling a repaint with arguments to avoid update clearing the entire background. Or write an override for the update method. If that does not solve the problem next try putting your drawing code in the paintComponent method of a Swing component.
I'm currently using an animation engine I designed that takes objects of type Drawable and adds them to a List. Drawable is an interface that has one method:
public void draw(Graphics2D g2d);
The extending animation manager iterates through this list and calls the draw() method on every object, passing the Graphics2D object obtained from the Swing component.
This method seemed to work well at first, but as I feared, it seems to be unable to handle multiple objects in the long run.
With merely two Drawables registered, both drawing images on screen, I'm seeing a bit of flashing after 30-60 seconds.
Is there a way to optimize this method? It currently calls upon the AWT thread (invokeLater) to handle all of the tasks. Concurrent drawing isn't really an option as this nearly always causes issues in Swing/AWT, in large part due to the fact that Graphics isn't synchronized.
If this just simply is a bad way of animating, what is a better method when you have multiple objects that all need things rendered with their own specific variables cough game cough?
Any help would be appreciated. Thanks!
EDIT:
I can't use repaint() beacuse my engine already calls the AWT thread to paint stuff. If I call invokeLater from the AWT thread, the image never gets painted for some reason.
I should also add that I'm using a system of ticks and fps. 60 ticks # 120 fps.
Each tick updates the game logic, while each frame render calls draw on the frame manager.
Is this a bad idea? Should I just use FPS and not ticks?
I think it would be more appropriate to override paintComponent(Graphics g) and regularly call the repaint method on the JPanel or whatever you're drawing on with a Timer. Your problems may be due to to you trying to draw and then Swing doing it's own draw.
public class Main {
public static void main(String[] args) {
JFrame frame = new JFrame();
JPanel panel = new JPanel() {
public void paintComponent(Graphics g) {
//draw here
}
};
panel.setPreferredSize(800, 600);
frame.add(panel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true)
new Timer(16, new ActionListener() {
public void actionPerformed(ActionEvent event) {
panel.repaint();
}
}).start();
}
}
I'm writing a pretty simple game in Java, and I'm running into the issue of very serious flickering when I play the game as an applet in a browser. That is, all of my sprites, which are being painted on top of a background, are sometimes shown onscreen, but usually not - they repetitively flash onto the screen and then disappear. I've read that double buffering is probably the solution to this, but I'm having trouble implementing it correctly.
I'm using a JApplet as the container for a JPanel. This JPanel is the container onto which the sprites and game objects are painted - that is, in the JPanel's paintComonent method. In my JApplet, I'm using init, paint, and update override methods as follows:
Image offscreen;
Graphics bufferGraphics;
Dimension dim;
public void init(){
dim = getSize();
setBackground(Color.BLACK);
offscreen = createImage(dim.width,dim.height);
bufferGraphics = offscreen.getGraphics();
}
public void paint(Graphics g){
bufferGraphics.clearRect(0,0,dim.width,dim.height);
//here is my question - i"m not sure what I should print to bufferGraphics
g.drawImage(offscreen, 0, 0, this);
}
public void update(Graphics g){
paint(g);
}
The problem I'm running into is that, at the commented line, I'm unsure of what to do to get the current applet image printed to bufferGraphics. I read an example in which the sprite was painted straight to the JApplet, without using a JPanel. In light of that, my guess is that I'd need to paint the JPanel onto bufferGraphics at the commented line. Am I on the right track here? Any help is greatly appreciated; I just would like to know any way to do this properly.
Swing is double buffered by default, there is no need to do anything special.
Your problem is probably the painting code. The code you posted is used for AWT painting, NOT Swing painting.
Custom painting is done by overriding the paintComponent() method of a JPanel or JComponent. I suggest you start by reading the Swing tutorial on Custom Painting for a working example.
I'm trying to make a paint editor with Java in which I have a toolbar with the objects that I would like to paste in the canvas. I'm using Swing components to make the GUI, but when I looked for the way of making the canvas, I only found the class canvas from AWT.
Is there any way to make something similar to canvas with Swing? (for example, JPanel?) I have read that using the class canvas from AWT with a GUI made with swing won't work correctly, is that true?
In order to make a custom 'Canvas' in swing you usually write a subclass of a JPanel. Then, you must overwrite the protected paintComponent(Graphics g) method of JPanel.
In the paint method, you can call methods on the Graphics object to actually draw on the JPanel.
As always, the Java Tutorials have a great reference on this to get you started.
You'll probably want to make a subclass of JPanel and implement your own way of painting components you want to draw onto the panel.
The basic approach will probably be along the line of assigning a MouseListener to the subclass of JPanel, then implement painting functionality.
The basic idea may be something along the line of:
class MyCanvas extends JPanel implements MouseListener
{
Image img; // Contains the image to draw on MyCanvas
public MyCanvas()
{
// Initialize img here.
this.addMouseListener(this);
}
public void paintComponent(Graphics g)
{
// Draws the image to the canvas
g.drawImage(img, 0, 0, null);
}
public void mouseClicked(MouseEvent e)
{
int x = e.getX();
int y = e.getY();
Graphics g = img.getGraphics();
g.fillOval(x, y, 3, 3);
g.dispose();
}
// ... other MouseListener methods ... //
}
The above example is incomplete (and not tested -- it definitely won't compile), but it gives an idea about how to implement a MyCanvas class in which a user can click on and draw circles.
The img object is used to hold the image of the canvas. The paintComponent method is used to paint the img object to the canvas. In the mouseClicked method, the Graphics object associated with img is retrieved in order to fillOval onto the image.
Since one the requirements is to paste images onto the canvas, it may be a good idea to hold some Images that you want to paste into the canvas. Perhaps something along the line of:
Image[] myImages; // Used to store images to paint to screen.
Then, in the routine to paint the image onto img stored in MyCanvas:
g.drawImage(myImage[INDEX_OF_DESIRED_IMAGE], 0, 0, null);
By using the drawImage method of the Graphics object, other Images can be drawn onto Images.
As for the question on AWT and Swing, yes, it is true that you do not want to mix components from the AWT and Swing, as they differ in the way they render GUI components. AWT is based on heavyweight components, meaning they native windowing for painting the GUI, while Swing is based on lightweight components, meaning the GUI is drawn by Java itself without using native components.
A good guide on the difference of AWT and Swing is provided in Painting in AWT and Swing article from Sun.
Simply subclass JComponent.
JPanel is an inappropriate class. It is often suggested as it appears to have setOpaque(true) invoked on it automatically. It's actually the PL&F which does that, and whether or not it actually happens is implementation and vendor dependent.
Canvas is a heavyweight component. That is to say that it is controlled by the underlying windowing system. The result is that it will typically be drawn over the top of Swing components, without respect to z-order or clipping (putting it in a scroll pane will give odd behaviour).
You might want to look at the Minueto API. It is a very simple to use graphics api, and you can combine the Java event listening with it to provide your drawing capability.
http://minueto.cs.mcgill.ca/