I have a fractal generation component (a subclass of JPanel) inside a JFrame. When the user resizes the window, it takes quite a while to update the fractal to the new size.
I currently have a ComponentListener on the JPanel, but its componentResized event is called every time the user moves the mouse while dragging the window border. This means that the fractal is told to resize many times, and slowly (over the course of a few minutes) grows to the new size.
Is there a way to be notified when the user releases the mouse button, so that I can only change the fractal size when the user has finished resizing?
Others have reported this happening when the listener is attached to the JFrame instead, but this doesn't work for me (and others), for some reason.
Instead of starting the calculation each time you receive a receive-event, you can only start the calculation after you received the last event by using a timer, e.g. in pseudo-code (or at least code which I typed here directly and not in my IDE)
private Timer recalculateTimer = new Timer( 20, myRecalculateActionListener );
constructor(){
recalculateTimer.setRepeats( false );
}
#Override
public void componentResized(ComponentEvent e){
if ( recalculateTimer.isRunning() ){
recalculateTimer.restart();
} else {
recalculateTimer.start();
}
}
And you can still combine this with Andrews suggestion to use an image which you stretch until the calculation has actually finished.
you have look at HierarchyListener, where you can listening for HierarchyEvent with interface to HierarchyBoundsListener
basically nothing wrong with ComponentListener, but you have to wrapping expected events to the Swing Timer, in the case that events repeated, only to call Timer#restart(), output from Swing Timer should be Swing Action
It's a bit late, but it looks like no one found the correct answer. I found that when you call child.updateUI() inside a ComponentListener (componentResized block) of a window, this child resizes it self and updates its content. Using timers is unsafe.
Related
I have a JScrollPane wrapped around a JPanel that contains potentially hundreds of JLabels that show thumbnail images (one thumbnail per JLabel). For memory reasons I don't want to build all the thumbnails. I want to build the thumbnails only for the JLabels that are visible and remove the thumbnails when their JLabels become not visible. They become visible/invisible when the user scrolls the JPanel. I tried to implement the loading/unloading the thumbnail by using the ComponentListener like this:
addComponentListener( new ComponentAdapter() {
#Override
public void componentShown( ComponentEvent e ) {
setIcon( new ImageIcon( getThumb() ) );
}
#Override
public void componentHidden( ComponentEvent e ) {
setIcon( null );
}
});
But this doesn't work. The JLabels are always empty. I could use the scroll event and calculate which thumbnails should be loaded but before I do that I would like to know if there is a simpler solution.
The "visible" property does not mean visible "on screen". It only indicates if the the component itself is to be displayed or not. Since components are visible by default and the listeners are only notified when a property changes your listener is never notified.
To the best of my knowledge there is no dedicated event involved telling a component when it enters the visible region of the display. Also note that setting an icon on a label may alter its preferred size, breaking the entire layout. This can be worked around by manually giving the labels a fixed preferred size (which should be simple in case of thumbnails).
A lazy approach would be to overwrite paintComponent on the labels and check if the thumb needs to be loaded in paintComponent:
protected void paintComponent(Graphics g) {
if (getIcon() == null) {
// create thumbnail
}
super.paintComponent(g);
}
This isn't the best approach, as your code will run inside Swings event dispatch thread. This means any delay in loading the thumbnail will block rendering of your UI.
A saner approach IMO would be to just request loading of the thumbnail and defer the actual loading to a background thread. When that thread completes loading, it can then use SwingUtilities.invoke (or invokeLater) to update the label (which triggers a repaint automatically if I'm not mistaken).
The effect would be that the labels scrolled in briefly show empty, then update as soon as the thumb is available.
I have done some digging around on this, however, I still can't seem to figure it out. Please excuse me, I haven't been programming for long.
Background: When I click on my run button it should create a second JFrame and update the background colours of JPanels on the second frame, periodically, once per iteration, throughout the run that the JButton starts.
Problem: The second frame is created, but stays blank until the loop, started by the JButton is finished, and it only displays the final state.
I have tried: invalidate(), validate(), repaint(), setVisible(true).
I have tried to run it in a separate thread.
I have even tried sleep(), in case it doesn't have enough time to update. Is there something else that I can try?
I think I would have overwritten the void paint(Graphics g) method which is called by the OS when redrawing is needed and add your drawing routine there. Don't forget the super.paint(g) call. You can then manually trigger redrawing (from inside your loop) by a call to void update (Graphics g); (calling void repaint() should work too)
The second frame is created, but stays blank until the loop, started by the JButton is finished, and it only displays the final state
If your ActionListener attached to the JButton (or the Action) is implemented like
public void actionPerformed( ActionEvent e ){
updateColor();
...
updateColor();
...
updateColor();
}
then the behavior you are seeing is exactly as expected.
Swing is a single threaded framework. When your ActionListener updates the background color, a repaint will be scheduled (emphasis on schedule, which is something different from performed). Since your loop in the ActionListener is still occupying the single thread (the EDT), the repaint cannot be executed.
As such, the first time the repaint can be executed is after you have released the EDT by finishing your loop. At that moment, the background color has already changed to its final color, and that is all what you will be seeing.
A possible solution to be able to see the background change is to use a javax.swing.Timer (and not the java.util version). A click on the JButton can start the timer, and each time the timer is triggered you change the background color to the next color. The moment the final color is reached, you stop the timer.
So I have this problem. I have a program that creates and load files.
When I load a file into my program, I rely on a component listener that tells me when is the specific component "full", and then moves all of the components according to that.
I add each component on the top, and then listener register when parent component is "filled" and moves the bottom compoennt to a new parent it also creates. Think of it as adding a new line of text to the begining of a houndred page MSWord document.
That listener also relys on the GUI setup - if everything in memory is not painted to the screen, it grabs wrong component heights (usually being 0) and uses them in it's calculations which then come out wrong, and everything gets messed up.
Here is a flow chart of that part of my program:
(everytning happens on a JPanel in a JFrame)
trigger opening method:
{
repeat this x (lets say e.g. 100) times:
{
trigger addComponent method
{
add component
{
adding component triggers the component listner 8if there is no more room in parent)
{
move all of the components one place down, move the ones out of bounds to next "page"
repaint and revalidate whole JFrame (inside listener)
}
}
repaint and revalidate whole JFrame (part of addComponent method)
}
repaint and revalidate whole JFrame(part of opening methid, after component addition)
}
repaint and revalidate whole JFrame (as a part of opening method, final repaint/validate)
}
The reason for this may repaint/validate is that addComponent method, as well as the listener has other functions and is called in other places where that is the only (optimal) place for repainting/validating.
The problem is that JFrame doesn't get repainted until the very last(final) validate/repaint call in opeoning method. I tried adding Thread.sleep(1000) to several places in the code (after validate/repaint) to proved this.
Also, as far a I know, when a component listener is triggered, it stops at the line which triggerd it, then executes itself, and then continues from that line, right?
How do I fix this? How do I force my program to repaint/validate after each new component was added ant then again after the listener did it's job?
Reply to the first two comments:
Firstly, Thread.sleep(1000) was just to diagnose the problem. Right before Thread.sleep(1000) method was repaint/validate method, so I thought if I pause the program right after reapint() was called, after every pause, GUI would be repainted ad I would see new element added, which was not the case.
Secondly, regarding to the lengthy calculations, those calculations aren't that long (executing this with 20 components gets momentary results, though not ones that are desired). Also, that calculations require removing and adding components to the GUI quite often (every 10-20 lines), so Incorporating SwingWorker in that is close to impossible, and not needed.
Thirdly, I think you missed the whole point. Length of execution is not the real problem here, nor the freezing of the GUI (whic doesn't really occurr, not long enough to be noticable, anyway). The problem is that repaint/validate was called in the loop for total of 3-4 times for every component (cca. 60-80 times if I open a file with 20 components), and the only time I saw it being executed is when the last time it was called, after the loop...
I even put System.out.println("something") method right before and right after the repaint/validate. It printed out "something" twice, but repaint/validate never happened.
I can see any issue with add / remove / modify JComponents in the container (JFrame --> JPanel e.i.)
depends if container (JPanel e.i.) is placed in JScrollPane
depends if you need to call pack() after add / remove / modify JComponents in the container, and then to change size of JFrame (for example) on the screen
(In my applicaton with Swing GUI) I want to display GlassPane during some work performed in a loop or method, which is called after clicking JButton.
For example:
(action performed after clicking a button)
if (item.equals(button)) {
glassPane.setVisible(true);
someTimeConsumingMethod();
glassPane.setVisible(false);
}
Running this code results in not showing the glassPane during execution of someTimeConsumingMethod() - GUI just freezes for a moment, before result is displayed. Removing last line in that loop (glassPane.setVisible(false);) results in showing glassPane after the method is done (when GUI unfreezes).
Is there a simple way to show that glassPane before GUI freezes, or I need to use some advanced knowledge here? (threads?)
UPDATE1:
I've updated my code according to davidXYZ answer (with two changes):
(action performed after clicking a button)
if (item.equals(button)) {
glassPane.setVisible(true);
new Thread(new Runnable(){
public void run(){
someTimeConsumingMethod(); // 1st change: running the someTimeConsumingMethod in new Thread
// instead of setting glassPane to visible
}
}).start();
// 2nd change: moved glassPane.setVisible(false); inside the someTimeConsumingMethod(); (placed at the end of it).
}
The point of 1st change is that setting glassPane visible in new thread right before running someTimeConsumingMethod in my GUI thread was revealing the glassPane after someTimeConsumingMethod finished (double-checked this).
Now it works fine, thank you for all answers. I will definitely check all the links you provided to actually understand threads!
UPDATE2:
Some more info: someTimeConsumingMethod(); in my application is prepering new Swing Components accoriding to the XML data (cards builded from JButtons and JLabels with few JPanels where needed, and adding them in correct places).
UPDATE3:
I am trying to make it work using SwingWorker's invokeLater method. Now it looks like that:
(action performed after clicking a button)
if (item.equals(button)) {
glassPane.setVisible(true);
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
someTimeConsumingMethod();
glassPane.setVisible(false);
}
});
}
It works not that good as code from UPDATE1 (but still - it works). Problems are:
glassPane loads without .gif animation (file is setted up in custom glassPane class - it works with UPDATE1 code)
there is small delay at the end of "working" process - first cursor changes to normal (from the WAIT_CURSOR), and after very short moment glassPane disappear.
Cursor is changed by the custom glassPane class on activation/deactivation (no delay using new Thread way).
Is it correct way of using SwingWorker's invokeLater method?
EDIT: My mistake, I confused SwingWorker with SwingUtilities.invokeLater(). I guess the image issue is due to GUI freezing when the someTimeCOnsumingMethod starts.
GUI just freezes for a moment, before result is displayed. Removing last line in that loop (glassPane.setVisible(false);) results in showing glassPane after the method is done (when GUI unfreezes).
this is common issue about Event Dispath Thread, when all events in EDT are flushed to the Swing GUI in one moment, then everything in the method if (item.equals(button)) { could be done on one moment,
but your description talking you have got issue with Concurency in Swing, some of code blocking EDT, this is small delay, for example Thread.sleep(int) can caused this issue, don't do that, or redirect code block to the Backgroung taks
Is there a simple way to show that glassPane before GUI freezes, or I need to use some advanced knowledge here? (threads?)
this question is booking example why SwingWorker is there, or easier way is Runnable#Thread
methods implemented in SwingWorker quite guarante that output will be done on EDT
any output from Runnable#Thread to the Swing GUI should be wrapped in invokeLater()
easiest steps from Jbuttons Action could be
show GlassPane
start background task from SwingWorker (be sure that listening by PropertyChangeListener) or invoke Runnable#Thread
in this moment ActionListener executions is done rest of code is redirected to the Backgroung taks
if task ended, then to hide GlassPane
create simple void by wrapping setVisible into invokeLater() for Runnable#Thread
in the case that you use SwingWorker then you can to hide the GlassPane on proper event from PropertyChangeListener or you can to use any (separate) void for hidding the GlassPane
best code for GlassPane by #camickr, or my question about based on this code
You are blocking the EDT (Event Dispatching Thread, the single thread where all UI events are handled) with your time consuming job.
2 solutions:
Wrap the calls to:someTimeConsumingMethod();glassPane.setVisible(false); in SwingUtilities.invokeLater(), this will allow the frame to repaint itself once more. However this will still freeze your GUI.
Move your someTimeConsumingMethod() into a SwingWorker (this is the recommended option). This will prevent your GUI from ever freezing.
Read the javadoc of SwingWorker to understand better what is going on and how to use it.
You may also learn a lot in this tutorial about Swing and multi-threading
JButton startB = new JButton("Start the big operation!");
startB.addActionListener(new ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent A) {
// manually control the 1.2/1.3 bug work-around
glass.setNeedToRedispatch(false);
glass.setVisible(true);
startTimer();
}
});
glasspane here used here is FixedGlassPane glass;
ref: http://www.java2s.com/Code/Java/Swing-JFC/Showhowaglasspanecanbeusedtoblockmouseandkeyevents.htm
Guillaume is right. When you are on the main thread, each line will finish before the next line. You definitely need another thread.
An easy way to solve your problem is to spin off the display of the glasspane in another thread (normal thread or Swing threads - either will work fine).
if (item.equals(button)) {
new Thread(new Runnable(){
public void run(){
glassPane.setVisible(true);
}
}).start();
someTimeConsumingMethod();
glassPane.setVisible(false);
}
That way, a different thread is blocked by setvisible(true) while someTimeConsumingMethod() runs on the main thread. When it's done, glasspane will disappear. The anonymous thread reaches the end of the run method and stops.
I have a simple GUI with a JTextField and an AWT Canvas (to prevent the counter-question as to why I'm using an AWT Canvas: I need to have a window handle).
The Canvas is to process input events, that means it must be focusable. I assure this by using setFocusable(true) in its constructor, later checks using isFocusable() confirm that it is indeed focusable.
Now, the JTextField gains the focus by default when the GUI opens. That's fine by me so far. However, there is no way to get the focus away from that JTextField.
The article "The AWT Focus Subsystem" clearly states that if a focusable component is being clicked on, it will gain the focus. This does not happen, in fact, I receive zero focus change events whatsoever, only if the window gets deactivated and activated again, but then the focus is right back to the JTextField.
Explicit invocations of requestFocus() and requestFocusInWindow() do not help either, the latter always returns false.
I have gotten the same results with any focusable component if I replace the JTextField. If the Canvas is the only focusable container, everything works fine because it will always have the focus.
Am I missing something here? Is there any way I can make my Canvas gain focus in the presence of another focusable component, preferably without making that one unfocusable?
basically in swing focus gained 1st. left(ToRight) JComponents on the top
in most completed GUI, and if there (together with creating JComponents) are added Listeners to the JComponents, then these Listeners (f.e. Document) can take focus...
but works for me on startUp:
last lines in something class about JComponets ..
myFrame.pack();
myFrame.setVisible(true);
Runnable doRun = new Runnable() {
public void run() {
myComponent.grabFocus();
myComponent.requestFocus();//or requestFocusInWindow
}
};
SwingUtilities.invokeLater(doRun);
Sorry for leaving some info out that turned out to be the root of the problem.
As mentioned, I'm using a heavyweight component so I have a window handle. I need one because it is passed to an OpenGL application in a native library, the AWT canvas is then used as a rendering canvas.
In Windows, Java uses the GWLP_USERDATA window field to store a pointer to an AWTComponent object. However, said OpenGL application overrides that field to store its own Window object pointer, which will of course break all AWT related functionality.
I solved this problem by creating a custom window message handler that delegates incoming messages to both the OpenGL application and Java's AWT part.