PyQt5 QML Signal to Python Slot? - qt

How can a python method/slot be connected to a QML signal? It looks like QtObject.connect() used to work in PyQt4 but it's no longer available in PyQt5.
#Sample QML File (stack.qml)
import QtQuick 2.0
Rectangle {
MouseArea {
anchors.fill: parent
onClicked: {
// relay this to python
}
}
}
--
#Sample Python File
from PyQt5.QtCore import QUrl
from PyQt5.QtGui import QGuiApplication
from PyQt5.QtQuick import QQuickView
if __name__ == '__main__':
import os
import sys
app = QGuiApplication(sys.argv)
view = QQuickView()
view.setWidth(500)
view.setHeight(500)
view.setTitle('Hello PyQt')
view.setResizeMode(QQuickView.SizeRootObjectToView)
view.setSource(QUrl.fromLocalFile(os.path.join(os.path.dirname(__file__),'stack.qml')))
def on_qml_mouse_clicked(mouse_event):
print 'mouse clicked'
view.show()
qml_rectangle = view.rootObject()
# this technique doesn't work #############################
qml_rectangle.mousePressEvent.connect(on_qml_mouse_clicked)
sys.exit(app.exec_())
Some of the PyQT examples pass an object into the QML context via "setContextProperty" and then relay QML events to slots on that object but that approach seems roundabout. Is there a better way?

qml_rectangle.mousePressEvent is not a signal, it's an event handler that is called on mouse events, so you can't connect to it. You could just replace it with your handler function (qml_rectangle.mousePressEvent = on_qml_mouse_clicked), but that's not a very clean way of working with Qt.
The better way would be to define a signal in your qml file and emit it from the rectangle's onClicked handler:
import QtQuick 2.0
Rectangle {
signal clicked()
MouseArea {
anchors.fill: parent
onClicked: {
parent.clicked() // emit the parent's signal
}
}
}
Then you can just connect to it from your python code:
...
def on_qml_mouse_clicked():
print('mouse clicked')
qml_rectangle.clicked.connect(on_qml_mouse_clicked)
...

I would recommend sub classing QQuickView and setting a property on its root context, for example MainWindow. Now all you need to do is adding functions in that class with decorations such as #pyqtSlot('QString'), and then you can set the event handler with onClicked: MainWindow.FunctionName(Arguments_According_To_Decoration)
Then your main.py would look like this
#!/bin/env python3
# -*- coding: utf-8 -*-
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtCore import QUrl
from PyQt5.QtQuick import QQuickView
from PyQt5.QtWidgets import QApplication
import sys
class MainWindow(QQuickView):
def __init__(self):
super().__init__()
self.setSource(QUrl('sample.qml'))
self.rootContext().setContextProperty("MainWindow", self)
self.show()
#pyqtSlot('QString')
def Print(self, value):
print(value)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MainWindow()
sys.exit(app.exec_())
And sample.qml like so
import QtQuick 2.0
import QtQuick.Controls 2.2
Rectangle {
width: 200; height: 200
Button {
text: "print Hello World"
onClicked: MainWindow.Print('hello world')
}
}
You can find more information in the docs
http://pyqt.sourceforge.net/Docs/PyQt5/signals_slots.html

Related

External WM_DESTROY received for QWidgetWindow in embedded QWindow

I am trying to add a QWidget to a QQuick 2 application.
The approach is the following:
Create a QQuickItem derived object, which creates the QWidget in an initialize function. The QWindow is obtained from the QWidget and reparented to the "main" QQuickWindow.
During runtime the result is very good (it looks like a normal view, not an external window).
However when I close the main window I receive:
External WM_DESTROY received for QWidgetWindow(0x202b3a4ffe0, name="QWidgetClassWindow") , parent: QWindow(0x0) , transient parent: QWindow(0x0)
Any idea how to avoid this warning?
QML:
import QtQuick 2.12
import QtQuick.Controls 2.5
import QtQuick.Window 2.2
import QWidgetWrapper 1.0
import QtQml 2.12
Item {
id: root
QWidgetWrapper {
id: ewas
anchors.fill: parent
Component.onCompleted: {
initialize(Window.window)
}
}
}
Cpp:
#pragma once
#include <QQuickItem>
#include <QWindow>
class QWidgetWrapper : public QQuickItem
{
Q_OBJECT
public:
QWidgetWrapper(QQuickItem *parent = nullptr) : QQuickItem(parent) {}
~QWidgetWrapper() {
}
Q_INVOKABLE void initialize(QWindow *window) {
m_widget = new QWidget();
m_widget->setWindowFlags(Qt::FramelessWindowHint);
m_widgetWindow = QWindow::fromWinId(m_widget->winId());
m_widgetWindow->setParent(window);
m_widget->show();
}
private:
QWidget *m_widget = nullptr;
QWindow *m_widgetWindow = nullptr;
};

