Home > Net >  Translation aware widgets using virtual events
Translation aware widgets using virtual events

Time:04-09

I have a multilingual Tkinter application. Since Tkinter itself doesn't expose the underlying msgcat functionality of Tk, I am using ttkbootstrap. As of now, I have the translations working, and I destroy and rebuild all the widgets when the language is changed via the interface.

Virtual events are a powerful feature of Tkinter, which has allowed me to structure my application in an OO way while using a combination of control variables and virtual events for inter-widget communication / message passing.

I tried to implement a translation-aware widget by this code, but as I now know, virtual events need widget focus, which won't be the case:

from ttkbootstrap import ttk
from ttkbootstrap.localization import MessageCatalog


def _tr(t: str) -> str:
    return MessageCatalog.translate(t)


class TrButton(ttk.Button):
    def __init__(self, master=None, text: str = "", **kwargs):
        super().__init__(master=master, text=_tr(text), **kwargs)
        self.bind(
            "<<LanguageChanged>>", lambda *_: self.configure(text=_tr(self["text"]))
        )

Now, rebuilding all the widgets is not a very expensive task really for my app, but I was wondering if there was a better way? A way to have truly translation aware widgets?

CodePudding user response:

You wrote that virtual events need widget focus, but that isn't true. Did you find some documentation on the internet that says this is true?

The simplest solution is for your class to keep track of its own instances. You can then iterate over all instances to generate the virtual event.

It would look something like this:

class TrButton(ttk.Button):
    instances = []
    def __init__(self, master=None, text: str = "", **kwargs):
        TrButton.instances.append(weakref.proxy(self))
        super().__init__(master=master, text=_tr(text), **kwargs)
        self.bind(
            "<<LanguageChanged>>", lambda *_: self.configure(text=_tr(self["text"]))
        )

    @classmethod
    def language_changed(self):
        for widget in TrButton.instances:
            if widget.winfo_exists():
                widget.event_generate("<<LanguageChanged>>")

Whenever the language changes you call call TrButton.language_changed() to send the event to every instance of the class.

This code isn't perfect, there should be a mechanism to remove deleted widgets from the list of instances. However, it illustrates how you can loop over a list of widgets to send them the virtual event without them having to have focus.

  • Related