LookAndFeel getLabelFont method breaks getComboBoxFont styling

I posted this last night in an existing thread, but after thinking on it further, it’s really its own issue…so moving it here. As I understand it, this isn’t a bug, per se, but it was a real counter-intuitive expectation for me.

I wanted to note some confusion around how the font-related LookAndFeelMethods affect the ComboBox (when applying a look and feel class to the whole plug-in editor component).

I was working on some code to conditionally change the font used in a ComboBox, and confirmed with DBG output that I had the getComboBoxFont method being called when expected – but it still wasn’t affecting the ComboBox’s font in the GUI.

Turns out the issue was that I had also implemented the L&F getLabelFont method, to set the font for some controls’ labels. However, because the ComboBox uses a label internally for its text display, the ComboBox’s font was actually being determined by getLabelFont instead of by getComboBoxFont.

If I removed the getLabelFont method from my L&F, then the ComboBox’s font was then determined by getComboBoxFont, as originally expected.

One solution for this case would be to write a more narrowly focused L&F that gets applied directly to the ComboBox component only (which I could then omit getLabelFont from), rather than applying a single L&F to the whole editor. Or, if trying to stick with the One-L&F-To-Rule-Them-All approach, I could add some conditional checking within getLabelFont to see which label it’s being applied to.

Not sure which way is going to be more convoluted and confusing to my future self. In any case, not realizing that getLabelFont would override the ComboBox styling in getComboBoxFont cost me several hours of troubleshooting, so I wanted to note that here and possibly save someone else the headache.

1 Like

Bumping this and was wondering if there’s an elegant solution on this, or are we stuck with needing to write a separate L&F just for the combobox?

@refusesoftware When you said “… add some conditional checking within getLabelFont to see which label it’s being applied to,” could you give an example of what that might look like?

By the way I tried an approach of overriding positionComboBoxText() and setting the font directly in label.setFont() but that didn’t work either.

Thanks for any help :slight_smile:

It’s a weird corner, LookAndFeels have a number of them… The default implementation works because getLabelFont() just returns label.getFont(), and positionComboBoxText() does label.setFont (getComboBoxFont (box)). As for conditional checking, if you return a specific subtype of Label in createComboBoxTextBox(), then in getLabelFont() you could do something like

if (auto cbLabel = dynamic_cast<ComboBoxLabel*>(&label))
2 Likes

Sure - I wound up adopting an approach that I outlined in this thread, using Component::getProperties, which accesses the NamedValueSet that’s a member of every Component. This value set can store an arbitrary set of var objects in any Component.

The way it works is, you set those properties in your Editor constructor, when you’re setting up the Components. And then in the L&F callbacks, you read property values out of the Component, and use it to determine font styling.

So you could use one of those properties to directly store the name of a font, like this (in Editor ctor):

myComponent.getProperties().set ("fontName", "Courier");

And then inside the L&F getLabelFont (Label& label) callback, use that property like:

String myFontName = label.getProperties()["fontName"];
return Font (myFontName, "Regular", 14.0f);

Make sense so far? (Note that calling the property “fontName” is completely arbitrary, I could have called it anything.) And you could also extend this by adding “fontHeight” and “typefaceStyle” properties as well, etc.

However, rather than take this approach of putting the font choices in the Editor, I prefer to keep those details in the L&F class. I do this by using a single guiClass property (again, this naming is arbitrary), with the idea of using named “classes” for styling, in the CSS sense of the word.

So instead of the above code, I have something more like this in the Editor ctor:

myComponent.getProperties().set ("guiClass", "header");

And then this in the L&F callbacks:

String guiClass = label.getProperties()["guiClass"];

if (guiClass == "header")
    return Font ("Courier", "Bold", 20.0f);
else
    return Font ("Courier", "Regular", 12.0f); 

Hope that helps.

1 Like

Thanks to both of you. I thought @kamedin had a solution that seemed easy to try, but unfortunately it still doesn’t work. Have I missed something? It does dynamically cast but still doesn’t change the font.
This is in my custom lnf…

    // Need to subclass ComboBox Labels to give its own lnf separate from our Label lnf
    class ComboBoxLabel : public juce::Label {};
    
    juce::Font getComboBoxFont (juce::ComboBox & box) override { return ThinFont; }
    ComboBoxLabel* createComboBoxTextBox (juce::ComboBox& box) override { return new ComboBoxLabel(); }
    
    void positionComboBoxText (juce::ComboBox& box, juce::Label& label) override
    {
        label.setBounds (1, 1,
                         box.getWidth() - 30,
                         box.getHeight() - 2);

        if (auto* cbLabel = dynamic_cast<ComboBoxLabel*>(&label))
            label.setFont (ThinFont);
        else
            jassertfalse;
    }

I also tried setting the font in a constructor for ComboBoxLabel…still no luck. I suppose since it’s still a label it will default to the label lnf. Not sure how I can differentiate it…short of copying all of the Label class and not inheriting from Label (which would mess up the other methods for ComboBox that I’m overriding).

So, when you run this code, you aren’t hitting that jassertfalse?

The method you have to disambiguate is getLabelFont(). I actually prefer refusesoftware suggestion, I just always forget the name of the method. It’s pretty much the same thing -either you tag with a named property

juce::Label* createComboBoxTextBox (juce::ComboBox&)
{
    auto label = new juce::Label();
    label->getProperties().set ("guiClass", "ComboBoxLabel");
    return label;
}

juce::Font getLabelFont (juce::Label& label)
{
    if (label.getProperties()["guiClass"] == "ComboBoxLabel")
        return ThinFont();

or with a subclass name, using RTTI under the hood

class ComboBoxLabel : public juce::Label {};

juce::Label* createComboBoxTextBox (juce::ComboBox&)
{
    return new ComboBoxLabel();
}

juce::Font getLabelFont (juce::Label& label)
{
    if (auto cbLabel = dynamic_cast<ComboBoxLabel*> (&label))
        return ThinFont();
  • createComboBoxTextBox() returns juce::Label*, not your type -otherwise it’s not an override.
  • is ThinFont a type or a number? If it’s a type, you need parenthesis to invoke the constructor. If it’s a number, I suggest a lowercase first letter.

That’s right, I’m not hitting the jassertfalse, and I’m not getting a warning or error that createComboBoxTextBox() isn’t overriding a virtual method. That seems to mean the compiler isn’t differentiating between my subclassed label and the JUCE label.

ThinFont is a static const juce::Typeface::Ptr that I have in my stylesheet (I’ve done it this way because my font attributes are separated), so no not a constructor.

I see where I missed out now. The property needs to be determined in getLabelFont(), not positionComboBoxText(). Thanks to both of you for your help! All working now.

1 Like

I always forget it too! So I made a pair of helper functions, getGuiClass & setGuiClass, and added them to my local library (as part of a module). Then I don’t have to worry about typos in the property name either…

namespace ref
{
	namespace IDs
	{
		const juce::Identifier guiClass ("guiClass");
	}

	inline String getGuiClass (const Component& c)
	{
		return c.getProperties()[IDs::guiClass];
	}

	inline void setGuiClass (Component& c, const String className)
	{
		c.getProperties().set (IDs::guiClass, className);
	}

}
2 Likes