QQmlEngine retranslate not translating other StackView items - c++

I have a custom class where I need to save the new selected language and change the app language at the same time. An example based on the StackView sample project in QtCreator:
//main.cpp
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication app(argc, argv);
qmlRegisterType<CustomClass>("io.qt.CustomClass", 1, 0, "CustomClass");
QTranslator translator;
translator.load(":/EN.qm");
app.installTranslator(&translator);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
//customclass.h
class CustomClass : public QObject
{
Q_OBJECT
public:
explicit CustomClass(QObject *parent = nullptr) : QObject(parent) {}
Q_INVOKABLE void change(){
QTranslator translator;
QApplication::removeTranslator(&translator);
translator.load(":/CZ.qm");
QApplication::installTranslator(&translator);
//QQmlApplicationEngine * engine = qobject_cast<QQmlApplicationEngine *>(qmlEngine(this));
QQmlEngine *engine = QQmlEngine::contextForObject(this)->engine();
engine->retranslate();
}
};
//main.qml
...
CustomClass{id:test}
...
ItemDelegate {
text: qsTr("Page 1")
width: parent.width
onClicked: {
test.change()
drawer.close()
}
}...
//.pro file
QT += quick gui core
...
TRANSLATIONS = EN.ts CZ.ts
...
HEADERS += \
customclass.h
In this example clicking on Page 1 button should change the language.
My application is based on StackView and when I call the function with this code everything seems to work. The strings are translated. However when I push a new item on the stack (like opening a new section from menu), the strings there are back in the original language before the change. It's like the retranslate changes only the currently visible strings.
Anybody knows where the problem is? I suspect the engine not to be correctly acquired. It is a custom class I need to have registered (qmlRegisterType) to use in qml and I am not sure how to properly get the engine there (since engine is created in the main function).

I ran into the same problem. The solution I found is to use the new operator when creating the QTranslator object. This causes the object to be created in the heap memory instead of the stack. If it is in the stack memory, the QTranslator will be deleted after the function is finished (at least, that is how I understood it. I'm sure people with more C++ experience could probably explain it better).
So then your customclass code would look like this:
//customclass.h
class CustomClass : public QObject
{
Q_OBJECT
public:
explicit CustomClass(QObject *parent = nullptr) : QObject(parent) {}
Q_INVOKABLE void change(){
QTranslator *translator = new QTranslator(qApp);
if (m_previousTranslator) {
QApplication::removeTranslator(m_previousTranslator);
m_previousTranslator->deleteLater();
m_previousTranslator = nullptr;
}
translator->load(":/CZ.qm");
m_previousTranslator = translator;
QApplication::installTranslator(translator);
QQmlEngine *engine = QQmlEngine::contextForObject(this)->engine();
engine->retranslate();
}
private:
QTranslator *m_previousTranslator = nullptr;
};
Note that I added a private variable m_previousTranslator to do some memory management. I haven't tested this example code, but I have used very similar code in my project and it worked

I used a dirty trick to achieve the whole app retranslation without the need to get the engine in the class. It's not prefect but it works. I have added a loop in main like this:
int returnValue = 0;
do
{
QApplication app(argc, argv);
QTranslator translator;
translator.load(":/translation/"+langString+".qm"); //langString might be the "CZ" as in the question example
app.installTranslator(&translator);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/src/qml/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
returnValue = app.exec();
langString = settings.value("language").toString();
}
while(returnValue == TRANSLATION_RESTART);
return returnValue;
So in my custom class I simply exit the app using:
qApp->exit(TRANSLATION_RESTART);
And I also save the langString value. This way the app is basically restarted with the new language.
UPDATE: While this works it's better to store the engine and app into singleton class that handles translations using the approach mentioned in the question.

Related

Switching between windows. Qt Widgets ( 1 widget in memory )

For example I have 2 widgets and I press button on first widget. I need to delete first widget and create new widget.
How is it possible? I mean some structure for this. I used stackedwidgets, but pages from stackedwidgets located in memory. I need to avoid this.
void Window::on_registrationButton_clicked(){
ui->logWindow->hide();
ui->RegistrWindow->show();
}
As you are going to eliminate the object you can not do it within the same class the object belongs to, you have to do it outside of it, for example in the following code I created a signal that is triggered when the button is pressed, this I have connected it to a lambda function where the new object is created and the object that emits it is eliminated.
class LogWindow: public QWidget{
Q_OBJECT
public:
LogWindow(const QString &text, QWidget *parent=Q_NULLPTR):QWidget(parent){
setLayout(new QVBoxLayout);
btn = new QPushButton(text, this);
layout()->addWidget(btn);
connect(btn, &QPushButton::clicked, this, &LogWindow::customSignal);
}
signals:
void customSignal();
private:
QPushButton *btn;
};
class RegWindow : public QWidget{
[...]
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
LogWindow *log= new LogWindow("LogWindow");
RegWindow *reg;
QObject::connect(log, &LogWindow::customSignal, [&reg, &log](){
reg = new RegWindow("RegWindow");
reg->show();
log->deleteLater();
});
log->show();
return a.exec();
}
#include "main.moc"
The complete example can be found in the following link

How to access QML loaded items from the callback for qmlRegisterSingletonType?

In my main QML file, I have defined a MediaPlayer. To have a low level access to the media buffer (through QAudioProbe), I need to obtain a reference to its mediaObject. My C++ backend interfaces with UI through a class registered by qmlRegisterSingletonType.
main.cpp
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
qmlRegisterSingletonType<BackendInterface>("_", 0, 1, "Backend", backendInterfaceProvider);
QQmlApplicationEngine engine;
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
return app.exec();
}
And here's the callback:
static QObject *backendInterfaceProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
{
Q_UNUSED(engine)
Q_UNUSED(scriptEngine)
return new BackendInterface(/* need a QMediaPlayer* here*/);
}
Question
How to access the QML heirarchy when I am creating my back-end interface (i.e. BackendInterface)?
Since you have a singleton type object, it wil be created on first usage, at which time your MediaPlayer object might not exist yet.
Instead of trying to retrieve the MediaPlayer from QML, make QML "register" the object with C++, i.e. my passing the object to the singleton.
Something like
class BackgroundInterface : public QObject
{
Q_OBJECT
public:
Q_INVOKABLE void registerMediaPlayer(QObject *player);
};
and
MediaPlayer {
id: mediaPlayer
Component.onCompleted: Backend.registerMediaPlayer(mediaPlayer)
}

