I want to draw a string in a runnable, then a few seconds later, add another word to that string, and repeat. The runnable works but it won't drawString?
I've made a mini-application with only what I want if anyone would need to compile and try it.
Here it is:
package timer.problem.example;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JFrame;
public class TimerProblemExample extends JFrame {
private static final long serialVersionUID = 1L;
public TimerProblemExample() {
setBackground(Color.black);
setVisible(true);
setTitle("Timer Problem Example");
setSize(950, 600);
setDefaultCloseOperation(3);
}
public static void main(String[] args) {
new TimerProblemExample();
}
public void timer(final Graphics graphics) {
new Timer().schedule(new TimerTask() {
int update = 6;
public void run() {
update--;
graphics.setFont(new Font("Splash", Font.PLAIN, 60));
graphics.setColor(Color.white);
if (update == 5) {
graphics.drawString("Test", 50, 130);
repaint();
}
if (update == 3) {
graphics.drawString("Test Test", 50, 100);
repaint();
}
if (update == 1) {
graphics.drawString("Test Test Test", 50, 50);
repaint();
}
}
}, 0L, 1000L);
}
#Override
public void paint(Graphics graphics) {
super.paint(graphics);
timer(graphics);
}
}
Please help with this, thanks.
There are a couple of issues with your sample code.
Every time you call repaint, a new timer is created. You have so many running (and so often) that each one keeps repainting over the last faster than the UI can update. You can test this by adding System.out.println("Timer thread: "+ Thread.currentThread().getId()); right before update--;. Addidtionally, any changes to the frame (changing it's size by dragging with the mouse, for example) also call paint(), which leads to more timers being spawned.
You really shouldn't call setVisible(true); until you've set the properties of the JFrame(size, title etc.). It's not a problem right now, but if you intend to add components to it, it'll be an issue.
I've amended your code and this works as per your need.
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JFrame;
public class TimerProblemExample extends JFrame {
private static final long serialVersionUID = 1L;
public TimerProblemExample() {
setBackground(Color.black);
setTitle("Timer Problem Example");
setSize(950, 600);
setDefaultCloseOperation(3);
setVisible(true);
timer(getGraphics()); //call the timer from here, so there's only one running
}
public static void main(String[] args) {
new TimerProblemExample();
}
public void timer(final Graphics graphics) {
new Timer().schedule(new TimerTask() {
int update = 6;
public void run() {
update--;
System.out.println("Timer thread: "+ Thread.currentThread().getId());
graphics.setFont(new Font("Splash", Font.PLAIN, 60));
graphics.setColor(Color.white);
if (update == 5) {
graphics.drawString("Test", 50, 130);
}
if (update == 3) {
graphics.drawString("Test Test", 50, 100);
}
if (update == 1) {
graphics.drawString("Test Test Test", 50, 50);
}
//Added this section to keep the update variable looping back
if(update<=0)
{
update = 6;
}
}
}, 1000L, 1000L); //0 Delay for start of timer was spawning lot's of threads in the first second. Slowed it down. Delay isn't needed anymore, but it's useful to have it to see the drawString methods being executed.
}
}
Lastly, it's a good idea not to draw anything directly on the Frame. Add a JLabel to the frame and put the text in that.
Related
Is there a way to create a layered system of components in Java so that at one moment only one layer will be updated (repainted)?
The simplest example would be a large map, with thousands of shapes and labels that will be placed on a "bottom" layer. And on the "top" layer you have another small shape, a custom tooltip would be a good example for this scenario. Now, whenever the tooltip's position is getting updated (by mouse move), even if you specifically call repaint() on that component only, the whole frame is being updated (thus a repaint is called on each layer, making the whole system pretty slow and ineffective).
I know you can achieve some optimization by caching contents as images and paint images instead of thousands of shapes, but, still, I'm interested in a "simpler" solution.
Thank you.
Sample code:
Basically there is a main frame (m) which loads a container (mc) and this container has two "layers" (ml). Now, when I call repaint() just for the 2nd layer, it's easy to see all paintComponent() are called again (console text appears but also BOTH rectangles change color to a new random one). And that is what I need to prevent.
File: m.java
import javax.swing.JFrame;
import java.awt.EventQueue;
import java.util.Timer;
import java.util.TimerTask;
public class m extends JFrame {
public mc omc;
public m() {
initUI();
}
private void initUI() {
this.pack();
this.setSize(600, 680);
this.setResizable(true);
this.setLocationRelativeTo(null);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
this.omc = new mc();
this.add(omc);
m self = this;
Timer oTimer = new Timer();
oTimer.schedule(new TimerTask() {
#Override
public void run() {
self.omc.oml1.repaint();
}
}, 2000);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
m ex = new m();
ex.setVisible(true);
}
});
}
}
File: mc.java
import javax.swing.JPanel;
import javax.swing.OverlayLayout;
import java.awt.Graphics;
public class mc extends JPanel {
public ml oml0;
public ml oml1;
public mc() {
this.setLayout(new OverlayLayout(this));
this.oml0 = new ml(0);
this.oml1 = new ml(1);
this.add(oml0);
this.add(oml1);
}
public void paintComponent(Graphics g) {
System.out.println("mc");
super.paintComponent(g);
}
}
File: ml.java
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
public class ml extends JPanel {
private int l = 0;
public ml(int ll) {
this.l = ll;
this.setOpaque(false);
}
public void paintComponent(Graphics g) {
System.out.println("ml:" + this.l);
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
if (this.l == 0) {
g2d.setPaint(new Color((int)Math.round(100 + 150 * Math.random()), (int)Math.round(100 + 150 * Math.random()), (int)Math.round(100 + 150 * Math.random())));
g2d.fillRect(30, 30, 300, 100);
}
if (this.l == 1) {
g2d.setPaint(new Color((int)Math.round(100 + 150 * Math.random()), (int)Math.round(100 + 150 * Math.random()), (int)Math.round(100 + 150 * Math.random())));
g2d.fillRect(130, 80, 300, 100);
}
}
}
I've made a JFrame with a canvas on it and I want to draw on that canvas. At a later date the canvas will be updating many times a second so I am using a buffer strategy for this. Here is the code:
package mainPackage;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import javax.swing.JFrame;
public class TickPainter {
//just some presets for a window.
public static JFrame makeWindow(String title, int width, int height) {
JFrame mainWindow = new JFrame();
mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainWindow.setSize(width, height);
mainWindow.setVisible(true);
mainWindow.setLocationRelativeTo(null);
mainWindow.setTitle(title);
return mainWindow;
}
public static void main(String[] args) {
JFrame mainWindow = makeWindow("Practice", 800, 600);
Canvas mainCanvas = new Canvas();
mainWindow.add(mainCanvas);
mainCanvas.setSize(mainWindow.getWidth(), mainWindow.getHeight());
mainCanvas.setBackground(Color.white);
mainCanvas.createBufferStrategy(3);
BufferStrategy bufferStrat = mainCanvas.getBufferStrategy();
Graphics g = bufferStrat.getDrawGraphics();
g.setColor(Color.black);
g.fillRect(250, 250, 250, 250);
g.dispose();
bufferStrat.show();
}
}
The program does not draw the black rectangle as intended, I feel like I've missed something really obvious here and I just can't see it. At the moment the program just makes a blank white canvas. I feel like part of the issue is that the buffer is just passing the frame with the rectangle faster than I can see, but there is no frame to load after that so I don't know why it's doing this.
A BufferStrategy has a number of initial requirements which must be meet before it can be rendered to. Also, because of the nature of how it works, you might need to repeat a paint phases a number of times before it's actually accepted by the hardware layer.
I recommend going through the JavaDocs and tutorial, they provide invaluable examples into how you're suppose to use a BufferStrategy
The following example uses a Canvas as the base component and sets up a rendering loop within a custom Thread. It's very basic, but shows the basic concepts you'd need to implement...
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
TestCanvas canvas = new TestCanvas();
JFrame frame = new JFrame();
frame.add(canvas);
frame.setTitle("Test");
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
canvas.start();
}
});
}
public class TestCanvas extends Canvas {
private Thread thread;
private AtomicBoolean keepRendering = new AtomicBoolean(true);
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
public void stop() {
if (thread != null) {
keepRendering.set(false);
try {
thread.join();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
public void start() {
if (thread != null) {
stop();
}
keepRendering.set(true);
thread = new Thread(new Runnable() {
#Override
public void run() {
createBufferStrategy(3);
do {
BufferStrategy bs = getBufferStrategy();
while (bs == null) {
System.out.println("get buffer");
bs = getBufferStrategy();
}
do {
// The following loop ensures that the contents of the drawing buffer
// are consistent in case the underlying surface was recreated
do {
// Get a new graphics context every time through the loop
// to make sure the strategy is validated
System.out.println("draw");
Graphics graphics = bs.getDrawGraphics();
// Render to graphics
// ...
graphics.setColor(Color.RED);
graphics.fillRect(0, 0, 100, 100);
// Dispose the graphics
graphics.dispose();
// Repeat the rendering if the drawing buffer contents
// were restored
} while (bs.contentsRestored());
System.out.println("show");
// Display the buffer
bs.show();
// Repeat the rendering if the drawing buffer was lost
} while (bs.contentsLost());
System.out.println("done");
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
} while (keepRendering.get());
}
});
thread.start();
}
}
}
Remember, the point of BufferStrategy is to give you full control over the painting process, so it works outside the normal painting process generally implemented by AWT and Swing
"At a later date the canvas will be updating many times a second so I am using a buffer strategy for this" - Before going down the "direct to hardware" solution, I'd consider using a Swing Timer and the normal painting process to see how well it works
I am working on a simple 2D game. Each tick, I want to check an effects queue that will start a thread for a certain effect(fading transitions, audio fade in and out, etc). For example, pressing "Play" on the menu screen will add a "FadeOut" message to this queue, which will be processed and start a thread to draw a black rectangle with an increasing alpha value over my GamePanel.
I'm overriding paintComponent() and sending my Graphics object to my GameStateManager, which passes along the Graphics object to the current states' draw(). I currently don't have an effects state (which maybe I should) to route the paintComponent() graphics object to, but I do pass my gamepanel to my effects thread, where I can use getGraphics() to draw on it. Drawing a rectangle to the GamePanel directly just causes flickering, as the gameloop is still rendering the game.
I found I can draw a black rectangle with increasing alpha to a BufferedImage, set the composite to AlphaComposite.Src (which causes the new draw to replace the old) then draw the BufferedImage over the game panel. The problem is the BufferedImages drawn to the game panel don't get overridden each draw, so the fade out happens really quickly because these black BufferedImages of various alphas just stack on each other.
I wrote this short program to test composite settings and see what is getting overridden. All drawing is done in the draw(), which would be my run() in the effects thread.
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class ScratchPad extends JPanel implements Runnable
{
private JFrame oFrame = null;
private Thread oGameThread = null;
private Graphics2D oPanelGraphics = null;
private Graphics2D oImageGraphics = null;
private BufferedImage oImage = null;
public static void main(String args[]) throws Exception
{
new ScratchPad();
}
public ScratchPad()
{
createFrame();
initPanel();
addAndShowComponents();
oGameThread = new Thread(this, "Game_Loop");
oGameThread.start();
}
private void addAndShowComponents()
{
oFrame.add(this);
oFrame.setVisible(true);
}
private void initPanel()
{
this.setOpaque(true);
this.setBackground(Color.cyan);
}
private void createFrame()
{
oFrame = new JFrame("Fade");
oFrame.setSize(700, 300);
oFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
oFrame.setLocationRelativeTo(null);
}
public void run()
{
oImage = new BufferedImage(200, 200, BufferedImage.TYPE_INT_ARGB);
while(true)
{
try
{
draw();
Thread.sleep(100);
}
catch(InterruptedException e)
{
}
}
}
private void draw()
{
oPanelGraphics = (Graphics2D)this.getGraphics();
oImageGraphics = oImage.createGraphics();
oImageGraphics.setComposite(AlphaComposite.Src);
oImageGraphics.setColor(new Color(0,0,0,90));
oImageGraphics.fillRect(0, 0, oImage.getWidth(), oImage.getHeight());
oPanelGraphics.drawImage(oImage, 10, 10, null);
oImageGraphics.setColor(new Color(0,0,0,60));
oImageGraphics.fillRect(0, 0, oImage.getWidth(), oImage.getHeight());
oPanelGraphics.drawImage(oImage, 220, 10, null);
oImageGraphics.setColor(new Color(0,0,0,30));
oImageGraphics.fillRect(0, 0, oImage.getWidth(), oImage.getHeight());
oPanelGraphics.drawImage(oImage, 430, 10, null);
// Drawing this image over location of first image, should overwrite first
// after setting composite to 'Src'
oPanelGraphics.setComposite(AlphaComposite.Src);
oImageGraphics.setColor(new Color(0,0,0,10));
oImageGraphics.fillRect(0, 0, oImage.getWidth(), oImage.getHeight());
oPanelGraphics.drawImage(oImage, 10, 10, null);
oImageGraphics.dispose();
oPanelGraphics.dispose();
}
} // end class
What's interesting is setting the composite on 'oPanelGraphics' causes any alpha to the BufferedImage to go away, resulting in a fully opaque black image being drawn over the image that was previously there. Even setting the color to something other than black doesn't have an effect.
What's also interesting is setting the composite for the BufferedImage to:
oImageGraphics.setComposite(AlphaComposite.SrcIn);
causes nothing to be shown. The Oracle documentation on compositing graphics in Java2D states this for 'SrcIn':
"If pixels in the source and the destination overlap, only the source pixels in the overlapping area are rendered."
So, I would expect this to have the same behavior I get with AlphaComposite.Src.
Maybe someone out there can shed some light on whats going on with these composites, and how I could achieve my desired effect.
There are a number issues with what you "seem" to be trying to do
Don't call getGraphics on a component. This can return null and only returns a snapshot of what was last painted during a Swing paint cycle. Anything you paint to it will be erased on the next paint cycle
You should also never dispose of Graphics context you did not create, doing so could effect other components that are painted by Swing
Painting is compounding, this means that painting to the same Graphics context (or BufferedImage) over and over again, will continue to apply those changes over the top of what was previously painted
You also don't seem to have a concept of how animation should work. Instead of trying to paint your fade effect in a single pass, where the results can't be applied to the screen, you need to apply a phase on each cycle and allow that to be updated to the screen before the next pass runs.
The following is a really basic example of what I'm talking about. It takes a "base" image (this could be the "base" state of the game, but I've used a static image) and the paints effects over the top.
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.imageio.ImageIO;
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();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private Engine engine;
private Image frame;
public TestPane() {
engine = new Engine();
engine.setEngineListener(new EngineListener() {
#Override
public void updateDidOccur(Image img) {
frame = img;
repaint();
}
});
engine.start();
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
engine.addEffect(new FadeOutEffect(Color.BLACK));
}
});
}
#Override
public Dimension getPreferredSize() {
return engine.getSize();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (frame != null) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.drawImage(frame, 0, 0, null);
g2d.dispose();
}
}
}
public interface EngineListener {
public void updateDidOccur(Image img);
}
public class Engine {
// This is the "base" image, without effects
private BufferedImage base;
private Timer timer;
private EngineListener listener;
private List<Effect> effects = new ArrayList<Effect>(25);
public Engine() {
try {
base = ImageIO.read(new File("/Volumes/Big Fat Extension/Dropbox/MegaTokyo/megatokyo_omnibus_1_3_cover_by_fredrin-d4oupef 50%.jpg"));
} catch (IOException ex) {
ex.printStackTrace();
}
timer = new Timer(10, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
int width = base.getWidth();
int height = base.getHeight();
BufferedImage frame = new BufferedImage(width, height, base.getType());
Graphics2D g2d = frame.createGraphics();
g2d.drawImage(base, 0, 0, null);
Iterator<Effect> it = effects.iterator();
while (it.hasNext()) {
Effect effect = it.next();
if (!effect.applyEffect(g2d, width, height)) {
it.remove();
}
}
g2d.dispose();
if (listener != null) {
listener.updateDidOccur(frame);
}
}
});
}
public void start() {
timer.start();
}
public void stop() {
timer.stop();
}
public void addEffect(Effect effect) {
effects.add(effect);
}
public void setEngineListener(EngineListener listener) {
this.listener = listener;
}
public Dimension getSize() {
return base == null ? new Dimension(200, 200) : new Dimension(base.getWidth(), base.getHeight());
}
}
public interface Effect {
public boolean applyEffect(Graphics2D context, int width, int height);
}
public class FadeOutEffect implements Effect {
private int tick = 0;
private Color fadeToColor;
public FadeOutEffect(Color fadeToColor) {
this.fadeToColor = fadeToColor;
}
#Override
public boolean applyEffect(Graphics2D context, int width, int height) {
tick++;
float alpha = (float) tick / 100.0f;
if (alpha > 1.0) {
return false;
}
Graphics2D g2d = (Graphics2D) context.create();
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
g2d.setColor(fadeToColor);
g2d.fillRect(0, 0, width, height);
g2d.dispose();
return true;
}
}
}
Remember, every effect or change should be applied within the same "main loop", this means you shouldn't have multiple threads, in fact, since Swing is not thread safe, you should avoid having any additional threads if possible. This example make use of a Swing Timer to act as the "main loop" because the ActionListers actionPerformed method is called within the context of the EDT, making it safe to update the UI from. It also provides a simple synchronisation method, as the UI can't be painted while the actionPerformed method is been called
All the other questions I found seem to be related to "flicker-free" whereas I actually want a flicker...
I am trying to build a small app that shows objects blinking at a given frequency.
Below is a small workable example of what I am trying to do. Currently I flicker the object via a scheduled Task that is executed with a given frequency. Within this task I paint the object, wait for half the time and then delete it. That should give me a object that is blinking with the given frequency.
However I found that even at frequencies as low as 4Hz the blinking isn't stable anymore. I think this is due to the fact that Java does not guarantee to paint the screen when asked to but that it is allowed to pull certain paint tasks together into one. I am not sure but I think I read something like this.
My question is: How can I make a stably blinking object with frequencies up to the half the screen refreshing rate?
public class NewJFrame extends javax.swing.JFrame {
private final int FlashFreqHz = 4;
private java.util.Timer timer;
/**
* Creates new form NewJFrame
*/
public NewJFrame() {
initComponents();
startFlashing();
}
private class FlashingTask extends TimerTask{
#Override
public void run() {
int w=jPanel1.getWidth();
int h=jPanel1.getHeight();
try {
jPanel1.getGraphics().fillRect(10, 10, w/2, h/2);
Thread.sleep((1000/FlashFreqHz)/2);
jPanel1.getGraphics().clearRect(10, 10, w/2, h/2);
} catch(InterruptedException ex) {
ex.getStackTrace();
}
}
}
public void startFlashing() {
if(timer!=null) stopFlashing(); // running, must stop to get new position correct
timer = new java.util.Timer();
timer.scheduleAtFixedRate(new FlashingTask(), 0, 1000/FlashFreqHz); // 40 ms delay
}
public void stopFlashing() {
if (timer != null) {
timer.cancel();
timer = null;
jPanel1.repaint();
}
}
Just to check, here is my 'corrected' code:
import java.awt.event.ActionEvent;
import javax.swing.Timer;
import java.awt.event.ActionListener;
import javax.swing.JPanel;
import java.awt.Graphics;
public class NewJFrame extends javax.swing.JFrame implements ActionListener{
private final int FlashFreqHz = 60;
private final Timer timer;
private boolean Flash = false;
FlashFrame FlashPanel;
/**
* Creates new form NewJFrame
*/
public NewJFrame() {
initComponents();
FlashPanel = new FlashFrame();
setContentPane(FlashPanel);
timer = new Timer(1000/FlashFreqHz,this);
timer.start();
}
class FlashFrame extends JPanel {
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
if(Flash) {
g.fillRect(10, 10, getWidth()/2, getHeight()/2);
} else {
g.clearRect(10, 10, getWidth()/2, getHeight()/2);
}
}
}
#Override
public void actionPerformed(ActionEvent e) {
FlashPanel.repaint();
Flash = !Flash;
}
Does this look any better to you?
Also the question I began with remains. Although the frequency is much much more stable then with my 'sleep' method there still is some notable instability, especially at higher frequencies.
Is there anything that can be done about that?
I'm trying to got a tooltip which displays the current progress of a task. So I want that the tooltip text change while the tooltip is displayed. But, when I call setToolTipText() the displayed text remains the same until I exit the mouse from the tooltip component and enter again. And call setToolTipText(null) before doesn't change anything.
Indeed it does not update itself, even when resetting the tooltip to null between calls.
So far, the only trick I found was to simulate a mouse-move event and forward it on the TooltipManager. It makes him think that the mouse has moved and that the tooltip must be relocated. Not pretty, but quite efficient.
Have a look at this demo code which displays a progress in % from 0 to 100:
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.ToolTipManager;
public class TestTooltips {
protected static void initUI() {
JFrame frame = new JFrame("test");
final JLabel label = new JLabel("Label text");
frame.add(label);
frame.pack();
frame.setVisible(true);
Timer t = new Timer(1000, new ActionListener() {
int progress = 0;
#Override
public void actionPerformed(ActionEvent e) {
if (progress > 100) {
progress = 0;
}
label.setToolTipText("Progress: " + progress + " %");
Point locationOnScreen = MouseInfo.getPointerInfo().getLocation();
Point locationOnComponent = new Point(locationOnScreen);
SwingUtilities.convertPointFromScreen(locationOnComponent, label);
if (label.contains(locationOnComponent)) {
ToolTipManager.sharedInstance().mouseMoved(
new MouseEvent(label, -1, System.currentTimeMillis(), 0, locationOnComponent.x, locationOnComponent.y,
locationOnScreen.x, locationOnScreen.y, 0, false, 0));
}
progress++;
}
});
t.start();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
initUI();
}
});
}
}
Here's a simplified version of Guillaume Polet's answer which is self-contained in a single method. This code assumes one has called component.setToolTip("..."); previously. This code does not show how to periodically update the tooltip to show the progress.
public static void showToolTip(JComponent component)
{
ToolTipManager manager;
MouseEvent event;
Point point;
String message;
JComponent component;
long time;
manager = ToolTipManager.sharedInstance();
time = System.currentTimeMillis() - manager.getInitialDelay() + 1; // So that the tooltip will trigger immediately
point = component.getLocationOnScreen();
event = new MouseEvent(component, -1, time, 0, 0, 0, point.x, point.y, 1, false, 0);
ToolTipManager.
sharedInstance().
mouseMoved(event);
}