Retrieve the path of selected files in a QTreeView - python-3.x

I would like to select files in a QTreeView and in the end I would like to get the path of the selected files, what should I do?
Here is a small example that I partly found on stackoverflow that I transformed a bit:
from PyQt5.QtCore import QDir, Qt
from PyQt5.QtWidgets import QFileSystemModel, QTreeView, QAbstractItemView, QApplication, QFileIconProvider
from PyQt5.QtGui import QIcon
import sys, os
from chemin_data_ressources import chemin_ressources
class ArbreArborescence(QTreeView):
def __init__(self, parent=None):
super(ArbreArborescence, self).__init__(parent)
self.setDragEnabled(True)
self.setDragDropMode(QAbstractItemView.DragOnly)
self.setDefaultDropAction(Qt.CopyAction)
self.setAlternatingRowColors(True)
self.setSelectionMode(QAbstractItemView.SingleSelection)
self.setStyleSheet("background-color: rgb(180, 180, 180);alternate-background-color: rgb(180, 180, 180);")
def set_source(self, folder):
""" """
self.up_folder = os.path.dirname(folder)
self.dirModel = QFileSystemModel()
self.dirModel.setRootPath(QDir.homePath()) # Sous windows QDir.Drives() éventuellement
self.setModel(self.dirModel)
self.setRootIndex(self.dirModel.index(self.up_folder)) #
self.setWordWrap(True)
self.hideColumn(1)
self.hideColumn(2)
self.hideColumn(3)
idx = self.dirModel.index(folder)
self.expand(idx)
self.scrollTo(idx, QAbstractItemView.EnsureVisible)
self.dirModel.setNameFilters(["*.jpg", "*.jpeg", "*.png", "*.gif"])
self.dirModel.setNameFilterDisables(False)
self.dirModel.setIconProvider(IconProvider())
class IconProvider(QFileIconProvider):
def icon(self, fileInfo):
if fileInfo.isDir():
return QIcon(chemin_ressources(os.path.join('ressources_murexpo', 'icone_png_dossier_apsc_256x256.png')))
return QFileIconProvider.icon(self, fileInfo)
if __name__=='__main__':
app = QApplication(sys.argv)
w = ArbreArborescence()
w.set_source(os.path.expanduser('~')) # Fonctionne ! (ou '/home')
w.show()
app.exec_()
Help me please.

Use QFileSystemModel.filePath to get the path of an item at a given QModelIndex.
class ArbreArborescence(QTreeView):
...
def currentChanged(self, current, previous):
print(self.model().filePath(current))
super().currentChanged(current, previous)

Related

How to control the behaviour of QGridLayout

