Can't reference existing QML elements from python - python

I do not create the elements dynamically from python, I just intend to access existing elements already declared in the qml file.
I use findChild to get a QObject reference and connect to signals. This works fine, but when I try to be more specific and get a widget (not a QObject) like a QComboBox, I always get None. Am I missing something or findChild is not meant to be used with widgets?
This is my simple qml code:
Window {
visible:true
width:600
height:400
Button {
id: clickMe
objectName: "clickMe"
x: 244
y: 263
text: qsTr("click me!")
}
ComboBox {
id: comboBox
objectName: "comboBox"
x: 199
y: 157
width: 200
}
}
And this is my python code:
# qt imports
from PyQt5.QtQml import QQmlApplicationEngine
from PyQt5.QtWidgets import QApplication, QWidget, QInputDialog, QLineEdit, QMessageBox
from PyQt5.QtWidgets import QComboBox, QPushButton
from PyQt5 import QtWidgets, QtGui
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import QObject
app = QApplication(sys.argv)
engine = QQmlApplicationEngine()
engine.load('main.qml')
win = engine.rootObjects()[0]
win.show()
# this does work because it is QObject:
clickMe = win.findChild(QObject, "clickMe")
clickMe.clicked.connect(Foo)
# this does not work, I get None so can't add items to the combobox:
comboBox = win.findChild(QComboBox, "comboBox")
comboBox.addItem("a")
sys.exit(app.exec_())

First of all, the QML Item Combobox is not a QtWidgets QComboBox so you should not filter using that class, that's why your attempt fails. It is also bad practice to access QML elements from python (or C++) since the life cycle is not managed (for example objects of the same "id" can be deleted and recreated without notifying), instead you must create QObject that allow exchanging information, for example for the QComboBox you can create a model:
main.py
import os
import sys
# qt imports
from PyQt5.QtCore import pyqtProperty, pyqtSlot, QObject, QUrl
from PyQt5.QtGui import QGuiApplication, QStandardItem, QStandardItemModel
from PyQt5.QtQml import QQmlApplicationEngine
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
class Manager(QObject):
def __init__(self, parent=None):
super().__init__(parent)
self._model = QStandardItemModel()
#pyqtProperty(QObject, constant=True)
def model(self):
return self._model
#pyqtSlot()
def foo(self):
print("clicked")
def main():
app = QGuiApplication(sys.argv)
manager = Manager()
engine = QQmlApplicationEngine()
engine.rootContext().setContextProperty("manager", manager)
filename = os.path.join(CURRENT_DIR, "main.qml")
engine.load(QUrl.fromLocalFile(filename))
item = QStandardItem("a")
manager.model.appendRow(item)
sys.exit(app.exec_())
if __name__ == "__main__":
main()
main.qml
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
Window {
visible:true
width:600
height:400
Button {
id: clickMe
x: 244
y: 263
text: qsTr("click me!")
onClicked: manager.foo()
}
ComboBox {
id: comboBox
x: 199
y: 157
width: 200
model: manager.model
textRole: "display"
}
}
I recommend you also read my other answers:
How to connect Python and QML with PySide2?
How Can I Update a Qml Object's Property from my Python file?

Related

To turn back from 2nd window to 1st window (different files)

An application is being created with a large number of different windows.
The bottom line is that I am trying to get from the main window (by clicking button) to the client window. There is "Back" button in the clients window, which should return the user to the main window.
Both codes are in different files. The problem occurs at the stage of pressing the "Customers" button (Process finished with exit code -1073740791 (0xC0000409)).
Moreover, if everything is in one file, then everything works fine. But only here the program is not planned to be small, so I don't want to put a huge code in one file.
Welcome_Screen.py
import sys
from PyQt5.uic import loadUi
from PyQt5 import QtWidgets, QtGui
from PyQt5.QtWidgets import QDialog, QApplication
import Customers
class WelcomeScreen(QDialog):
def __init__(self):
super(WelcomeScreen, self).__init__()
loadUi('screens/welcomescreen.ui', self)
self.CustomerData.clicked.connect(self.go_to_CustomerData)
def go_to_CustomerData(self):
client = Customers.CustomerScreen()
widget.addWidget(client)
widget.setCurrentIndex(widget.currentIndex() + 1)
# main
app = QApplication(sys.argv)
app.setWindowIcon(QtGui.QIcon('Icons/MainIcon.png'))
app.setApplicationDisplayName('Bonus Program')
welcome = WelcomeScreen()
widget = QtWidgets.QStackedWidget()
widget.addWidget(welcome)
widget.setFixedWidth(600)
widget.setFixedHeight(475)
widget.show()
try:
sys.exit(app.exec_())
except:
print("Exiting")
Customers.py
import sys
from PyQt5.uic import loadUi
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtWidgets import QDialog, QApplication, QWidget, QMessageBox, QTableWidget, QTableWidgetItem, QTableView
from PyQt5.QtSql import QSqlDatabase, QSqlTableModel, QSqlQuery
from PyQt5.QtGui import QIcon
import Welcome_Screen as ws
import sqlite3
db_path = 'Bonus_db.sqlite'
class CustomerScreen(QDialog):
def __init__(self):
super(CustomerScreen, self).__init__()
loadUi("screens/customerscreen.ui", self)
self.back_btn.setIcon(QIcon('Icons/Back.png'))
self.back_btn.setIconSize(QtCore.QSize(45, 60))
self.back_btn.clicked.connect(self.go_Back_to_WelcomeScreen)
# self.SoldTos.clicked.connect(self.gotoSoldTos)
def go_Back_to_WelcomeScreen(self):
welcome = ws.WelcomeScreen()
ws.widget.addWidget(welcome)
ws.widget.setCurrentIndex(ws.widget.currentIndex() + 1)
Customer_app = QApplication(sys.argv)
cust_window = CustomerScreen()
cust_window.show()
sys.exit(Customer_app.exec_())

