Reputation: 483
The objective:
I'm writing a Gui front-end for a Matplotlib-based library for nested samples (pip install anesthetic
if you want to have a look).
How I would go about it in C++: My previous experience with QML was a C++
program, where instead of going into QML to find a canvas to render to, I created a C++ object, registered it in QML's type system, and had it behave as a QtQuick controls widget. As far as I know this is the recommended way of doing things: have all the rendering be done in QML, and have all the business-end-logic in C++.
THe best approach and why I can't do it: This approach doesn't work here. AFAIK you can only implement custom QML using C++, and I need for the program to be pure-ish Python (for others to be able to maintain it) some JS is accessible and QML is pretty easy to understand and edit, so I had no objections (C++ was a hard no).
what I got working: I have a working implementation of what I want. It was all in one file. So, naturally I wanted to split the canvas to which I'm drawing to into a separate file: figure.qml
. Trouble is, I can't seem to find the object by that name whenever it's loaded from a separate file (the next step is to use a Loader
, because the Figure
is quite clunky).
I have a two-file project with view.qml
being the root, and a component in Figure.qml
.
The trouble is, it only works if I load the thing with objectName: "component"
in view.qml
and not in Component.qml
.
So how does one findChild
in Pyside
for an objectName that's in a different .qml
file?
MWE:
main.py
import sys
from pathlib import Path
from matplotlib_backend_qtquick.backend_qtquickagg import FigureCanvasQtQuickAgg
from matplotlib_backend_qtquick.qt_compat import QtGui, QtQml, QtCore
def main():
app = QtGui.QGuiApplication(sys.argv)
engine = QtQml.QQmlApplicationEngine()
displayBridge = DisplayBridge()
context = engine.rootContext()
qmlFile = Path(Path.cwd(), Path(__file__).parent, "view.qml")
engine.load(QtCore.QUrl.fromLocalFile(str(qmlFile)))
win = engine.rootObjects()[0]
if win.findChild(QtCore.QObject, "figure"):
print('success') # This fails
app.exec_()
view.qml
import QtQuick.Controls 2.12
import QtQuick.Windows 2.12
ApplicationWindow{
Figure {
}
}
Figure.qml
import QtQuick.Controls 2.12
import QtQuick 2.12
Component{
Rectangle{
objectName: "figure"
}
}
Upvotes: 2
Views: 1749
Reputation: 243897
Component
is used to define a QML element, it does not instantiate it, therefore you cannot access the object. Creating a Figure.qml is equivalent to creating a Component, and you are creating a Component inside another Component.
The solution is not to use Component:
Figure.qml
import QtQuick.Controls 2.12
import QtQuick 2.12
Rectangle{
objectName: "figure"
}
But it is not recommended to use objectName since, for example, if you create multiple components, how will you identify which component it is? o If you create the object after a time T, or use Loader or Repeater you will not be able to apply that logic. Instead of them it is better to create a QObject that allows obtaining those objects:
from PySide2 import QtCore
import shiboken2
class ObjectManager(QtCore.QObject):
def __init__(self, parent=None):
super().__init__(parent)
self._qobjects = []
@property
def qobjects(self):
return self._qobjects
@QtCore.Slot(QtCore.QObject)
def add_qobject(self, obj):
if obj is not None:
obj.destroyed.connect(self._handle_destroyed)
self.qobjects.append(obj)
print(self.qobjects)
def _handle_destroyed(self):
self._qobjects = [o for o in self.qobjects if shiboken2.isValid(o)]
# ...
object_manager = ObjectManager()
context = engine.rootContext()
context.setContextProperty("object_manager", object_manager)
qmlFile = Path(Path.cwd(), Path(__file__).parent, "view.qml")
engine.load(QtCore.QUrl.fromLocalFile(str(qmlFile)))
# ...
import QtQuick.Controls 2.12
import QtQuick 2.12
Rectangle{
Component.onCompleted: object_manager.add_qobject(this)
}
Upvotes: 3