swing: BoxLayout and transverse alignment? - swing

I have a stumper which doesn't seem to make sense.
If I run the program below, I get two dialog boxes each containing JPanels.
The first one has a couple of JLabels that are left-aligned. The second one extends the first and adds another JPanel as a subpanel, but the group of labels is right-aligned (even though within the group they are left aligned). What gives, and how do I fix it so that everything is left-aligned? (Or how would I right-align them all?)
package com.example.test.gui;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
public class BoxLayoutQuestion {
public static class TestPanel1 extends JPanel
{
public TestPanel1()
{
super();
initTestPanel1();
}
void initTestPanel1()
{
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
add(new JLabel("Four score and seven years ago"));
add(new JLabel("One small step for man"));
add(new JLabel("To be or not to be, that is the question"));
}
}
public static class TestPanel2 extends TestPanel1
{
public TestPanel2()
{
super();
initTestPanel2();
}
void initTestPanel2()
{
JPanel subpanel = new JPanel();
subpanel.setBorder(
BorderFactory.createTitledBorder("something special"));
subpanel.add(new JLabel("Where's the beef?"));
add(subpanel);
}
}
public static void main(String[] args) {
JOptionPane.showMessageDialog(null, new TestPanel1(),
"quotations", JOptionPane.INFORMATION_MESSAGE);
JOptionPane.showMessageDialog(null, new TestPanel2(),
"more quotations", JOptionPane.INFORMATION_MESSAGE);
}
}

I've found box layout to be very sensitive to the alignment of the components it contains.
You probably need to set the component alignmentX for the components you are adding to the dialog.
If you call setAlignmentX(LEFT_ALIGNMENT) on each component before you add them you should get consistent left aligned behaviour.

Related

Switching between multiple JPanels within a JFrame

http://imgur.com/VT3JHH8
This is what I am trying to create essentially to start my menu based game. I am wondering if my approach is good. I have created a frame class "SonomaRoller" shown below. In that class it adds "frame" my "panel1" as shown in the diagram. As of now I also have my images drawn in that class that appear as "panel2". I want the user to be able to switch between panels with the buttons in panel1. Some of the panels where panel 2 is will have buttons of their own as well. What is the best approach for this? Should i make seperate classes for the panels and add them in JFrame? Should I switch between panels using JFrame since it adds the first panel? I have included my code below for my two classes. I also have a Jtext pane below where the panels are.
Thanks in advance
___________________MY FRAME___________________
package sonomaroller;
import javax.swing.*;
import java.awt.*;
import static javax.swing.JFrame.*;
public class SonomaRoller extends JFrame {
public static Dimension size = new Dimension(550,550); //Dimension of Frame
public static String title = "Sonoma Roller v0.00" ;
//Creates new object f to create the window
//boolean
public boolean addShop=false;
public SonomaRoller(){
setTitle(title);
setSize(size);
setResizable(false);
setLocationRelativeTo(null); // null centers window on screen
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
init(addShop);
}
public void init(boolean addShop){
frame panel=new frame();
panel.setLayout(null);
add(panel);
setVisible(true);
}
public static void main(String[] args) {
SonomaRoller object1=new SonomaRoller();
}
}
___________________MY PANEL w/buttons___________________
package sonomaroller;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultCaret;
import javax.swing.text.StyledDocument;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
public class frame extends JPanel implements Runnable {
public void run(){
}
public frame(){
loadpics();
attackButton();
magicButton();
travelButton();
shopButton();
textField();
}
public void paintComponent(Graphics g){
}
}
public void textField(){
}
public void attackButton(){
}
public void magicButton(){
}
public void travelButton(){
}
public void shopButton(){
}
public void loadpics(){
Sonoma = new ImageIcon("C:\\Users\\Camtronius\\Documents\\NetBeansProjects\\SonomaRoller\\src\\sonomaroller\\testImage.jpg").getImage();
System.out.println("image loaded");
loaded = true;
repaint();
}
}
Can you not use ActionListener to set the panel visible or invisible by pressing a button. For example:
panel_1Button.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
panel1.setVisible(false)
panel2.setVisible(true);
}
});

NullPointerException at java.awt.Window.access$700(Window.java:132) while painting JPanel

