I have a movie-player type application. You have a movie of JPanels that you can "play", that is you can have the frames change either automatically on a timer or by advancing a frame at a time manually. I've created 4 trivial frames (simple JPanels with different colors). It can go through all the frames (JPanels) once, but then it cannot do it again. When a frame changes, it is removed from the larger JPanel and replaced with a different frame. Clearly something is wrong when I am removing and adding frames from my BorderLayout. Can someone please run this and see where I am going wrong (relevant line numbers should be lines 182 - 194. That's where I am removing the current JPanel and adding a different one)?

Please note that in the above explanation, I am referring to "frame" as a "movie frame", not a JFrame. The lingo can get a bit confusing.

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



public class FramePlayer extends JPanel implements ActionListener, ChangeListener
{
    JPanel frames[];
    JPanel currentFrame;
    javax.swing.Timer timer;
    int timeBetweenFrames;
    int numFrames;
    int currentFrameNumber;

    Color playerColor;
    JButton playButton, pauseButton, firstFrameButton, lastFrameButton, nextFrameButton,
            prevFrameButton;
    JSlider speedSlider;
    JLabel displayLabel;
    boolean timerMode;
    JPanel viewPanel;



    public FramePlayer (JPanel[] theframes, Color viewBorderColor, int playerWidth,
            int playerHeight, int frameWidth, int frameHeight)
    {
        frames = theframes;
        numFrames = frames.length;
        timerMode = false;
        playButton = new JButton ("Play");
        pauseButton = new JButton ("Pause");
        firstFrameButton = new JButton ("First Frame");
        lastFrameButton = new JButton ("Last Frame");
        nextFrameButton = new JButton ("Next Frame");
        prevFrameButton = new JButton ("Prev. Frame");
        playButton.addActionListener (this);
        pauseButton.addActionListener (this);
        pauseButton.setEnabled (false);
        firstFrameButton.addActionListener (this);
        lastFrameButton.addActionListener (this);
        nextFrameButton.addActionListener (this);
        prevFrameButton.addActionListener (this);
        speedSlider = new JSlider (1, 100, 25);
        speedSlider.addChangeListener(this);
        timer = new javax.swing.Timer (250, this);
        displayLabel = new JLabel ();

        this.setLayout (new BorderLayout ());
        this.setPreferredSize (new Dimension (playerWidth, playerHeight));

        int controlPanelHeight = 200;
        int controlPanelWidth = playerWidth;
        int viewPanelHeight = playerHeight - controlPanelHeight;
        int viewPanelWidth = playerWidth;

        viewPanel = new JPanel ();
        viewPanel.setLayout (new BorderLayout ());
        viewPanel.setPreferredSize (new Dimension (viewPanelWidth, viewPanelHeight));
        JPanel controlPanel = new JPanel ();
        controlPanel.setLayout (new GridLayout (3, 1));
        controlPanel.setPreferredSize (new Dimension (controlPanelWidth, controlPanelHeight));

        for (int i = 0; i < numFrames; i++)
            frames[i].setPreferredSize (new Dimension (frameWidth, frameHeight));

        JPanel topBorder = new JPanel ();
        topBorder.setPreferredSize(new Dimension (viewPanelWidth, (viewPanelHeight - frameHeight) / 2));
        JPanel botBorder = new JPanel ();
        botBorder.setPreferredSize(new Dimension (viewPanelWidth, (viewPanelHeight - frameHeight) / 2));
        JPanel leftBorder = new JPanel ();
        leftBorder.setPreferredSize(new Dimension ((viewPanelWidth - frameWidth) / 2, frameHeight));
        JPanel rightBorder = new JPanel ();
        rightBorder.setPreferredSize(new Dimension ((viewPanelWidth - frameWidth) / 2, frameHeight));

        topBorder.setBackground (viewBorderColor);
        botBorder.setBackground (viewBorderColor);
        leftBorder.setBackground (viewBorderColor);
        rightBorder.setBackground (viewBorderColor);
        
        viewPanel.add (topBorder, BorderLayout.PAGE_START);
        viewPanel.add (botBorder, BorderLayout.PAGE_END);
        viewPanel.add (leftBorder, BorderLayout.LINE_START);
        viewPanel.add (rightBorder, BorderLayout.LINE_END);
        FrameChange (1);

        // control panel setup
        JPanel panel1 = new JPanel ();
        panel1.setLayout (new GridLayout (1, 6));
        panel1.add (playButton);
        panel1.add (pauseButton);
        panel1.add (firstFrameButton);
        panel1.add (lastFrameButton);
        panel1.add (nextFrameButton);
        panel1.add (prevFrameButton);
        JPanel panel2 = new JPanel ();
        panel2.setLayout (new GridLayout (1, 2));
        JLabel speedLabel = new JLabel ("Speed : ");
        speedLabel.setHorizontalAlignment(JLabel.RIGHT);
        panel2.add (speedLabel);
        panel2.add (speedSlider);
        controlPanel.add (panel1);
        controlPanel.add (panel2);
        displayLabel.setHorizontalAlignment(JLabel.CENTER);
        controlPanel.add (displayLabel);

        this.add (viewPanel, BorderLayout.CENTER);
        this.add (controlPanel, BorderLayout.PAGE_END);
    }


