-
-
Save daveray/1021984 to your computer and use it in GitHub Desktop.
import java.awt.event.ComponentAdapter; | |
import java.awt.event.ComponentEvent; | |
import java.awt.event.HierarchyEvent; | |
import java.awt.event.HierarchyListener; | |
import javax.swing.JFrame; | |
import javax.swing.JLabel; | |
import javax.swing.JSplitPane; | |
import javax.swing.SwingUtilities; | |
public class JSplitPainInTheAss { | |
public static JSplitPane setDividerLocation(final JSplitPane splitter, | |
final double proportion) { | |
if (splitter.isShowing()) { | |
if(splitter.getWidth() > 0 && splitter.getHeight() > 0) { | |
splitter.setDividerLocation(proportion); | |
} | |
else { | |
splitter.addComponentListener(new ComponentAdapter() { | |
@Override | |
public void componentResized(ComponentEvent ce) { | |
splitter.removeComponentListener(this); | |
setDividerLocation(splitter, proportion); | |
} | |
}); | |
} | |
} | |
else { | |
splitter.addHierarchyListener(new HierarchyListener() { | |
@Override | |
public void hierarchyChanged(HierarchyEvent e) { | |
if((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0 && | |
splitter.isShowing()) { | |
splitter.removeHierarchyListener(this); | |
setDividerLocation(splitter, proportion); | |
} | |
} | |
}); | |
} | |
return splitter; | |
} | |
public static void main(String[] args) { | |
SwingUtilities.invokeLater(new Runnable() { | |
@Override | |
public void run() { | |
final JFrame frame = new JFrame("JSplitPainInTheAss"); | |
final JSplitPane splitter = new JSplitPane(JSplitPane.VERTICAL_SPLIT, new JLabel("TOP"), new JLabel("BOTTOM")); | |
setDividerLocation(splitter, 0.75); | |
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); | |
frame.setContentPane(splitter); | |
frame.setSize(800, 600); | |
frame.setVisible(true); | |
}}); | |
} | |
} |
That's true, but I usually don't know the top-level container, at least for larger apps, or in "declarative" contexts like Seesaw. If there was a callback for when a component becomes "displayable" that would be ideal, short of setDividerLocation just working as you'd expect in the first place :)
It would be interesting to see how many times the "invokeLater" actually runs before it is "effective". Almost like spin-waiting.
I agree that some way to set the divider location so that it takes effect at display time would be nice. Swing has lots of these "gotchas". Setting the cursor position in a JTextComponent subclass is also a similar problem. And "addComponentListener(...)" does not work on non-top-level containers because Swing does not generate an event when they are shown.
I've just started testing it on a much much larger example. As in all Swing workaround, I've already had to add some additional hacks (the width and height checks) :)
It appears to retry twice in this larger system, but I could imagine a case where it ends up spinning if the splitpane is created but not displayed soon. It seems to cover the common case so far though. I'll keep updating this gist as I learn more...
Actually, it turns out that a HierarchyChangeListener is notified when the splitter becomes displayable. That combined with a temporary component resize listener seems to also work without having to worry about spamming the UI thread with a lot of invokeLaters. I'll experiment some more and update as needed.
Cool! I'll have to see if that works for the JTextComponent subclasses. Check this out.
Yep. That works. In my larger test case, it doesn't work with the single event handler. In my case, the split pane is created one the fly and inserted dynamically into an already visible pane. In this case, the showing-changed event happens before the split pane is laid out in its parent so width and height are still zero. I have to listen for the following resize before setting the divider location. It's progress though :)
If you know the containing JFrame or JDialog at the time you want to set the divider location, I believe you can use "addComponentListener(...)" on the JFrame/JDialog and implement the "componentShown" method and have it set the divider location.