Should I Avoid the Use of Set(Preferred|Maximum|Minimum)Size Methods in Java Swing

Should I avoid the use of set(Preferred|Maximum|Minimum)Size methods in Java Swing?

  1. Should I completely avoid the use of those methods?

    Yes for application code.

  2. The methods have been defined for a reason. So when should I use them? In which context? For what purposes?

    I don't know, personally I think of it as an API design accident. Slightly forced by compound components having special ideas about child sizes. "Slightly", because they should have implemented their needs with a custom LayoutManager.

  3. What exactly are the negative consequences of using those methods? (I can only think adding portability between systems with different screen resolution.)

    Some (incomplete, and unfortunately the links are broken due to migration of SwingLabs to java.net) technical reasons are for instance mentioned in the Rules (hehe) or in the link @bendicott found in his/her comment to my answer. Socially, posing tons of work onto your unfortunate fellow who has to maintain the code and has to track down a broken layout.

  4. I don't think any LayoutManager can exactly satisfy all desired layout needs. Do I really need to implement a new LayoutManager for every little variation on my layout?

    Yes, there are LayoutManagers powerful enough to satisfy a very good approximation to "all layout needs". The big three are JGoodies FormLayout, MigLayout, DesignGridLayout. So no, in practice, you rarely write LayoutManagers except for simple highly specialized environments.

  5. If the answer to 4 is "yes", won't this lead to a proliferation of LayoutManager classes which will become difficult to maintain?

    (The answer to 4 is "no".)

  6. In a situation where I need to define proportions between children of a Component (for example, child 1 should use 10% of space, child 2 40%, child 3 50%), is it possible to achieve that without implementing a custom LayoutManager?

    Any of the Big-Three can, can't even GridBag (never bothered to really master, too much trouble for too little power).

Why should you not use setXXXSize()?

There are a lot of good answers on the linked duplicate (Should I avoid the use of set(Preferred|Maximum|Minimum)Size methods in Java Swing?).

Adding to the reasons why: With few exceptions, if you are using these methods you are probably fine-tuning your GUI to look good on a specific look-and-feel (and with your system-specific settings, e.g. your preferred desktop font, etc.). The methods themselves aren't inherently evil, but the typical reasons for using them are. As soon as you start tuning pixel positions and sizes in a layout you run the risk of your GUI breaking (or at minimum, looking bad), on other platforms.

As an example of this, try changing your application's default look-and-feel. Even just with the options available on your platform, you will no doubt be surprised at how poorly the results can be rendered.

So, in the name of keeping your GUI functional and nice-looking on all platforms (remember, one of the major benefits of Java is its cross-platformness), you should rely on layout managers, etc., to automatically adjust the sizes of your components so that it renders correctly outside of your specific development environment.

All that said, you can certainly conceive of situations where these methods are justified. Again, they aren't inherently evil, just make sure you are aware of the high potential for complications when you use them, and always try and think if there is another look-and-feel-independent solution to your problems.

Java - Swing - Layout - Ignore Wanted Size of Elements

None of the standard layout managers provide this functionality directly.

Check out the Relative Layout. It will allocate space to each component as a percentage of the total space available. The allocation will change dynamically as the available space changes.

Basic logic:

RelativeLayout rl = new RelativeLayout(RelativeLayout.X_AXIS);
JPanel panel = new JPanel( rl );
panel.add(smallLabel, new Float(3));
panel.add(largeLabel, new Float(7));

Using the standard layout managers you might be able to use the BoxLayout. It respects the "maximum" size of components, so you might be able to override the getMaximumSize() size method to do what you want.

Something like:

import java.awt.*;
import javax.swing.*;

public class SSCCE extends JPanel
{
public SSCCE()
{
setLayout( new BoxLayout(this, BoxLayout.X_AXIS) );

JLabel small = new JLabel("some long text that will get truncated")
{
@Override
public Dimension getMaximumSize()
{
Dimension size = getParent().getSize();
Dimension maximum = super.getMaximumSize();
maximum.width = (int)(size.width * .3);

return maximum;
}
};
small.setOpaque(true);
small.setBackground(Color.YELLOW);
add(small);

JLabel large = new JLabel("text")
{
@Override
public Dimension getMaximumSize()
{
Dimension size = getParent().getSize();
Dimension maximum = super.getMaximumSize();
maximum.width = (int)(size.width * .7);

return maximum;
}
};
large.setOpaque(true);
large.setBackground(Color.ORANGE);
add(large);
}

private static void createAndShowGUI()
{
JFrame frame = new JFrame("SSCCE");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new SSCCE());
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationByPlatform( true );
frame.setVisible( true );
}

public static void main(String[] args) throws Exception
{
java.awt.EventQueue.invokeLater( () -> createAndShowGUI() );
}
}

Why does setting preferred sizes in a JFrame fail when entering fullscreen?

Don't use a BorderLayout and try to add spacing components.

Instead I would suggest you can set the layout manager of the content panel to be a GridBagLayout. When using the default GridBagConstraints any component added to the GridBagLayout will automatically be centered.

Then for the "inside" panel that you add to the frame you need to override the getPreferredSize() method to calculate the size of the panel.

To determine its preferred size you would get the size of its parent container and then you determine its preferred size based on your rules.

There would be no need for the ComponentListener because the layout manager is automatically invoked whenever the frame is resized.

Simple example to get you started:

import java.awt.*;
import javax.swing.*;

public class JFrameResize
{
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGUI());
}

public static void createAndShowGUI() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

