How to report errors from custom QML components? - qt

I've made a MyComponent QML component with some property:
Q_PROPERTY(PendingList* list MEMBER list)
and a function:
Q_INVOKABLE void send();
It can be created in QML:
MyComponent {
id: myComponent
}
When I call somewhere the myComponent.send() while the list property is not defined, how can I properly report the problem in the stderr? I'd like to see the *.qml file name and line number where send() was called or the line number of the myComponent creation.
Is there a proper way to maybe get the QML stack trace or generate QQmlError or throw an exception that will be handled by the QML engine?

Will have to use some private stuff.
QTDD14 - Categorized logging in QML - Giuseppe D'Angelo
Git repository with the slides and the code: qmllogging
Short Version
Add to CMakeLists.txt:
include_directories(${Qt5Quick_PRIVATE_INCLUDE_DIRS})
Add some QV8Engine stuff to your Q_INVOKABLE function or slot:
#include <QtQml>
#include <private/qv4engine_p.h>
#include <private/qv8engine_p.h>
void Logger::log(const QString &message)
{
const QV4::StackFrame frame = QV8Engine::getV4(m_engine)->currentStackFrame();
QMessageLogger(qPrintable(frame.source),
frame.line,
qPrintable(frame.function)).warning("%s", qPrintable(message));
}
Geting the engine for that function:
QQuickView view;
m_engine = view.engine();
Also set the message pattern to actually show the line number (somewhere in the beginning of main.cpp is fine):
qSetMessagePattern("%{file}:%{line} - %{message}");

Related

How may I fix my error prone on_foo_bar slots

I have eight list widgets in a tab widget. They have similar names, and Designer's "Go to slot" mechanism has made links to slots it names (in the "private slots" section of "mainwindow.h") like:
void on_SR0listWidget_itemClicked(QListWidgetItem *item);
I saw warnings that "Slots named on_foo_bar are error-prone," and now I need to change their names in order to discover if that's the cause of the weird behaviour I'm getting.
I tried simply refactoring the names, but that stopped the slot code from working. I used Designer's graphical "Edit Signal/Slot" mechanism and was able to connect a newly added list widget's "itemClicked(QListWidgetItem *item)" signal to a newly added slot, and it looks OK in the graphical representation, but there's no debug message (that I set up in the Slot function) when an item is clicked.
I also use those widgets' "entered" signals, so there will be at least 16 to fix. I would write a script if it could be done by parsing the relevant files.
One example of exactly how to rename one of my replacement slots and connect an "item clicked" or "entered" signal to it (and where it should go) would be a great help.
Signals/slots setup through the designer rely on the names of the widgets involved. This can lead to problems if the widget names are changed. There are times when using the designer method will lead to code that compiles but doesn't actually make the connections you expect. This is why you are getting that warning.
You can get more reliable behavior by connecting the signals and slots programmatically. For example, let's say you have a class header such as:
#include <QMainWindow>
namespace Ui {
class MyWindow;
};
class QListWidgetItem;
class MyWindow : public QMainWindow {
Q_OBJECT
public:
explicit MyWindow(QWidget* parent = nullptr);
~MyWindow() override;
private:
void handleItemClicked(QListWidgetItem* item); // this is your slot
Ui::MyWindow* ui;
};
You can connect the signal/slot together in the cpp file like this:
#include "MyWindow.h"
#include "ui_MyWindow.h"
#include <QDebug>
MyWindow::MyWindow(QWidget* parent)
: QWidget(parent),
ui(new Ui::MyWindow()) {
ui->setupUi(this);
// connect() has many overloads, but in this case we are passing:
// 1. the object emitting the signal
// 2. the signal being emitted
// 3. the object whose slot we want to call
// 4. the slot to connect to
connect(
ui->listWidget, &QListWidget::itemClicked,
this, &MyWindow::handleItemClicked);
}
MyWindow::~MyWindow() {
delete ui;
}
void MyWindow::handleItemClicked(QListWidgetItem* item) {
qDebug() << "item clicked";
}
You can still use the designer to layout your UI - but prefer to manage connections directly in code rather than through the designer.

Qt 5.7 - C++ Signal / QML Slot - Custom Meta type wrapped in QVariant

I'm trying to connect a C++ signal to a QML slot. The exchanged data is a constant reference to an object of my own class. Thereby this class is registered to the Qt meta system, before the QML file is loaded.
In code it looks like this:
The header of the custom meta type:
class QSong : public QObject
{
private:
Q_OBJECT
Q_PROPERTY( QString artist READ artist CONSTANT )
Q_PROPERTY( QString album READ album CONSTANT )
Q_PROPERTY( QString title READ title CONSTANT)
QString m_artist; ///< Data field containing the song's artist
QString m_album; ///< Data field containing the song's album
QString m_title; ///< Data field containing the song's title
public:
/* Constructors, copy assignment operator and getters */
};
Q_DECLARE_METATYPE(QSong)
The meta type registration (Before loading the qml file):
qmlRegisterType<QSong>("webradio.types", 1, 0, "Song");
The C++ signal:
void currentSongChanged( const QSong& song );
The QML slot:
Connections {
target: Global.MPD; // This is a tested to be working singleton
onCurrentSongChanged: {
print(song);
print(song.artist);
}
}
As far as I understood the procedure, this should be sufficient for transferring the data. Still whenever the signal is triggered with a valid QSong object, the output is the following:
qml: QVariant(QSong)
qml: undefined
I know similar questions have been asked before, however the solutions stated there have not worked for me.
For instance if I try assigning the slot parameter to a local property as suggested in this thread, the output states
Cannot assign QSong to QSong*
I'm using Qt 5.7.1 with the GCC at version 6.2.1.

