Search code examples
javaswingcustom-componentlook-and-feeluimanager

Extending existing Swing look and feels with custom JComponents


I'm writing a custom JComponent, that should look differently for different look and feels. I intend to have different ComponentUi classes at least for the WindowsLookAndFeel, the MetalLookAndFeel and for the MotifLookAndFeel.

Now, while this task seems easy enough, I couldn't find out how to easily extend an existing look and feel with my custom ComponentUi classes.

How would I go about registering the correct ComponentUi for the different look and feels? Is this possible at all? If not, what is the preferred way to write a custom component for different look and feels?


To be a bit more specific, at the moment I'm overriding JComponent#updateUI in my custom component to set the different ComponentUi instances:

@Override
public void updateUI() {
    LookAndFeel feel = UIManager.getLookAndFeel();
    if (feel instanceof WindowsLookAndFeel) {
        setUI(MyWindowsCustomUi.createUI(this));
    } else if (feel instanceof MotifLookAndFeel) {
        setUI(MyMotivCustomUi.createUI(this));
    } else if (feel instanceof MetalLookAndFeel) {
        setUI(MyMetalCustomUi.createUI(this));
    } else{
        setUI(MyBasicCustomUi.createUI(this));
    }
}

But this approach seems to entirely defeat the purpose and usefulness of look and feels. I'd like to be able to change this to the following:

public void updateUI() {
    setUI((MyCustomUi)UIManager.getUI(this));
}

And this should set the correct subclass of MyCustomUi for the current look and feel.

I know, that I could easily achieve this by creating custom subclasses of each supported LookAndFeel, that register the corresponding ComponentUi during e.g. BasicLookAndFeel#initComponentDefaults(UIDefaults) - but this is not what I want to do.


Solution

  • If you want it or not - you have to register your custom UIs somehow with the UIManager, how else could it know about them ;-)

    What you don't need, though, is a custom subclass of the supported LAFs: you can register them manually (and update the registration when the LAF is changed, that is you'll need a propertyChangeListener on the UIManager to be notified in such a case).

    Assuming a JCustom with a classID of "CustomUI" and ui implementations following the usual conventions (that is BasicCustomUI, WindowsCustomUI ... ) the registration would be something like:

     String prefix = UIManager.getLookAndFeel().getID();
     UIManager.getLookAndFeelDefaults().put("CustomUI", myUIPackage + "." + prefix + CustomUI);
    

    Note that the custom ui needs a static createUI which returns an instance of the ui:

     public static ComponentUI createUI(JComponent comp) {
         return new BasicCustomUI(); 
     }
    

    and the component needs to publish its uiClassID, lookup and set its ui:

    @Override
    public String getUIClassID() {
        return "CustomUI";
    }
    
    @Override
    public void updateUI() {
        setUI(UIManager.getUI(this));
    }
    

    The benefit of using SwingX is to provide the infrastructure to automagically register custom components. You'll need an additional class - CustomAddon - which provides the per-LAF configuration and the custom component has to contribute that addon:

    // in JCustom
    
    static {
        LookAndFeelAddons.contribute(new CustomAddon());
    }
    
    // in CustomAddon
    
    @Override
    protected void addBasicDefaults(LookAndFeelAddons addon, DefaultsList defaults) {
        super.addBasicDefaults(addon, defaults);
        defaults.add("CustomUI",
                "mypackage.BasicCustomUI");
    }
    
    @Override
    protected void addMacDefaults(LookAndFeelAddons addon, DefaultsList defaults) {
        super.addMacDefaults(addon, defaults);
        defaults.add("CustomUI",
                "mypackage.MacCustomUI");
    }
    
    //... similar methods for all supported LAFs