Dynamic retranslation without reimplementing the changeEvent in every window/widget
-
I have a fairly big and comprehensive application that consists of a few dozent windows, custom widgets and dialogs that should allow dynamic retranslation as per customer request.
It all works fine however having to reimplement the changeEvent in every single class just feels wrong to me. However I also have no idea how to really do it. I thought about creating a custom widget which is used for inheritance that just calls a pure virtual function but then I do not really save a lot of code as I have to reimplement that function in every new class.
Is there any better way to do it?
My first idea:
#ifndef MYBASEWIDGET_H #define MYBASEWIDGET_H #include <QWidget> #include <QEvent> class MyBaseWidget : public QWidget { Q_OBJECT public: explicit MyBaseWidget(QWidget *parent = nullptr); protected: void changeEvent(QEvent * event) override; virtual void updateUI(void)=0; signals: }; #endif // MYBASEWIDGET_H
#include "mybasewidget.h" MyBaseWidget::MyBaseWidget(QWidget *parent) : QWidget{parent} { } void MyBaseWidget::changeEvent(QEvent * event) { // In the case of events changing the application language if (event->type() == QEvent::LanguageChange) { updateUI(); } QWidget::changeEvent(event); }
#ifndef MYDERIVEDFORMULARWIDGET_H #define MYDERIVEDFORMULARWIDGET_H #include "mybasewidget.h" namespace Ui { class MyDerivedFormularWidget; } class MyDerivedFormularWidget : public MyBaseWidget { Q_OBJECT public: explicit MyDerivedFormularWidget(QWidget *parent = nullptr); ~MyDerivedFormularWidget(); protected: void updateUI(void) override; private: Ui::MyDerivedFormularWidget *ui; }; #endif // MYDERIVEDFORMULARWIDGET_H
#include "myderivedformularwidget.h" #include "ui_myderivedformularwidget.h" MyDerivedFormularWidget::MyDerivedFormularWidget(QWidget *parent) : MyBaseWidget(parent), ui(new Ui::MyDerivedFormularWidget) { ui->setupUi(this); } MyDerivedFormularWidget::~MyDerivedFormularWidget() { delete ui; } void MyDerivedFormularWidget::updateUI(void) { ui->retranslateUi(this); }
-
Macro time!
#define MAKE_TRANSLATABLE(BaseClass) \ protected: \ void changeEvent(QEvent * event) override{ \ if (event->type() == QEvent::LanguageChange) \ ui->retranslateUi(this); \ BaseClass::changeEvent(event); \ } #define MAKE_TRANSLATABLE_F(BaseClass, TranslateMethod) \ protected: \ void changeEvent(QEvent * event) override{ \ if (event->type() == QEvent::LanguageChange) \ TranslateMethod(); \ BaseClass::changeEvent(event); \ } class TranslatableWidget : public QWidget{ MAKE_TRANSLATABLE(QWidget) }; class TranslatableCustomWidget : public QWidget{ MAKE_TRANSLATABLE_F(QWidget,customRetranslateMethod) protected: void customRetranslateMethod(); };
-
@Christian-Woznik
There may well be a better way (I don't use translation). But in general in Qt if you want to do something to every widget instead of hooking onto each one (e.g.changeEvent()
) one implements bool QObject::eventFilter(QObject *watched, QEvent *event) or maybe bool QObject::event(QEvent *e). My understanding is you can do this on some top-level widget (window/dialog/main window/whatever) and it will see all events that are destined for descendent widgets.Alternatively if you mean there is some global
QEvent::LanguageChange
event, can you catch that at the top level and visit all your widgets via QWidgetList QApplication::allWidgets() to do whatever you have to do to change their language? -
Macro time!
#define MAKE_TRANSLATABLE(BaseClass) \ protected: \ void changeEvent(QEvent * event) override{ \ if (event->type() == QEvent::LanguageChange) \ ui->retranslateUi(this); \ BaseClass::changeEvent(event); \ } #define MAKE_TRANSLATABLE_F(BaseClass, TranslateMethod) \ protected: \ void changeEvent(QEvent * event) override{ \ if (event->type() == QEvent::LanguageChange) \ TranslateMethod(); \ BaseClass::changeEvent(event); \ } class TranslatableWidget : public QWidget{ MAKE_TRANSLATABLE(QWidget) }; class TranslatableCustomWidget : public QWidget{ MAKE_TRANSLATABLE_F(QWidget,customRetranslateMethod) protected: void customRetranslateMethod(); };
-
@JonB Unfortunately that does not help me. I need to call ui->translate(*QWidget) in each of the widgets. And of course the UI is only defined as private inside the individual widgets / windows.
I would either have to set the UI elements to public, an idea I do not like at all, to then update it or I have to do the same with a virtual public function I would call during the iteration. -
@Christian-Woznik said in Dynamic retranslation without reimplementing the changeEvent in every window/widget:
ui->translate(*QWidget)
in each of the widgetsIf you say so. What does
ui->translate()
do that actually requires theui
instance/object to do its work?In any case, if @VRonin says to do it with a macro I'm sure he's right! Though how that helps you not have to do it (and subclass) against every different type of widget you use I don't see....?
-
@Christian-Woznik actually, if all your widgets are in a parent child relation, than calling
ui->retranslateUi(this);
on your top parent widget should recursively retranslate all children as well.exception are of cause all strings you have set inside your code "manually". Those have to be called again
-
@VRonin I guess that is an option to not always write it. I am just not sure how I feel about it yet as I am normally trying to avoid macros. I feel it makes it hard to maintain the code but I guess in this instance it is a valid usecase.
Thanks.
-
@J-Hilk are you sure? I have my mainWindow containing a stackedWidget. I just took a look inside the generated ui_mainwindow.h but the retranslateUI function does not call the translate function for the individual pages so how would it call it recursively? I am not even sure how it would be possible.
The only thing the function does it call QCoreApplication::translate for all of the set texts.
void retranslateUi(QMainWindow *MainWindow) { MainWindow->setWindowTitle(QCoreApplication::translate("MainWindow", "abc1", nullptr)); lbMachineType->setText(QCoreApplication::translate("MainWindow", "abc2", nullptr)); lbManufacturer->setText(QCoreApplication::translate("MainWindow", "abc3", nullptr)); lbManufacturerGraphic->setText(QString()); pbLang->setText(QString()); lbPageNR->setText("1000"); pbLeft->setText(QString()); pbRight->setText(QString()); pbSettings->setText(QString()); } // retranslateUi
@JonB
As you can see it needs to access all the ui elements individually to set the text. So it is unavoidable to have access to the ui instance of each widget. -
@Christian-Woznik said in Dynamic retranslation without reimplementing the changeEvent in every window/widget:
are you sure?
No, I was going of on memory and it seems I was wrong :(
-
@VRonin
Yeah I agree. It would require subclassing for each type. So QMainWindow, QWidget and QDialog in my situation. This is one of the reasons why I do not like it. I guess in the end the macro alternative is the best way. -
@JonB said in Dynamic retranslation without reimplementing the changeEvent in every window/widget:
If you say so. What does
ui->translate()
do that actually requires theui
instance/object to do its work??
I find it hard to believe/understand what you need to do which cannot be done by visiting each widget individually via
QWidgetList QApplication::allWidgets()
, without needingui
/private variables, like I said. -
@JonB Well then tell me how. The UI object is private inside the individual widget / window class. There is no public function by default that I could use to invoke the retranslate. I mean QT's documentation says you have to do it via the event.
I am also not happy with that solution but using macros does the trick and avoids writing the same code dozents of times. If you know a better alternative I would gladly take it.
-
@Christian-Woznik said in Dynamic retranslation without reimplementing the changeEvent in every window/widget:
@JonB Unfortunately that does not help me. I need to call
ui->translate(*QWidget)
in each of the widgetsThat's what you wrote. Turns out there is no such
ui->translate()
, and you meantui->retranslateUi()
. I am not a mind reader and didn't know that.Now that I look at that implementation from a designer-generated
ui_....h
file I can see that it not only visits each widget --- which could be done viaQApplication::allWidgets()
instead without needing to access anyui
private variable --- but it also holds the the original-language text for each widget which it callsQApplication::translate()
on. It's not how I would have implemented it :) though I can see it saves space and is convenient if you're not doing any translating. But it stops us from doing a "global retranslate all widgets to new language", which seems a shame.Given that I can now see why
ui->retranslateUi()
must actually be called again when language changes. And that must be done for each top-level/designed widget-type you have. And you have to define a separate class likeTranslatableWidget
for every one of them and use that instead of the class you would have had in Designer, such as via the macros and classes in the example above. Nasty :(Only you/ @VRonin know whether this might be achieved via a template in C++ rather than a macro, if you prefer that.
If you want to avoid the need to subclass to use @VRonin's approach --- which is what really bothers me --- you could presumably have each of your top-level/designed UI widgets define a slot which calls its private
ui->retranslateUi()
. And then have a single application-wide capture ofQEvent::LanguageChange
which emits a signal that you connect to each of these slots when you callui->setupUi()
in each top-level/designed widget. That is probably what I would do, as I don't want to have to sub-class each one just to make it translatable. @VRonin is welcome to comment/criticise if I have said anything erroneous. -
@JonB Well sorry for the confusion. I just wrote it wrongly there. But in my original post its correct as it was actual testcode and it compiled.
As for the global capture. I do not see where the subclassing is bad? I anyways have to do it for each window or custom dialog I want to show. I mean that is exactly what QT is doing when you add a new window, it creates a subclass from the QMainWindow, QWidget or QDialog class. And there I can just add the macro, it works fine. It is just that the usage of macros at my workplace is not really welcome but
I also would not know how to do it your way without subclassing as well? Or am I missing something there? Even the basic mainWindow is a subclass of QMainWindow.
-
@JonB said in Dynamic retranslation without reimplementing the changeEvent in every window/widget:
Only you/ @VRonin know whether this might be achieved via a template in C++ rather than a macro, if you prefer that.
You can use templates:
template <class T> class TranstatableW : public T{ static_assert(std::is_base_of<QWidget,T>::value,"Template argument must be a QWidget"); public: using T::T; protected: void changeEvent(QEvent * event) override{ if (event->type() == QEvent::LanguageChange) retranslate(); BaseClass::changeEvent(event); } virtual void retranslate() = 0; };
now all you need is to replace
class MyWidget : public QWidget
withclass MyWidget : public TranstatableW<QWidget>
but you'll always have to provide an overload forretranslate
so probably more code