I have 3 files (along with CMakeLists.txt and qrc):
main.cpp - C++
BDialog.qml - a "base" for my dialog (contains the property definition)
BEditorDialog.qml - a dialog with a label
They contain:
// main.cpp
#include <QApplication>
#include <QQuickView>
#include <QQmlContext>
int main( int argc, char **argv )
{
QApplication app( argc, argv );
QQuickView view;
view.rootContext()->setContextProperty( "myText", "WOW!" ); // expected to work but doesn't
view.setSource( QUrl( "qrc:/BEditorDialog.qml" ) );
view.show();
return app.exec();
}
//BDialog.qml
import QtQuick 2.6
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
Item {
// This property I want to set from C++
property string myText
}
// BEditorDialog.qml
import QtQuick 2.6
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
BDialog {
id: root
Label {
id: label
text: root.myText // doesn't work
}
}
If I assign initial value to the myText property:
property string myText: "MEH!"
this initial value is displayed, but how to assign it from C++ ? I suspect I should use the setInitialProperties member function, but I have only Qt 5.12 installed.
Thank you in advance.
You've mixed up context and object. You're setting the property "myText" on the QQmlContext which is not the root object of your application. To get the root object you can use rootObject() on your QQuickView and get a QQuickItem back. On that you can call setProperty().
This works
#include <QApplication>
#include <QQmlContext>
#include <QQuickItem>
#include <QQuickView>
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QQuickView view;
view.setSource(QUrl("qrc:/BEditorDialog.qml"));
// First set the source and then access the rootObject,
// otherwise it isn't created yet
view.rootObject()->setProperty("myText", "WOW!");
view.show();
return app.exec();
}
In addition to the answer of iam_peter, you can actually also use the context property in the way you have set it, but with the warning that a name collision is present. This might be more appropriate in bigger projects (it seems here you only want a way to show a dialog from C++ and be done with it)
Given a proper name, say 'myDialogText', you can use it as follows:
// BEditorDialog.qml
import QtQuick 2.6
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
BDialog {
id: root
Label {
id: label
text: myDialogText
}
}
Related
I'm using qt widgets on embedded device and have problem with virtual keyboard. Keyboard is shown as fullscreen and overlaps all app.
In article Virtual keyboard top black screen in Yocto is described hack how to solve this issue.
In short, you need to find the QQuickWindow with the keyboard and call setMask on this window. Then the area above the keyboard will be transparent
I have problem how to find QQuickWindow with virtual keyboard. I tried to use
QApplication::allWidgets()
but the window isn't here.
To obtain all the windows you can use QGuiApplication::allWindows() but that is not enough since the QtVirtualKeyboard window is not necessarily created at the beginning, so the visibleChanged signal of the QInputMethod must be used. I did not filter using the information from the QQuickWindow since in general the application could have others, instead it uses the name of the class to which the window belongs.
#include <QApplication>
#include <QWindow>
#include <cstring>
static void handleVisibleChanged(){
if (!QGuiApplication::inputMethod()->isVisible())
return;
for(QWindow * w: QGuiApplication::allWindows()){
if(std::strcmp(w->metaObject()->className(), "QtVirtualKeyboard::InputView") == 0){
if(QObject *keyboard = w->findChild<QObject *>("keyboard")){
QRect r = w->geometry();
r.moveTop(keyboard->property("y").toDouble());
w->setMask(r);
return;
}
}
}
}
int main(int argc, char *argv[])
{
qputenv("QT_IM_MODULE", QByteArray("qtvirtualkeyboard"));
QApplication a(argc, argv);
QObject::connect(QGuiApplication::inputMethod(), &QInputMethod::visibleChanged, &handleVisibleChanged);
// ...
Python version:
import os
import sys
from PySide2 import QtCore, QtGui, QtWidgets
# from PyQt5 import QtCore, QtGui, QtWidgets
def handleVisibleChanged():
if not QtGui.QGuiApplication.inputMethod().isVisible():
return
for w in QtGui.QGuiApplication.allWindows():
if w.metaObject().className() == "QtVirtualKeyboard::InputView":
keyboard = w.findChild(QtCore.QObject, "keyboard")
if keyboard is not None:
r = w.geometry()
r.moveTop(keyboard.property("y"))
w.setMask(QtGui.QRegion(r))
return
def main():
os.environ["QT_IM_MODULE"] = "qtvirtualkeyboard"
app = QtWidgets.QApplication(sys.argv)
QtGui.QGuiApplication.inputMethod().visibleChanged.connect(handleVisibleChanged)
w = QtWidgets.QLineEdit()
w.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
I have a solution for people who use Raspberry with PyQt5.
To complete the answer of eyllanesc because the Python version does not work with PyQt5, in fact, we have this problem :
TypeError: setMask(self, QRegion): argument 1 has unexpected type
'QRect'
To solve it :
def handleVisibleChanged():
if not QtGui.QGuiApplication.inputMethod().isVisible():
return
for w in QtGui.QGuiApplication.allWindows():
if w.metaObject().className() == "QtVirtualKeyboard::InputView":
keyboard = w.findChild(QtCore.QObject, "keyboard")
if keyboard is not None:
region = w.mask()
rect = [w.geometry()]
rect[0].moveTop(keyboard.property("y"))
region.setRects(rect)
w.setMask(region)
return
You can use findChildren with any class that inherits QObject such as QApplication. For example in main.cpp:
QApplication a(argc, argv);
QList<QQuickWindow *> wind = a.findChildren<QQuickWindow *>();
This will give you a list of pointers pointing to all the QQuickWindow in your application.
I need to parse a QML tree and get ids of all QML objects along the way which have it. I noticed that ids don't behave like normal properties (see the example below) – value returned from obj->property call is an invalid QVariant.
My question is – is there a way to retrieve object's id, even in some hacky (but reproductible) way?
Simplified example:
main.qml:
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
visible: true
Item {
id: howToGetThis
objectName: "item"
}
}
main.cpp:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QTimer>
#include <QDebug>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
QTimer::singleShot(1000, [&]() {
auto item = engine.rootObjects()[0]->findChild<QObject*>("item");
qDebug() << item->property("objectName");
qDebug() << item->property("id");
});
return app.exec();
}
Output:
QVariant(QString, "item")
QVariant(Invalid)
I think what you need is:
QString QQmlContext::nameForObject(QObject *object)
You can find the description here:
https://doc.qt.io/qt-5/qqmlcontext.html#nameForObject
Returns the name of object in this context, or an empty string if object is not named in the context. Objects are named by setContextProperty(), or by ids in the case of QML created contexts.
Based on comments received, a common pitfall is to call nameForObject using the wrong QQmlContext. (When that happens, you just the empty string.) To help with that, here is a more complete example:
QQuickItem* const focus_item = my_QQuickWindow->activeFocusItem();
if (!focus_item) {
fprintf(stderr, "no item has focus");
} else {
// There are many contexts in a hierarchy. You have to get the right one:
QQmlContext* const context = qmlContext(focus_item);
if (!context) {
// Unsure if this branch of code is even reachable:
fprintf(stderr, "item is not in any context?");
} else {
const QString focus_item_id = context->nameForObject(focus_item);
fprintf(stderr, "focus item: %s\n", focus_item_id.toStdString().c_str());
}
}
Having a QLineEdit with a plain vanilla QStringList QCompleter. I wonder if I can change the appearance of the dropdown (I want to have either a min. size or smaller scrollbar).
Clarification: I want to set it in a stylesheet, not in the code.
Summary of my findings so far:
Pretty good summary here: https://forum.qt.io/topic/26703/solved-stylize-using-css-and-editable-qcombobox-s-completions-list-view/12
I have to use QStyledItemDelegate and
give the popup a name for the qss selector
I have tried that and it does not work for me, but seems to work for others
A simple straight forward solution is to set the stylesheet of the QScrollBar used by the popup of the QCompleter. My knowledge of qss is little, so I don't know if you can set a minimum size that way, but you can always have a look at verticalScrollBar().
Here is some code for the qss way:
#include <QAbstractItemView>
#include <QCompleter>
#include <QLineEdit>
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QLineEdit edit;
edit.show();
QStringList completionList;
for (int a = 0 ; a < 10 ; ++a) {
completionList << QString("test%1").arg(a);
}
QCompleter completer(completionList);
edit.setCompleter(&completer);
QAbstractItemView *popup = completer.popup();
popup->setStyleSheet("QScrollBar{ width: 50px;}");
return a.exec();
}
I have two combo boxes, the data for the second one being determined by that of the first one. The number of strings in the second combo box varies from 2 to 4. If I:
select a new string in the first combo box and
the last choice is selected
in the second combo box with a longer list than the previous list of
that box,
the currentString in the second combo box remains and overrides the correct text
For instance, if I select Scubapro in the first combo box (4 options in 2nd box) and Smart in the second combo box (the 4th option), then select any other choice in the first combo box again (< 4 options in 2nd box), the entry in the second combo box remains "Smart", which is inappropriate. The correct list is, however, loaded into the 2nd combo box. Inspection of the underlying stringlist also suggests that it contains the correct data. The problem appears to be the visual updating of the second combo box. The heart of the algorithm comes from Stackoverflow and is the generator called each time text in combo box 1 changes.
What can one do to rectify this?
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickWindow>
#include <QQuickView>
#include <QQuickItem>
#include <QStringListModel>
#include <QQmlContext>
#include <QDebug>
QStringList dat1, dat2, dat3;
QStringList vendorList;
class Generator : public QObject
Q_OBJECT
QStringListModel * m_model;
public:
Generator(QStringListModel * model) : m_model(model) {}
Q_INVOKABLE void generate(const QVariant & val) {
m_model->removeRows(0,m_model->rowCount()); // This has no effect
if (QString::compare(val.toString(), QString::fromStdString("Mares"), Qt::CaseInsensitive) == 0) {
m_model->setStringList(dat1);
}
else {
if (QString::compare(val.toString(), QString::fromStdString("ScubaPro"), Qt::CaseInsensitive) == 0) {
m_model->setStringList(dat2);
}
else
m_model->setStringList(dat3);
}
};
int main(int argc, char *argv[])
{
QStringListModel model1, model2;
generator(&model2);
dat1 << "Puck" << "Nemo" << "Matrix";
dat2 << "Aladin" << "Meridian" << "Galilio" << "Smart";
dat3 << "D4" << "D6";
vendorList << "Mares" << "Scubapro" << "Suunto" << "Shearwater";
model1.setStringList(vendorList);
QGuiApplication app(argc, argv);
QQuickView view;
QQmlContext *ctxt = view.rootContext();
ctxt->setContextProperty("model1", &model1);
ctxt->setContextProperty("model2", &model2);
ctxt->setContextProperty("generator", &generator);
view.setSource(QUrl("qrc:main.qml"));
view.show();
return app.exec();
}
#include "main.moc"
Here is the QML:
import QtQuick 2.0
import QtQuick.Controls 1.0
Rectangle {
width: 400; height: 300
Text { text: "Vendor"; }
Text {
x: 200
text: "Product"; }
ComboBox {
id: box2
objectName: "productBox"
x:200; y:25; width: 180
model: model2
textRole: "display"
}
ComboBox {
y:25; width: 180
id: box1
model: model1
textRole: "display"
onCurrentTextChanged: {
generator.generate(currentText)
}
}
}
Any comments are highly appreciated.
The ComboBox item does not react to changes performed under the hood to the model.
There are a couple of solutions to work around it.
A possible one is to reassign the model to itself at the end of the signal handler, by using the statement:
model = model;
As from the documentation:
Changing the model after initialization will reset currentIndex to 0.
Otherwise, you can explicitly set currentIndex to your preferred value or, even better, to -1.
In fact, from the documentation we have that:
Setting currentIndex to -1 will reset the selection and clear the text label.
Threaded renderer is not working in the following code. I'm using Qt 5.4 on Arch linux-3.14 with proprietary drives.
---------- mytext.h -----------
#include <QObject>
class Thing : public QObject {
Q_OBJECT
Q_PROPERTY(int qm_no READ qm_no NOTIFY qm_noChanged)
public:
Q_INVOKABLE void loop();
int qm_no();
signals:
void qm_noChanged();
private:
int m_no;
};
---------- mytext.cpp ----------
#include "mytext.h"
#include <unistd.h>
int Thing::qm_no() {
return m_no;
}
void Thing::loop() {
while(true) {
m_no += 1;
emit qm_noChanged();
usleep(1000000);
}
}
--------- main.cpp -----------
#include <QQmlContext>
#include <QQuickView>
#include <QGuiApplication>
#include <QtQml>
#include "mytext.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
Thing myTh;
QQuickView view;
view.rootContext()->setContextProperty("qmyTh",&myTh);
view.setSource(QUrl::fromLocalFile("main.qml"));
view.show();
return app.exec();
}
------- main.qml ----------
import QtQuick 2.0;
Rectangle {
id: root
width: 200
height: 200
property var name: "test"
Text {
anchors.fill: parent
text: name
}
MouseArea {
anchors.fill: parent
onClicked: {
qmyTh.loop()
}
}
Connections {
target:qmyTh
onQm_noChanged: {
name = qmyTh.qm_no;
}
}
}
Explanation::
There is a classes Thing , with its object myTh. The function of class Thing is to provide an invokable function which here is loop. This function will then continuously update the m_no value and emit signal. Now the question is that how can I update the Text (name property) while the infinite loop is running which keeps on updating the value to be displayed ?
The code is correct for what concerns the QML part (now) and it works correctly. What is not correct is the C++ implementation. if you remove the while loop, leaving its content, and execute your code you'll see that the text is correctly updated.
The reason of such behaviour should be researched in the Qt quick render implementation. On certain platforms the render is not threaded by default. I guess you are working on Windows (see "Qt Quick" here). Hence, in a not threaded setting, by updating the variable and then sleeping, you are blocking the whole application, preventing the gui update.
You can use a QTimer to schedule the method execution at intervals, or set up a QML Timer for the exact same purpose.
Also, you don't need to save the new value in a temp variable (especially a var one which adds useless checks in this case). By setting an id inside the Text element you can directly set the text property. Here the revisited code:
import QtQuick 2.0;
Rectangle {
id: root
width: 200
height: 200
Text {
id: myText // the id!
anchors.fill: parent
text: "dummy" // dummy text || left empty || use "qmyTh.qm_no" (ensure a correct value is returned at creation time)
}
MouseArea {
anchors.fill: parent
onClicked: {
qmyTh.loop()
}
}
Connections {
target:qmyTh
onQm_noChanged: myText.text = qmyTh.qm_no // text directly set!
}
}
EDIT
It seems like the used render is threaded, hence my reasoning does not apply. There should be other problems. You can try to track down the problem exploiting the debugger and by adding console.info(...) statements in the JS handlers. Searching for the problem could be useful to track (possible) bugs in the libraries.
Depending on the background processing you have to run, I still think that using timers wouldn't be that bad. It really, truly depends on what you want to achieve. However, if you want to try threads, Qt documentation is full of explanations (as usual).
Have a look at this, this
and also absolutely this. Mind that a "moved" object (see the links) cannot be registered as a context property so you have to use one of the other ways to work with threads in a QML project.