Balder Balder - 1 month ago 8
Java Question

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.

Answer

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
Comments