    public void setLabelString ()
    {
        timeBetweenFrames = speedSlider.getValue ();
        String labelString = "Frame " + Integer.toString (currentFrameNumber) + " of ";
        labelString = labelString.concat (Integer.toString (numFrames) + " : Time Between Frames = ");
        labelString = labelString.concat (Integer.toString (timeBetweenFrames) + " / 100 sec.");
        displayLabel.setText(labelString);
    }


    public void actionPerformed(ActionEvent e)
    {
        Object obj = e.getSource();

        if (obj == playButton)
        {
            if (timerMode)
                return;

            timerMode = true;
            timer.start ();
            playButton.setEnabled (false);
            pauseButton.setEnabled (true);
        }
        else if (obj == pauseButton)
        {
            if (!timerMode)
                return;

            timerMode = false;
            timer.stop ();
            playButton.setEnabled (true);
            pauseButton.setEnabled (false);
        }
        else if (obj == firstFrameButton)
        {
            currentFrameNumber = 1;
            timer.stop ();
            timerMode = false;
            pauseButton.setEnabled (false);
            if (numFrames > 1)
                playButton.setEnabled (true);
            else
                playButton.setEnabled (false);
        }
        else if (obj == lastFrameButton)
        {
             FrameChange (numFrames);
        }
        else if (obj == nextFrameButton)
        {
             FrameChange (currentFrameNumber + 1);
        }
        else if (obj == prevFrameButton)
        {
             FrameChange (currentFrameNumber - 1);
        }
        else if (obj == timer)
        {
            if (!FrameChange (currentFrameNumber + 1))
                timer.stop ();
        }
    }


    public boolean FrameChange (int framenumber)
    {
        if (framenumber < 1 || framenumber > numFrames)
            return false;

        currentFrameNumber = framenumber;
        currentFrame = frames[currentFrameNumber - 1];
        setLabelString();

        BorderLayout bl = (BorderLayout) viewPanel.getLayout ();
        JPanel currentCenterPanel = (JPanel) bl.getLayoutComponent(BorderLayout.CENTER);
        if (currentCenterPanel != null)
            bl.removeLayoutComponent (currentCenterPanel);

        viewPanel.add (currentFrame, BorderLayout.CENTER);

        if (currentFrameNumber == 1)
        {
            firstFrameButton.setEnabled (false);
            prevFrameButton.setEnabled (false);
        }
        else
        {
            firstFrameButton.setEnabled (true);
            prevFrameButton.setEnabled (true);
        }

        if (currentFrameNumber >= numFrames)
        {
            nextFrameButton.setEnabled (false);
            lastFrameButton.setEnabled (false);
            timerMode = false;
        }
        else
        {
            nextFrameButton.setEnabled (true);
            lastFrameButton.setEnabled (true);
        }

        if (timerMode)
        {
            playButton.setEnabled (false);
            pauseButton.setEnabled (true);
        }
        else
        {
            if (currentFrameNumber >= numFrames)
                playButton.setEnabled (false);
            else
                playButton.setEnabled (true);

            pauseButton.setEnabled (false);
        }

        return true;
    }