Qt module alternative for PyQt6

I'm just migrating my application from PyQt5 to PyQt6. I understand that the Qt module has been removed in Qt6. I have stuff like 'Qt.AlignCenter', 'Qt.ToolButtonTextUnderIcon', 'Qt.LeftToolBarArea', which are no longer working. Is there any alternative for this functionality in Qt6?
The Qt module only exists in PyQt5 (not in Qt5) that allowed access to any class or element of any submodule, for example:
$ python
>>> from PyQt5 import Qt
>>> from PyQt5 import QtWidgets
>>> assert Qt.QWidget == QtWidgets.QWidget
That module is different from the Qt namespace that belongs to the QtCore module, so if you want to access Qt.AlignCenter then you must import Qt from QtCore:
import sys
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QApplication, QLabel
def main():
app = QApplication(sys.argv)
w = QLabel()
w.resize(640, 498)
w.setAlignment(Qt.Alignment.AlignCenter)
w.setText("Qt is awesome!!!")
w.show()
app.exec()
if __name__ == "__main__":
main()
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QIcon
from PyQt6.QtWidgets import QApplication, QMainWindow, QStyle, QToolBar
def main():
import sys
app = QApplication(sys.argv)
toolbar = QToolBar()
toolbar.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextUnderIcon)
icon = app.style().standardIcon(QStyle.StandardPixmap.SP_DesktopIcon)
toolbar.addAction(icon, "desktop")
w = QMainWindow()
w.addToolBar(Qt.ToolBarAreas.LeftToolBarArea, toolbar)
w.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()
Currently, the AlignCenter and others can be found under AlignmentFlag enum:
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QPushButton, QVBoxLayout
def create_widget():
layout = QVBoxLayout()
button = QPushButton('Cancel')
layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(button)

How to control the checkbox using the Python itself, and not by a mouse click?

I created a checkbox using PyQT, which normally works using mouse clicks.
I want to know if there is a way with which I can uncheck and check the checkbox using the program itself, and not a mouse click.
Basically I want to check and uncheck the box 10 times in 20 seconds and display it happening.
Here is my code for just the checkbox:
import sys
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QMainWindow, QLabel, QCheckBox, QWidget
from PyQt5.QtCore import QSize
class ExampleWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setMinimumSize(QSize(140, 40))
self.setWindowTitle("Checkbox")
self.b = QCheckBox("Yes",self)
self.b.move(20,20)
self.b.resize(320,40)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
mainWin = ExampleWindow()
mainWin.show()
sys.exit( app.exec_() )
checked : bool
This property holds whether the button is checked
Only checkable buttons can be checked. By default, the button is unchecked.
The QTimer class provides repetitive and single-shot timers.
More ... https://doc.qt.io/qt-5/qtimer.html
import sys
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QMainWindow, QLabel, QCheckBox, QWidget
from PyQt5.QtCore import QSize
class ExampleWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setMinimumSize(QSize(140, 40))
self.setWindowTitle("Checkbox")
self.b = QCheckBox("Yes",self)
self.b.move(20,20)
self.b.resize(320,40)
self.num = 0
self.timer = QtCore.QTimer()
self.timer.setInterval(2000) # msec
self.timer.timeout.connect(self.update_now)
self.timer.start()
def update_now(self):
self.b.setChecked(not self.b.isChecked()) # +++
self.num += 1
if self.num == 10: self.timer.stop()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
mainWin = ExampleWindow()
mainWin.show()
sys.exit( app.exec_() )

Different screen between qmlscene and Python : Toolbar

