1

From the BorderLayout javadoc:

A border layout lays out a container, arranging and resizing its components to fit in five regions: north, south, east, west, and center. Each region may contain no more than one component, and is identified by a corresponding constant: NORTH, SOUTH, EAST, WEST, and CENTER.

I have a program, based on another SO question, which creates a JFrame UI containing a button in the BorderLayout center, waits 5 seconds, then puts a different button in the same position. Because of the statement (Each region may contain no more than one component", I would expect the 2nd button to replace the first. However, the original button remains "hanging around". Here's the code:

import java.awt.BorderLayout;
import java.awt.Component;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class ReplaceBorderLayoutComponent extends JFrame
{
  private static final long serialVersionUID = 1L;

  public static void say(String msg) { System.out.println(msg); }

    public void createUI()
    {
      setDefaultCloseOperation(DISPOSE_ON_CLOSE);
      try
      {
        SwingUtilities.invokeAndWait
        (
            new Runnable()
            {
              public void run()
              {
//                setLayout(new BorderLayout());
                JButton longText = new JButton("Quite a long text");
                /*getContentPane().*/add(longText, BorderLayout.CENTER);
                pack();
                setVisible(true);
                say("longText = " + longText.toString());
              }
            }
        );
      }
      catch (Exception e) {}
    }

    public void replaceComponent()
    {
      try
      {
        SwingUtilities.invokeAndWait
        (
            new Runnable()
            {
              public void run()
              {
                JButton anotherText = new JButton("Another text");
                /*getContentPane().*/add(anotherText, BorderLayout.CENTER);
                pack();
                validate();
                repaint();
                say("anotherText = " + anotherText.toString());
              }
            }
        );
      }
      catch (Exception e)      {      }
      say("replaced");
    }

    public Component getComponent(String constraint)
    {
      BorderLayout bl = (BorderLayout)getContentPane().getLayout();
      return bl.getLayoutComponent(constraint);
    }

    public static void main(String ... args) 
    { 
        ReplaceBorderLayoutComponent st = new ReplaceBorderLayoutComponent();
        st.createUI();
        try { Thread.sleep(5000); } catch (InterruptedException ie) {}
        st.replaceComponent();
        Component c = st.getComponent(BorderLayout.CENTER);
        say("Center component = " + c.toString());

        String otherPlaces[] = { "North", "South", "East", "West", "First", "Last", "Before", "After" };
        for (String s : otherPlaces)
        {
          Component c2 = st.getComponent(s);
          if (c2 != null) { say("found additional component at " + s + ", " + c2.toString()); }
        }
    }
}

When I run it, the first button duly takes up all the space of the frame. If I resize it, it expands as expected. When the delayed code runs, it puts the second button in the frame, but if I expand the frame, I can see both buttons. The second button expands as is normal for the center component; the first button remains the size that it was at the time the second button was added (expanded, for instance, if I expand the frame before the addition).

After adding, hovering over the second button hides the first; resizing the frame redisplays the first on top of the second. If the first button is not visible, hovering over its position will display it, at least sometimes.

I have to conclude that the first button is still a part of the frame layout somewhere (it shows default animation on being clicked, for instance), but I can't figure out where. I have code to ensure that it is not in any of the other BorderLayout components, and that the center component is indeed the second button.

I removed one and both calls to pack() - same behavior with respect to the two buttons.

The poster of the original question got around his problem by removing the first component before adding the second, but I would like to understand where the first button is still being referenced in this program. Can anyone help?

Community
  • 1
  • 1
arcy
  • 12,845
  • 12
  • 58
  • 103
  • 2
    don't quite understand why you are surprised: once you added a component to a container via the container.add(..) method, it will be a child of the container until you remove it explicitly. Managing the children is the task of the container, not of the layoutManager. – kleopatra Sep 07 '13 at 13:04
  • 1
    For many components in one space, use a [`CardLayout`](http://docs.oracle.com/javase/7/docs/api/java/awt/CardLayout.html) as seen in this [short example](http://stackoverflow.com/a/5786005/418556). – Andrew Thompson Sep 07 '13 at 13:23
  • @kleopatra I am surprised because, as I said, the BorderLayout javadoc indicates that only one component can live at that position, and I thought therefore that placing another component at that position would replace the first one. If the 2nd one does NOT replace the first, then where IS it in the layout? Are you saying it is in the content pane, but no longer in the layout? If adding another component to the same layout position makes an orphan of the first component, and that's 'expected', fine, but it does not follow the "principle of least astonishment'. – arcy Sep 07 '13 at 14:21
  • @AndrewThompson I am not asking for a way to perform the indicated operations, I'm asking why these operations produce the result that they do. If (a container with) BorderLayout does not support these operations, that's useful information, but CardLayout behavior is not useful in this case. – arcy Sep 07 '13 at 14:23
  • *"I'm asking why these operations produce the result that they do."* AFAIU, the addition of multiple components to one single constraint of a `BorderLayout` is **undefined and faulty**. So I doubt that anyone (besides you) actually cares why that behavior is seen. *Don't do it!* – Andrew Thompson Sep 07 '13 at 14:31
  • agreed, the api doc is a bit badly worded. But expecting the manager to clean up the container is an unjustified expectation (how could it, it doesn't even know which container it's laying out), not a violation of "least surprise" :-) – kleopatra Sep 07 '13 at 14:31

2 Answers2

1

I am going to take a crack at answering my own question, based largely on the helpful comments of kleopatra and AndrewThompson.

The BorderLayout documentation says that "Each region may contain only one component"; evidently there is no code to enforce this, and it is up to the user to do so.

My best guess about the behavior of the program: when the second component is added to the container, it is duly put in place in the "center" region of the borderlayout (which has its own reference to the component). The first component is still in the container, but not in the layout, so it can still get rendered and may appear on top of or underneath other items; behavior is not really defined since it is no longer managed by the layout container.

I think this is quite unfortunate; it is the first case I've run across where a component can be in the container but not in the layoutmanager, and in fact its the first time I ever contemplated such a thing. I regard them as two objects that interact closely, not as a pair whose coordination I need to help manage.

I would recommend that the BorderLayout javadoc be beefed up with a mention of the user's responsibility not to put two components into one region, if I knew who to suggest it to.

arcy
  • 12,845
  • 12
  • 58
  • 103
  • I'm happy to report substantive evidence to the contrary, but right now the evidence (and kleopatra's comment) is that the 2nd add causes a 2nd component to be added to the container. And my sleep is in main(), not in the EDT as far as I can tell. Container#add can't comprehensively replace a single component, or things like FlowLayout wouldn't work at all. – arcy Sep 07 '13 at 23:11
0

I had the same problem. I ditched the BorderLayout and instead used JInternalFrame. JInternalFrame creates a JFrame inside of a JFrame. It can replace all of its components using setContentPane() and then refreshing it.

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

JFrame F = new JFrame();
JInternalFrame f = new JInternalFrame();

//Build outer frame
F.setBounds(100, 100, 500, 500);
F.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
F.getContentPane().setLayout(null);

//Build inner frame
f.setVisible(true);
f.setBounds(120, 120, 120, 120);
F.add(f);

//Replace content of inner frame
f.setContentPane(JComponent);
f.revalidate();
f.repaint();
Jeremy Caney
  • 7,102
  • 69
  • 48
  • 77
Ryan
  • 1