Saturday, June 30, 2012

Switching between views inside a container

I've come across many answers on StackOverflow suggesting that developers should use CardLayout to switch between views or panels. I know that it works and isn't difficult to implement, but I don't believe that CardLayout is the most appropriate way to do this. I've written a starting point for a class I'm calling ViewSwitcher which takes care of switching views within a Container. If you take out the comments you'll see that it's actually a very small class and it's easy to use. Feel free to add try / catch blocks for when a requested view has not been registered (ie. avoid NullPointerException)

You can also consider adding onShow() and onHide() methods to an interface called View and change all instances of Container to View. This would allow for future extensions to handling view switches - something that CardLayout may not be able to offer.
import java.awt.BorderLayout;
import java.awt.BorderLayout;
import java.awt.Container;
import java.util.HashMap;
import javax.swing.SwingUtilities;

/**
 * Used to switch views within a container.
 *
 * @author FHMP
 */
public class ViewSwitcher {

    /**
     * Map to keep track of views according to their specified names
     */
    private HashMap<String, Container> views = new HashMap<String, Container>();
    /**
     * The host container that contains the views to be switched between
     */
    private Container host;
    /**
     * Used to keep track of the current view
     */
    private Container current;

    public ViewSwitcher() {}

    public ViewSwitcher(Container host) {
        this.host = host;
    }

    /**
     * Registers a view bound to a specified name
     *
     * @param key
     * @param view the view
     */
    public void registerView(Container view, Object key) {
        views.put(key.toString(), view);
    }

    /**
     * Sets the host container that will contain the view
     *
     * @param host the host container
     */
    public void setHost(Container host) {
        this.host = host;
    }

    /**      
     * Switches to the view bound to the specified name
     *
     * @param key the key of the view to switch to
     */
    public void switchTo(final Object key) {
        if(host == null) return;
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                Container view = views.get(key.toString());

                if (current != null) {
                    host.remove(current);
                }
                current = view;

                host.add(view, BorderLayout.CENTER);
                host.validate();
                host.repaint(); // just to make sure  
            }
        });

    }
}
It's easy to adapt if you need to and the switching is very efficient. In one of my applications I use constant fields for each view instead of having to register each view dynamically by loading them into a map. This example just serves to demonstrate the idea of removing / adding components rather than using CardLayout.
You can use it like this:
// JPanels a, b, c already initialised
// JFrame frame is the main window

ViewSwitcher switcher = new ViewSwitcher();

switcher.setHost(frame.getContentPane());

switcher.registerView(a, "menu");
switcher.registerView(b, "main");
switcher.registerView(c, "help");

switcher.switchTo("menu");

No comments:

Post a Comment