How to insert QML view in a QWidget

I am a beginner in QML and try to insert a QML View in QWdiget but I don't understand why it doesn't work.
Here is a simple example of my qml file (this is not the real file):
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Window 2.2
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.2
import QtQml.Models 2.1
ObjectModel {
id: itemModel
Rectangle {
color: "orange"
anchors.fill: parent
}
Rectangle {
color: "orange"
anchors.fill: parent
}
Rectangle {
color: "orange"
anchors.fill: parent
}
ListView {
id: my_list
anchors.fill: parent
model: itemModel
}
}
And this is how I load it in my mainwindow:
QQuickView *view = new QQuickView();
QWidget *container = QWidget::createWindowContainer(view, this);
container->setMinimumSize(200, 200);
container->setFocusPolicy(Qt::TabFocus);
view->setSource(QUrl("main.qml"));
ui->dockWidget->setWidget(container);
How could I insert my view in a QWidget?
At this time, I really need to use a QML view and because I need to use it in an already existing application, I can't just use a QML project.
Thanks a lot for your help and have a good day!
There exist a special QQuickWidget, dedicated to that exact purpose.
QQuickWidget *view = new QQuickWidget;
view->setSource(QUrl::fromLocalFile("myqmlfile.qml"));
view->show();
QQmlApplicationEngine *m_engine in MainWindow.h
in MainWindows.cpp set :
m_engine->addImportPath("qrc:/qml/imports");
m_engine->load(QUrl(QStringLiteral("qrc:/qml/main.qml")));
// m_engine->rootContext()->setContextProperty("mainWindows", this);
qDebug() << "Ok engine created";`
`QWindow *qmlWindow = qobject_cast<QWindow*>(m_engine->rootObjects().at(0));
QWidget *container = QWidget::createWindowContainer(qmlWindow, this);
container->setMinimumSize(200, 200);
container->setMaximumSize(1200, 900);
ui->verticalLayout->addWidget(container);`

QT 5.8 how does Webview do a request and how can I intercept it?

I am trying to pass QnetworkRequest to a webView located in my main.qml file instead of a "url". I pass the url by referecing the webView object and the setproperty function. However, haven't found the right function and really do not know where to start in order to either create a new function or modify the existing webView code in order to get this to work. Is there a way to edit the source code to the webView in the .qml file. Of course i am just learning the QT framework.
I have tried WebengineView but the Webview is MUCH faster in loading a page. and that is critical for the application
I guess the real question is how does Webview do a request and how can I intercept it?
main.qml
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtWebView 1.1
import QtQuick.Layouts 1.1
import QtQuick.Controls.Styles 1.2
ApplicationWindow {
flags: Qt.FramelessWindowHint
visible: true
x: 600
y: 400
width: 500
height: 500
title: webView.title
WebView {
id: webView
anchors.fill: parent
objectName: "webView"
//setting this value through main.cpp
// url: "https://www.google.com"
onLoadingChanged: {
if (loadRequest.errorString)
console.error(loadRequest.errorString);
}
}
}
main.cpp
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QtWebView::initialize();
QQmlApplicationEngine engine;
//How to Pass THIS request to the webview instead of url?
QNetworkRequest request;
request.setUrl(QUrl("http://google.com"));
request.setRawHeader("Accept-Charset", "UTF-8,*;q=0.5");
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
QObject *rootObject = engine.rootObjects().first();
QObject *qmlObject = rootObject->findChild<QObject*>("webView");
//Able to set the URL for the webView:
qmlObject->setProperty("url", "https://www.google.com" );
return app.exec();
}
AFAIK, all QML components use QNetworkAccessManager. You can register your own factory to have you own class handle the networking. You could overload the get method and change the header if the url matches google, or whatever you want to do:
class MyNetworkManager : public QNetworkAccessManager {
public:
QNetworkReply *QNetworkAccessManager::get(const QNetworkRequest &request) {
if (request.url.contains("google")) {
request.setRawHeader("Accept-Charset", "UTF-8,*;q=0.5");
}
return QNetworkAccessManager::get(request)
}
}
Of course, here request is const so you would have to construct a new request, but I hope this shows the basic idea!
Example on how to register your own NetworkAccessManagerFactory in main.cpp:
http://doc.qt.io/qt-5/qtqml-networkaccessmanagerfactory-example.html

QML import item from sibling folder

Suppose I have the following structure in my qml.qrc
<RCC>
<qresource prefix="/">
<file>main.qml</file>
<file>Style.qml</file>
</qresource>
<qresource prefix="/components">
<file>Test.qml</file>
</qresource>
main.qml
import QtQuick 2.5
import QtQuick.Window 2.2
import QtQuick.Layouts 1.0
import QtQuick.Controls 2.0
import "components"
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Test {
color: "red"
}
}
Test.qml
import QtQuick 2.0
import ".."
Rectangle {
width: Style.test * 200
height: 200
}
Style.qml
import QtQuick 2.0
pragma Singleton
QtObject {
property real test: 1.0
}
I have for some time tried to import Style.qml from Test.qml with import "../" but I keep getting ReferenceError: Style is not defined
I know the import statement is the cause for this and I have tried a few different variants of what "might" work but the docs are lacking on this and I am stuck. Help appreciated.
With the following code, I have no errors, and see the expected visual result. I'd suggest including a more complete example, if you can't spot your problem from this working sample. Note that all paths I give here are relative from the "root" project directory (so e.g. main.cpp is a file in the "root", components/Test.qml is located in a "components" subdirectory):
main.cpp:
#include <QQmlApplicationEngine>
#include <QGuiApplication>
int main(int argc, char **argv) {
QGuiApplication app(argc, argv);
QQmlApplicationEngine qAppEngine(QUrl("qrc:/main.qml"));
return app.exec();
}
main.qml
import "components"
Test {
}
components/Test.qml:
import ".."
Style {
}
Style.qml:
import QtQuick 2.6
import QtQuick.Window 2.2
Window {
visible: true
width: 500
height: 500
color: "red"
}
Built with the following:
test.pro:
QT += quick qml
RESOURCES += test.qrc
SOURCES += main.cpp
test.qrc:
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file>main.qml</file>
<file>Style.qml</file>
<file>components/Test.qml</file>
</qresource>
</RCC>

Using QtCreator 5.3 for Qt Quick UI project, how to link QML button resource to C++ function call

This is my first post on StackOverflow so please excuse any formatting mistakes I might have made.
I'm building a Qt Quick UI project using Qt Quick Controls 1.1 and I have a simple Button in my QML code that I would like to call into my C++ action class. I see a number of examples on this with earlier versions of Qt, but they do not seem to work in 5.3. I chose Qt Quick Controls 1.1 in the project settings. I know this must not be too complicated to do, but I can't seem to find examples using QtCreator 5.3
Here is my main.qml file:
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Layouts 1.1
ApplicationWindow {
id: parentWnd
visible: true
width: 640
height: 480
Action {
id: actionSend
onTriggered: console.log("SEND")
}
Button {
id: send
text: "Send Request"
action: actionSend
signal sendSignal()
}
}
Here is my main.cpp:
#include <QApplication>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
return app.exec();
}
Here is the action class where I would like the QML button to call "doSend":
#include<QDebug>
#include<QObject>
class Action : public QObject
{
Q_OBJECT
public:
Action();
public slots:
void doSend();
};
Finally here is my project file:
TEMPLATE = app
QT += qml quick widgets
SOURCES += main.cpp \
action.cpp
RESOURCES += qml.qrc
# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH =
# Default rules for deployment.
include(deployment.pri)
HEADERS += \
action.h
When I run this, I see the button and I see the logging of "SEND" to the console so I know the QML Action is setup correctly. Any help on how to make the Action call into my action class would be much appreciated!
There are three issues that you're running into here.
The first is that you haven't registered your Action class with QML in main.cpp:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
qmlRegisterType<Action>("StevesModule", 1, 0, "Action");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
return app.exec();
}
The second is that Action is a QML type in the Qt Quick Controls module. It doesn't know anything about your Action class in C++. If you want to use your Action class instead of Qt Quick Controls' Action type, you must import it in your QML file:
import StevesModule 1.0
The third is that you're not calling the doSend() slot anywhere. You can do this in the onClicked handler of Button:
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Layouts 1.1
import StevesModule 1.0
ApplicationWindow {
id: parentWnd
visible: true
width: 640
height: 480
Action {
id: actionSend
}
Button {
id: send
text: "Send Request"
onClicked: actionSend.doSend()
}
}

Resources