Qt button not appearing in main window

(Note: I started learning Qt yesterday and I did my search before asking this.)
After a bit of playing with Qt Designer I decided to make a more serious program, all programatically. Whereas before, simple taskes seemed.. simple, now, diaplaying a button is hell of a complicated because it doesn't show up.
main.cpp
int main(int argc, char * argv[])
{
QApplication app(argc, argv);
PixelPeep p;
p.show();
return app.exec();
}
pixelpeep.h - relevant part
class PixelPeep : public QMainWindow
{
Q_OBJECT
public:
explicit PixelPeep(QWidget *parent = 0);
signals:
public slots:
private:
QToolBar * toolBar;
QHBoxLayout * toolbarLayout;
QToolButton * addButton; // add new image
QScrollBar * zoomBar;
};
pixelpeep.cpp - relevant part
PixelPeep::PixelPeep(QWidget *parent) :
QMainWindow(parent)
{
resize(600,375);
toolBar = new QToolBar;
addButton = new QToolButton;
addButton->setGeometry(20,20,20,20);
toolBar->addWidget(addButton);
toolbarLayout = new QHBoxLayout;
toolbarLayout->addWidget(addButton);
}
After all this, I get an empty window.
Possible cause, AFAIK:
button would go out of scope after being created in the class constructor - it's not the case here because it's dynamically allocated and pointer by the private member addButton
not being in a layout or having size 0 - it's not the case since both of these were addressed in the code
What else could it be?
Sorry for such a noob question...
Call addToolBar(toolBar); inside the PixelPeep constructor.
You didn't set any icon on your button so it will appear as invisible. Hover over it and you will see it's there:

How to add a Menu Bar into a Window Frame? [QT with C++]

