This is a fragment of an application with several tabs hung off a JTabbedPanel:-
I have generated the image with code in the standard following manner:-
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.drawImage(device.getVisualisation(), 0, 0, null);
}
So every time something happens to the application gui, the paintComponent method is called and the image displays. That's normal.
This paint event also happens if the tab focus changes, or the mouse passes over the tab as you'd expect. The problem is this takes several seconds as generating the graphic takes a lot of time. This delay is unavoidable and I accept it. You also get many paint events as the gui system does what it has to. Normally this would be okay, but with the processing delay, the gui stops /flashes /updates several times over the period of say 10 seconds.
I thought that I could deal with this by only manually calling repaint() from a "REFRESH" JButton somewhere on the gui. But I can't turn off the automatic repainting if you tough the tabs. How do I only paint a component by pressing a button, and not automatically?
I would do things a bit differently by first figuring out what is the expensive process here. It's not painting but rather the calculation and creation of the special image that is displayed by the painting. So with this in mind, rather than turning off painting, which would involve only kludges, instead store the image drawn an image field, and carefully control when it is recreated via the expensive device.getVisualisation() method.
If this method is truly long-running, then it don't call it within paintComponent, a method which never should hold cpu-intense or time crunching code, and in fact, the method should be called off of the Swing event thread, and instead within a background thread. Then when the background thread is done processing, update that same BufferedImage and call repaint(), and display the new image.
For example:
private Image image = null; // holds our image
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
if (image != null) {
// this will hardly take any time at all to run
g2d.drawImage(image, 0, 0, this);
}
}
public void myDrawImage() {
// create a SwingWorker for background threading work
new SwingWorker<Image, Void>() {
#Override
protected Image doInBackground() throws Exception {
// run this long-running code within this background thread
return device.getVisualisation();
};
#Override
protected void done() {
try {
// when the thread is done, get the new image,
// put it into our image field, and repaint the component
image = get();
repaint();
} catch (InterruptedException | ExecutionException e) {
// TODO handle any exceptions that occur with drawing
}
};
}.execute();
}
Now the image drawn will only change when and if your program specifically calls the myDrawImage() method, so that now the calling of the long-running code is under your total control.
Related
Before you read, this will be informative: Java JFrame won't show up after using .setVisible(true) after being invisible
Hello I am working on a library API that let's you capture an area of the screen, and it returns you a class that contains the ByteArrayInputStream and utility methods like createBufferedImage, createFile, etc.
Basically you create a Bootstrap instance, and pass the capturer type you want as a dependency (ScreenshotCapturer or GifCapturer):
Bootstrap b = new Bootstrap(new ScreenshotCapturer());
And the beginCapture method receives an object that implements ScreenCaptureCallback which is the callback event that the captured result will be passed to.
This is a short background.
Now when you use the beginCapture method, basically what it does is creates new instance of SelectionCamera, this is basically the component that paints the selection area you're selecting when dragging the mouse, and updates the listeners.
once created instance, it calls super.setVisible(true);
After that method gets called, the frame will show up, and also show the old painted screen for like 600-500miliseconds, I am not exactly sure, but it disappears so quickly.
Take a look at this live example:
Note use the video option, otherwise you will not see what I'm seeing as gif is too slow to show it!
http://gyazo.com/d2f0432ada37842966b42dfd87be4240
You can see after I click Screenshot again, it shows the old selected area and disappears quickly. (by the way the frame you see in the gif is not part of the app, just dummy hello world example usage).
The process of image capture.
Step 1
beginCapture gets called:
public void beginCapture(final ScreenCaptureCallback c) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
capturer.setCallback(c);
capturer.beginSelection();
}
});
}
Step 2
beginSelection gets called in the Capturer class (ScreenshotCapturer extends Capturer (abstract)
#Override
public void beginSelection() {
super.init();
this.setHotkeys();
super.getCamera().startSelection();
}
Step 3
CaptureCamera#startSelection() gets called
public void startSelection() {
super.getContentPane().removeAll();
super.getContentPane().repaint();
super.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
this.selector = new SelectionCamera();
this.selectionMosueAdapter.updateCamera(this.selector);
this.selectionMouseMotion.updateCamera(this.selector);
super.add(this.selector);
super.setVisible(true);
super.repaint();
super.getContentPane().repaint();
}
Step 4
The user selects an area, and both mouse listener and mouse motion listens to it(Take a look at mouse motion):
#Override
public void mouseDragged(MouseEvent e) {
Point dragPoint = e.getPoint();
Point startPoint = this.selector.getStartPoint();
int x = Math.min(startPoint.x, dragPoint.x);
int y = Math.min(startPoint.y, dragPoint.y);
int width = Math.max(startPoint.x - dragPoint.x, dragPoint.x - startPoint.x);
int height = Math.max(startPoint.y - dragPoint.y, dragPoint.y - startPoint.y);
this.selector.setCameraDimension(width, height);
this.selector.setCoordinates(x, y);
this.camera.repaint(); // important
}
by the way this.selector is SelectorCamera which is the component that paints the selection area.
Step 5
CaptureCamera#endSelection() gets called, this method gets the x,y, width, height from the selection camera and passes it to the capturer class which uses Robot to get screenshot with that rectangle, and before that it removes ALL components from the content pane, and repaints everything and then sets visibility to false.
public void endSelection() {
super.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
int x = this.selector.getCameraX();
int y = this.selector.getCameraY();
int w = this.selector.getCameraWidth();
int h = this.selector.getCameraHeight();
super.getContentPane().removeAll();
super.getContentPane().repaint();
//super.repaint();
super.setVisible(false);
this.c.startCapturing(x, y, w, h);
}
Basically this is the last step, rest steps are unnecessary for the debugging as it only sends back the callback.
I really tried my best explaining the process of my application, I've tried figuring it out for 5 and half hours now, and no luck at all. Tried different ways, by creating new SelectionCamera object as you see, doesn't work.
Why is it doing this? Is it something to do with the swing core?
SelectionCamera code: https://github.com/BenBeri/WiseCapturer/blob/master/src/il/ben/wise/SelectionCamera.java
Thanks in advance.
Based on this example...
try {
final Bootstrap b = new Bootstrap(new ScreenshotCapturer());
b.beginCapture(new ScreenCaptureCallback() {
#Override
public void captureEnded(CapturedImage img) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
b.beginCapture(new ScreenCaptureCallback() {
#Override
public void captureEnded(CapturedImage img) {
System.out.println("...");
JFrame frame = new JFrame();
frame.add(new JLabel(new ImageIcon(img.getBufferedImage())));
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
});
}
});
}
});
System.out.println("Hello");
} catch (AWTException exp) {
exp.printStackTrace();
}
I won't focus on the initialise stage of the first round, I will focus on the initialisation of the second round as this is where the problem is...
b.beginCapture call's this.capturer.beginSelection();, which calls super.getCamera().startSelection(); which calls setVisible(true) (CaptureCamera been a JFrame).
This will immediately show what ever was previously displayed on the CaptureCamera. It's important to note here, that no new instances of objects were created through the process...
Now, I made a lot of changes to the base testing this, but it appears that the problem is with the restoration of the frame when it's made visible for the second time. This seems to be an issue with the transparency support of the Window as it seems to restore the last "known" state instead of repainting the window immediately...
Now, I tried clearing the selector before making the CaptureCamera invisible to no eval, as the window seems to be made invisible before the selector is painted.
The final solution I came up with was to call dispose on the CaptureCamera, which releases it's native peer and therefore destroys the old graphics context, forcing the frame to rebuild itself when it is made visible again.
"A" problem with this could be the fact that when all the windows are disposed (and the only running threads are daemon threads), the JVM will exit...
This was an issue during my testing as I was using a javax.swing.Timer to put a delay between the first and second capture process so I could see where the problem was occurring and this caused my JVM to exit (as the timer uses a daemon thread and I had no other windows open).
I got around this by creating a tiny (1x1) transparent window in the Capturer class, keep this in mind if the JVM exists gracefully for no reason ;)
Side Notes...
Now, there is an issue with SelectionCamera (which extends JPanel), it is opaque, but is using a transparent background, this is incredibly dangerous as Swing only knows how to deal with opaque or fully transparent components.
public SelectionCamera() {
super.setBackground(new Color(0, 0, 0, 0));
super.setVisible(false);
}
Should be updated to something like...
public SelectionCamera() {
setOpaque(false);
//super.setBackground(new Color(0, 0, 0, 0));
super.setVisible(false);
}
I'm also confused over the use of super.xxx, the only reason you would do this is if you had overrriden those methods and didn't want to call them at this time...In my testing, I removed all the calls to super where a method wasn't overridden in the current class (and I wasn't already in the overriden method)
Also, the paintComponent method should be calling super.paintComponent
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(new Color(0, 0, 0, 0.5f));
g.fillRect(this.x, this.y, this.width, this.height);
}
Make Frame left to be -10,000 then set visible true, add a timer 2 seconds (try lower to 25-100 milliseconds, just to give it sligth pause to invalidate content) , on timer :left to 0 . I think it works due to caching & double buffereing. Frame shows what it had in buffer, buffer points to old image due to caching/ lazy repaint.
Alternative :
Maybe a repaint or invalidate before your show would work too, and don't need to do the left -10,000. I dont work much with ui-swing, just a but years back and remember some strange things like this.
My problem is that when I run my program I get a white screen and text from an earlier build instead of the background image that's suppose to be displayed. I've deleted all the code that was associated with that build.
I've looked around for help and all the threads I've seen say to write the code how I've set it up. I don't understand where the displayed background is even coming from.
Here is the relivent code:
package tactics;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.swing.JFrame;
public class Tactics2 extends JFrame{
private Screen s;
private BufferedImage bg;
private BufferedImage template;
private boolean loaded = false;
public static void main(String[] args) throws IOException{
DisplayMode dm = new DisplayMode(1024, 768, 16, DisplayMode.REFRESH_RATE_UNKNOWN);
Tactics2 t = new Tactics2();
t.run(dm);
}
//run method
public void run(DisplayMode dm) throws IOException{
loadpics();
s = new Screen();
try{
s.setFullScreen(dm, this);
try{
Thread.sleep(5000);
}catch(InterruptedException ex){}
}finally{
s.restoreScreen();
}
}
public void loadpics() throws IOException{
bg = new BufferedImage(1024, 768, BufferedImage.TYPE_INT_RGB);
template = new BufferedImage(1024, 768, BufferedImage.TYPE_INT_RGB);
ChaosBack cb = new ChaosBack();
bg = cb.ChaosBack(bg, template);
loaded = true;
repaint();
}
#Override
public void paint(Graphics g){
if(loaded){
g.drawImage(bg, 0, 0, null);
}
}
}
You've broken the paint chain
#Override
public void paint(Graphics g){
if(loaded){
g.drawImage(bg, 0, 0, null);
}
}
Basically, you've failed to call super.paint. Graphics is a shared resource, that is, everything painted for a given paint cycle uses the same Graphics context.
Part of the job of the paint chain is to prepare it for painting by clearing the Graphics context.
You should avoid overriding paint of a top level container for a number reasons. It's not double buffered, so it may flicker as it's updated and it doesn't take into consideration the frame decorations, meaning you can end up painting underneath the borders of the frame, instead within the viewable area.
You'd better of creating a custom component, extending from something like JPanel and overriding it's paintComponent method (making sure you call super.paintComponent)
Thread.sleep(5000); is a REALLY bad idea within a Swing application. It's possible to actually stop your application cold and stop it from been updated/painted or respond to any user interaction.
Swing is not thread safe. This means that all changes to the UI must be made from within the context of the Event Dispatching Thread.
Take a look at:
Performing Custom Painting
Painting in AWT and Swing
Concurrency in Swing
Initial Threads
How to Use Swing Timers
For details and ideas
I have two button listeners for game board using java swing.
A tetris grid is created initially and then addition functionality within each button listener.
I set the board up like so in my Play.java:
final TetrisGame g = new TetrisGame(11,1);
final BoardGraphics graphics = new BoardGraphics(TetrisBoard.BOARD_WIDTH, 40, g);
The button listeners are then created in the same Play.java:
graphics.btnStart.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
Action arc = p.getAction(g);
g.update(arc);
graphics.colours.clear();
graphics.setColor(g.getBoard().getGrid());
while (arc instanceof Store){
arc = p.getAction(g);
g.update(arc);
graphics.colours.clear();
graphics.setColor(g.getBoard().getGrid());
}
graphics.tiles.redraw();
System.out.println();
System.out.println(g.toString());
System.out.println();
}
});
graphics.btnAuto.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
while (!g.gameEnded()){
Action arc = p.getAction(g);
g.update(arc);
graphics.colours.clear();
graphics.setColor(g.getBoard().getGrid());
while (arc instanceof Store){
arc = p.getAction(g);
g.update(arc);
//graphics.colours.clear();
graphics.setColor(g.getBoard().getGrid());
}
graphics.tiles.redraw();
System.out.println();
System.out.println(g.toString());
System.out.println();
/*try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}*/
}
}
});
The btnStart works perfectly, pressing it once, painting the tetrisboard according to the next move given by AI agent.
I would like the btnAuto to play each move out without the user pressing btnStart to generate move until the end. However, my btnAuto does not paint anything on to the grid but the final state of the game, the finishing state.
Can anyone see why this might be not repainting the grid after each move is generated in the while loop?
Your while loop is being called on the Swing event thread and is thus preventing the the thread from doing its necessary actions including rendering the GUI and interacting with the user:
while (!g.gameEnded()){
Action arc = p.getAction(g);
// ....
}
I would use a Swing Timer here instead of a while (true) loop. Another option is to use a background thread, but since all you desire is a very simple game loop and don't need to run some long-running in the background, I think that this second option would be more complex with no additional benefit.
As an aside, I'm curious how you're doing your drawing and how you're getting your Graphics object to draw with. You're not calling getGraphics() on a component, are you?
Edit you state in a comment:
I currently have a class with a nested class that extends JPanel. The drawing of the grid and getGraphics() is done within the nested class.The parent class creates the component and sets the layout of the GUI as a whole
Don't get a Graphics object by calling getGraphics() on a GUI component as the Graphics object obtained will not persist. To see that this is so, simply minimize and then restore your application and tell me what happens to your graphics after doing this. You should do all of your drawing in the JPanel's paintComponent override. One option is to call getGraphics() on a BufferedImage and use it to draw to the BufferedImage, and then display the BufferedImage in the paintComponent override. If you use the second technique, don't forget to dispose of the BufferedImage's Graphics object after you are done using it so that you don't hog system resources.
I have to write a simple Java app which can load pictures, show it in a GUI form, allow the user to apply some transformation, and show the transformed picture.
My solution is working fine, but the UI is flickering a bit, because the repaint method called too often (for example when the user scaling the image with a JSlider)
My code looks like this:
public class ImageCanvas extends Canvas
{
private BufferedImage image;
// ...
#Override
public void paint(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
if(image != null)
{
// I draw out the image...
}
}
public void setImage(BufferedImage image)
{
this.image = image;
this.repaint();
}
public void setRotation(double rotation)
{
this.rotation = rotation;
this.repaint();
}
public void setScale(double scaleX, double scaleY)
{
//set the scaling field, then repaint ....
}
// and so on...
}
And, of course, I have an ImageCanvas control on my main UI, and I simply call the public methods (see for example the "setRotation" method above) which repaint the canvas area. I know it's a simple question, but I don't even find a DoubleBuffered property on the Canvas...
Any help appreciated.
Double buffering is built-in for Swing (i.e. JComponent derived) classes.
If you want built-in double-buffering, you should extend JPanel rather than Canvas, and override paintComponent, not paint.
If you can use JPanel than go for it. Please make sure you are not overriding the JPanel.paint method, override JPanel.paintComponent instead.
See this link for details.
Usually graphic lags in these applications can be caused by setting a empty variable at the top of the script, then changing its value, then waiting for the repaint to update it. You could try changing the:
setRotation(double rotation);
so that it rotates the image in that method.
Just a general thing I happen to see while dealing with graphics.
I am trying to achieve double buffering of my game in Java by overriding the update method for my JPanel, I do all the usual code etc and still it won't work, it throws a stack overflow error, below is the specific error:
Exception in thread "AWT-EventQueue-0" java.lang.StackOverflowError
at java.awt.Rectangle.<init>(Rectangle.java:193)
at java.awt.Rectangle.<init>(Rectangle.java:208)
at sun.awt.image.BufImgSurfaceData.getBounds(BufImgSurfaceData.java:369)
at sun.java2d.loops.GraphicsPrimitive.convertFrom(GraphicsPrimitive.java:533)
at sun.java2d.loops.GraphicsPrimitive.convertFrom(GraphicsPrimitive.java:523)
at sun.java2d.loops.MaskBlit$General.MaskBlit(MaskBlit.java:171)
at sun.java2d.loops.Blit$GeneralMaskBlit.Blit(Blit.java:186)
at sun.java2d.pipe.DrawImage.blitSurfaceData(DrawImage.java:927)
at sun.java2d.pipe.DrawImage.renderImageCopy(DrawImage.java:550)
at sun.java2d.pipe.DrawImage.copyImage(DrawImage.java:54)
at sun.java2d.pipe.DrawImage.copyImage(DrawImage.java:982)
at sun.java2d.SunGraphics2D.drawImage(SunGraphics2D.java:2979)
at sun.java2d.SunGraphics2D.drawImage(SunGraphics2D.java:2964)
at epicgame.Menu.displayMenu(Menu.java:71)
at epicgame.GUI$1.paintComponent(GUI.java:64)
at javax.swing.JComponent.paint(JComponent.java:1029)
at epicgame.GUI$1.update(GUI.java:117)
at epicgame.GUI$1.paintComponent(GUI.java:98)
at javax.swing.JComponent.paint(JComponent.java:1029)
My code isn't particularly complex either:
mainPanel = new JPanel()
{
#Override protected void paintComponent(Graphics g)
{
//super.paintComponent(g);
if(menuEnabled == 1)
{
Menu.displayMenu(g, mainPanel);
}
else if(gameNum == 1)
{
StreetFighter.StreetFighter(g, mainPanel);
// Calls the controls method within the controls class.
Controls.controls(Calendar.getInstance().getTimeInMillis() - timeOld);
timeOld = Calendar.getInstance().getTimeInMillis();
}
else if(gameNum == -1)
{
Menu.scoreBoard(g, mainPanel);
if(loaded != true)
{
Menu.loadScoreBoard(mainPanel);
loaded = true;
}
}
if(gameNum > 0)
{
if(longcat == true && longcatloaded != true)
{
Extras.loadLongCat();
longcatloaded = true;
}
if(longcatloaded == true && longcat == true)
{
Extras.displayLongCat(g, mainPanel);
}
}
// Causes an infinite loop, e.g makes the screen render over and over.
//repaint();
update(g);
}
#Override public void update(Graphics g)
{
System.err.println("Updating screen and using double buffer!");
// initialize buffer
if(dbImage == null)
{
dbImage = createImage (this.getSize().width, this.getSize().height);
dbg = dbImage.getGraphics ();
}
// clear screen in background
dbg.setColor (getBackground ());
dbg.fillRect (0, 0, this.getSize().width, this.getSize().height);
// draw elements in background
dbg.setColor (getForeground());
paint(dbg);
// draw image on the screen
g.drawImage (dbImage, 0, 0, this);
try
{
Thread.sleep(200);
}
catch (InterruptedException ex)
{
System.err.print("cant delay repaint.");
}
}
};
I was hoping someone could point out where I went wrong, I'm thinking maybe something to do with the update being called too many times, or possible update is the wrong method?
Don't call paint() or update() methods from paintComponent().
Also don't call Thread.sleep() in any painting methods. Instead, create a thread that updates your game model every x milliseconds and then calls repaint() on your custom component where you have overridden paintComponent() so that it draws the game state.
You're calling paint within the component's paintComponent, which will cause the component to keep repainting itself. This will cause a StackOverflowException. Also, the API admonishes a developer about explicitly invoking paint in an application:
Invoked by Swing to draw components.
Applications should not invoke paint
directly, but should instead use the
repaint method to schedule the
component for redrawing.
This method actually delegates the
work of painting to three protected
methods: paintComponent, paintBorder,
and paintChildren. They're called in
the order listed to ensure that
children appear on top of component
itself. Generally speaking, the
component and its children should not
paint in the insets area allocated to
the border. Subclasses can just
override this method, as always. A
subclass that just wants to specialize
the UI (look and feel) delegate's
paint method should just override
paintComponent.
You need to call g.dispose() every frame after you are done with it, otherwise it will never be released from memory and you get the stack overflow error you see. http://download.oracle.com/javase/1.3/docs/api/java/awt/Graphics.html