How can I pass a QML object reference into Qt C++?

I am attempting to create something like jQuery's autocomplete as a widget in QML and Qt C++. Toward that end, I created a C++ AutoCompleteListener child of QObject and then register it with:
qmlRegisterType<AutoCompleteListener>(
"foo.AutoCompleteListener",0,1,"AutoCompleteListener");
Then, I instantiate the listener and the AutoCompleteForm like:
import QtQuick 2.5
import com.foo.AutoCompleteListener 0.1
Item {
AutoCompleteForm { id: autocomplete_form }
AutoCompleteListener { id: listener }
}
How can I pass a reference to the QML object AutoCompleteForm into AutoCompleteListener?
I tried passing the autocomplete_form field into:
Q_INVOKABLE void set_autocomplete_form(QQmlComponent *autocomplete_form);
on the onCompleted signal:
Item {
AutoCompleteForm {
id: autocomplete_form
Component.onCompleted: {
console.log("AutoCompleteForm completed");
listener.set_autocomplete_form(autocomplete_form);
}
}
AutoCompleteListener {
id: listener
Component.onCompleted: {
console.log("AutoCompleteListener completed");
}
}
}
However, the reference is a nullptr even though both AutoCompleteListener and AutoCompleteForm have been instantiated:
Instantiating AutoCompleteListener and parent is QObject(0x0)
qml: AutoCompleteListener completed
qml: AutoCompleteForm completed
Setting autocomplete_form = QObject(0x0)
How can I get a reference to the AutoCompleteForm or AutoCompleteListener's QML parent? I want to avoid crawling down the entire QML hierarchy with something like:
QObject* f = mView.rootObject();->findChild<QObject *>("AutoCompleteForm");
I plan to support having multiple AutoComplete widgets instantiated in parallel so a relative path (../AutoCompleteForm) to manipulate the QML objects seems better than having to crawl through the tree.
How can I pass a QML object reference into Qt C++?
You can't, the language was not designed for that. But you can get references from within C++ using findChild and findChildren. But read below for your real solution.
Also, I think your question is about an XY Problem. You have a problem X and you think Y solves it, so you ask for Y.
Correct solution for your original problem:
I am attempting to create something like jQuery's autocomplete as a
widget in QML and Qt C++.
In order to solve your real problem you need to use property bindings correctly. QML is a declarative language and self obsession with imperative programming makes it difficult to be straightforward.
Use this pattern for QML:
AutocompleteForm{
id: form
text: "Search here..."
suggestedTerms: helper.results
}
AutoCompleteHelper{
id: helper
searchFor: form.text
}
And for C++ implement
class AutoCompleteHelper : public QObject
{
Q_OBJECT
Q_PROPERTY(QString searchFor READ searchFor WRITE setSearchFor NOTIFY searchForChanged)
Q_PROPERTY(QStringList results READ results NOTIFY resultsChanged)
public:
AutoCompleteHelper() {}
virtual ~AutoCompleteHelper() {}
QString searchFor() const { return m_searchFor; }
QStringList results() const { return m_results; }
public slots:
void setSearchFor(QString searchFor)
{
if (m_searchFor == searchFor)
return;
m_searchFor = searchFor;
emit searchForChanged();
// 1. Search for it...
// 2. Some time later fill m_results
// 3. Then: emit resultsChanged()
}
signals:
void searchForChanged();
void resultsChanged();
private:
QString m_searchFor;
QStringList m_results;
};
Then you will see it magically works, because as soon as you change the form.text the binding sets the value in helper.searchFor, which then immediately fires the C++ slot where you can react even instantaneously, and in C++ the emission of resultsChanged() magically fills form.suggestedTerms.
You will find this pattern is extremely efficient so you'll even want to delay it by restarting a timer in C++. Furthermore, it is also beautifully declarative and clean.
How can I get a reference to the AutoCompleteForm or
AutoCompleteListener's QML parent?
The only alternative to rootObject()->findChild() appears to be QQmlProperty::read. There is several of read() function overloads so you can specify the context more precise. You have to provide the name for the object you would like to fetch and make it a property of some root object to start with. I like the general article on this subject of interfacing between QML and C++.
To accomplish precisely what you want or access the parent of certain known property you can try QQmlContext::parentContext together with QQmlProperty::read that accepts the context and see if the empty object name allows to resolve the object then.
You can pass it as a QVariant:
Q_INVOKABLE void QmlLink::pass_object(QVariant v)
{
YourObject* tempObject = (YourObject*) v.value<void *>();
...
}
qml:
my_bound_property.pass_object(the_object);

