Manage QIcon instances on a application global level

Maurizio Berti maurizio.berti at gmail.com
Fri Jul 7 01:46:12 BST 2023


The main issue with commonly used resources like QIcon is that they require
an existing QGuiApplication instance.

When using a single script with the canonical if __name__ == '__main__':
that's not a real problem, since the module can be loaded within that block
and it can be accessed from outside it due to the global scope.

Unfortunately, this is obviously not possible for modules that are not the
main script but still use those QIcons and are imported before the
QApplication construction.

A possible solution could be to create a custom metaclass that actually
returns a QIcon and a class based on it that implements its "enum", then
import that class instead.

The concept is similar to that of enums, with the difference that the
returned value is actually a QIcon.
Using an object reference is required since the "attribute" is not static,
but it's returned at runtime.

Since we can assume that an icon will only be required from a QWidget
instance, using this approach should be safe enough.

Let's suppose that this is the content of a MyIcons.py script:

class MetaIcon(type):
    _names = {
        PAUSE: 'media-playback-pause',
        ...
    }
    def __getattr__(self, attr):
        if attr in self._names:
            return QIcon.fromTheme(self._names[attr])
        return QIcon()

class Icon(metaclass=MetaIcon): pass

The above is necessary because an unknown class attribute has to be that of
a "class of the class" (aka, a type): you cannot just create a basic Icon
class and override __getattr__, since it only works on the instance. Doing
that in the metaclass is fine, because it works on the "instance" of the
type.

Then, you can just import Icon from that module, and `Icon.PAUSE` will
return a proper QIcon.

from MyIcons import Icon
...
button = QPushButton(icon=Icon.PAUSE)

Note that the constructor requirement of QIcon (as it goes for QPixmap)
remains: it shouldn't be created without an existing QApplication instance;
this means that you should not try to create class attributes using the
"icon enum" above (for example, to create class defaults).

If you want to achieve some level of optimization (to avoid unnecessary
duplicates of QIcon instances - hence, further QIconEngine instances) or
code tidiness, use the @cached_property decorator or create a null class
attribute, then overwrite it at the class level within the instance init:

class MyPlayer(QWidget):
    icons = None
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if not self.icons:
            self.icons.__class__.icons = [Icons.PLAY, Icons.PAUSE]
    ...
    def playerStateChanged(self, state):
        self.playButton.setIcon(self.icons[state])

The above will also work for deeper inheritance levels as long as the super
__init__ is called first, because the __init__ of the super class will
automatically make the attribute valid.

Best regards,
MaurizioB

Il giorno gio 6 lug 2023 alle ore 12:15 <c.buhtz at posteo.jp> ha scritto:

> Hello,
>
> I do use PyQt5.
>
> I have a module "icon.py" in my application containing lines with
> constants like this:
>
>      PAUSE = QIcon.fromTheme('media-playback-pause')
>
> Somewhere else in my application I do use "icon.PAUSE". But before doing
> this I have to "import icon" of course.
>
> The problem is that I'm not able to put the import statement in the
> beginning of the py files like you usually do it with imports. I have to
> put it e.g. in the __init__() method of my QMainWindow derived classed
> and after QApplication() was instanciated. I understand why it is that
> way.
>
> But maybe you can explain how you do manage icons in your application?
>
> Kind
> Christian
>


-- 
È difficile avere una convinzione precisa quando si parla delle ragioni del
cuore. - "Sostiene Pereira", Antonio Tabucchi
http://www.jidesk.net
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://www.riverbankcomputing.com/pipermail/pyqt/attachments/20230707/10be7871/attachment-0001.htm>


More information about the PyQt mailing list