I'm trying to code a layout for a small phonebook app using Java Swing. I came across a problem with size I cannot fix. I want filters (displayed under search results) to be wide enough to show whole title when search phrase is short. Here's what it looks like:
As you can see name is shortened. Surname is carrying longer text and is displayed just as I want.
Filter class:
import javax.swing.*;
import javax.swing.border.CompoundBorder;
import java.awt.*;
public class Filter
{
private JLabel label;
private JPanel filterPane;
Filter(String name)
{
// Filter text
label = new JLabel();
label.setForeground(new Color(0xAA0000));
// Closing button
JButton closeButton = new JButton("X");
closeButton.setFont(new Font("Dialog", Font.BOLD, 20));
closeButton.setMargin(new Insets(0, 0, 0, 0));
filterPane = new JPanel();
filterPane.setLayout(new BoxLayout(filterPane, BoxLayout.X_AXIS));
// Frame with title + 50px padding on the right side for the next filter
filterPane.setBorder(new CompoundBorder(
BorderFactory.createEmptyBorder(0, 0, 0, 50),
BorderFactory.createTitledBorder(name)));
filterPane.add(closeButton);
filterPane.add(Box.createRigidArea(new Dimension(5, 0)));
filterPane.add(label);
filterPane.setVisible(false);
}
public void setValue(String value) {
if (value == null || value.isEmpty())
filterPane.setVisible(false);
else
{
this.label.setText(value);
filterPane.setVisible(true);
}
}
public JPanel getFilterPane() {
return filterPane;
}
}
Main class:
import javax.swing.*;
import java.awt.*;
import java.lang.reflect.InvocationTargetException;
public class TestWindow
{
private static void init()
{
JFrame frame = new JFrame("Stackoverflow test window");
frame.setSize(new Dimension(480, 320));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
// Main panel - 20 px padding from each side
JPanel pane = new JPanel();
pane.setLayout(new BorderLayout());
pane.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
// Create panel for filters
JPanel filterPane = new JPanel();
filterPane.setLayout(new BoxLayout(filterPane, BoxLayout.X_AXIS));
filterPane.setPreferredSize(new Dimension(0, 60));
// Add sample filters
Filter filter = new Filter("name");
filter.setValue("i");
filter.getFilterPane().setAlignmentY(Component.BOTTOM_ALIGNMENT);
filterPane.add(filter.getFilterPane());
filter = new Filter("surname");
filter.setValue("loooooong text");
filter.getFilterPane().setAlignmentY(Component.BOTTOM_ALIGNMENT);
filterPane.add(filter.getFilterPane());
// Add everything to main panel
pane.add(new JTextArea("Nothing fancy here, just to fill out space"), BorderLayout.CENTER);
pane.add(filterPane, BorderLayout.SOUTH);
frame.setContentPane(pane);
frame.setVisible(true);
}
public static void main (String [] args) throws InterruptedException, InvocationTargetException {
SwingUtilities.invokeLater(TestWindow::init);
}
}
I tried setMinimumSize and setPreferredSize both for label and filterPane in Filter constructor, but it didn't help. Could you help me please?
The problem is actually composed of two problems.
When using TitledBorder, the component does not take the size of the title into account when calculating its preferred size.
When using BoxLayout, the preferred width is ignored, unless you add "horizontal glue" after the component.
So, to fix the first problem, you need to override the getPreferredSize() method of JPanel. But for this to work, you also need to bypass the second problem by adding glue.
To override the getPreferredSize(), you can use something like:
class MinSizePanel extends JPanel {
public Dimension getPreferredSize() {
Dimension original = super.getPreferredSize();
TitledBorder titleBorder = (TitledBorder)((CompoundBorder)getBorder()).getInsideBorder();
int width = (int)Math.max( original.getWidth(), 60 + (int)titleBorder.getMinimumSize(this).getWidth());
return new Dimension( width, (int)original.getHeight() );
}
}
This gets the TitledBorder from the CompoundBorder, gets its minimum size (this is a method that takes the title width into consideration), adds some extra for the empty border and insets (you can add the appropriate calculations, I did a shortcut and just used 60...), and uses that (if it's more than the width of the component as-is).
Instead of
filterPane = new JPanel();
Use
filterPane = new MinSizePanel();
(You should really move all the rest of the construction of the panel into that class as well).
And in the TestWindow class, after the last
filterPane.add(filter.getFilterPane());
Also add
filterPane.add(Box.createHorizontalGlue());
Related
So, in the code below I have a JTextArea on the left side. A JScrollPane on the upper right side that looks fine. Using the same code I also add a JScrollPane on the lower right side, but despite identical code, save the preferred sizes and absolute positioning, the vertical scroll bar does not seem to show up.
I will add a screenshot of the GUI after the code. Thank you in advance for any help resolving this issue.
frame = new JFrame("Title");
frame.setLayout(null);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().setPreferredSize(new Dimension(width, height));
frame.pack();
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
frame.setLocation(dim.width/2-frame.getSize().width/2, dim.height/2-frame.getSize().height/2);
frame.setResizable(false);
frame.addKeyListener(this);
//scroll and text area
textArea = new JTextArea();
textArea.setText("Static Text\n");
textArea.setFont(new Font("Consolas", 0, 12));
textArea.setColumns(50);
textArea.setLineWrap(true);
textArea.setEditable(false);
scrollPane = new JScrollPane(textArea);
scrollPane.setPreferredSize(new Dimension(width/2, height * 4 / 5));
scrollPane.setBounds(width/2, 0, width/2, height * 4 / 5);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
frame.add(scrollPane);
inputTextArea = new JTextArea();
inputTextArea.setText(">");
inputTextArea.setFont(new Font("Consolas", 0, 12));
inputTextArea.setColumns(50);
inputTextArea.setLineWrap(true);
inputScrollPane = new JScrollPane(inputTextArea);
inputScrollPane.setPreferredSize(new Dimension(width/2, height / 5));
inputScrollPane.setBounds(width/2, height * 4 / 5, width, height);
inputScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
frame.add(inputScrollPane);
//map
mapView = new JTextArea();
mapView.setFont(new Font("Consolas", 0, 8));
mapView.setEditable(false);
mapView.setPreferredSize(new Dimension(width/2, height));
mapView.setText(state.getCurrentMap().toString());
mapView.addKeyListener(this);
mapView.setBounds(0, 0, width/2, height);
frame.add(mapView);
frame.pack();
frame.setVisible(true);
You've several significant issues with that code including
Use of null layouts. While null layouts and setBounds() might seem to Swing newbies like the easiest and best way to create complex GUI's, the more Swing GUI'S you create the more serious difficulties you will run into when using them. They won't resize your components when the GUI resizes, they are a royal witch to enhance or maintain, they fail completely when placed in scrollpanes, they look gawd-awful when viewed on all platforms or screen resolutions that are different from the original one. This is making your debugging work more difficult, believe me. For that reason you're far better off learning about and using the layout managers. You can find the layout manager tutorial here: Layout Manager Tutorial, and you can find links to the Swing tutorials and to other Swing resources here: Swing Info.
You're setting the sizes/bounds of your JTextAreas. This prevents them from expanding appropriately when text is added, and will prevent scrollbars from the surrounding JScrollBars from appearing. Set the JTextArea column and row properties instead.
Adding a KeyListener to your text components. While this is not causing your current error, it is something that should be avoided and will often mess with the function of the component. Much better to use higher level listeners such as DocumentListener or DocumentFilter.
For example, the code below shows how to use simple layouts, text area column and row properties, as well as use of key bindings to capture the user's pressing the enter key, in case this is desired:
import java.awt.BorderLayout;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.*;
#SuppressWarnings("serial")
public class LayoutExample extends JPanel {
private static final int MV_ROWS = 65;
private static final int MV_COLS = 100;
private static final int TA_ROWS = 34;
private static final int TA_COLS = 54;
private static final int ITA_ROWS = 8;
private static final Font MV_FONT = new Font("Consolas", 0, 8);
private static final Font TA_FONT = new Font("Consolas", 0, 12);
private JTextArea mapView = new JTextArea(MV_ROWS, MV_COLS);
private JTextArea textArea = new JTextArea("Static Text\n", TA_ROWS, TA_COLS);
private JTextArea inputTextArea = new JTextArea(ITA_ROWS, TA_COLS);
public LayoutExample() {
mapView.setFont(MV_FONT);
mapView.setEditable(false);
mapView.setFocusable(false);
JScrollPane mvScrollPane = new JScrollPane(mapView);
textArea.setFont(TA_FONT);
textArea.setLineWrap(true);
textArea.setWrapStyleWord(true);
textArea.setEditable(false);
textArea.setFocusable(false);
JScrollPane taScrollPane = new JScrollPane(textArea);
taScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
setEnterKeyBindings(inputTextArea);
inputTextArea.setFont(TA_FONT);
inputTextArea.setLineWrap(true);
inputTextArea.setWrapStyleWord(true);
JScrollPane itaScrollPane = new JScrollPane(inputTextArea);
itaScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
JPanel rightPanel = new JPanel(new BorderLayout());
rightPanel.add(taScrollPane, BorderLayout.CENTER);
rightPanel.add(itaScrollPane, BorderLayout.PAGE_END);
setLayout(new GridLayout(1, 0));
add(mvScrollPane);
add(rightPanel);
inputTextArea.setText(">");
}
// to capture the "enter" key being pressed without having to use a
// KeyListener
private void setEnterKeyBindings(final JTextArea textComponent) {
// only accept input when this component is focused
int condition = WHEN_FOCUSED;
InputMap inputMap = textComponent.getInputMap(condition);
ActionMap actionMap = textComponent.getActionMap();
// only will bind one keystroke -- that for enter key
KeyStroke enterKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
inputMap.put(enterKeyStroke, enterKeyStroke.toString());
// action to take if enter is pressed
actionMap.put(enterKeyStroke.toString(), new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
// get text from input text area, and then clear text
String text = textComponent.getText();
textComponent.setText(">");
// append this text to the upper text area
textArea.append(text + "\n");
// TODO: send text elsewhere via chat?
}
});
}
private static void createAndShowGui() {
LayoutExample mainPanel = new LayoutExample();
JFrame frame = new JFrame("Title");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
I have a component I've written that uses BoxLayout to layout some JLabels horizontally, followed by glue (I'd like the JLabels' width to remain fixed). For example:
I need to add two lines of text to each JLabel, so I'm using a bit of HTML. As soon as I added the HTML, the BoxLayout stopped respecting the glue. I get something like:
I can get around this by specifying that the maximum size should be equal to the preferred size (specifying preferred size has no effect). Is this the correct approach? Is there some explanation for why glue seems to be ignored when there's HTML in my JLabels?
The MWE:
import javax.swing.*;
import java.awt.*;
public class LabelBreak extends JFrame {
JPanel panel;
public LabelBreak() {
setTitle("Frame");
panel = new MyPanel();
panel.setPreferredSize(new Dimension(500, 100));
add(panel);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
JFrame frame = new LabelBreak();
frame.pack();
frame.setVisible(true);
});
}
private static class MyPanel extends JPanel {
private MyPanel() {
super();
this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
JPanel header = new JPanel();
header.setLayout(new BoxLayout(header, BoxLayout.X_AXIS));
//JLabel label = new JLabel("One");
JLabel label = new JLabel("<html>One<br>is<br>the<br>loneliest<br>number</html>");
label.setBorder(BorderFactory.createLineBorder(Color.RED, 2));
header.add(label);
label = new JLabel("Two");
label.setBorder(BorderFactory.createLineBorder(Color.RED, 2));
header.add(label);
header.add(Box.createHorizontalGlue());
this.add(header);
}
}
}
Is there some explanation for why glue seems to be ignored when there's HTML in my JLabels?
A BoxLayout will respect the maximum size (and minimum size) for components.
For normal text the maximum size would be the preferred size of the component, so the glue works as expected.
It looks like the calculation for the maximum size is different for HTML vs regular text.
I can get around this by specifying that the maximum size should be equal to the preferred size
Yes, this is a reasonable approach, but I would override the getMaximumSize() method to simply return the getPreferredSize() value.
#Override
public Dimension getMaximumSize()
{
return getPreferredSize();
}
This way if you change the HTML it will still work.
BoxLayout respects maximum size of JLabel so you have to set it.
To control width between two labels you can use Box.createHorizontalStrut(width).
Note that two labels are centered in header. I would use GridLayout to place components in one row with different space between columns.
private MyPanel() {
super();
this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
JPanel header = new JPanel();
header.setLayout(new GridLayout(1,4,20,0));
// JLabel label = new JLabel("One");
JLabel label = new JLabel("<html>One<br>is<br>the<br>loneliest<br>number</html>");
label.setBorder(BorderFactory.createLineBorder(Color.RED, 2));
label.setMaximumSize(label.getPreferredSize());
header.add(label);
// header.add(Box.createHorizontalStrut(10));
label = new JLabel("Two");
label.setBorder(BorderFactory.createLineBorder(Color.RED, 2));
header.add(label);
// header.add(Box.createHorizontalGlue());
this.add(header);
}
Trying to change the look of a JOptionPane while its open, depending on which radiobutton the user clicks. What am I doing wrong? It works perfect if I for example add a button and move a JLabel from side to side of the window.
import java.awt.BorderLayout;
import java.awt.event.*;
import javax.swing.*;
import static javax.swing.JOptionPane.*;
public class ChangePanel extends JFrame{
private JButton click = new JButton("CLICK ME!");
ChangePanel(){
add(click, BorderLayout.SOUTH);
click.addActionListener(new ButtonListen());
setVisible(true);
setSize(300,100);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLocationRelativeTo(null);
}
public class ButtonListen implements ActionListener{
public void actionPerformed(ActionEvent e){
PopUpPanel pop = new PopUpPanel();
showConfirmDialog(ChangePanel.this, pop, "Changeable", OK_CANCEL_OPTION);
}
}
//Send this as Parameter to the ConfirmDialog
public class PopUpPanel extends JPanel implements ActionListener{
JRadioButton jewelry = new JRadioButton("Jewelry");
JRadioButton shares = new JRadioButton("Shares");
JRadioButton machine = new JRadioButton("Machine");
PopUpPanel(){
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
ButtonGroup bg = new ButtonGroup();
JPanel north = new JPanel();
bg.add(jewelry);
jewelry.addActionListener(this);
bg.add(shares);
shares.addActionListener(this);
bg.add(machine);
machine.addActionListener(this);
north.add(jewelry);
north.add(shares);
north.add(machine);
add(north);
}
//Listener for RadioButtons
public void actionPerformed(ActionEvent e){
JTextField info1Txt = new JTextField(12);
JTextField info2Txt = new JTextField(12);
JTextField info3Txt = new JTextField(3);;
JRadioButton b = (JRadioButton)e.getSource();
if(b.getText().equals("Jewelry")){
//Dummy test text
System.out.println("Jewelry");
JPanel info1 = new JPanel();
info1.add(new JLabel("info1:"));
info1.add(info1Txt);
add(info1);
JPanel info2 = new JPanel();
info2.add(new JLabel("info2:"));
info2.add(info2Txt);
add(info2);
JPanel info3 = new JPanel();
info3.add(new JLabel("info3:"));
info3.add(info3Txt);
add(info3);
validate();
repaint();
}else if(b.getText().equals("Shares")){
//Dummy test text
System.out.println("Shares");
}else
//Dummy test text
System.out.println("Machine");
}
}
public static void main(String[] args){
new ChangePanel();
}
}
As you are working with BoxLayout, you should provide size hints to the PopUpPanel panel, which you haven't given.
When a BoxLayout lays out components from top to bottom, it tries to size each component at the component's preferred height. If the vertical space of the layout does not match the sum of the preferred heights, then BoxLayout tries to resize the components to fill the space. The components either grow or shrink to fill the space, with BoxLayout honoring the minimum and maximum sizes of each of the components.
check out the official tutorial page discussion: BoxLayout Feature
Call revalidate() and repaint() on the container after removing or adding components to it. So if you change the following lines:
validate();
repaint();
to:
revalidate();
repaint();
The content should appear. Though, it will not fit the original size of the JOptionPane. You can override PopUpPanel.getPreferredSize() to return desired size so that JOptionPane is packed properly, ie:
#Override
public Dimension getPreferredSize() {
return new Dimension(300, 300);
}
You can also use JDialog instead of JOptionPane.
Also, consider using CardLayout instead of swapping components manually. Check How to Use CardLayout for examples.
Why not just use setPreferredSize(new Dimension(300, 300)) in PopUpPanel constructor? Works fine for me. Good eye on revalidate and repaint.
I'm trying to place a series of JLabels at specific X and Y coordinates on a JPanel (and set its height and width, too). No matter what I do, each label winds up immediately to the right of the previous label and has the exact same size as all of the others.
Right now, my Jpanel is in a Grid Layout. I've tried Absolute Layout (illegal argument exception results), Free Design (no labels appear), Flow Layout (everything just gets squeezed to the center), and a few others.
Not sure what I need to do to make this work. Can anyone help? Thanks!
JLabel lbl1 = new JLabel("label 1");
JLabel lbl2 = new JLabel("label 2");
JLabel lbl3 = new JLabel("label 3");
JLabel lbl4 = new JLabel("label 4");
JLabel lbl5 = new JLabel("label 5");
myPanel.add(lbl1);
myPanel.add(lbl2);
myPanel.add(lbl3);
myPanel.add(lbl4);
myPanel.add(lbl5);
lbl1.setLocation(27, 20);
lbl2.setLocation(123, 20);
lbl3.setLocation(273, 20);
lbl4.setLocation(363, 20);
lbl5.setLocation(453, 20);
lbl1.setSize(86, 14);
lbl2.setSize(140, 14);
lbl3.setSize(80, 14);
lbl4.setSize(80, 14);
lbl5.setSize(130, 14);
You have to set your container's Layout to null:
myPanel.setLayout(null);
However is a good advise also to take a look at the Matisse Layout Manager, I guess it is called GroupLayout now. The main problem with absolute positioning is what happens when the window changes its size.
Set the container's layout manager to null by calling setLayout(null).
Call the Component class's setbounds method for each of the container's children.
Call the Component class's repaint method.
Note:
Creating containers with absolutely positioned containers can cause problems if the window containing the container is resized.
Refer this link:
http://docs.oracle.com/javase/tutorial/uiswing/layout/none.html
Layout managers are used to automatically determine the layout of components in a container. If you want to put components at specific coordinate locations, then you should not use a layout manager at all.
myPanel = new JPanel(null);
or
myPanel.setLayout(null);
My advise is to use an IDE like NetBeans with its GUI editor. To inspect the code and because there are many ways:
Setting the layout manager, or for absolute positioning doing a myPanel.setLayout(null), has several influences.
In general, assuming you do your calls in the constructor of a JFrame, you can call pack() to start the layouting.
Then, every layout manager uses its own implementation of add(Component) or add(Component, Constraint). BorderLayout's usage is with add(label, BorderLayout.CENTER) and so on.
// Best solution!!
import java.awt.Dimension;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class Main {
public static void main(String args[]) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = (JPanel) frame.getContentPane();
panel.setLayout(null);
JLabel label = new JLabel("aaa");
panel.add(label);
Dimension size = label.getPreferredSize();
label.setBounds(100, 100, size.width, size.height);
frame.setSize(300, 200);
frame.setVisible(true);
}
}
You can use your own method that calling by setSize, setLocation values for directly....! `
As well i show you how to use JProgress Bar
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
class installComp{
void install(Component comp, int w, int h, int x, int y){
comp.setSize(w,h);
comp.setLocation(x,y);
}
}
class MyFrame extends JFrame{
int cur_val = 0;
JButton btn = new JButton("Mouse Over");
JProgressBar progress = new JProgressBar(0,100);
MyFrame (){
installComp comp=new installComp();
comp.install(btn,150,30,175,20);
comp.install(progress,400,20,50,70);
btn.addMouseListener(new MouseAdapter(){
public void mouseEntered(MouseEvent evt){
cur_val+=2;
progress.setValue(cur_val);
progress.setStringPainted(true);
progress.setString(null);
}
});
add(btn);
add(progress);
setLayout(null);
setSize(500,150);
setResizable(false);
setDefaultCloseOperation(3);
setLocationRelativeTo(null);
setVisible(true);
}
}
class Demo{
public static void main(String args[]){
MyFrame f1=new MyFrame();
}
}
I'm trying to create a custom JDialog. I would like to have the components stacked on top of eachother with their preferred height, but the width should fill the container. Similar to LinearLayout in Android. I want that the components keeps their preferred height when the window is resized.
From Using Layout Managers:
Scenario: You need to display a few components in a compact row at their natural size.
Consider using a JPanel to group the components and using either the JPanel's default FlowLayout manager or the BoxLayout manager. SpringLayout is also good for this.
E.g. I would like to have a JTextField above a JTabbedPane. I have tried with all the suggested layout managers FlowLayout, BoxLayout, SpringLayout but they don't preserve the natural size of my components when the JDialog window get increased height.
Is there any Layout Manager in Java Swing that I can use for my situation?
Here is a small example that shows my problem with the Swing layouts:
import javax.swing.BoxLayout;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
public class TestDialog extends JDialog {
public TestDialog() {
setLayout(new BoxLayout(this.getContentPane(), BoxLayout.PAGE_AXIS));
//setLayout(new SpringLayout());
JTextField field1 = new JTextField();
JTextField field2 = new JTextField();
JTextField field3 = new JTextField();
JTextField field4 = new JTextField();
JPanel panel1 = new JPanel();
JPanel panel2 = new JPanel();
panel1.setLayout(new BoxLayout(panel1, BoxLayout.PAGE_AXIS));
panel2.setLayout(new BoxLayout(panel2, BoxLayout.PAGE_AXIS));
panel1.add(field2);
panel2.add(field3);
panel2.add(field4);
JTabbedPane tabs = new JTabbedPane();
tabs.addTab("Tab 1", panel1);
tabs.addTab("Tab 2", panel2);
add(field1);
add(tabs);
//field1.setMaximumSize(field1.getPreferredSize());
//SpringUtilities.makeCompactGrid(this.getContentPane(), 2, 1, 2, 2, 2, 2);
this.pack();
this.setVisible(true);
}
public static void main(String[] args) {
new TestDialog();
}
}
I would like to have a JTextField above a JTabbedPane. I have tried with all the suggested layout managers
A vertical BoxLayout should work fine. If the components grow when you increase the frame size then you may need to add:
textField.setMaximumSize( textField.getPreferredSize() );
tabbedPane.setMaximumSize( tabbedPane.getPreferredSize() );
If you need more help then post the SSCCE that demonstrates the problem.
Edit:
You need to specify your preferred size for text fields. This is done by using:
JTextField textField = new JTextField(10);
The null layout (basically x/y) might be what you are looking for, or perhaps Group Layout.
MigLayout also does what you probably want out of the box (you have to give it a specific directive to grow with the window, otherwise it doesn't).
Conceptually I think you are confused by the fact that the layout ultimately decides the size of components, so the "natural" size is a bit misleading. I guess what you are after is the size as set on the component? Or perhaps you mean the default when none of the setSize methods have been called on the component?
Either way, with Swing you have to be more explicit in your sizing expectations to get good results.
I quite didn't get what you meant by natural size of a component. But if you don't want the Layout manager messing up with the size of the components, i would suggest you use either the setMaximumSize(int width,int height) or set the layout to null and use setBounds(int x,int y,int width,int height) method to size your component.
If you are looking to tweak how the component's sizes change relative to each other, then i would suggest looking at GridBagLayout and using weightx and weighty properties.
I ended up with implementing my own StackLayout, the code is below. This is how the same application in my question look like. The components are stacked from top to bottom, and takes up the full width, similar to LinearLayout for Android.
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.LayoutManager;
public class StackLayout implements LayoutManager {
#Override
public void layoutContainer(Container parent) {
Insets insets = parent.getInsets();
int maxWidth = parent.getWidth() - (insets.left + insets.right);
int y = insets.top;
for(int n = 0; n < parent.getComponentCount(); n++) {
Component c = parent.getComponent(n);
int height = c.getPreferredSize().height;
c.setBounds(0, y, maxWidth, height);
y += height;
}
}
#Override
public Dimension minimumLayoutSize(Container c) {
int sumHeight = 0;
int maxWidth = 0;
for(int n = 0; n < c.getComponentCount(); n++) {
Dimension preferredSize = c.getComponent(n).getPreferredSize();
if(n == 0) {
maxWidth = preferredSize.width;
} else {
maxWidth += preferredSize.width;
}
sumHeight += preferredSize.height;
}
// add the containers insets
Insets insets = c.getInsets();
sumHeight += insets.top + insets.bottom;
maxWidth += insets.left + insets.right;
return new Dimension(maxWidth, sumHeight);
}
#Override
public Dimension preferredLayoutSize(Container c) {
return minimumLayoutSize(c);
}
#Override
public void addLayoutComponent(String arg0, Component c) {}
#Override
public void removeLayoutComponent(Component c) {}
}
If you use GridBagLayout and don't specify a fill or weight you should get what you want. Essentially you will have a 1 column 2 row grid bag layout. Just create your GridBagConstraints and set only the x and y options when adding the components - do not set any other options.
I would suggest a much simpler layout manager - BorderLayout() - from what you are describing. Code follows: you need to specify width of at least 1 JTextField to set up the width of the JDialog. You can use EmptyBorder if you want some spacings. Hope this helps, - M.S.
import java.awt.*;
import javax.swing.*;
public class TestDialog extends JDialog {
private JTextField field1 = new JTextField(20),
field2 = new JTextField(),
field3 = new JTextField(),
field4 = new JTextField();
public TestDialog(Frame f) {
super (f, "Test Dialog");
Container cp = getContentPane();
cp.setLayout (new BorderLayout());
cp.add (northPanel(), "North");
cp.add (tabbedPane(), "Center");
pack();
}
private JPanel northPanel () {
JPanel nPanel = new JPanel(new BorderLayout());
nPanel.add (field1, "Center");
// nPanel.setBorder (...);
return nPanel;
}
private JTabbedPane tabbedPane () {
JTabbedPane tp = new JTabbedPane();
tp.add ("Tab 1", panel1());
tp.add ("Tab 2", panel2());
return tp;
}
private JPanel panel1 () {
JPanel tfPanel = new JPanel(new BorderLayout());
tfPanel.add (field2, "North");
return tfPanel;
}
private JPanel panel2 () {
JPanel tfPanel = new JPanel(new BorderLayout()),
t2Panel = new JPanel(new BorderLayout());
t2Panel.add (field3, "North");
t2Panel.add (field4, "Center");
tfPanel.add (t2Panel, "North");
return tfPanel;
}
public static void main (String[] args) {
new TestDialog (null).setVisible (true);
}}