how to translate string added dynamicaly in Qapp with Qtranslator?

i trying to created a Qt application multilanguage with Qt linguist.
I place this code in an function of my MainWindow :
translator.load(":/lang/English");
qApp->installTranslator(&translator);
ui->retranslateUi(this);
With the QTranslator declare in my MainWindow.h and all my string i want translate enclose by tr() . But with that, all QObject added dynamicaly by the code of my MainWindow.cpp, for example the title of a QTableWidget, are not translated.
If i place an other translator in my main.cpp, all my string are translate but i must created language button in my application and so i can't place translator in main.cpp.
Do you have an idea to help me?
Thx for your answers.
Gat
When you add a translation in your application using qApp->installTranslator(& aTranslator) then all the following calls to QObject::tr() (and similar functions) will look up in the translator for a translated text. So you should call retranslateUi() after qApp->installTranslator(). Actually you might event not call it there, you may reimplement QWidget::changeEvent() and intercept any QEvent::LanguageChange event.
void MainWindow::changeEvent(QEvent *e)
{
QMainWindow::changeEvent(e);
switch (e->type()) {
case QEvent::LanguageChange:
// Someone called qApp->installTranslator() with a new translation.
// Let's update the user visible strings.
// This function was created by uic from the Designer form.
// It translates all user visible texts in the form.
ui->retranslateUi(this);
// This function must be written by you. It translates other user visible
// texts that are not in the form. See down for an example.
this->retranslateUi();
break;
default:
break;
}
}
ui->retranslateUi() just calls QObject::tr() for each user visible string in the ui. It is called automatically at the end of setupUi() and populates the form's widgets with translated text (have a look, it is defined by uic in ui_MainWindow.h file). You may want to take a similar approach with other user visible texts, like the title of a QTableWidget. All the strings are set in a function (named perhaps retranslateUi() for consistency) which is called at application starts (or, better, after the relevant widgets are created) and every time a new translations is loaded.
MainWindow::MainWindow(QWidget * parent)
: QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
// Creates other widgets, but do not set their user visible texts.
tableWidget = new QTableWidget(this);
...
someControl = new QLineEdit(this);
someOtherControl = new QSpinBox(this);
someModel = new MyModel(this);
...
// Ok, *now* we set their texts.
this->retranslateUi();
}
...
void MainWindow::retranslateUi()
{
// This function will be called (either manually or automatically by
// `changeEvent()`) when a new translator has installed, hence all the `tr()`
// calls will return the right translation for the last installed language.
QStringList labels;
labels.append(tr("First column"));
labels.append(tr("Second column"));
labels.append(tr("Third column"));
tableWidget->setHorizontalHeaderLabels(labels);
someControl->setText(tr("Control name"));
someOtherControl->setText(tr("Other control name"));
// Perhaps you have a model that need to be retranslated too
// (you need to write its `retranslateUi()` function):
someModel->retranslateUi();
...
}
Also, please note that if you are doing something like
void MainWindow::someFunction()
{
...
QTranslator translator;
translator.load(":/lang/English");
qApp->installTranslator(& translator);
...
}
as soon as that function returns the variable translator gets destroyed, so next time QObject::tr() is called the address stored with qApp->installTranslator(& translator) will be invalid. So you must allocate translator on the heap with new (and possibly deleting it with delete when you do not need it anymore). An exception is if you are doing that in main() before calling QCoreApplication::exec() since that function is blocking and will not return until application is closed.
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
...
QTranslator translator;
translator.load(":/lang/English");
app.installTranslator(& translator);
...
app.exec(); // This function will return only on application's exit.
// Hence `translator` will outlive the application, there is
// no need to worry about it.
}

Qt's moc causing "undefined reference to:" error

I am working on a simple drawing widget in Qt (all of the following is within one class). In the header file, I have defined
private:
QPointF translateToCanvas (QPointF input);
and in the CPP file I have defined
QPointF translateToCanvas (QPointF input) {
return input - QPointF(CANVAS_MARGIN_X, CANVAS_MARGIN_Y);
}
Somewhere else in the code, I call this with
QPointF newPoint = translateToCanvas(anotherPoint);
Whenever I compile, it gives me the error "undefined reference to `MyClass::translateToCanvas(QPointF)'", and this is happening inside the stuff that moc's generating and not actually my literal code.
What could be causing this error in Qt? (I'm using Qt Creator with Qt 4.5.)
This has nothing to do with Qt.
QPointF translateToCanvas (QPointF input) {
return input - QPointF(CANVAS_MARGIN_X, CANVAS_MARGIN_Y);
}
defines a standalone function named translateToCanvas, which has nothing to do with the private method you declared in your class, other than happening to have the same name. You want
QPointF MyClass::translateToCanvas (QPointF input) {
return input - QPointF(CANVAS_MARGIN_X, CANVAS_MARGIN_Y);
}

Resources