I'm trying to paint component inside paint(Graphics) method of JPanel.
The following code snippet works just fine, a JButton is painted nicely in my JPanel:
#Override
public void paint(Graphics g) {
super.paint(g);
JButton btn = new JButton("hello");
Dimension dim = btn.getPreferredSize();
btn.setSize(dim.width, dim.height);
btn.paint(g); // paint the button
}
The code snippet works perfectly also for other components (JLabel, JTree, ...) except JPanel.
The following code will cause very strange NullPointerException at java.awt.Window.access$700(Window.java:132).
#Override
public void paint(Graphics g) {
super.paint(g);
JPanel panel = new JPanel();
panel.setSize(10, 10);
panel.paint(g); // paint the panel
}
Here the full stacktrace:
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
at java.awt.Window.access$700(Window.java:132)
at java.awt.Window$1.isOpaque(Window.java:3458)
at javax.swing.RepaintManager.getVolatileOffscreenBuffer(RepaintManager.java:983)
at javax.swing.RepaintManager$PaintManager.paint(RepaintManager.java:1395)
at javax.swing.BufferStrategyPaintManager.paint(BufferStrategyPaintManager.java:294)
at javax.swing.RepaintManager.paint(RepaintManager.java:1224)
at javax.swing.JComponent.paint(JComponent.java:1015)
at test.paintcontainer.TestPaintContainerMain$TestContentPane.paint(TestPaintContainerMain.java:48)
at javax.swing.JComponent.paintChildren(JComponent.java:862)
at javax.swing.JComponent.paint(JComponent.java:1038)
at javax.swing.JLayeredPane.paint(JLayeredPane.java:567)
at javax.swing.JComponent.paintChildren(JComponent.java:862)
at javax.swing.JComponent.paintToOffscreen(JComponent.java:5131)
at javax.swing.BufferStrategyPaintManager.paint(BufferStrategyPaintManager.java:278)
at javax.swing.RepaintManager.paint(RepaintManager.java:1224)
at javax.swing.JComponent.paint(JComponent.java:1015)
at java.awt.GraphicsCallback$PaintCallback.run(GraphicsCallback.java:21)
at sun.awt.SunGraphicsCallback.runOneComponent(SunGraphicsCallback.java:60)
at sun.awt.SunGraphicsCallback.runComponents(SunGraphicsCallback.java:97)
at java.awt.Container.paint(Container.java:1780)
at java.awt.Window.paint(Window.java:3375)
at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:796)
at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:713)
at javax.swing.RepaintManager.seqPaintDirtyRegions(RepaintManager.java:693)
at javax.swing.SystemEventQueueUtilities$ComponentWorkRequest.run(SystemEventQueueUtilities.java:125)
at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:209)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:641)
at java.awt.EventQueue.access$000(EventQueue.java:84)
at java.awt.EventQueue$1.run(EventQueue.java:602)
at java.awt.EventQueue$1.run(EventQueue.java:600)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.AccessControlContext$1.doIntersectionPrivilege(AccessControlContext.java:87)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:611)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:269)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:184)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:174)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:169)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:161)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:122)
Any idea how to solve this problem? I need to paint JPanel inside paint(Graphics) method.
I wrote a simple test application which you can copy-paste to reproduce the aforementioned exception:
package test.paintcontainer;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
public class TestPaintContainerMain extends JFrame {
public static void main(String[] args) {
TestPaintContainerMain test = new TestPaintContainerMain();
test.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
test.setBounds(0, 0, 300, 200);
test.setContentPane(new TestContentPane());
test.setVisible(true);
}
static class TestContentPane extends JPanel {
JRadioButton paintButtonCheck;
JRadioButton paintPanelCheck;
public TestContentPane() {
paintButtonCheck = createRadioButton("paint button", true);
paintPanelCheck = createRadioButton("paint panel", false);
ButtonGroup buttonGroup = new ButtonGroup();
buttonGroup.add(paintButtonCheck);
buttonGroup.add(paintPanelCheck);
add(paintButtonCheck);
add(paintPanelCheck);
}
#Override
public void paint(Graphics g) {
super.paint(g);
g.translate(100, 100);
if (paintButtonCheck.isSelected()) {
createButton().paint(g);
} else {
createPanel().paint(g);
}
}
private JButton createButton() {
JButton button = new JButton("button");
button.setSize(button.getPreferredSize().width, button.getPreferredSize().height);
return button;
}
private JPanel createPanel() {
JPanel panel = new JPanel();
panel.setBackground(Color.GREEN);
panel.add(createButton());
panel.setSize(panel.getPreferredSize().width, panel.getPreferredSize().height);
return panel;
}
private JRadioButton createRadioButton(String title, boolean selected) {
JRadioButton radio = new JRadioButton(title, selected);
radio.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
TestContentPane.this.repaint();
}
});
return radio;
}
}
}
This is most likely not a bug in Swing, but more of a problem because you are trying to paint a component which has not yet been realized, meaning it has no active graphic context. You can realize a component by adding it to already realized component like your JFrame - which itself gets realized by setVisible(true).
Also one should probably never call JComponent.paint(Graphics) manually, because this is the job of Swing (more precisely the Event Dispatcher Thread) - it even says so in the documentation of the paint method:
Applications should not invoke paint directly, but should instead use the repaint method to schedule the component for redrawing.
What you can call is the method printAll(Graphics g), which paints the component and all its subcomponents. Also in Swing one should also not override paint but paintComponent.
So here is a test code:
JButton button = createButton();
JPanel panel = createPanel();
public TestContentPane() {
paintButtonCheck = createRadioButton("paint button", true);
paintPanelCheck = createRadioButton("paint panel", false);
ButtonGroup buttonGroup = new ButtonGroup();
buttonGroup.add(paintButtonCheck);
buttonGroup.add(paintPanelCheck);
add(paintButtonCheck);
add(paintPanelCheck);
//Hack, just prove something (realize both components)
add(panel);
add(button);
}
...
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.translate(100, 100);
if (paintButtonCheck.isSelected()) {
button.paintAll(g);
} else {
panel.paintAll(g);
}
g.translate(-100, -100);
}
This should work (although you will obviously have two components on the screen you don't want). Also note "reset" the graphics object, because it will still be used afterwards by Swing.
So this is the theory, but it's not yet an actual solution.
My solution to your problem is: "Don't do it like this"!
Components are not like images, in the sense that they don't look the same everywhere. The output of the paintAll call will be different, depending on how (or where) the components were realized.
So one suggestion is to show actual components. Create your tooltip box, add your panel and your button and let them draw themselves. You can even subclass these components and override their paintComponent() methods, add transparency and all. It will require some work, but Swing was never known to be easy.
I just found a solution.
The only modification of a sample code from my question is that called panel.setDoubleBuffered(false) on JPanel I was trying to paint.
However, I would still consider the exeption to be a Swing bug. If double buffering should be turned off by design you shouldn't get NullPointerException but some other, more meaningful exception which explains the condition.
Here is a fixed sample application:
package test.paintcontainer;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
public class TestPaintContainerMain extends JFrame {
public static void main(String[] args) {
TestPaintContainerMain test = new TestPaintContainerMain();
test.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
test.setBounds(0, 0, 300, 200);
test.setContentPane(new TestContentPane());
test.setVisible(true);
}
static class TestContentPane extends JPanel {
JRadioButton paintButtonCheck;
JRadioButton paintPanelCheck;
public TestContentPane() {
paintButtonCheck = createRadioButton("paint button", false);
paintPanelCheck = createRadioButton("paint panel", true);
ButtonGroup buttonGroup = new ButtonGroup();
buttonGroup.add(paintButtonCheck);
buttonGroup.add(paintPanelCheck);
add(paintButtonCheck);
add(paintPanelCheck);
}
#Override
public void paint(Graphics g) {
super.paint(g);
g.translate(100, 100);
if (paintButtonCheck.isSelected()) {
createButton().paint(g);
} else {
createPanel().paint(g);
}
}
private JButton createButton() {
JButton button = new JButton("button");
button.setSize(button.getPreferredSize().width, button.getPreferredSize().height);
return button;
}
private JPanel createPanel() {
JPanel panel = new JPanel();
panel.setBackground(Color.GREEN);
panel.add(createButton());
// --------------------------------
panel.setDoubleBuffered(false); // <-- TURN OFF DOUBLE BUFFERING
// --------------------------------
panel.setSize(panel.getPreferredSize().width, panel.getPreferredSize().height);
return panel;
}
private JRadioButton createRadioButton(String title, boolean selected) {
JRadioButton radio = new JRadioButton(title, selected);
radio.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
TestContentPane.this.repaint();
}
});
return radio;
}
}
}