Using PyQT6
I am trying to create and app that adds a new frame (stack1) whenever the check box is clicked. It all works well until the app hides the stack when you untick the checkbox.
Clearly what I'd like is for the app to return to the initial shape when I hide the stack. I am aware that since I have added a column to the QTGrid,the window has likely reshaped taking into account the extra space. I am getting the same behaviour using deleteLater. So my question is how can I avoid that? Is there a way to reset the gridLayout to go back to just 1 column?
Here is a reproducible example:
import sys
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
from PyQt6.QtWidgets import QWidget, QApplication, QVBoxLayout, QStackedWidget,
QLabel, QPushButton
import sys
from PyQt6.QtGui import QIcon
from PyQt6 import QtGui
class Window(QDialog):
def __init__(self):
super().__init__()
self.setWindowTitle("Anticoagulant Dose calculator")
self.buttons = {'metrics': ['Age?', 'Height in cm?', 'Serum Creatinine?', 'Weight in kg?', 'Sex?'],
'anticoagulant': ['DOAC', 'warfarin, \nacenocumarol'],
'indication': ['VTE', 'Atrial Fibrilation', 'Mechanical Heart valve'],
'VTE': ['single VTE > 12 months', 'VTE 3-12 months or \n multiple, large volume PE or \n active cancer', 'VTE < 3 months \n or known antithrombin deficiency or \n antiphospholipid syndrome'],
'af': ['No',"Yes, within the past 12 months","Yes, within the past 3 months"],
'Mech': 'mech',
'sexo': ['Male', 'Female']}
self.dialogLayout = QVBoxLayout()
self.anticoagLayout = QVBoxLayout()
self.rejillaLayout = QGridLayout()
self.anticoagButtongroup = QButtonGroup()
self.indicationButtongroup = QButtonGroup()
self.UIcomponents()
self.setLayout(self.rejillaLayout)
self.stack1 = QWidget()
self.stack1UI()
def stack1UI(self):
for a in self.buttons['anticoagulant']:
self.anticoag_rb = QRadioButton(a)
self.anticoagButtongroup.addButton(self.anticoag_rb)
self.dialogLayout.addWidget(self.anticoag_rb)
for i in self.buttons['indication']:
self.indication_rb = QRadioButton(i)
self.indicationButtongroup.addButton(self.indication_rb)
self.dialogLayout.addWidget(self.indication_rb)
self.stack1.setLayout(self.dialogLayout)
def cb_onclicked(self):
self.cbox = self.bridgingCheckBox.sender()
try:
if self.cbox.isChecked():
print('Puenteando')
self.rejillaLayout.addWidget(self.stack1, 0, 2)
self.stack1.show()
else:
self.stack1.deleteLater()
print('No puenteando')
except RuntimeError:
pass
def UIcomponents(self):
self.entries = {}
formas = QFormLayout()
for m in self.buttons['metrics']:
if m != 'Sex?':
self.ent = QLineEdit()
formas.addRow(m, self.ent)
else:
print('Sexo?')
self.sComboBox = QComboBox()
self.sComboBox.addItems(self.buttons['sexo'])
sLabel = QLabel('Sex:?')
sLabel.setBuddy(self.sComboBox)
formas.addRow(sLabel, self.sComboBox)
self.entries[m] = self.ent
self.bridgingCheckBox = QCheckBox("Bridging dose")
self.bridgingCheckBox.toggled.connect(self.cb_onclicked)
formas.addRow(self.bridgingCheckBox, None)
self.rejillaLayout.addLayout(formas, 0, 0)
if __name__ == '__main__':
app = QApplication([])
window = Window()
window.setGeometry(50, 50, 300, 160)
window.show()
sys.exit(app.exec())
Many thanks in advance.
Thanks yo #ekhumoro for the answer:
gridding the stack at the init stage then hiding it, and then adding the code to the checkbox connector does it.
def cb_onclicked(self):
self.cbox = self.bridgingCheckBox.sender()
self.stack1.setVisible(self.bridgingCheckBox.isChecked())
self.adjustSize()

Is there a fast and robust way to unittest operations on files?

I’m writing a small pywin32 program that converts text files to docx/pdf and that’s all chill and working. BUT… I haven’t figured out a way to unittest the coverter that would not require creating tempfiles. The proccess is really slow.
Is there a commonly accepted good practice on unittesting operations on files?
The converter + unittests below.
# file converter.py
import win32com.client as win32
class Converter:
def __init__(self):
self.word = win32.Dispatch('Word.Application')
self.word.Visible = False
self._word_format = {
'docx': 12,
'pdf': 17
}
#property
def word_format(self):
return self._word_format
def open_and_close(func):
def wrapper(self, fpath):
doc = self.word.Documents.Open(str(fpath))
doc.Activate()
func(self, fpath)
doc.Close()
return wrapper
#open_and_close
def to_docx(self, fpath):
new_fpath = str(fpath).replace('.doc', '.docx')
self.word.ActiveDocument.SaveAs(new_fpath, FileFormat=self.word_format['docx'])
#open_and_close
def to_pdf(self, fpath):
new_fpath = str(fpath).replace('.doc', '.pdf')
self.word.ActiveDocument.SaveAs(new_fpath, FileFormat=self.word_format['pdf'])
def __del__(self):
self.word.Quit()
# file test_converter.py
import unittest
import tempfile
import os
from pathlib import Path
from converter import Converter
class TestConverterToDocx(unittest.TestCase):
#classmethod
def setUpClass(cls):
cls.converter = Converter()
def setUp(self):
self.tempdir = tempfile.TemporaryDirectory()
for _ in range(5):
tempfile.NamedTemporaryFile(dir=self.tempdir.name,
suffix='.doc', delete=False)
def tearDown(self):
self.tempdir.cleanup()
def test_to_docx(self):
for root, dirs, files in os.walk(self.tempdir.name):
for file in files:
self.converter.to_docx(str(Path(root) / file))
files = [file for file in os.listdir(self.tempdir.name)
if file.endswith('.docx')]
self.assertEqual(len(files), 5)
def test_to_pdf(self):
for root, dirs, files in os.walk(self.tempdir.name):
for file in files:
self.converter.to_pdf(str(Path(root) / file))
files = [file for file in os.listdir(self.tempdir.name)
if file.endswith('.pdf')]
self.assertEqual(len(files), 5)

QThread closes whenever QFileDialog Called After Migrating from PyQt5 to Pyside2