I'm a beginner in C++ and I started learning how to use QT components through code at MVS IDE. I still don't know if that was the best option to begin, but since I'm a java programmer, I made the path I made with Java (Swing components). So, my problem is, how to comunicate two class of my code, since in one I made the window frame and in the other I made my menu bar?
In java I would make something like:
JFrame frame = new JFrame();
JMenu menu = new JMenu();
frame.add(menu);
Anyway, This is my code:
#include "Header.h"
class MainWindow{
private:
QWidget *widget;
public:
void buildWindow(QApplication& app){
widget = app.desktop();
QMainWindow *main_window = new QMainWindow();
QWidget *mainWid = new QWidget(main_window);
MyMenuBar myMenuBar(mainWid);
main_window->setWindowState(mainWid->windowState() | Qt::WindowMaximized);
main_window->setWindowTitle("QT Trainning");
main_window->show();
}
};
class MyMenuBar:QMainWindow {
public:
MyMenuBar(QWidget* mainWid){
QAction *quit = new QAction("&Quit", this);
QMenuBar *menu = new QMenuBar(mainWid);
QMenu *file;
menu->addMenu(file);
file = menuBar()->addMenu("&File");
file->addAction(quit);
connect(quit, SIGNAL(triggered()), qApp, SLOT(quit()));
}
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MainWindow frame;
frame.buildWindow(app);
return app.exec();
}
I tryed to create an Instance of MenuBar inside the Window class but wans't so helpfull and to be honest most of the materials I found to deal with QT interface they supose that you are using the QT GUI...Any tips about how to solve the problem or what should I really do to practice C++??
Thanks in advance
You should specify access specifier for inheritance,otherwise default mode is public.
Also, if you are going to have all the classes in the same file the ordering is important(i think). In your case MyMenuBar should come before MainWindow. So, it is a better practice to have different components in different headers and then include them as necessary.
Here is the code for the case where you need two classes separately:
class TrainingMenu:public QMainWindow {
public:
TrainingMenu(QMenuBar *menubar){
QAction *quit = new QAction("&Quit", menubar);
QMenu *file;
file = menubar->addMenu("&File");
file->addAction(quit);
connect(quit, SIGNAL(triggered()), qApp, SLOT(quit()));
}
};
class MainWindows:public QMainWindow{
private:
TrainingMenu* _menu;
public:
MainWindows(QMainWindow *parent = 0):QMainWindow(parent) {
_menu=new TrainingMenu(MainWindows::menuBar());
this->setWindowTitle("Qt training");
this->setWindowState(Qt::WindowMaximized);
this->show();
}
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MainWindows window;
return app.exec();
}
This example should be good enough. You do the following:
Create a QMenu with the top widget as a parent
Add submenu QMenu instances to the root level menu

How can I send signals from C to QML? [duplicate]

