1

I'm trying to change current visible in card layout with slide effect. But I see a flick at the start of slide which I'm not able to debug/solve. How can I avoid that flick?

Here is sample code to reproduce error:

import java.awt.CardLayout;

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 = new JPanel(new CardLayout());
        JLabel label1 = new JLabel("Harry Joy");
        panel.add(label1, "1");
        JLabel label2 = new JLabel("Harsh Raval");
        panel.add(label2, "2");
        frame.add(panel);
        frame.pack();
        frame.setVisible(true);
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(500L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            label2.setLocation(label1.getX() + 10 - label1.getWidth(), label1.getY());
            label1.setLocation(label1.getX() + 10, label1.getY());
            label2.setVisible(true);
        }
        label2.setVisible(false);
        CardLayout cl = (CardLayout)panel.getLayout(); 
        cl.next(panel);
    }
}

Here at first label1("Harry Joy") is displayed. Then I make label2("Harsh Raval") visible and try to change location of both to provide a slide effect. But what happening here is first time both labels are displayed on top of each other and then it starts to slide. How can I stop that, I mean displaying both labels on top of each other? You can get better idea of what I mean if you run it once.

Harry Joy
  • 58,650
  • 30
  • 162
  • 207
  • *"Here is SSCCE:"* Umm.. no. An SSCCE requires a well formed class and a `main()` (unless an applet). – Andrew Thompson Jul 05 '12 at 11:50
  • This is not an SSCCE as there are no main and no imports. Your example "works" only if run out of the EDT which is a bad idea. – Guillaume Polet Jul 05 '12 at 11:52
  • 1
    Check out [this answer](http://stackoverflow.com/a/11283582/418556). It might give you some ideas.. – Andrew Thompson Jul 05 '12 at 11:53
  • @AndrewThompson hmm.. That's a good example for slide effect. I will use that, but still I'm curious to know the reason behind what I'm facing. If you can put some light on it? – Harry Joy Jul 05 '12 at 12:08
  • For two panels, you can animate `JSplitPane`, as shown [here](http://stackoverflow.com/a/5071109/230513). – trashgod Jul 05 '12 at 21:34

3 Answers3

3

The main problem is that you are stepping on CardLayout's toes. CardLayout both manages bounds (location and size) and visibility of your components, so your loop here:

    for (int i = 0; i < 10; i++) {
        try {
            Thread.sleep(500L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        label2.setLocation(label1.getX() + 10 - label1.getWidth(), label1.getY());
        label1.setLocation(label1.getX() + 10, label1.getY());
        label2.setVisible(true);
    }

is conflicting with what CardLayout does. By default, CardLayout will automatically show the first component you added and hide all the others. It also sets up the bounds of all components to the same bounds.

At the end of the first iteration, the visibility of label2 changes (from false to true) which eventually triggers your CardLayout to reperform the layout of your components which sets the bounds of all the components to the same bounds which is why you are seeing the overlapping. CardLayout is not meant to have multiple components visible simultaneously.

Note that you are running all this off the EDT (Event Dispatching Thread) which is really a bad idea. It can cause deadlocks and unpredictable behaviour (such as the one you are seeing here).

Guillaume Polet
  • 47,259
  • 4
  • 83
  • 117
  • In my app, I do use `SwingUtilities.invokeLater(..)` and face same problem. So I don't think it's due to I'm running it out off EDT. About setting visibility of label2 to true: I'm doing this cause if I don't do it then I will not see label2 sliding, only first will slide out and then at last label2 will be displayed which is not the case I need. I tried removing `setVisible(true)` line and works good, no overlapping at all, but then as I said there's only slide out effect of first label and no slide in effect of second label. So it seems like problem is due to setting visibility of label2. – Harry Joy Jul 05 '12 at 12:48
  • 1
    @HarryJoy I you run the same example as the one shown above within `SwingUtilities.invokeLater()`, you won't see anything since you would be blocking the EDT, meaning that "Paint events" cannot be processed. The overlapping could also not happen because the change of visibility of `label2` triggers the layout to be performed, but also in future event of the EventQueue, so your issue is both linked to the fact you are running this off the EDT and the visibility change of label2. Consider using a Swing Timer to do your sliding effect. – Guillaume Polet Jul 05 '12 at 12:57
  • Ok. I guess I have to tell you what I have exactly: At first I create a frame and my custom panel which has CardLayout to it, make this frame visible. I do this operations in main method and inside `SwingUtilities.invokeLater()`. Then I start a `java.swing.Timer` for changing location of components, what is inside the loop in this example is actually inside `actionPerformed()` method of `ActionListener` for `Timer`. Is this what you are suggesting? – Harry Joy Jul 05 '12 at 13:06
  • 1
    @HarryJoy 1) I would change the LayoutManager (possibly consider using null layout or implement your own) 2) inside your actionPerformed() start the sliding effect. Use Swing timer to perform the various steps of the sliding effects. The link provided in comment by Andrew Thompson is actually quite close to what I am describing here. There are also third-party libraries that can do this for you (such as SwingX) – Guillaume Polet Jul 05 '12 at 13:10
  • @HarryJoy Inside Swing Timer you don't have to call SwingUtilities.invokeLater since you are already inside the EDT, so you can safely manipulate Swing components directly inside the actionPerformed. As for the overlapping issues, this is all caused by conflicts with CardLayout. Although the order in which events occur is not exactly the same as the one of your question, the causes and consequences are the same. The solution is the same: implement your own LayoutManager, use a null layout or use a third-party lib. – Guillaume Polet Jul 06 '12 at 07:07
  • Ok, I will drop idea of card layout and try to implement it other way. Thanks for your help :) – Harry Joy Jul 06 '12 at 07:12
2

May be this http://java-sl.com/tip_slider.html ?

StanislavL
  • 56,971
  • 9
  • 68
  • 98
  • Same thing happens in this example. When you slide from last component to first you will sometime see same problem as me. To see it properly increase timer delay in listener class in this example. I'm curious to know reason for this and if possible solution for this. – Harry Joy Jul 05 '12 at 12:12
2

a few comments and could be comments only

  • idea with Thread.sleep is correct but started only from Runnable.Thread

  • for Swing GUI is there Swing Timer,

  • Swing Timer guarantee to move any of (without flickering with already) visible JComponents layed by standard LayoutManager

but in this (my code example) case I'm again using Swing Timer nor that all events would be done on EDT, by invoking from Runnable living own life without impact to the rest of Swing events, GUI e.i.,

notice this issue is based on AWT nested / inherits, for example for JComboBoxes popup (resize and with doLayout()) doesn't works

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

public class ShakingButtonDemo implements Runnable {

    private JButton button;
    private JRadioButton radioWholeButton;
    private JRadioButton radioTextOnly;

    public static void main(String[] args) throws Exception {
        SwingUtilities.invokeLater(new ShakingButtonDemo());
    }

    @Override
    public void run() {
        radioWholeButton = new JRadioButton("The whole button");
        radioTextOnly = new JRadioButton("Button text only");
        radioWholeButton.setSelected(true);
        ButtonGroup bg = new ButtonGroup();
        bg.add(radioWholeButton);
        bg.add(radioTextOnly);
        button = new JButton("  Shake with this Button  ");
        button.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                shakeButton(radioWholeButton.isSelected());
            }
        });
        JPanel p1 = new JPanel();
        p1.setBorder(BorderFactory.createTitledBorder("Shake Options"));
        p1.setLayout(new GridLayout(0, 1));
        p1.add(radioWholeButton);
        p1.add(radioTextOnly);
        JPanel p2 = new JPanel();
        p2.setLayout(new GridLayout(0, 1));
        p2.add(button);
        JFrame frame = new JFrame();
        frame.setTitle("Shaking Button Demo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(p1, BorderLayout.NORTH);
        frame.add(p2, BorderLayout.SOUTH);
        frame.setSize(240, 160);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    private void shakeButton(final boolean shakeWholeButton) {
        final Point point = button.getLocation();
        final Insets margin = button.getMargin();
        final int delay = 75;
        Runnable r = new Runnable() {

            @Override
            public void run() {
                for (int i = 0; i < 30; i++) {
                    try {
                        if (shakeWholeButton) {
                            moveButton(new Point(point.x + 5, point.y));
                            Thread.sleep(delay);
                            moveButton(point);
                            Thread.sleep(delay);
                            moveButton(new Point(point.x - 5, point.y));
                            Thread.sleep(delay);
                            moveButton(point);
                            Thread.sleep(delay);
                        } else {// text only
                            setButtonMargin(new Insets(margin.top, margin.left + 3, margin.bottom, margin.right - 2));
                            Thread.sleep(delay);
                            setButtonMargin(margin);
                            Thread.sleep(delay);
                            setButtonMargin(new Insets(margin.top, margin.left - 2, margin.bottom, margin.right + 3));
                            Thread.sleep(delay);
                            setButtonMargin(margin);
                            Thread.sleep(delay);
                        }
                    } catch (InterruptedException ex) {
                        ex.printStackTrace();
                    }
                }
            }
        };
        Thread t = new Thread(r);
        t.start();
    }

    private void moveButton(final Point p) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                button.setLocation(p);
            }
        });
    }

    private void setButtonMargin(final Insets margin) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                button.setMargin(margin);
            }
        });
    }
}
mKorbel
  • 109,525
  • 20
  • 134
  • 319