I have a problem with Toolbar when I use the qml file with PyQt5. The result is not the seem : no background image when mouse is over, image no resize automatically.
I want to know if it's normal.
How can I do for have the same result with PyQt5
The result with qmlscene:
The result with Python:
Thanks you for your help.
File : _test.py
from PyQt5.QtCore import (
pyqtProperty,
pyqtSignal,
pyqtSlot,
QAbstractListModel,
QModelIndex,
QObject,
Qt,
QTimer,
)
from PyQt5.QtGui import QGuiApplication
from PyQt5.QtQml import QQmlApplicationEngine
from PyQt5.QtQuick import QQuickView
class MainWindow(QObject):
def __init__(self, parent=None):
super().__init__(parent)
if __name__ == "__main__":
import sys
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
engine.quit.connect(app.quit)
main_window = MainWindow()
engine.load("_test.qml")
if not engine.rootObjects():
sys.exit(app.exec_())
sys.exit(app.exec())
File : _test.qml
import QtQuick 2.4
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.3
import QtQuick.Controls.Styles 1.3
ApplicationWindow {
width: 500
height: 200
visible: true
ToolBar {
Layout.fillWidth: true
RowLayout {
anchors.fill: parent
ToolButton {
//width: parent.height
anchors.margins: 4
iconSource: "ico/abacus.png"
}
ToolButton {
width: parent.height
Image {
source: "ico/quitter.png"
anchors.fill: parent
anchors.margins: 4
}
}
ToolButton {
width: parent.height
iconSource: "ico/start.png"
anchors.margins: 4
}
ToolButton {
width: parent.height
Image {
source: "ico/stop.png"
anchors.fill: parent
anchors.margins: 4
}
}
}
}
}
Analyzing the source code of qmlscene and testing with the --apptype option I get the following:
qmlscene _test.qml --apptype gui
qmlscene _test.qml --apptype widgets
So analyzing the fundamental difference is that QApplicacion is being used and not QGuiApplication, so internally it should activate some flag that scales the icons.
Considering the above, the solution is:
from PyQt5.QtCore import QUrl
from PyQt5.QtWidgets import QApplication
from PyQt5.QtQml import QQmlApplicationEngine
if __name__ == "__main__":
import os
import sys
app = QApplication(sys.argv)
engine = QQmlApplicationEngine()
current_dir = os.path.dirname(os.path.realpath(__file__))
file = os.path.join(current_dir, "_test.qml")
engine.load(QUrl.fromLocalFile(file))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
According to the docs of Qt Quick Controls 1:
Note: We are using QApplication and not QGuiApplication in this
example. Though you can use QGuiApplication instead, doing this will
eliminate platform-dependent styling. This is because it is relying on
the widget module to provide the native look and feel.
So it seems that the scaling of the icons is part of the style of the platform.
Each type of project requires a QXApplication:
Console application: You can use any of the 3 types of QXApplication, but using QCoreApplication is the most optimal since the other QXApplication require that they have a window system that in that case is an unnecessary requirement.
QML Application: It requires at least one QGuiApplication, but for certain ones such as the need to use the styles of each platform it is necessary to use QApplication.
Qt Widgets Application: A QApplication is necessary because QWidgets use the styles of each platform.
The fact that sizes change, is this a problem of QtQuick.Controls 1?
Yes, one of the main differences between QQC1 and QQC2 is that the first one is developed to support desktop platforms so you use the styles, unlike the second one that is designed for embedded systems where the main objective is performance. For more information read Differences with Qt Quick Controls 1
Conclusions:
If you want your GUI made with QML to respect the styles of your desktop platform then you must use QQC1 with QApplication.
If your goal is that the style of your application does not respect the style of the desktop in addition to wanting more performance you should use QQC2 with QGuiApplication.

FileDialog shows different interface with Python3 vs QML

I have a python file and a qml file.
There is a button in the qml file to load a FileDialog. When I directly use qmlscene test.qml, the FileDialog is ok. But when I use python3 main.py, the FileDialog is strange, and I can't select a file by it. Please tell me how to fix it.
This is the normal file-dialog:
And this is the strange file-dialog:
The code is the following:
test.qml
import QtQuick 2.4
import QtQuick.Dialogs 1.2
import QtQuick.Controls 1.3
import QtQuick.Controls.Styles 1.3
import QtQuick.Layouts 1.1
Rectangle {
width: 400
height:30
Button {
id: save
text: "save"
onClicked: {
fileDialogLoader.item.open()
}
}
Loader {
id: fileDialogLoader
sourceComponent: fileDialog_com
}
Component{
id: fileDialog_com
FileDialog {
id: fileDialog
title: "select a file"
nameFilters: ["pdf files(*.pdf)"]
selectExisting: false
onAccepted: {
console.log(" you choose: "+ fileDialog.fileUrls)
}
}
}
}
main.py
#!/usr/bin/env python
# encoding: utf-8
from PyQt5.QtCore import QUrl, QObject, pyqtSlot
from PyQt5.QtGui import QGuiApplication
from PyQt5.QtQuick import QQuickView
class MyMain(QObject):
pass
if __name__ == '__main__':
path = 'test.qml'
app = QGuiApplication([])
view = QQuickView()
con = MyMain()
context = view.rootContext()
context.setContextProperty("con",con)
view.engine().quit.connect(app.quit)
view.setSource(QUrl(path))
view.show()
app.exec()
The "strange" file-dialog is a default implementation that has been written entirely in QML. Qt will use this as a fallback when it cannot create either the platform's native dialog or the built-in QFileDialog.
The reason why your example uses the qml fallback, is because you are using QGuiApplication, which is not widget-based. If you switch to QApplication, your example will work as expected:
# from PyQt5.QtGui import QGuiApplication
from PyQt5.QtWidgets import QApplication
...
# app = QGuiApplication([])
app = QApplication([])

Categories

Resources