I want to send a Signal from C++ to a Slot in my QML File.
I already got it working without and primitive type parameters, although if I want to send a QString to my QML Slot I get an error whilst connecting.
I connect in main.cpp
QObject *contentView = rootObject->findChild<QObject*>(QString("contentView"));
QObject::connect(&myObj, SIGNAL(finishedGatheringDataForItem(QString)),
contentView, SLOT(updateViewWithItem(QString)));
the relavant part of my qml File
Rectangle {
objectName: "contentView"
function updateViewWithItem(string) { console.log('got some Items'); } // slot
}
Error:
Object::connect: No such slot QDeclarativeRectangle_QML_2::updateViewWithItem(QString)
You should use Connections in this case (maybe it's the only way to connect).
Put your object myObj to QML file by setContextProperty
qmlVectorForm->rootContext()->setContextProperty("YourObject", myOb);
Your signal is
finishedGatheringDataForItem(QString signalString)
In QML file, add Connectios likes below:
Connections {
target: YourObject
onFinishedGatheringDataForItem: {
qmlString = signalString
}
}
I think it would be best if you check this tutorial:
http://doc.qt.io/qt-4.8/qtbinding.html
especially this section:
http://doc.qt.io/qt-4.8/qtbinding.html#receiving-signals
I think your mistake in this case might either be that you didn't declare it as a slot or you didn't make it invocable. Both options are explained in the Qt Tutorial.
Also, you need to use a QVariant in order to exchange data between C++ and QML.
You can also register types, e.g. Widgets and stuff, so that you can use them in QML as a "native" type like a rectangle. In most cases this is not recommended, except if you need some certain extern class or some data that you cannot display otherwise in your QML Interface.
The reason for the QVariant is the Script based approach of QML. The QVariant basically contains your data and a desription of the data type, so that the QML knows how to handle it properly. That's why you have to specify the parameter in QML with String, int etc.. But the original data exchange with C++ remains a QVariant
I have used the qmlRegisterType before, but it is a very inconvenient Solution for simple data types. It is rather used for more complex data, such as custom Widgets, Canvas or Video elements that QML does not natively support or extended QStandardItemModels . It is a more convenient way to exchange data between QML and C++ and does not need Signals or Slots in first instance, because the QStandardItemModel updates the GUI automatically. For using the QStandardItemModel you need to register the Type with qmlRegisterType.. . The Model can then be used in Model based Views such as the ListView etc.
I attached a tutorial for this topic, it describes how to use the QListModel.
http://doc.qt.io/qt-4.8/qdeclarativemodels.html
For those who also stumbled upon this question, I want to say that Everything is much simpler. You just need the signal from C++ to have QVariant arguments. For example:
QObject::connect(&recipient, SIGNAL(resTalk(QVariant)), engine.rootObjects().at(0)->findChild<QObject*>("winSettings"),
SLOT(showWithErrorNetwork(QVariant)));
My signal is declared like this:
signals:
void resTalk(QVariant res);
So I'm calling the signal:
emit resTalk(true); //For more complex types, use 'emit yourSignal(QVariant(yourArg))'
And here is the slot I have in QML:
function showWithErrorNetwork(isNoError=false) {
if(!isNoError) {
visible = true
warningText.text = "Network error. Check the data."
warningText.visible = true
}
}
Solution without Connections and any context is by connecting not signal-slot, but signal-signal. Found here.
Example code is as follows.
qml:
Window{
signal qmlSend(string textOut)
signal qmlReceive(string textIn)
onQmlReceive:{
console.log(textIn)
}
}
Header file of Background class contains
public signals:
void cppSend(QString textOut);
public slots:
void cppReceive(QString textIn);
And main.cpp connects them in this way:
1.From qml to cpp:
QObject::connect(qmlRootObject, SIGNAL(qmlSend(QString)),
backgroundObject, SLOT(cppReceive(QString)));
2.From cpp to qml:
QObject::connect(backgroundObject, SIGNAL(cppSend(QString)),
qmlRootObject, SIGNAL(qmlReceive(QString)));
I have tried a lot of solutions to succeed in just update QML from a C++ signal but many did not work.
This solution works and has been tested, it is based on this answer: https://stackoverflow.com/a/59502860/2486332 (by #Adriano Campos)
You can send data from C++ to qml using signals, like this:
main.cpp:
#include <QQmlContext>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
// Class init
YourClass yourObject;
// Embedding C++ Objects into QML with Context Properties
QQmlContext* ctx = engine.rootContext();
ctx->setContextProperty("yourObject", &yourObject);
return app.exec();
}
main.qml:
import QtQuick 2.6
Window {
id: mainWindow
Connections {
target: yourObject
onSignalData: {
console.log("Data: " + signal_param)
textToChange.text = "Changed to: " + signal_param
}
}
Text {
id: textToChange
text: "beforeChange"
}
}
yourClass.h:
class YourClass : public QObject
{
Q_OBJECT
signals:
// Signal from YourClass
void signalData(QString signal_param);
}
yourClass.cpp:
emit signalData("Hello QML"); // Signal from yourClass
A complete tutorial about "How to Expose a Qt C++ Class with Signals and Slots to QML" is available on this page: https://felgo.com/cross-platform-development/how-to-expose-a-qt-cpp-class-with-signals-and-slots-to-qml
Why not use rootContext?
in c++ side you have:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
//--------------------------------------------------------
#include <myClass.h>
//--------------------------------------------------------
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
//--------------------------------------------------------
myClass * myobj = new myClass(&app);
//--------------------------------------------------------
//--------------------------------------------------------
engine.rootContext()->setContextProperty("myobj",myobj);
//--------------------------------------------------------
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
}
and in qml side you have:
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
id: window
visible: true
width: 640
height: 480
title: qsTr("Hello World")
//--------------------------------------------------------
Component.onCompleted: {
myobj.onSomeSignal.connect(signalHandling)
}
//--------------------------------------------------------
//--------------------------------------------------------
function signalHandling(){
console.log("Signal emitted from c++ side")
}
//--------------------------------------------------------
}