    public void stateChanged(ChangeEvent e)
    {
        Object obj = e.getSource();

        if (obj == speedSlider)
        {
            timeBetweenFrames = speedSlider.getValue();
            timer.setDelay (timeBetweenFrames * 10);
            setLabelString ();
        }
    }


    public static void main (String args[])
    {
        JFrame frame = new JFrame ();
        frame.setSize (800, 700);
        JPanel frames[] = new JPanel[4];
        Color colors[] = {Color.BLUE, Color.GREEN, Color.RED, Color.YELLOW};
        for (int i = 0; i < 4; i++)
        {
            frames[i] = new JPanel ();
            frames[i].setBackground (colors[i]);
        }

        FramePlayer fp = new FramePlayer (frames, Color.BLACK, 800, 700, 300, 300);
        frame.getContentPane ().add (fp);
        frame.setVisible (true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
}

Two changes:
Line # 182

if (framenumber < 0 || framenumber > numFrames)

Line # 195

viewPanel.add (currentFrame, BorderLayout.CENTER);
    viewPanel.setComponentZOrder(currentFrame,0);
    viewPanel.repaint();
Comments
That fixed it.

Two changes:
Line # 182

if (framenumber < 0 || framenumber > numFrames)

I think this was actually correct before. The array has indexes 0 through 3, but I am displaying 1 through 4 in the label, so I'm passing 1 through 4 to the function and subtracting 1 (red below) to get to the right array index. If I allow 0 to be get by, subtracting 1 will give me an invalid index of -1 here:

if (framenumber < 1 || framenumber > numFrames)
            return false;

        currentFrameNumber = framenumber;
        currentFrame = frames[currentFrameNumber - 1];

Line # 195

viewPanel.add (currentFrame, BorderLayout.CENTER);
    viewPanel.setComponentZOrder(currentFrame,0);
    viewPanel.repaint();

The other suggestion you had makes it work. I changed this:

if (framenumber < 1 || framenumber > numFrames)
            return false;

        currentFrameNumber = framenumber;
        currentFrame = frames[currentFrameNumber - 1];
        setLabelString();

        BorderLayout bl = (BorderLayout) viewPanel.getLayout ();
        JPanel currentCenterPanel = (JPanel) bl.getLayoutComponent(BorderLayout.CENTER);
        if (currentCenterPanel != null)
            bl.removeLayoutComponent (currentCenterPanel);

        viewPanel.add (currentFrame, BorderLayout.CENTER);

to this:

if (framenumber < 1 || framenumber > numFrames)
            return false;

        currentFrameNumber = framenumber;
        currentFrame = frames[currentFrameNumber - 1];
        setLabelString();

        BorderLayout bl = (BorderLayout) viewPanel.getLayout ();
        JPanel currentCenterPanel = (JPanel) bl.getLayoutComponent(BorderLayout.CENTER);
        if (currentCenterPanel != null)
            bl.removeLayoutComponent (currentCenterPanel);

        viewPanel.add (currentFrame, BorderLayout.CENTER);
        viewPanel.setComponentZOrder(currentFrame,0);
        viewPanel.repaint();

I understand why I needed to add the repaint () on line 15, but line 14 is puzzling me. viewPanel has five components, all JPanels (top, bottom, left, right, and center panels), none of which are supposed to overlap each other. I've removed the old center panel and added a new one. What difference does it make what order the components are painted if they don't overlap? Shouldn't I still be able to see the new center panel even if it's painted last?

That's a good question, and one I can't answer. Question though: is there any reason you didn't use a CardLayout for the center panel or whichever one was displaying the frames? I think that would make the display easier to manage.

That's a good question, and one I can't answer. Question though: is there any reason you didn't use a CardLayout for the center panel or whichever one was displaying the frames? I think that would make the display easier to manage.

I have a center panel and I want to surround that panel with another color, like the siding around a TV screen, so I created four surrounding JPanels (center panel is the content I care about, the four surrounding panels are just for decoration). Five JPanels in all, one of them in the center. BorderLayout seemed perfect for that.

It is, but I was suggesting that the center panel could have its own layout, and that layout could be a CardLayout. That way you could flip to the next frame simply by using the CardLayout's show method, and none of the repaint etc would be necessary. Since your project works now its kind of pointless though, but its a thought for the future.

Comments
Good suggestion regarding CardLayout.

It is, but I was suggesting that the center panel could have its own layout, and that layout could be a CardLayout. That way you could flip to the next frame simply by using the CardLayout's show method, and none of the repaint etc would be necessary. Since your project works now its kind of pointless though, but its a thought for the future.

Ah, OK, I see now. I wasn't following you before. That's a great idea and I changed the program to utilize it. Didn't take much changing and it works great. Thanks.

So my program is working now, but if anyone understands why line 14 is necessary in post 3, please explain because it seems like it shouldn't be needed to me. Thanks.

You may avoid setComponentZOrder if your code ensure that only a single instance of frame is to be added into viewPanel.

if (currentCenterPanel != null) {
           // remove prev frame if exists.
           viewPanel.remove(currentCenterPanel); 
  }
 
viewPanel.add (currentFrame, BorderLayout.CENTER);
 //    viewPanel.setComponentZOrder(currentFrame,0);
 viewPanel.repaint();

Suggestion - You never called validate on the JFrame after removing the previous center panel and adding the new one. If you were to call validate, then call repaint, I think the call to the ZOrder method might not be needed. The reason for this suggestion is because calling validate causes the layout manager to re-lay out its components. If it re lays out its components, and there is only one component in the center, you should not have to set the Z order. The Z order is just the "Z" positioning of a Component, if you thing of an X-Y-Z axis graph, I think.

Comments
I agree.
if (framenumber < 1 || framenumber > numFrames)
            return false;

        currentFrameNumber = framenumber;
        currentFrame = frames[currentFrameNumber - 1];
        setLabelString();

        BorderLayout bl = (BorderLayout) viewPanel.getLayout ();
        JPanel currentCenterPanel = (JPanel) bl.getLayoutComponent(BorderLayout.CENTER);
        if (currentCenterPanel != null)
            bl.removeLayoutComponent (currentCenterPanel);

        viewPanel.add (currentFrame, BorderLayout.CENTER);
        viewPanel.setComponentZOrder(currentFrame,0);
        viewPanel.repaint();

The problem is line 11. When I change line 11 to adatapost's suggestion:

viewPanel.remove(currentCenterPanel);

I can delete line 14 and it works. Apparently my original line 11 doesn't remove the center panel. I had thought that it did and still don't understand why it doesn't. But it definitely doesn't because when I add the following line after the above code:

System.out.println (viewPanel.getComponentCount());

when my original line 11 is in there, it steadily increases (5, 6, 7, 8, ...), whereas adatapost's line makes it stay at 5, which is what I want.

So one mystery solved, one new mystery. Shouldn't my line 11 and adatapost's line 11 both remove the center panel? Why doesn't mine?

If it re lays out its components, and there is only one component in the center, you should not have to set the Z order.

Please see my last post. Apparently there is more than one component in the center. Not sure why, since I thought I had removed it.

ahh, sorry, I see now. That is indeed very strange, since the API says that calling the remove method on the container is equivalent to calling the removeLayoutComponent method directly.

@ adatapost: Yes, but he is saying the removeLayoutComponent method is not working, while the other method is. This might indicate that the other method is doing some extra work that removeLayoutComponent does not do. Perhaps removing the Component from the Layout and removing it from the Container are different? I don't think so though.

Yeah, it's a puzzler. Seems like it SHOULD work, but it doesn't. Since I now have three methods that DO work (Change the Z order, remove the JPanel from viewPanel itself rather than removing it from the LayoutManager, and using CardLayout), I'm going to move on with what I know works. I'm going with the CardLayout option.

However, I'm still curious, so for curiosity's sake, I'll leave this unsolved for now in case someone knows why the two removal methods from post 10 aren't the same. Thanks for your input guys!

I read this book.
Introduction to Java Programming
and also java documentation; both says that removeLayoutComponent method is invoked though the container's remove or removeAll method.

http://java.sun.com/docs/books/tutorial/uiswing/layout/custom.html

Exactly. Shouldn't that mean that EITHER line should do the same thing (remove the component)? The component appears to only be removed when remove is called, not when removeLayoutComponent is called and I don't know why.

I think removeLayoutComponent method should be protected but it is not possible because it is from LayoutManager interface. A removeLayoutComponent method removes an object from its layout just before an an object is removed from its container.

This article has been dead for over six months. Start a new discussion instead.