我正在创建一个简单的文件浏览器,并尝试实现像 macOS Finder 中那样的 Miller Columns。 Qt 提供了
QColumnView
和 QFileSystemModel
,这应该可以轻松组合并获得我想要的功能。
但是,如果您单击几级目录,然后单击当前目录上几级的空白区域,视图不会改变。突出显示将从您单击的文件夹中删除,但这是视觉效果的唯一更改。
作为我正在尝试做的示例,顶部是 Finder,底部是我当前的应用程序:
我已经尝试拦截尽可能多的
Signals
和Slots
,包括pressed
,clicked
,entered
,selectionModel().currentRowChanged
和UpdateRequest
来覆盖Qt的行为并设置正确的 currentIndex
,但尚未发现模型或视图中的状态信息对于设置正确的索引有用。
我还尝试记录每个事件(使用裸的
def event()
覆盖),当我在根文件夹中单击取消选择时,似乎甚至没有触发event
。
MCVE:
from pathlib import Path
from PySide6.QtWidgets import (
QApplication,
QFileSystemModel,
QColumnView,
QMainWindow,
)
class CustomColumns(QColumnView):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
class BrowserWindow(QMainWindow):
def __init__(self, location=None):
super().__init__()
if location is None:
location = Path.home()
location = Path(location)
self.setGeometry(625, 333, 1395, 795)
self.setWindowTitle(location.name)
self._root = str(location)
self._files = QFileSystemModel()
self._files.setRootPath(self._root)
self._view = CustomColumns()
self._view.setModel(self._files)
self.setCentralWidget(self._view)
def show(self):
super().show()
self._view.setRootIndex(self._files.index(self._root))
class FileBrowser(QApplication):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setQuitOnLastWindowClosed(False)
self.window = BrowserWindow(location=Path.home())
self.window.show()
def main():
app = FileBrowser()
app.exec()
if __name__ == "__main__":
main()
这里的问题是
QColumnView
实际上是由一系列 QListView
列组成。因此,在尝试处理大多数信号和事件时,列视图本身并没有多大帮助。解决此问题的一种方法是重新实现 createColumn 并在每个视图的视口上安装事件过滤器,以便可以跟踪相关的鼠标事件。这是必要的,因为点击空白区域时,QListView
的内置信号不会触发。
下面是一个基本演示,展示了实现此目的的一种方法。一个单独的
QObject
类用于监视事件,以免干扰列视图的现有事件过滤器。单击任何列的空白区域会将当前索引移动到单击的列:
from pathlib import Path
from PySide6.QtWidgets import (
QApplication, QFileSystemModel, QColumnView, QMainWindow, QListView,
)
from PySide6.QtCore import (
QEvent, QObject, Signal, QModelIndex,
)
class ColumnWatcher(QObject):
columnClicked = Signal(QModelIndex, bool)
def eventFilter(self, source, event):
try:
if (isinstance(view := source.parent(), QListView) and
event.type() == QEvent.MouseButtonPress):
index = view.indexAt(event.position().toPoint())
self.columnClicked.emit(view.rootIndex(), not index.isValid())
return super().eventFilter(source, event)
except RuntimeError:
return False
class CustomColumns(QColumnView):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._watcher = ColumnWatcher(self)
self._watcher.columnClicked.connect(self.handleClicked)
def createColumn(self, index):
view = super().createColumn(index)
view.viewport().installEventFilter(self._watcher)
return view
def handleClicked(self, root, blank):
if blank:
if root != self.rootIndex():
self.setCurrentIndex(root)
else:
self.setRootIndex(root)
self.setCurrentIndex(QModelIndex())
class BrowserWindow(QMainWindow):
def __init__(self, location=None):
super().__init__()
if location is None:
location = Path.home()
location = Path(location)
self.setGeometry(625, 333, 1395, 795)
self.setWindowTitle(location.name)
self._root = str(location)
self._files = QFileSystemModel()
self._files.setRootPath(self._root)
self._view = CustomColumns()
self._view.setModel(self._files)
self.setCentralWidget(self._view)
def showEvent(self, event):
self._view.setRootIndex(self._files.index(self._root))
class FileBrowser(QApplication):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setQuitOnLastWindowClosed(False)
self.window = BrowserWindow(location=Path.home())
self.window.show()
def main():
app = FileBrowser()
app.exec()
if __name__ == "__main__":
main()