I am trying to drag and drop a folder into a a QTreeWidget and it will print out all the files in that folder and any other folder structures under that. I am very unfamiliar with QTreeWidget and in my example I was just trying to add data under the 'Name' column everything I tried i got different errors from model is a private method to argument 1 has unexpected type 'Tree'. How can I add data under each column?
import os
from PyQt5.QtWidgets import QFileSystemModel, QApplication, QAbstractItemView, \
QMainWindow, QPushButton, QTreeWidget, QTreeWidgetItem
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class Tree(QTreeWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setAcceptDrops(True)
self.resize(600, 600)
self.setHeaderLabels(['Name', 'Size', 'Upload Status'])
model = QFileSystemModel()
model.setRootPath(QDir.currentPath())
model.setReadOnly(False)
self.setSelectionMode(self.SingleSelection)
self.setDragDropMode(QAbstractItemView.InternalMove)
self.setDragEnabled(True)
self.setAcceptDrops(True)
self.setDropIndicatorShown(True)
def dragEnterEvent(self, event):
if event.mimeData().hasUrls:
event.accept()
else:
event.ignore()
def dragMoveEvent(self, event):
if event.mimeData().hasUrls():
event.setDropAction(Qt.CopyAction)
event.accept()
else:
event.ignore()
def dropEvent(self, event):
if event.mimeData().hasUrls():
event.setDropAction(Qt.CopyAction)
event.accept()
for url in event.mimeData().urls():
if url.isLocalFile():
rootPath = url.toString()[8:]
if os.path.isfile(rootPath):
address = rootPath
item = QTreeWidgetItem()
item.setData(self, 0, 0, address)
self.addItem(item)
else:
for path, subdirs, files in os.walk(rootPath):
for file in files:
address = str(url.toLocalFile())
path = path.replace("\\", "/")
directory_name = path.replace(rootPath, "")
displayName = directory_name + '/' + file
address += displayName
item = QTreeWidgetItem()
item.setData(self, 0, 0, address)
self.addItem(item)
else:
event.ignore()
class Main(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('Login Form')
self.resize(1200, 600)
self.treeBox = Tree(self)
self.btn = QPushButton('Upload', self)
self.btn.setGeometry(850, 400, 200, 50)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
w = Main()
w.show()
sys.exit(app.exec_())
For example if I drop a folder that contains file1, file2, subfolder, and subfolder2 the treewidget will display the root folder with each file as a child. Along with the subfolders in the root will also display their children(files inside the subfolder).
Related
I'm trying to create a drag 'n drop label which accepts only PDF files.
To do that I try using the mimeData().hasFormat.
def dragEnterEvent(self, event):
if event.mimeData().hasFormat(application/pdf):
event.accept()
else:
event.ignore()
def dragMoveEvent(self, event):
if event.mimeData().hasFormat(application/pdf):
event.accept()
else:
event.ignore()
def dropEvent(self, event):
if event.mimeData().hasFormat(application/pdf):
event.setDropAction(Qt.CopyAction)
file_path = event.mimeData().urls()[0].toLocalFile()
self.set_path(file_path)
event.accept()
else:
event.ignore()
It does not seem to work though.
I tried it with mimeData().hasImage for an image file and it works.
Does .hasFormat support pdf files?
You can get the mimetype based on the url and verify that it corresponds to a pdf:
from PyQt5.QtCore import QMimeDatabase
from PyQt5.QtWidgets import QApplication, QLabel
class Label(QLabel):
def __init__(self, parent=None):
super().__init__(parent)
self.setAcceptDrops(True)
def dragEnterEvent(self, event):
if self.find_pdf(event.mimeData()):
event.accept()
else:
event.ignore()
def dragMoveEvent(self, event):
if self.find_pdf(event.mimeData()):
event.accept()
else:
event.ignore()
def dropEvent(self, event):
urls = self.find_pdf(event.mimeData())
if urls:
for url in urls:
print(url.toLocalFile())
event.accept()
else:
event.ignore()
def find_pdf(self, mimedata):
urls = list()
db = QMimeDatabase()
for url in mimedata.urls():
mimetype = db.mimeTypeForUrl(url)
if mimetype.name() == "application/pdf":
urls.append(url)
return urls
def main():
app = QApplication([])
label = Label()
label.resize(640, 480)
label.show()
app.exec_()
if __name__ == "__main__":
main()
I'm making a custom QTreeView with QFileSystem model, and I have a MouseMoveEvent set up to print the path of the item that is hovered over.
I'm way down the rabbit hole and doing all kinds of weird things to make this work.
Here is the latest minimal reproducible code:
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
class MainWindow(QWidget):
def __init__(self):
QWidget.__init__(self)
self.resize(500, 300)
self.layout = QVBoxLayout()
self.setLayout(self.layout)
self.myList = CustomTreeWidget()
self.myList.model.setRootPath("/Volumes/Home/User/Desktop/testsrc")
self.myList.setObjectName("/Volumes/Home/User/Desktop/testsrc")
self.layout.addWidget(self.myList)
class CustomTreeWidget(QTreeView):
def __init__(self):
super().__init__()
self.model = QFileSystemModel()
self.model.setFilter(QDir.NoDotAndDotDot | QDir.Files)
self.setModel(self.model)
self.setAlternatingRowColors(True)
self.setDragDropMode(QAbstractItemView.DragDrop)
self.setIndentation(0)
self.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.setDragEnabled(True)
self.setAcceptDrops(True)
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.setMouseTracking(True)
self.model.directoryLoaded.connect(self._runwhenloaded)
def _runwhenloaded(self):
self.setRootIndex(self.model.index(self.objectName()))
self.model.setRootPath(self.objectName())
def mouseMoveEvent(self, event):
prev = ""
if self.selectedIndexes():
prev = self.selectedIndexes()[0]
x = event.x()
y = event.y()
self.setSelection(QRect(x, y, 1, 1), QItemSelectionModel.ClearAndSelect)
self.setCurrentIndex(self.selectedIndexes()[0])
print(self.model.filePath(self.currentIndex()))
if prev:
self.setCurrentIndex(prev)
# pos = QCursor.pos()
# indexat = self.indexAt(pos).row() # why -1?
# print(indexat) # why -1?
# print(self.indexAt(pos).row())
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
Obviously this example is not proper at all, as it destroys multiple selections and scrolls to the previously selected item whenever the mouse moves, and just a hack in general.
I've gone through many iterations and read everything I could put my hands on but I can't figure it out.
The closest answer seems to be HERE, but it's in C and I don't understand it.
So the question is: How to print the file path when the mouse hovers over an item in this QTreeView?
A possible solution is to create an event filter that tracks the hover events and, according to that information, emits a signal that has the QModelIndex:
import sys
from PyQt5.QtCore import (
pyqtSignal,
pyqtSlot,
Qt,
QDir,
QEvent,
QModelIndex,
QObject,
QPersistentModelIndex,
QStandardPaths,
)
from PyQt5.QtWidgets import (
QAbstractItemView,
QApplication,
QFileSystemModel,
QMainWindow,
QTreeView,
)
from PyQt5 import sip
class HoverViewHelper(QObject):
hovered = pyqtSignal(QModelIndex)
def __init__(self, view):
super().__init__(view)
self._current_index = QPersistentModelIndex()
if not isinstance(view, QAbstractItemView):
raise TypeError(f"The {view} must be of type QAbstractItemView")
self._view = view
self.view.viewport().setAttribute(Qt.WA_Hover)
self.view.viewport().installEventFilter(self)
#property
def view(self):
return self._view
def eventFilter(self, obj, event):
if sip.isdeleted(self.view):
return True
if self.view.viewport() is obj:
if event.type() in (QEvent.HoverMove, QEvent.HoverEnter):
p = event.pos()
index = self.view.indexAt(p)
self._update_index(index)
elif event.type() == QEvent.HoverLeave:
if self._current_index.isValid():
self._update_index(QModelIndex())
return super().eventFilter(obj, event)
def _update_index(self, index):
pindex = QPersistentModelIndex(index)
if pindex != self._current_index:
self._current_index = pindex
self.hovered.emit(QModelIndex(self._current_index))
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.model = QFileSystemModel()
self.model.setRootPath(QDir.rootPath())
self.view = QTreeView()
self.view.setModel(self.model)
path = QStandardPaths.writableLocation(QStandardPaths.DocumentsLocation)
self.view.setRootIndex(self.model.index(path))
self.setCentralWidget(self.view)
helper = HoverViewHelper(self.view)
helper.hovered.connect(self.handle_hovered)
#pyqtSlot(QModelIndex)
def handle_hovered(self, index):
if not index.isValid():
return
path = self.model.filePath(index)
print(f"path: {path}")
def main():
app = QApplication(sys.argv)
w = MainWindow()
w.resize(640, 480)
w.show()
app.exec_()
if __name__ == "__main__":
main()
Im using QFileSystemModel and QTreeView in my application(Pyqt5). I was looking for a way which I can clear file selection when press on the white blank area... to be more specific im need a way to know how to check whether the user press on the blank area in order to not choose at any file.
You have to detect the click with an event filter and then determine if a valid QModelIndex is associated, and in the case of the empty area it is not associated with a QModelIndex:
import os
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.model = QtWidgets.QFileSystemModel(self)
self.view = QtWidgets.QTreeView()
self.setCentralWidget(self.view)
self.view.setModel(self.model)
self.view.viewport().installEventFilter(self)
path = CURRENT_DIR
self.model.setRootPath(path)
self.view.setRootIndex(self.model.index(path))
def eventFilter(self, obj, event):
if (
obj is self.view.viewport()
and event.type() == QtCore.QEvent.MouseButtonDblClick
):
ix = self.view.indexAt(event.pos())
if not ix.isValid():
print("empty area")
self.view.clearSelection()
return super(MainWindow, self).eventFilter(obj, event)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
I am trying to detect internal move signal from QListWidget while I have my drag and drop effect implement. But currently with my code, because I am self-handling dragEnterEvent, dragMoveEvent, and moveEvent, the internal move signal is being ignored. Any way I can get around that issue? Below is a simple code to duplicate the issue.
You can drag external items to the list widget but you now cannot move the items around.
import sys, os
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QLineEdit, QListWidget, \
QVBoxLayout, QHBoxLayout, QAbstractItemView
from PyQt5.QtCore import Qt
class ListWidget(QListWidget):
def __init__(self, parent=None):
super().__init__(parent=None)
self.setAcceptDrops(True)
self.setStyleSheet('''font-size:25px''')
def dragEnterEvent(self, event):
# print(event.mimeData().urls())
# print(dir(event.mimeData()))
if event.mimeData().hasUrls():
event.accept()
else:
event.ignore()
def dragMoveEvent(self, event):
if event.mimeData().hasUrls():
event.setDropAction(Qt.CopyAction)
event.accept()
else:
print('y')
event.ignore()
def dropEvent(self, event):
if event.mimeData().hasUrls():
event.setDropAction(Qt.CopyAction)
event.accept()
pdfFiles = []
for url in event.mimeData().urls():
if url.isLocalFile():
pdfFiles.append(str(url.toLocalFile()))
self.addItems(pdfFiles)
else:
event.ignore()
class AppDemo(QWidget):
def __init__(self):
super().__init__()
self.resize(1200, 800)
data = ['Microsoft', 'Facebook', 'Google']
mainLayout = QHBoxLayout()
self.lst = ListWidget()
self.lst.addItems(data)
self.lst.setStyleSheet('''
font-size:30px
''')
self.lst.setDragDropMode(QAbstractItemView.InternalMove)
mainLayout.addWidget(self.lst)
self.setLayout(mainLayout)
if __name__ == '__main__':
app = QApplication(sys.argv)
demo = AppDemo()
demo.show()
sys.exit(app.exec_())
Instead of invoking event.ignore() when the mimedata has no urls, call the parent's method so the drag & drop will be handled like the default QListWidget. Also, another way to determine the source of the drag operation is with event.source().
def dragEnterEvent(self, event):
if event.mimeData().hasUrls():
event.accept()
else:
super().dragEnterEvent(event)
def dragMoveEvent(self, event):
if event.mimeData().hasUrls():
event.setDropAction(Qt.CopyAction)
event.accept()
else:
super().dragMoveEvent(event)
def dropEvent(self, event):
if event.mimeData().hasUrls():
event.setDropAction(Qt.CopyAction)
event.accept()
pdfFiles = []
for url in event.mimeData().urls():
if url.isLocalFile():
pdfFiles.append(str(url.toLocalFile()))
self.addItems(pdfFiles)
else:
super().dropEvent(event)
I am using PyQt5, and I want to make a Drag&Drop system.
I got the code on this post : PyQT4: Drag and drop files into QListWidget
When I Run, I got the following error : AttributeError: 'MainForm' object has no attribute 'connect'
The code:
import sys
import os
from PyQt5.Qt import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
class TestListView(QListWidget):
def __init__(self, type, parent=None):
super(TestListView, self).__init__(parent)
self.setAcceptDrops(True)
self.setIconSize(QSize(72, 72))
def dragEnterEvent(self, event):
if event.mimeData().hasUrls:
event.accept()
else:
event.ignore()
def dragMoveEvent(self, event):
if event.mimeData().hasUrls:
event.setDropAction(Qt.CopyAction)
event.accept()
else:
event.ignore()
def dropEvent(self, event):
if event.mimeData().hasUrls:
event.setDropAction(Qt.CopyAction)
event.accept()
links = []
for url in event.mimeData().urls():
links.append(str(url.toLocalFile()))
self.emit(Qt.SIGNAL("dropped"), links)
else:
event.ignore()
class MainForm(QMainWindow):
def __init__(self, parent=None):
super(MainForm, self).__init__(parent)
self.view = TestListView(self)
self.connect(self.view, Qt.SIGNAL("dropped"), self.pictureDropped)
self.setCentralWidget(self.view)
def pictureDropped(self, l):
for url in l:
if os.path.exists(url):
print(url)
icon = QIcon(url)
pixmap = icon.pixmap(72, 72)
icon = QIcon(pixmap)
item = QListWidgetItem(url, self.view)
item.setIcon(icon)
item.setStatusTip(url)
def main():
app = QApplication(sys.argv)
form = MainForm()
form.show()
app.exec_()
if __name__ == '__main__':
main()
The problem is that you are using an old form of connection, you must update the following things:
create the signal:
class TestListView(QListWidget):
dropped = pyqtSignal(list)
Also change:
self.emit(Qt.SIGNAL("dropped"), links)
to:
self.dropped.emit(links)
And change:
self.connect(self.view, Qt.SIGNAL("dropped"), self.pictureDropped)
to:
self.view.dropped.connect(self.pictureDropped)
For more information you can read here
Complete code:
import sys
import os
from PyQt5.Qt import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
class TestListView(QListWidget):
dropped = pyqtSignal(list)
def __init__(self, type, parent=None):
super(TestListView, self).__init__(parent)
self.setAcceptDrops(True)
self.setIconSize(QSize(72, 72))
def dragEnterEvent(self, event):
if event.mimeData().hasUrls:
event.accept()
else:
event.ignore()
def dragMoveEvent(self, event):
if event.mimeData().hasUrls:
event.setDropAction(Qt.CopyAction)
event.accept()
else:
event.ignore()
def dropEvent(self, event):
if event.mimeData().hasUrls:
event.setDropAction(Qt.CopyAction)
event.accept()
links = []
for url in event.mimeData().urls():
links.append(str(url.toLocalFile()))
self.dropped.emit(links)
else:
event.ignore()
class MainForm(QMainWindow):
def __init__(self, parent=None):
super(MainForm, self).__init__(parent)
self.view = TestListView(self)
self.view.dropped.connect(self.pictureDropped)
self.setCentralWidget(self.view)
def pictureDropped(self, l):
for url in l:
if os.path.exists(url):
print(url)
icon = QIcon(url)
pixmap = icon.pixmap(72, 72)
icon = QIcon(pixmap)
item = QListWidgetItem(url, self.view)
item.setIcon(icon)
item.setStatusTip(url)
def main():
app = QApplication(sys.argv)
form = MainForm()
form.show()
app.exec_()
if __name__ == '__main__':
main()