First of all, I'm currently migrating my source code from PyQt5 to PySide2 which requires me to change some of the syntaxes. As this site said that it only needs 3 things to do migrate from PyQt to Pyside2.
1.app.exec_. exec_ was used as exec is a Python2 keyword. Under Python3, PyQt5 allows the use of exec but not PySide2.
2.Under PyQt5 it’s QtCore.pyqtSignal and QtCore.pyqtSlot and under PySide2 it’s QtCore.Signal and QtCore.Slot .
3.loading Ui files.
But anyway later on when I tried to run my code it gave me this following error:
QThread: Destroyed while thread is still running
I had more than 2000 lines of code and I cannot determine which is the cause of this other than my last action which is trying to call QFileDialog which shouldn't be a problem (I've tested this with PyQt import and there's no problem and no warning at all). But in PySide2 it definitely might be the cause of it. I look up into this, he doesn't have the same problem as mine exactly. I'm not trying to call QFileDialog from different thread.
this is the minimal reproducible example of my working code in PyQt5:
import sys
import os
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QMainWindow, QFileDialog, QMessageBox, QWidget, QDialog
import random
class MyWidget(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.path = os.path.abspath(os.path.dirname(sys.argv[0]))
self.button = QtWidgets.QPushButton("Open File")
self.labelFile = QtWidgets.QLabel("empty")
self.labelData = QtWidgets.QLabel("None")
self.layout = QtWidgets.QVBoxLayout()
self.layout.addWidget(self.button)
self.layout.addWidget(self.labelFile)
self.layout.addWidget(self.labelData)
self.setLayout(self.layout)
self.button.clicked.connect(self.open_file)
timer = QtCore.QTimer(self)
timer.timeout.connect(self.update_data_value)
timer.start(1000)
def open_file(self):
x = QFileDialog.getOpenFileName(self,"Pilih File CSV yang Ingin Diproses",self.path,"CSV Files (*.csv)")
self.labelFile.setText(x[0])
def update_data_value(self):
self.DataProcess = DataProcess()
self.DataProcess.progress.connect(self.update_data_label)
self.DataProcess.start()
def update_data_label(self,x):
self.labelData.setText(str(x[0]))
class DataProcess(QtCore.QThread):
progress = QtCore.pyqtSignal(object)
def __init__(self):
QtCore.QThread.__init__(self)
def run(self):
x = random.randint(1,100)
self.progress.emit([str(x)+ " from thread"])
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
widget = MyWidget()
widget.show()
sys.exit(app.exec_())
and this is the non-working one in PySide2 after renaming import accordingly to PySide2 also renaming 'pyqtsignal' to 'Signal'
import sys
import os
from PySide2 import QtCore, QtGui, QtWidgets
from PySide2.QtWidgets import QMainWindow, QFileDialog, QMessageBox, QWidget, QDialog
import random
class MyWidget(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.path = os.path.abspath(os.path.dirname(sys.argv[0]))
self.button = QtWidgets.QPushButton("Open File")
self.labelFile = QtWidgets.QLabel("empty")
self.labelData = QtWidgets.QLabel("None")
self.layout = QtWidgets.QVBoxLayout()
self.layout.addWidget(self.button)
self.layout.addWidget(self.labelFile)
self.layout.addWidget(self.labelData)
self.setLayout(self.layout)
self.button.clicked.connect(self.open_file)
timer = QtCore.QTimer(self)
timer.timeout.connect(self.update_data_value)
timer.start(1000)
def open_file(self):
x = QFileDialog.getOpenFileName(self,"Pilih File CSV yang Ingin Diproses",self.path,"CSV Files (*.csv)")
self.labelFile.setText(x[0])
def update_data_value(self):
self.DataProcess = DataProcess()
self.DataProcess.progress.connect(self.update_data_label)
self.DataProcess.start()
def update_data_label(self,x):
self.labelData.setText(str(x[0]))
class DataProcess(QtCore.QThread):
progress = QtCore.Signal(object)
def __init__(self):
QtCore.QThread.__init__(self)
def run(self):
x = random.randint(1,100)
self.progress.emit([str(x)+ " from thread"])
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
widget = MyWidget()
widget.show()
sys.exit(app.exec_())
so after creating this minimal example, I realized that PySide QFileDialog makes the QThread stop while PyQt QFileDialog doesn't freeze the main thread. Is there anything I could do to handle this in similar syntax architecture? (e.g not using "movetothread" or "QObject")
The problem is that you're overwriting self.DataProcess every time a new thread is created, which may cause the previous object to be garbage-collected by Python before Qt has a chance to delete it. This can result in a core-dump if Qt tries to delete an object which is no longer there. Problems of this kind are quite common in both PyQt and PySide, and are usually caused by not keeping proper references to dependant objects. The normal solution is to ensure that the affected objects are given a parent and, if necessary, explicitly delete them at an appropriate time.
Here is one way to fix your example:
class MyWidget(QtWidgets.QWidget):
...
def update_data_value(self):
# ensure the thread object has a parent
process = DataProcess(self)
process.progress.connect(self.update_data_label)
process.start()
def update_data_label(self,x):
self.labelData.setText(str(x[0]))
class DataProcess(QtCore.QThread):
progress = QtCore.Signal(object)
def __init__(self, parent):
# ensure the thread object has a parent
QtCore.QThread.__init__(self, parent)
def run(self):
x = random.randint(1,100)
self.progress.emit([str(x)+ " from thread"])
# explicitly schedule for deletion
self.deleteLater()
It's hard to say exactly why PySide behaves differently to PyQt in this particular case. It usually just comes down to low-level differences between the two implementations. Probably there are equivalent cases that affect PyQt but not PySide. However, if you manage object references and cleanup carefully, such differences can usually be eliminated.

Drag and drop in QTreeView fails with items that hold a QImage

I have a list of items in a QTreeView. Each item holds a QImage object. If I try to drag and drop the item, the program freezes. But when I comment out the line objMod._Image = QImage(flags = Qt.AutoColor), the program runs fine.
How can I drag and drop the items with the QImage object? The QImage holds an image which is rendered. The rendering process takes a while, so it would be nice to keep the QImage object.
import sys
import os
from PySide.QtCore import *
from PySide.QtGui import *
from PySide.QtUiTools import *
from PIL import Image, ImageCms, ImageQt
class ObjModel:
def __init__(self):
self._Image = None
class DragMoveTest(QMainWindow):
def __init__(self):
super(DragMoveTest,self).__init__()
self.initGUI()
self.show()
def initGUI(self):
self.treeView = QTreeView()
modelTreeView = QStandardItemModel()
self.treeView.setModel(modelTreeView)
for i in range(0, 4):
objMod = ObjModel()
objMod._Image = None
objMod._Image = QImage(flags = Qt.AutoColor)
item = QStandardItem('Test: %s' % str(i))
item.setData(objMod, Qt.UserRole + 1)
modelTreeView.invisibleRootItem().appendRow(item)
self.treeView.setDragDropMode(QAbstractItemView.InternalMove)
self.setCentralWidget(self.treeView)
def main(args):
app = QApplication(sys.argv)
qt_main_wnd = DragMoveTest()
ret = app.exec_()
sys.exit(ret)
if __name__ == "__main__":
main(sys.argv)
This is caused by a bug in PySide. During a drag and drop operation, the data in the dragged item must be serialized. This will be handled by Qt for most data-types, but for types that are specific to Python, special handling is required. This special handling seems to be broken in PySide. If your example is converted to PyQt, a TypeError is raised when trying to drag items, but the program does not freeze.
The source of the problem is that you are storing data using a custom Python class. PyQt uses pickle to serialize custom data-types, but it is not possible to also pickle the QImage that is stored in its __dict__, so the operation fails. I assume PySide must attempt something similar, but for some reason it does not raise an error when it fails. Qt grabs the mouse whilst dragging, so if the operation fails abnormally, it won't be released again, and the program will appear to freeze.
The simplest way to fix this is to avoid using a custom class to hold the QImage, and instead store the image directly in the item:
image = QImage()
item = QStandardItem('Test: %s' % i)
item.setData(image, Qt.UserRole + 1)
To store more data items, you can either use a different data-role for each one, or use a dict to hold them all:
data = {'image': QImage(), 'title': 'foo', 'timestamp': 1756790}
item.setData(data, Qt.UserRole + 1)
However, if you do this, you must always use string keys in the dict, otherwise you will face the same problems as before. (Using string keys means the dict can be converted into a QMap, which Qt knows how to serialize).
(NB: if you want to know whether a Qt class can be serialized, check the docs to see whether it defines the datastream operators).
I come up with a different solution. It is easier to have a object that holds a io.BytesIO. Your store the ImageData into the bytesIO variable. Upon on your image library you can open the image from the bytesIO variable.
In the demo the class ObjModel can handle QImage and Image from Pillow/PIL. If you use the set methods the image object will be converted into a bytesIO.
In short here a working example:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import os
import io
from PySide.QtCore import *
from PySide.QtGui import *
from PySide.QtUiTools import *
from PIL import Image, ImageCms, ImageQt
########################################################################
class ObjModel:
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
self._ImageByteIO = None
#----------------------------------------------------------------------
def getObjByte(self):
""""""
return self._ImageByteIO
#----------------------------------------------------------------------
def getQImage(self):
""""""
try:
self._ImageByteIO.seek(0)
qImg = QImage.fromData(self._ImageByteIO.getvalue())
return qImg
except:
return None
#----------------------------------------------------------------------
def getPILImage(self):
""""""
try:
self._ImageByteIO.seek(0)
img = Image.open(tBytesIO)
return img
except:
return None
#----------------------------------------------------------------------
def setObjByte(self, fileName):
""""""
try:
tBytesIO = io.BytesIO()
f = open (fileName, 'rb')
tBytesIO.write(f.read())
f.close()
self._ImageByteIO = tBytesIO
except:
self._ImageByteIO = None
#----------------------------------------------------------------------
def setQImage(self, qImg):
""""""
try:
tBytesIO = io.BytesIO()
qByteArray = QByteArray()
qBuf = QBuffer(qByteArray)
qBuf.open(QIODevice.ReadWrite)
qImg.save(qBuf, 'PNG')
tBytesIO = io.BytesIO()
tBytesIO.write(qByteArray.data())
self._ImageByteIO = tBytesIO
except:
self._ImageByteIO = None
#----------------------------------------------------------------------
def setPILImage(self, pImg):
""""""
tBytesIO = io.BytesIO()
pImg.save(tBytesIO, 'png')
self._ImageByteIO = tBytesIO
#----------------------------------------------------------------------
class DragMoveTest(QMainWindow):
def __init__(self):
""""""
super(DragMoveTest,self).__init__()
self.initGUI()
self.show()
#----------------------------------------------------------------------
def initGUI(self):
""""""
self.treeView = QTreeView()
modelTreeView = QStandardItemModel()
self.treeView.setModel(modelTreeView)
for i in range(0, 4):
objMod = ObjModel()
objMod.setQImage(QImage(flags = Qt.AutoColor))
item = QStandardItem('Test: %s' % str(i))
item.setData(objMod, Qt.UserRole + 1)
modelTreeView.invisibleRootItem().appendRow(item)
self.treeView.setDragDropMode(QAbstractItemView.InternalMove)
self.setCentralWidget(self.treeView)
#----------------------------------------------------------------------
def main(args):
app = QApplication(sys.argv)
qt_main_wnd = DragMoveTest()
ret = app.exec_()
sys.exit(ret)
#----------------------------------------------------------------------
if __name__ == "__main__":
main(sys.argv)

RuntimeError: Please destroy the QApplication singleton before creating a new QApplication instance

When i run python-pyside2 project server first, then it works well.
And the site also work well, but if i press F5 btn to refresh in browser.
Site shows error page Runtime at/
import sys
from urllib.request import urlopen
from bs4 import BeautifulSoup
from PySide2.QtGui import *
from PySide2.QtCore import *
from PySide2.QtWebKitWidgets import *
from PySide2.QtWidgets import QApplication
class dynamic_render(QWebPage):
def __init__(self, url):
self.frame = None
self.app = QApplication(sys.argv)
QWebPage.__init__(self)
self.loadFinished.connect(self._loadFinished)
self.mainFrame().load(QUrl(url))
QTimer.singleShot(0, self.sendKbdEvent)
QTimer.singleShot(100, app.quit)
self.app.exec_()
def _loadFinished(self, result):
self.frame = self.mainFrame()
self.app.quit()
self.app = None
Below, scaping code using pyside2:
I don't know how can i fix it?
Best regards.
Thanks.
Check if already an instance of QApplication is present or not as the error occurs when an instance is already running and you are trying to create a new one.
Write this in your main function:
if not QtWidgets.QApplication.instance():
app = QtWidgets.QApplication(sys.argv)
else:
app = QtWidgets.QApplication.instance()
For my pyside2 unit test the following worked fairly well
import PySide2
import unittest
class Test_qapplication(unittest.TestCase):
def setUp(self):
super(Test_qapplication, self).setUp()
if isinstance(PySide2.QtGui.qApp, type(None)):
self.app = PySide2.QtWidgets.QApplication([])
else:
self.app = PySide2.QtGui.qApp
def tearDown(self):
del self.app
return super(Test_qapplication, self).tearDown()
it was loosely based on stack overflow : unit-and-functional-testing-a-pyside-based-application

Resources