I'm iterating over selected indexes from a
QTreeViewconnected to aQFileSystemModeland passing each index tosetCurrentIndex(). Then I try to use the return value ofsetCurrentIndex()to build a file path. But I get aTypeErrorsayingNoneTypeis being provided. The indexes are definitelyQModelIndexobjects, so why doessetCurrentIndex()seem to returnNone?
This is a common source of confusion when working with Qt's model/view classes. The answer is straightforward once you see it: setCurrentIndex() doesn't return anything. In Python terms, that means it returns None.
So when you write:
index = self.treeView.setCurrentIndex(item)
...the variable index will always be None, regardless of what you pass in. That None then gets passed to Path(), which causes the TypeError you're seeing.
Why setCurrentIndex() returns None
In Qt, methods that start with set are setters — they change the state of a widget but don't return a value. setCurrentIndex() tells the tree view "make this index the current one," and that's all it does. It has no return value (or in C++ terms, it returns void).
This is a consistent pattern across Qt. For example:
setWindowTitle()— sets the title, returns nothingsetText()— sets text on a label, returns nothingsetCurrentIndex()— sets the current index, returns nothing
The corresponding getters are the ones that return values:
windowTitle()— returns the current titletext()— returns the current textcurrentIndex()— returns the current index
What you actually need
Looking at the original code, the goal is to get a file path from a QModelIndex so it can be used in a subprocess command. You don't need setCurrentIndex() at all for this. The QFileSystemModel can give you a file path directly from any QModelIndex using its filePath() method.
Here's the corrected approach. Instead of:
# Wrong: setCurrentIndex() returns None
index = self.treeView.setCurrentIndex(item)
index_path = Path(index)
You want:
# Correct: get the file path directly from the model
file_path = self.fileSystemModel.filePath(item)
index_path = Path(file_path)
The filePath() method on QFileSystemModel takes a QModelIndex and returns the corresponding file path as a string. That string can then be passed to Path() without any issues.
The fixed build_project method
Here's what the corrected build_project() method looks like:
def build_project(self, item):
if item and item.isValid():
file_path = self.fileSystemModel.filePath(item)
else:
file_path = self.fileSystemModel.filePath(self.treeView.currentIndex())
index_path = Path(file_path)
parent_dir = index_path.parents[0]
out = subprocess.run(
[
"bash", "-c",
f"source {self.default_dir}/cdk/opencpi-setup.sh -r "
f"&& cd {parent_dir} "
f"&& ocpidev build project {index_path.name}"
],
stderr=subprocess.STDOUT,
stdout=subprocess.PIPE,
)
self.console_output.on_update_text(out.stdout.decode())
Notice a couple of changes:
- We use
self.fileSystemModel.filePath(item)to convert theQModelIndexinto a file path string. - We also check
item.isValid()in addition to truthiness. AQModelIndexcan exist but be invalid (for example, a default-constructed index), so this is a good safety check.
Processing multiple selections
The add_item() method that iterates over selected indexes also deserves a closer look. When you call selectedIndexes() on a QTreeView, it returns one index per column for each selected row. If your model has four columns, selecting one row gives you four indexes.
To avoid processing the same file multiple times, you can filter to only the first column:
def add_item(self):
"""Add Project Explorer selected assets to Working Directory."""
indexes = self.project.treeView.selectedIndexes()
for index in indexes:
if index.column() == 0: # Only process each row once
self.project.build_project(index)
Alternatively, you can use the selection model to get just the selected rows:
def add_item(self):
"""Add Project Explorer selected assets to Working Directory."""
selection_model = self.project.treeView.selectionModel()
indexes = selection_model.selectedRows() # One index per row, column 0
for index in indexes:
self.project.build_project(index)
A complete working example
Here's a self-contained example that demonstrates selecting multiple items from a QTreeView backed by a QFileSystemModel and retrieving their file paths. You can run this, select some files or folders, and click the button to see the paths printed. If you're new to PyQt6, you might want to start with creating your first window before working through this example.
import sys
from pathlib import Path
from PyQt6.QtWidgets import (
QApplication,
QAbstractItemView,
QMainWindow,
QPushButton,
QTreeView,
QTextEdit,
QVBoxLayout,
QWidget,
)
from PyQt6.QtGui import QFileSystemModel
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("QTreeView File Path Example")
self.resize(800, 600)
# Set up the file system model
self.file_system_model = QFileSystemModel()
self.file_system_model.setRootPath(str(Path.home()))
# Set up the tree view
self.tree_view = QTreeView()
self.tree_view.setModel(self.file_system_model)
self.tree_view.setRootIndex(
self.file_system_model.index(str(Path.home()))
)
self.tree_view.setSelectionMode(
QAbstractItemView.SelectionMode.ExtendedSelection
)
# A button to process selections
self.process_button = QPushButton("Process Selected Items")
self.process_button.clicked.connect(self.process_selections)
# A text area to show output
self.output = QTextEdit()
self.output.setReadOnly(True)
# Layout
layout = QVBoxLayout()
layout.addWidget(self.tree_view)
layout.addWidget(self.process_button)
layout.addWidget(self.output)
container = QWidget()
container.setLayout(layout)
self.setCentralWidget(container)
def process_selections(self):
"""Get file paths from all selected rows."""
self.output.clear()
selection_model = self.tree_view.selectionModel()
indexes = selection_model.selectedRows()
if not indexes:
self.output.append("No items selected.")
return
for index in indexes:
# Get the file path directly from the model
file_path = self.file_system_model.filePath(index)
path = Path(file_path)
self.output.append(f"Name: {path.name}")
self.output.append(f"Full path: {path}")
self.output.append(f"Parent: {path.parent}")
self.output.append(f"Is file: {path.is_file()}")
self.output.append("---")
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
Select one or more items in the tree (hold Ctrl or Shift to select multiples), then click the button. Each selected item's path information will appear in the text area below.
Summary
The root cause of the original error was assigning the return value of setCurrentIndex() to a variable and then trying to use it as a file path. Since setCurrentIndex() is a setter with no return value, it always gives back None in Python.
To get a file path from a QModelIndex when using QFileSystemModel, call filePath() on the model itself, passing in the index. This gives you a clean string path that you can convert to a Path object or use however you need.
This example uses a QVBoxLayout to arrange the widgets — for more on organizing your UI, see the PyQt6 layouts tutorial. If you're interested in learning more about how Qt's model/view system works, take a look at the Model View Architecture guide. And for a deeper dive into tree views specifically, see the QTreeView tutorial.
Packaging Python Applications with PyInstaller by Martin Fitzpatrick
This step-by-step guide walks you through packaging your own Python applications from simple examples to complete installers and signed executables.