I am working on a project using Synth for the UI and want to implement some custom buttons. The buttons need to make use of style settings from a synth XML settings file - e.g. font colors which are different for different states (MOUSE_OVER, PRESSED, etc).
The problem I'm stuck on is that some of the buttons need to have extra sub-components - e.g. some need more than one label. I want the sub-components to pick up the same style settings as the standard button sub-components.
I feel like I ought to be able to just extend JButton
and override/extend paintComponent
to call the draw methods of some child components. I'm a bit unsure about a few aspects of that approach though: e.g. what parameters to pass to paintComponent
; and how to ensure the sub-components get the correct Synth style settings (particularly wrt. the states).
An aside: I have tried extending JPanel
but have run into some difficulties with that approach (see here: JPanel states for Synth).
EDIT: So, I've discovered that it is possible to add sub-components to buttons and have them render correctly. It seems that even though JButton.getLayout()
returns null, the button will use an OverlayLayout
unless you call JButton.setLayout()
. Calling JButton.setLayout(null)
does prevent the OverlayLayout being used, so that's how I'm handling the layout.
I'm looking into a couple of different approaches to updating the styles for the child controls, will report back on those later.
So, in case it's of use to anyone else here's the approach I took in the end:
class CustomButton extends JButton {
CustomButton() {
// ... normal button init
// Enable absolute positioning of sub-components.
setLayout(null);
updateStyles();
getModel().addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
updateStyles();
}
});
}
private void updateStyles() {
// See below for implementation.
}
private int getSynthComponentState() {
// This is basically a copy of SynthButtonUI.getComponentState(JComponent)
int state = SynthConstants.ENABLED;
if (!isEnabled()) {
state = SynthConstants.DISABLED;
}
if (model.isPressed()) {
if (model.isArmed()) {
state = SynthConstants.PRESSED;
} else {
state = SynthConstants.MOUSE_OVER;
}
}
if (model.isRollover()) {
state |= SynthConstants.MOUSE_OVER;
}
if (model.isSelected()) {
state |= SynthConstants.SELECTED;
}
if (isFocusOwner() && isFocusPainted()) {
state |= SynthConstants.FOCUSED;
}
if (isDefaultButton()) {
state |= SynthConstants.DEFAULT;
}
return state;
}
}
I found 2 approaches to how to implement the updateStyles() method: (A) change the name of the component to use a different named style, or (B) copy the style settings from the button to the sub-components. Approach (A) is pretty straightforward, approach (B) works as follows:
private void updateStyles() {
SynthStyle ss = SynthLookAndFeel.getStyle(this, Region.BUTTON);
SynthContext sc = new SynthContext(this, Region.BUTTON, ss, getSynthComponentState());
for (Component c : getComponents()) {
c.setFont(ss.getFont(sc));
c.setBackground(ss.getColor(sc, ColorType.BACKGROUND));
c.setForeground(ss.getColor(sc, ColorType.FOREGROUND));
// ... and so on if you have other style elements to be changed.
}
}
Approach (A) is probably better if you're changing more than a couple of style settings with each different state, although it could get unwieldy if you have different styles for a lot of different states. If you're only changing a couple of style settings (e.g. in my case I only care about colours, at least for now) then approach (B) seems best.
There's also the approach suggested by trashgod of implementing a custom UI delegate (extending BasicButtonUI
) but if you take that route I think you'll have to re-implement much of SynthButtonUI.