Closing another JFrame from another method

I've been working on this for some time, and I'd really appreciate some help right now.
I'm trying to get the JFrame containing the text input fields to close from my actionPerformed method, but I can't seem to get anything to work. JFrame.dispose wont let me access the right Jframe, and setVisible(false) is equally useless, unless I'm doing this completely wrong.
//halp
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
class PersonInput extends JPanel
implements ActionListener {
//Fields for data entry
private JFormattedTextField firstField, lastField, dateField;
public String x[] = new String[3];
public PersonInput() {
//Values for the fields
String first = "First Name";
String last = "Last Name";
String date = "MM/DD/YYYY";
//Create the text fields and set them up.
firstField = new JFormattedTextField();
firstField.setValue(new String(first));
lastField = new JFormattedTextField();
lastField.setValue(new String(last));
dateField = new JFormattedTextField();
dateField.setValue(new String(date));
dateField.setColumns(10);
JButton ok = new JButton("OK");
ok.setVerticalTextPosition(AbstractButton.BOTTOM);
ok.setHorizontalTextPosition(AbstractButton.CENTER);
ok.setActionCommand("ok");
ok.addActionListener(this);
ok.setToolTipText("Confirms user input and continues with the program.");
JPanel buttons = new JPanel(new GridLayout(0,1));
buttons.add(ok);
//Layout the text fields in a panel.
JPanel fieldPane = new JPanel(new GridLayout(0,1));
fieldPane.add(firstField);
fieldPane.add(lastField);
fieldPane.add(dateField);
//Put the panels in this panel, labels on left,
//text fields on right.
setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
add(fieldPane, BorderLayout.CENTER);
add(buttons, BorderLayout.LINE_END);
}
public void actionPerformed(ActionEvent e) {
if ("ok".equals(e.getActionCommand()))
{
JFrame frame1 = new JFrame("People Sorter");
x[0] = firstField.getText();
x[1] = lastField.getText();
x[2] = dateField.getText();
JOptionPane.showMessageDialog(frame1, "Person has been added.");
dispPerson();
frame.setVisible(false);
}
}
public void dispPerson()
{
System.out.println(x[0] + x[1] + x[2]);
}
public static void createAndShowGUI() {
//Create and set up the window.
JFrame frame = new JFrame("Person Input");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Add contents to the window.
frame.add(new PersonInput());
//Display the window.
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
//Turn off metal's use of bold fonts
UIManager.put("swing.boldMetal", Boolean.FALSE);
createAndShowGUI();
}
});
}
}
I'm all ears if anyone has any ideas; I've been stressed over this all day. Thanks much for lending me your time!
EDIT: Just for clarification, the frame I'm trying to close is the one instantiated in the createAndShowGUI method.
it seems that the problem is that we are trying to merge both static and non static contents. For a short explanation static contents can be referred without need of creating an instance (object) of that class. Which means that createAndShowGUI can be called:
inside another static method (like main)
From class reference PersonInput.createAndShowGUI()
or from an object, but that method or attribute will be always the same, static attributes are shared.
I can suggest 2 ways to solve your problem.
One is pass the object frame to PersonInput
//halp
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
class PersonInput extends JPanel
implements ActionListener {
//Fields for data entry
private JFormattedTextField firstField, lastField, dateField;
public String x[] = new String[3];
JFrame frame;
public PersonInput(JFrame frame) {
this.frame = frame;
//the rest of your code
}
The other way is to have the frame object outside the method and declare it static.
static JFrame frame = new JFrame("Person Input");;
public static void createAndShowGUI() {
//Create and set up the window.
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Add contents to the window.
frame.add(new PersonInput());
//Display the window.
frame.pack();
frame.setVisible(true);
}
Remember that static variable cannot be referenced from a static context

JButton display issue

I have created a JFrame by creating a class(Display.java) that extends JFrame class. In that class I have created a JPanel object.
class Display extends JFrame {
JPanel jp= new JPanel();
And in another class(TestBroadCastNode.java) I am adding JButtons to that JPanel by using reference to the class Display as shown below:
class TestBroadCastNode {
Display disp;
Graphics g =disp.getGraphics();
JButton bt = new JButton("One");
disp.jp.add(bt);
}
Now I am not able to display the buttons. Help me.... If you want I can send you the complete file.
I tried to match your code as close as possible, but since you only gave limited snippets I had to make some assumptions. But the following code shows the button just fine:
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class TestClass {
public static class Display extends JFrame {
public JPanel panel = new JPanel( );
public Display(){
super( "TestFrame");
getContentPane().add( panel );
}
}
public static class TestBroadCastNode{
Display display;
public TestBroadCastNode( Display aDisplay ) {
display = aDisplay;
display.panel.add( new JButton( "One" ) );
}
}
public static void main( String[] args ) throws Exception {
SwingUtilities.invokeLater( new Runnable() {
public void run() {
Display display = new Display();
display.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
new TestBroadCastNode( display );
display.pack();
display.setVisible( true );
}
} );
}
}

Need the height of an invalidated Swing component

The basic setup is this: I have a vertical JSplitPane that I want to have a fixed-size bottom component and a resizing top component, which I accomplished by calling setResizeWeight(1.0). In this application there is a button to restore the "default" window configuration. The default height of the window is the desktop height, and the default divider location is 100 pixels from the bottom of the split pane.
To set the divider location to 100px, I take the JSplitPane height - 100. The problem is, just before this I resize the JFrame, and since the code is in a button callback, the JSplitPane has been invalidated but not yet resized. So the divider location is set incorrectly.
Here is a SSCCE. Click the button twice to see the problem. The first click will resize the window, but the divider location remains the same (relative to the bottom of the window). The second click properly moves the divider, since the window size didn't change.
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GraphicsConfiguration;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JSplitPane;
public class SSCCE {
/**
* #param args unused
*/
public static void main(String[] args) {
new SSCCE();
}
private final JFrame f = new JFrame("JSplitPane SSCE");
private final JSplitPane sp = new JSplitPane(JSplitPane.VERTICAL_SPLIT,true);
public SSCCE() {
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
sp.add(new JLabel("top"));
sp.add(new JLabel("bottom"));
sp.setResizeWeight(1.0);
f.getContentPane().add(sp);
f.getContentPane().add(new JButton(new AbstractAction("Resize to Default") {
#Override
public void actionPerformed(ActionEvent e) {
restoreDefaults();
}
}),BorderLayout.PAGE_END);
f.setSize(400,300);
f.setVisible(true);
}
void restoreDefaults() {
f.setSize(f.getWidth(), getDesktopRect(f.getGraphicsConfiguration()).height);
sp.setDividerLocation(sp.getSize().height - 100); // Does not work on first button press
}
Rectangle getDesktopRect(GraphicsConfiguration gc) {
Toolkit toolkit = Toolkit.getDefaultToolkit();
Dimension size = toolkit.getScreenSize();
Insets insets = toolkit.getScreenInsets(gc);
return new Rectangle(insets.left, insets.top, size.width - (insets.left + insets.right), size.height - (insets.top + insets.bottom));
}
}
I have thought of a few ways I might get around this, but they all seem sort of hackish. So far the best idea I've had has been to call f.validate() in between setting the frame size and setting the divider location, but I'm concerned there might be side effects to forcing validation early.
The other option I thought of is to use EventQueue.invokeLater() to put the call to set the divider location at the end of the event queue. But that seems risky to me - I'm assuming the JSplitPane will have been validated at that point, and I'm concerned that may be a faulty assumption to make.
Is there a better way?
Took a while (probably due to being early morning here :-) to understand the problem, so just to make sure I got it:
the size of the bottom component can be whatever the user decides at all times
when resizing the frame all height change should happen to the top component
there's an option to restore to default sizes, independent of any setting before
"default" means the bottom component must have a fixed height of xx
If so, the solution is to separate the frame resizing from the sizing the bottom component. Your second option is dead on: resize the frame and wrap the bottom comp resize into a invokeLater (EventQueue or SwingUtilities, doesn't matter).
void restoreDefaults() {
f.setSize(f.getWidth(), getDesktopRect(f.getGraphicsConfiguration()).height);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
sp.setDividerLocation(sp.getSize().height - 100);
}
});
}
That's guaranteed to work as expected, because the invokeLater puts the request as last after all already queued events:
/**
* Causes <i>doRun.run()</i> to be executed asynchronously on the
* AWT event dispatching thread. This will happen after all
* pending AWT events have been processed. [...]
* If invokeLater is called from the event dispatching thread --
* for example, from a JButton's ActionListener -- the <i>doRun.run()</i> will
* still be deferred until all pending events have been processed.
You could create a custom action class that handles the button click and the resize event. This approach would look like this:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GraphicsConfiguration;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JSplitPane;
public class SSCCE {
/**
* #param args unused
*/
public static void main(String[] args) {
new SSCCE();
}
private final JFrame f = new JFrame("JSplitPane SSCE");
private final JSplitPane sp = new JSplitPane(JSplitPane.VERTICAL_SPLIT,true);
public SSCCE() {
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
sp.add(new JLabel("top"));
sp.add(new JLabel("bottom"));
sp.setResizeWeight(1.0);
f.getContentPane().add(sp);
CustomListener resizeViaButtonListener = new CustomListener("Resize to Default");
f.getContentPane().add(new JButton(resizeViaButtonListener), BorderLayout.PAGE_END);
f.addComponentListener(resizeViaButtonListener);
f.setSize(400,300);
f.setVisible(true);
}
void restoreDefaults() {
f.setSize(f.getWidth(), getDesktopRect(f.getGraphicsConfiguration()).height);
sp.setDividerLocation(sp.getSize().height - 100); // Does not work on first button press
}
Rectangle getDesktopRect(GraphicsConfiguration gc) {
Toolkit toolkit = Toolkit.getDefaultToolkit();
Dimension size = toolkit.getScreenSize();
Insets insets = toolkit.getScreenInsets(gc);
return new Rectangle(insets.left, insets.top, size.width - (insets.left + insets.right), size.height - (insets.top + insets.bottom));
}
class CustomListener extends AbstractAction implements ComponentListener {
CustomListener(String actionDescription) {
super(actionDescription);
}
private boolean resizedViaButtonClick = false;
#Override
public void actionPerformed(ActionEvent arg0) {
resizedViaButtonClick = true;
f.setSize(f.getWidth(), getDesktopRect(f.getGraphicsConfiguration()).height);
sp.setDividerLocation(sp.getSize().height - 100);
// you need this also here because if the component is not resized when clicking the button
// it is possible that the divider location must be changed. This happens when the user clicks
// the button after changing the divider but not resizing the frame.
}
#Override
public void componentResized(ComponentEvent e) {
if ( resizedViaButtonClick ) {
resizedViaButtonClick = false;
sp.setDividerLocation(sp.getSize().height - 100);
}
}
#Override
public void componentHidden(ComponentEvent e) { /* do nothing */ }
#Override
public void componentMoved(ComponentEvent e) { /* do nothing */ }
#Override
public void componentShown(ComponentEvent e) { /* do nothing */ }
}
}
This way the code that is responsible for handling the logical task of setting the standard size will be in one single and easy to understand class.
nothing complicated, basic Swing Rules
import java.awt.*;
import java.awt.event.ActionEvent;
import javax.swing.*;
public class SSCCE {
/**
* #param args unused
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
SSCCE sSCCE = new SSCCE();
}
});
}
private final JFrame f = new JFrame("JSplitPane SSCE");
private final JSplitPane sp = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
true);
public SSCCE() {
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
sp.add(new JLabel("top"));
sp.add(new JLabel("bottom"));
sp.setResizeWeight(1.0);
f.getContentPane().add(sp);
f.getContentPane().add(new JButton(new AbstractAction(
"Resize to Default") {
private static final long serialVersionUID = 1L;
#Override
public void actionPerformed(ActionEvent e) {
System.out.println(sp.getLastDividerLocation());
restoreDefaults();
}
}), BorderLayout.PAGE_END);
f.setPreferredSize(new Dimension(400, 300));
f.pack();
f.setVisible(true);
}
void restoreDefaults() {
//EventQueue.invokeLater(new Runnable() {
// #Override
// public void run() {
f.setPreferredSize(new Dimension(f.getWidth(),
getDesktopRect(f.getGraphicsConfiguration()).height));
f.pack();
sp.setDividerLocation(sp.getSize().height - 100);
// Does not work on first button press
// }
//});
}
Rectangle getDesktopRect(GraphicsConfiguration gc) {
Toolkit toolkit = Toolkit.getDefaultToolkit();
Dimension size = toolkit.getScreenSize();
Insets insets = toolkit.getScreenInsets(gc);
return new Rectangle(insets.left, insets.top,
size.width - (insets.left + insets.right),
size.height - (insets.top + insets.bottom));
}
}
but I think pack() may be better than validate()
I generally try to avoid invoking setPreferredSize() on any component. I would rather let the layout manager do its job. In this case this would mean setting the size of the frame and let the BorderLayout take all the available space.
void restoreDefaults() {
// f.setSize(f.getWidth(), getDesktopRect(f.getGraphicsConfiguration()).height);
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
Rectangle bounds = env.getMaximumWindowBounds();
f.setSize(f.getWidth(), bounds.height);
f.validate();
sp.setDividerLocation(sp.getSize().height - 100); // Does not work on first button press
}