I have an application which depending on user input changes the entire content of the JFrame. I do this by removing the JPanel containing the current components and replace it by a new JPanel with new components.
After that one of the components needs to get focus and a JScrollpane should scroll to this component. In most cases this works properly.
Now one scenario leads to a JPanel beeing added, which itself contains more than 500 components. Rendering this takes some time and it seems that scrollRectToVisible() is called at a point, where the UI is not fully rendered. If I debug I can actually see that it first scrolls to the right position, but then further rendering is done and the component is moved out of the viewport again.
So I was trying find a Listener, which is called, when rendering is fully done. I tried with ComponentListener and AnchestorListener. Both didn't receive most of the events I was expecting. But even when they did the callback methods were called before any UI change was visible on the screen.
I swap the JPanels in EDT and call validate() on the JFrame afterwards. After that I do not process any further code. However, if I set a breakpoint in the last executed line and go one step further, the UI has not changed on screen. The EDT is actively doing something (I assume rendering the UI). And I would like to get notified, when the EDT has finished rendering.
Another thing I tried:
If I create another Thread that just sleeps for a few seconds (until the UI is definitely rendered) and call the scrollRectToVisible() then, everything works fine.
I'm sorry not to provide an SSCCE. I tried, but it seems to be rather complex. I really appreciate any idea on how I could get notified on the UI beeing fully rendered and visible to the user.
Thanks
If I create another Thread that just sleeps for a few seconds (until the UI is definitely rendered) and call the scrollRectToVisible() then, everything works fine.
Instead of sleeping, just wrap the scrollRectToVisibl() code in a SwingUtilities.invokeLater(). This will add the code to the end of the EDT so it should be processed after all the other rendering.
Add a property change listener:
jPanel.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(final PropertyChangeEvent evt) {
System.out.println(evt);
}
});
Produces something like:
java.beans.PropertyChangeEvent[propertyName=ancestor; oldValue=null; newValue=javax.swing.JPanel[null.contentPane,0,0,0x0,invalid,layout=javax.swing.JRootPane$1,alignmentX=0.0,alignmentY=0.0,border=,flags=9,maximumSize=,minimumSize=,preferredSize=]; propagationId=null; source=javax.swing.JPanel[,0,0,0x0,invalid,layout=java.awt.FlowLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=9,maximumSize=,minimumSize=,preferredSize=]]
Maybe JComponent.addNotify() is what you need.
If you override that method, make sure that you call the super.addNotifty() as well!
Related
My Java application working flow:
get a input keyword, then translate it to a query to HTTP POST
new a Jframe, witch shows some words like "keyword processing... pls waiting."
do the POST in [1], it may take many minutes.
after POST returns a result, extract the data I need.
show the data in the Jframe, which replaces the content in [2]
I first try to code the flow logic in Jframe's constructor.
But the frame will hang on the HTTP POST stage, and doesn't show the words in [2].
After POST done, it shows the data in [5] directly.
Cause jumping the stage [2], users may think the process blocking means the something wrong, it does not do the search work.
Now I add a JOptionPane.showMessageDialog in stage[2]. It stops the process because needing a mouse click on "OK" Button in the dialog.
In that moment, the panels in the frame shows the words, "keyword processing... pls waiting.".
I'd like to know why the stop point of the MessageDialog can paint the old panel, which means the words in [2].
Such as revalidate() or repaint() do not paint the old panel.
Don't block the EDT (Event Dispatch Thread). The GUI will 'freeze' when that happens. See Concurrency in Swing for details and the fix (a SwingWorker).
BTW - Use a CardLayout as shown in this answer for changing panels.
I had this question while setting a JLabel visible when a button is clicked it is like a loading icon. The p.make() method is executed but the Label is still invisible after the Method returns the Label is visible.
Can someone explain what is happening?
ActionPerformed:
String[] args = {jTextFieldDrgzusatzVariable.getText(),jTextFieldAusgabe.getText(),"C:\\CPOracle",jTextFieldKatalog.getText()};
this.jLblLoading.setVisible(true);
if(jLblLoading.isVisible()){
try{
new P21Make(args[0],args[1],args[2],args[3]).make();
}catch(Exception e){
e.printStackTrace();
}
}
The reason is very simple: Swing is single threaded (see the Swing concurrency tutorial for more information).
What happens is that the actionPerformed method is called on the Swing thread (the E(vent)D(ispatch)T(hread)). When the
this.jLblLoading.setVisible(true);
statement is reached, it will immediately mark the jLblLoading as visible. However, this has no effect yet on the UI. The UI needs to be repainted before the change in visibility has any effect. This repaint is scheduled on the EDT (which is not the same as immediately executed).
This explains why your
if(jLblLoading.isVisible()){
check succeeds, and you still do not see the difference in the UI. The component is marked as visible, but the repaint is still pending. The repaint will remain pending until the EDT becomes available again. Since the thing that is currently occupying the EDT is your actionPerformed call, the rest of the code in that actionPerformed method will be executed before the repaint (meaning before you see a change in the UI).
Your solution using a different thread can indeed fix this. You can however only use that if the new P21Make(...).make() does not affect the UI. If that statement interacts with Swing components in any way, it should be executed on the EDT. In that case, an alternative is to wrap the statement in a SwingUtilities#invokeLater call.
You should probably look at using SwingUtilities.invokeLater to allow actions which modify the gui to complete.
http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
I have action thread and since it is Swing software, EDT.
I want my program to draw dialog window, and when it appears and it's filled with data, I want to get focus on selected text field.
Code flow: When I execute, it will run main thread, which calls method to draw dialog in invokeLater on EDT. Then program proceeds and in main thread it calls next methods that are being run in ED thread, again using invokeLater.
Problem: When I run it normally, it will not get focus on my text field.
Observation: But when I add some sleep (300 milis) to main thread, introducing time gap between one invokeLater call and next call in EDT, it works just like I want.
It seems to me like two actions added to AWT queue must be separated by some time, otherwise the second one doesn't work. I mean here setVisible(true) on dialog, and then requestFocus() on textField. Maybe requestFocus() only work when it sees dialog window drawn?
Question:How can I make things work, some synchronization method, maybe checking on dialog before calling requestFocus() (may be hard, because its in other class).
Solution:I forgot about most important thing - after calling setVisible() next thing I do is call to setEnabled(false) so user cannot do anything before data filling is completed. The problem was there, in setEnabled() I also was adding tasks to AWT queue (by invokeLater()). This task caused corruption of next steps. What I do now to fix it is calling this setEnabled(false) from my main thread inside invokeAndWait(). If I understand it correctly, now the dialog popup section is called first, and then main thread waits until EDT proceed his work and then setEnabled(false) is called. So technically user is not enabled to do anything after the window is drawn, which makes sense for me.
Anyway thanks for your responses.
It's better to call the focus setting from the dialog. Add a WindowListener to the dialog and use either
public void windowOpened(WindowEvent e)
public void windowActivated(WindowEvent e);
to set focus on the JTextField instance
The requestFocusInWindow() method can only be invoked on a visible component. That means the frame/dialog must already be visible when you invoke the method.
If you are trying to do this on a modal dialog you may have problems. Check out Dialog Focus for a simple listener you can use to set focus on a component.
For my program I have a JPane that as the game progresses it adds labels to the panel, however the only way I can make the panels show up is by using add(label) then revalidating and vice versa for removing labels.
My problem is that once it gets to the point that I have more than 40 labels on the screen the revalidate has to process too many things, so how can I override the revalidate(), ether works, so that it only revalidates the specific component that was added, and not every component on the screen. I know there is a loop somewhere within the revalidate() method that will run through a loop of all components, but I just can not for the life of me find it. I would like to be able to call revalidate(component-here) or validate (component-here) and have it only update that specific component.
I know there are other ways of writing this program but I am only interested in how to override revalidate() so no "you could have redone your whole code this way which should only take you like 6 hours >.<".
http://www.fileserve.com/file/jFdQ6nv/FINAL_PROJECT.zip a link to my eclipse project, if anyone who wants to help would like to see what im actually talking abouyt
I just tried this example. Adding 1000 text areas only takes a second, and updates are instant. Labels go even faster. You might want to look at something else slowing it down.
Also, you could look at CellRendererPane. It overrides invalidate() to do nothing.
public void invalidate() {}
Apologies for the somewhat unclear question - couldn't think of a better way of putting it.
I use a JXTaskPane (from the Swing labs extension API) to display some information.
The user can "click" the title to expand the panel. The JXTaskPane is in a container JPanel, which is then added to a JFrame, my main application window.
I want my application window to resize to the size of the expanded task pane. To achieve this, I added a component listener to my container JPanel which would set size to the now expanded panel.
panel.addComponentListener(new ComponentListener()
{
public void componentResized(ComponentEvent e)
{
Dimension newSize = ((JXTaskPane)e.getSource()).getSize();
reSizeFrame(newSize);
}
}
private void reSizeFrame(Dimension newSize)
{
if ((newSize.height < maxSize.height) && (newSize.width < maxSize.width))
{
containerPanel.setSize(newSize);
appFrame.setSize(containerPanel.getSize());
appFrame.pack();
}
}
The problem is that the componentResized method is called as the task pane expands, as a result the resizeFrame method is called lots of times, and looks really awful on the screen.
How can I detect when the JXTaskpane has finished resizing? I thought of two approaches:
Put the resizeFrame() method in a SwingUtilities.invokeLate(..) call.
Put in a timer resizeFrame call, so any subsequent calls do not do anything until the timer fires. This should give enough time for the panel to resize.
What is the best way forward?
Also - This is my first serious Java GUI app after years of server side program. StackOverflow has been very helpful. So thanks!
I know you've already selected an answer, but overriding the paint method is definitely not correct, and while you may be able to hack something in place, it won't be ideal.
Looking at the source for JXTaskPane and specifically looking in setExpanded() (line 387), you can see it calls JXCollapsiblePane.setCollapsed(...) and then fires a property change event for expanded. A listener on that property won't be correct, because it'll fire before the animation is complete. So, if you go into JXCollapsiblePane and look at setCollapsed(...) (line 470) you'll see that if it's animated, it sets the paramaters and starts a timer. We want to know when the animation ends, so in that file, look at the animator (line 620, and specifically 652-667), which shows that when the animation ends, it fires a property change for ANIMATION_STATE_KEY with a value of "collapsed" or "expanded". This is the event you actually want. However, you don't have access to JXCollapsiblePane, so go back to JXTaskPane and search for ANIMATION_STATE_KEY, and you find line 208, which shows that JXTaskPane creates a listener on JXCollapsiblePane.ANIMATION_STATE_KEY and refires it as it's own event.
Since you do have access to JXTaskPane, you can listen for that event, so doing ...
taskPane.addPropertyChangeListener(JXCollapsiblePane.ANIMATION_STATE_KEY, new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent e) {
if(e.getNewValue().equals("expanded") {
...
}
else if(e.getNewValue().equals("collapsed") {
...
}
}
}
should get your event exactly when you want it.
The correct way to listen for events in Swing is through property listeners. Unfortunately, the only way to find out what the correct properties and values are is by digging through source code.
As a suggestion, have you tried overriding the paint method, first calling super and then putting your resize code at the end of that if (and only if) the size has changed significantly.
I'm not familiar with JXTaskPane, but my first reaction is that maybe you're handling the wrong event. You want the frame to resize when the user clicks on the header - so why not handle that event (perhaps using EventQueue.invokeLater() to resize the frame after the task pane has been resized)?
But if that doesn't work and you need to use the approach you've outlined above, using a javax.swing.Timer is probably best. Set it for 200 milliseconds or so and just restart() it every time componentResized() fires.