Alex Petrosyan
Alex Petrosyan

Reputation: 483

QML findChild from a different component

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

Answers (1)

eyllanesc
eyllanesc

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

Related Questions