2

I'm developing a look-and-feel with two themes.

The problem is:

  • I want to be able to switch dynamically between the two themes (it means changing the themes after startup).
  • But the themes have two different sets of icons (in fact the same icons with different colors).

I don't know how to change dynamically the icons in the entire application.

One solution would be to register each component with an icon and the icon ID on an icon manager to switch between the two kinds of icons, but it seems a very heavy solution!

mKorbel
  • 109,525
  • 20
  • 134
  • 319
paranoia25
  • 626
  • 1
  • 5
  • 23
  • Wouldn't `installUI` take care of this?? – MadProgrammer Sep 04 '12 at 20:04
  • Don't you think that you should mention what theme are you creating ? I mean for which purpose. Is it for Google chrome ? If so mention that. – Ishan Sep 04 '12 at 17:27
  • For Ishan: No it's not for google chrome. I'm working on a java swing application. – paranoia25 Sep 04 '12 at 23:13
  • For MadProgammer: No, installUi doesn't take care of this. The problem is that there's about 100 icons of my own which have to be changed between the two themes and I have to find a solution to change these icons. – paranoia25 Sep 04 '12 at 23:15

1 Answers1

3

There might be a lot of different approaches (and i cannot say which one is the best):

  1. Create your own icon class based on javax.swing.Icon and paint there an actual icon based on currently installed L&F
  2. Setup component icons inside the installUI method since it is called when component UI is installed OR reinstalled (for example when L&F is changed)
  3. Create your own components or their UIs that handles icon painting
  4. Override components which uses such icons and replace icon retrieval methods

There might be more, but those are first that comes to mind...

Here is the 1st solution working example:

import com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel;

import javax.swing.*;
import javax.swing.plaf.metal.MetalLookAndFeel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

/**
 * @see http://stackoverflow.com/a/12301173/909085
 */

public class LafIcon implements Icon
{
    private Map<String, Icon> lafIcons;

    public LafIcon ()
    {
        super ();
        lafIcons = new HashMap<String, Icon> ();
    }

    public void addIcon ( String laf, Icon icon )
    {
        lafIcons.put ( laf, icon );
    }

    private String getLaf ()
    {
        return UIManager.getLookAndFeel ().getClass ().getCanonicalName ();
    }

    private Icon getCurrentIcon ()
    {
        return lafIcons.get ( getLaf () );
    }

    public void paintIcon ( Component c, Graphics g, int x, int y )
    {
        Icon icon = getCurrentIcon ();
        if ( icon != null )
        {
            icon.paintIcon ( c, g, x, y );
        }
    }

    public int getIconWidth ()
    {
        Icon icon = getCurrentIcon ();
        return icon != null ? icon.getIconWidth () : 0;
    }

    public int getIconHeight ()
    {
        Icon icon = getCurrentIcon ();
        return icon != null ? icon.getIconHeight () : 0;
    }

    public static void main ( String[] args )
    {
        installMetalLookAndFeel ();

        JFrame frame = new JFrame ();
        frame.setLayout ( new FlowLayout ( FlowLayout.CENTER, 5, 5 ) );

        frame.add ( new JButton ( "Test button", createIcon () ) );

        String[] laf = { "Metal Look and Feel", "Nimbus Look and Feel" };
        final JComboBox lafType = new JComboBox ( laf );
        lafType.addActionListener ( new ActionListener ()
        {
            public void actionPerformed ( ActionEvent e )
            {
                if ( lafType.getSelectedIndex () == 0 )
                {
                    installMetalLookAndFeel ();
                }
                else
                {
                    installNimbusLookAndFeel ();
                }
            }
        } );
        frame.add ( lafType );

        frame.setDefaultCloseOperation ( JFrame.EXIT_ON_CLOSE );
        frame.pack ();
        frame.setLocationRelativeTo ( null );
        frame.setVisible ( true );
    }

    private static LafIcon createIcon ()
    {
        LafIcon icon = new LafIcon ();    
        try
        {
            icon.addIcon ( MetalLookAndFeel.class.getCanonicalName (), new ImageIcon (
                    new URL ("http://cdn3.iconfinder.com/data/icons/fatcow/32x32_0020/application_form.png") ) );
            icon.addIcon ( NimbusLookAndFeel.class.getCanonicalName (), new ImageIcon (
                    new URL ("http://cdn3.iconfinder.com/data/icons/fatcow/32x32_0040/application_view_gallery.png") ) );
        }
        catch ( MalformedURLException e )
        {
            e.printStackTrace ();
        }    
        return icon;
    }

    private static void installMetalLookAndFeel ()
    {
        installLookAndFeel ( MetalLookAndFeel.class.getCanonicalName () );
    }

    private static void installNimbusLookAndFeel ()
    {
        installLookAndFeel ( NimbusLookAndFeel.class.getCanonicalName () );
    }

    private static void installLookAndFeel ( String name )
    {
        try
        {
            UIManager.setLookAndFeel ( name );

            Window[] windows = Window.getWindows ();
            if ( windows.length > 0 )
            {
                for ( Window window : windows )
                {
                    SwingUtilities.updateComponentTreeUI ( window );
                    window.pack ();
                }
            }
        }
        catch ( ClassNotFoundException e )
        {
            e.printStackTrace ();
        }
        catch ( InstantiationException e )
        {
            e.printStackTrace ();
        }
        catch ( IllegalAccessException e )
        {
            e.printStackTrace ();
        }
        catch ( UnsupportedLookAndFeelException e )
        {
            e.printStackTrace ();
        }
    }
}

Actually, as you can see, most part of the code is the example. You can modify the icon code to provide additional icons add/set/remove methods to make the icon much more simple to create.

Also you don't need to listen to L&F changes using this way, since on component UI change it will be repainted and revalidated so the icon size and paint methods will get called again and will provide the new icon for updated L&F.

Mikle Garin
  • 10,083
  • 37
  • 59
  • 1) Do you mean: create an icon which change its painting code (or its internal image) when the theme changes? 2) I don't think it's possible because the icons are not specifically attached to a component (e.g. icons are the ones used in toolbar) 3) I'm not sure to understand this solution, but I think the problem is the same that with the solution 2 4) Interesting idea! – paranoia25 Sep 07 '12 at 09:07
  • @paranoia25 If your style(l&f)-dependant icons are not specifically attached to any component than solutions 2 and 3 are out of question. You should look into 1st one (4th one is a "backup" idea actually). And yes, in 1st solution i mean that you should change icon's painting method and paint real ImageIcons there depending on the style - see my updates in the answer. – Mikle Garin Sep 07 '12 at 09:33