JPanel inside = new JPanel()
{
@Override
public Dimension getPreferredSize()
{
Dimension parent = getParent().getSize();
Dimension child = new Dimension();

int value = (parent.width * 3) - (parent.height * 4);

if (value > 0)
{
child.height = parent.height;
child.width = (int)(child.height * 4 / 3);
}
else
{
child.width = parent.width;
child.height = (int)(child.width * 3 / 4);
}

return child;
}
};

inside.setBackground(Color.BLUE);

frame.setLayout( new GridBagLayout() );
frame.add(inside, new GridBagConstraints());
frame.pack();
frame.setVisible(true);
}
}

How can I set a minimum size of a JFrame, to stop the user to resize it to smaller?

With the lack of compilable code, it is hard to tell what is going wrong there / in your code. Possibly the call the setMinimumSize(..) is occurring at the wrong time and/or with the wrong values.

Here, this code will not allow the frame to go smaller than it first appears, but will allow the user to drag it larger. Check the comments for tips.

import java.awt.BorderLayout;
import javax.swing.*;
import javax.swing.border.EmptyBorder;

public class MinimumSizeFrame {

private JComponent ui = null;

MinimumSizeFrame() {
ui = new JPanel(new BorderLayout(4,4));

JLabel label = new JLabel("Text in big label");
label.setBorder(new EmptyBorder(40, 100, 40, 100));
ui.add(label);
}

public JComponent getUI() {
return ui;
}

public static void main(String[] args) {
Runnable r = () -> {
MinimumSizeFrame o = new MinimumSizeFrame();

JFrame f = new JFrame(o.getClass().getSimpleName());
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
f.setLocationByPlatform(true);

f.setContentPane(o.getUI());
// have the JVM calculate the ideal size of this GUI
f.pack();
// now use THAT as minimum size!
f.setMinimumSize(f.getSize());

f.setVisible(true);
};
SwingUtilities.invokeLater(r);
}
}

How to Correctly Set Minimum Sizes in Java?

Firstly, the method setMinimumSize is a suggestion to the LayoutManager API. A suggestion that may be ignored.

In order to be able to even come close to supporting this, you will need to use something like a ComponentListener and monitor the componentResized event.

The best solution I can think of is to use a LayoutManager that actually uses the minimum and maximum size constraints, something like GridBagLayout.

Use this on a "content" pane and place you're JSplitPane onto this (setting it's minimum and maximum size accordingly) then add the "content" pane to frame.

UPDATE

Sorry, I'm probably missing something really obvious, but I put this little test together, I hope it has some ideas that help :P

public class TestFrameSize extends JFrame {

public TestFrameSize() throws HeadlessException {

setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(600, 600);
setLocationRelativeTo(null);

setMinimumSize(new Dimension(250, 250));

JLabel left = new JLabel("Left");
JLabel right = new JLabel("Right");

Dimension pSize = new Dimension(100, 100);
Dimension mSize = new Dimension(25, 100);

left.setPreferredSize(pSize);
left.setMinimumSize(mSize);
right.setPreferredSize(pSize);
right.setMinimumSize(mSize);

JSplitPane pane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, left, right);

JPanel content = new JPanel(new GridBagLayout());
content.add(pane);

setLayout(new BorderLayout());
add(content);

}

/**
* @param args the command line arguments
*/
public static void main(String[] args) {

new TestFrameSize().setVisible(true);

}
}

Window size is wrong or not setting in Swing

Remove frame.pack(); from your code.

More information:

http://docs.oracle.com/javase/tutorial/uiswing/components/frame.html

http://docs.oracle.com/javase/6/docs/api/java/awt/Window.html#pack%28%29

Setting the preferred size of a JTextFieldis not working

It does make a difference, but the layout may choose to ignore preferred size settings. The center area of BorderLayout gets as much of the available space as possible. See How to Use BorderLayout for more details.

Consider this example that packs the frame, as a result the preferred size of the text field is respected. But once the frame is resized, the text field is resized as well.

import javax.swing.*;
import java.awt.*;

class Demo {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JPanel loginJPanel = new JPanel(new BorderLayout());
JTextField usernameJTextField = new JTextField();
usernameJTextField.setPreferredSize(new Dimension(50, 100));
loginJPanel.add(usernameJTextField);

JFrame frame = new JFrame();
frame.add(loginJPanel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
});
}
}

Take a look at Visual Guide to Layout Managers and perhaps you would find a more suitable layout for your needs.

Also, see Should I avoid the use of set(Preferred|Maximum|Minimum)Size methods in Java Swing?.

EDIT:

Note that you're usually encouraged to specify the number of columns when initializing text fields. This number is used to calculate preferred width. For example textField = new JTextField(20); See How to Use Text Fields for more details:

If you do not specify the number of columns or a preferred size, then
the field's preferred size changes whenever the text changes, which
can result in unwanted layout updates.

Changing preferred size of a Swing component

Suggestions:

  • Use a class extends JPanel (or JComponent),
  • give it a getPreferredSize() method override where you return a Dimension with the parameters that you desire.
  • For instance if the diameter will be based on a BufferedImage, you could have something like:

getPreferredSize example:

public Dimension getPreferredSize() {
if (myBuffImg != null) {
return new Dimension(myBuffImg.getWidth(), myBuffImg.getHeight());
}
// default return value
return super.getPreferredSize();
}

Edit

Regarding your comment:

how would you handle the case of the component's content image changing though? is it just a a case of triggering a re-layout in the surrounding container?

You'd give this class a setImage(Image image) method of course, and you could repaint() this panel from within this method. The method I suppose could call revalidate() on this JPanel's ancestor causing this component to be re-layed out, but I'm not too crazy about methods in this class having side effects on its ancestor and think that likely it will be better for the code calling the setImage(...) method to suggest that the container revalidate itself.



Related Topics



Leave a reply



Submit