If you've split your QML interface across multiple files — which is a great habit for keeping things organized — you may have run into a frustrating problem: you can't seem to connect your Python model to a TableView that lives inside a QML file loaded by a Loader. The TableView is right there in your QML, you've written a perfectly good QAbstractTableModel in Python, but the two just won't talk to each other.
Let's walk through why this happens and, more importantly, how to fix it.
Understanding the Problem
Here's the typical setup. You have a main.qml that uses a Loader to bring in another QML file:
// main.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
ApplicationWindow {
visible: true
width: 800
height: 600
Loader {
id: pagesOther
anchors.fill: parent
source: Qt.resolvedUrl("pages/other.qml")
}
}
And then inside other.qml, you have a TableView:
// pages/other.qml
import QtQuick 2.15
TableView {
id: activityTable
anchors.fill: parent
columnSpacing: 1
rowSpacing: 1
clip: true
model: ActivityTableModel {}
delegate: Rectangle {
implicitWidth: 100
implicitHeight: 50
Text {
text: display
}
}
}
And on the Python side, you're loading everything like this:
if __name__ == "__main__":
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
main = MainWindow()
engine.rootContext().setContextProperty("backend", main)
engine.load(os.path.join(os.path.dirname(__file__), "qml/main.qml"))
The problem? QML doesn't know what ActivityTableModel is. You've defined the model class in Python, but you haven't told QML how to find it. Writing ActivityTableModel {} in QML as if it were a built-in QML type won't work unless you've explicitly registered that Python type with the QML engine.
There are two clean approaches to solve this, and we'll cover both.
Approach 1: Use a Context Property (Simplest)
The most straightforward solution — and the one that works naturally across Loader boundaries — is to set your model as a context property on the engine. Context properties are available to all QML files loaded by that engine, including files pulled in by a Loader. This is key.
Define Your Model in Python
First, let's create a basic QAbstractTableModel subclass. If you haven't written one before, don't worry — the structure is quite formulaic. You just need to tell Qt how many rows and columns you have, and what data to return for each cell. For a more thorough introduction to the model/view architecture, see our PySide2 ModelView Architecture tutorial.
import sys
import os
from PySide2.QtCore import Qt, QAbstractTableModel, QModelIndex
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine
class ActivityTableModel(QAbstractTableModel):
def __init__(self, parent=None):
super().__init__(parent)
# Some sample data — a list of lists
self._data = [
["Running", "30 min", "Morning"],
["Swimming", "45 min", "Afternoon"],
["Cycling", "60 min", "Evening"],
]
self._headers = ["Activity", "Duration", "Time of Day"]
def rowCount(self, parent=QModelIndex()):
return len(self._data)
def columnCount(self, parent=QModelIndex()):
return len(self._data[0]) if self._data else 0
def data(self, index, role=Qt.DisplayRole):
if not index.isValid():
return None
if role == Qt.DisplayRole:
return self._data[index.row()][index.column()]
return None
This gives us a simple 3x3 table of activities. The data() method returns values for the display role, which is what our QML delegate uses when it references display.
Set the Model as a Context Property
Now, instead of trying to instantiate ActivityTableModel from within QML, we create it in Python and hand it to the QML engine:
if __name__ == "__main__":
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
# Create the model instance in Python
activity_model = ActivityTableModel()
# Make it available to ALL QML files as "activityModel"
engine.rootContext().setContextProperty("activityModel", activity_model)
engine.load(os.path.join(os.path.dirname(__file__), "qml/main.qml"))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
The line setContextProperty("activityModel", activity_model) makes the Python object available everywhere in QML under the name activityModel.
Use the Context Property in QML
Now update other.qml to use the context property instead of trying to create the model as a QML type:
// pages/other.qml
import QtQuick 2.15
TableView {
id: activityTable
anchors.fill: parent
columnSpacing: 1
rowSpacing: 1
clip: true
model: activityModel // <-- use the context property name
delegate: Rectangle {
implicitWidth: 100
implicitHeight: 50
Text {
text: display
}
}
}
Notice the change: instead of model: ActivityTableModel {} (which tries to construct a new QML type), we now have model: activityModel (which references the Python object we injected).
That's it. The Loader in main.qml doesn't need to change at all:
// main.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
ApplicationWindow {
visible: true
width: 800
height: 600
Loader {
id: pagesOther
anchors.fill: parent
source: Qt.resolvedUrl("pages/other.qml")
}
}
Because context properties are global to the engine's root context, the loaded QML file sees activityModel just fine.
Approach 2: Register the Python Type with QML
If you'd prefer to instantiate the model directly in QML — using the ActivityTableModel {} syntax from the original code — you can do that too. You just need to register the Python class as a QML type first.
Register the Type Before Loading QML
In your Python entry point, use qmlRegisterType to make your class available as a QML type:
from PySide2.QtQml import QQmlApplicationEngine, qmlRegisterType
# ... (ActivityTableModel class definition as before) ...
if __name__ == "__main__":
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
# Register the Python class as a QML type
qmlRegisterType(ActivityTableModel, "MyModels", 1, 0, "ActivityTableModel")
engine.load(os.path.join(os.path.dirname(__file__), "qml/main.qml"))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
The arguments to qmlRegisterType are:
| Argument | Meaning |
|---|---|
ActivityTableModel |
The Python class to register |
"MyModels" |
The QML import module name (you choose this) |
1 |
Major version |
0 |
Minor version |
"ActivityTableModel" |
The name to use in QML |
Import and Use the Type in QML
Now in other.qml, import your custom module and use the type directly:
// pages/other.qml
import QtQuick 2.15
import MyModels 1.0 // <-- import your registered module
TableView {
id: activityTable
anchors.fill: parent
columnSpacing: 1
rowSpacing: 1
clip: true
model: ActivityTableModel {} // <-- now QML knows what this is
delegate: Rectangle {
implicitWidth: 100
implicitHeight: 50
Text {
text: display
}
}
}
This approach creates a new instance of ActivityTableModel inside QML. It's useful when you want QML to own the lifecycle of the model, or when you might use the same model type in multiple places with different data.
Which Approach Should You Use?
Both approaches work well, but they serve slightly different purposes:
| Context Property | Registered Type | |
|---|---|---|
| Model created in | Python | QML |
| Best when | Python needs to control or update the model | QML manages its own data |
| Number of instances | One shared instance | As many as you create in QML |
| Loader-friendly | Yes, automatically | Yes, with the import |
For most cases where your Python backend is feeding data into the table — which is the common scenario — Approach 1 (context properties) is simpler and gives you easy access to the model from both Python and QML.
If you're building reusable components and want QML to be more self-contained, Approach 2 (registered types) is the cleaner architectural choice.
A Common Pitfall: Trying to Find Objects by ID
You might be tempted to try to "find" the TableView by its id from Python and set its model that way. Something like searching the QML object tree for an item named activityTable. While this can work in simple cases using findChild(), it's fragile and breaks easily with Loader because the loaded content may not exist yet when you try to access it. The Loader creates its content asynchronously (or at least independently), so the child object might not be in the tree when your Python code runs.
The two approaches above avoid this problem entirely. You're not searching for objects — you're either injecting a model that QML picks up automatically, or you're giving QML the ability to create the model itself. Both are more robust and idiomatic.
Summary
When you split your QML across multiple files and use a Loader, components inside the loaded file can't see Python types unless you've explicitly made them available. The two reliable ways to do this are:
- Set a context property with
engine.rootContext().setContextProperty()— this makes a Python object globally available to all QML loaded by that engine. - Register a Python type with
qmlRegisterType()— this lets QML create instances of your Python class directly, as long as you import the module.
Either way, your TableView will be able to use the model, regardless of which QML file it lives in or how deeply it's nested inside Loader components.
If you're looking to go further with QML and PySide2, check out our tutorials on building QtQuick/QML applications with Python and QML animations and transformations. For working with TableView models using numpy and pandas, see PySide TableView with numpy & pandas.
Bring Your PyQt/PySide Application to Market
Stuck in development hell? I'll help you get your project focused, finished and released. Benefit from years of practical experience releasing software with Python.