Concatenate Two QFileSystemModels - python

In this post, my goal is to concatenate two QFileSystemModels to one and display them together. (Lots of updates has been made)
Context :
In my C drive , I created the folder MyFolder (https://drive.google.com/drive/folders/1M-b2o9CiohXOgvjoZrAnl0iRVQBD1sXY?usp=sharing) , in which there are some folders and some files, for the sake of producing the minimal reproducible example . Their structure is :
The following Python code using PyQt5 library (modified from How to display parent directory in tree view?) runs after importing necessary libraries:
#The purpose of the proxy model is to display the directory.
#This proxy model is copied here from the reference without modification.
class ProxyModel(QSortFilterProxyModel):
def __init__(self, parent=None):
super().__init__(parent)
self._root_path = ""
def filterAcceptsRow(self, source_row, source_parent):
source_model = self.sourceModel()
if self._root_path and isinstance(source_model, QFileSystemModel):
root_index = source_model.index(self._root_path).parent()
if root_index == source_parent:
index = source_model.index(source_row, 0, source_parent)
return index.data(QFileSystemModel.FilePathRole) == self._root_path
return True
#property
def root_path(self):
return self._root_path
#root_path.setter
def root_path(self, p):
self._root_path = p
self.invalidateFilter()
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.create_treeview()
self.setCentralWidget(self.treeView_1) #The line I will be talking about.
def create_treeview(self):
self.treeView_1 = QTreeView()
self.dirModel_1 = QFileSystemModel()
self.dirModel_1.setRootPath(QDir.rootPath())
path_1 = 'C:/MyFolder/SubFolder1' # Changing the path is sufficient to change the displayed directory
root_index_1 = self.dirModel_1.index(path_1).parent()
self.proxy_1 = ProxyModel(self.dirModel_1)
self.proxy_1.setSourceModel(self.dirModel_1)
self.proxy_1.root_path = path_1
self.treeView_1.setModel(self.proxy_1)
proxy_root_index_1 = self.proxy_1.mapFromSource(root_index_1)
self.treeView_1.setRootIndex(proxy_root_index_1)
self.treeView_2 = QTreeView()
self.dirModel_2 = QFileSystemModel()
self.dirModel_2.setRootPath(QDir.rootPath())
path_2 = 'C:/MyFolder'
root_index_2 = self.dirModel_2.index(path_2).parent()
self.proxy_2 = ProxyModel(self.dirModel_2)
self.proxy_2.setSourceModel(self.dirModel_2)
self.proxy_2.root_path = path_2
self.treeView_2.setModel(self.proxy_2)
proxy_root_index_2 = self.proxy_2.mapFromSource(root_index_2)
self.treeView_2.setRootIndex(proxy_root_index_2)
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
The line self.setCentralWidget(self.treeView_1) gives:
Changing self.setCentralWidget(self.treeView_1) to self.setCentralWidget(self.treeView_2) gives:
Objective:
My goal is to concatenate the two trees together. That is, when click run, the user should be able to see:
The order which they show up does not matter. All I care is that MyFolder and SubFolder1 show up as if they are completely independent items (even though in reality one is a subfolder of the other). I should remark that everything is static. That is, we are not trying to detect any changes on folders or files. The only time we ever need to peak at the existing folders and files will be when we click on run.
Update:
After several days of studying and trying, a major progress has been made. I thank musicamante for the hint of using QTreeWidget. The idea is, as said in comments, traverse through models and gradually move everything into one new QTreeWidget. To avoid freeze, my solution is to ask the QFileSystemModel to fetchMore whenever the user wants to see more (i.e. when the user wants to extend QTreeWidget).
The following code runs and almost solves my problem:
import os
from PyQt5.QtCore import*
from PyQt5.QtWidgets import*
from PyQt5 import QtTest
class To_Display_Folder(QSortFilterProxyModel):
def __init__(self, disables=False, parent=None):
super().__init__(parent)
#self.setFilterRegularExpression(r'^(.*\.dcm|[^.]+)$')
self._disables = bool(disables)
self._root_path = ""
def filterAcceptsRow(self, source_row, source_parent):
source_model = self.sourceModel()
#case 1 folder
if self._root_path and isinstance(source_model, QFileSystemModel):
root_index = source_model.index(self._root_path).parent()
if root_index == source_parent:
index = source_model.index(source_row, 0, source_parent)
return index.data(QFileSystemModel.FilePathRole) == self._root_path
return True
'''
#case 2 file
file_index = self.sourceModel().index(source_row, 0, source_parent)
if not self._disables:
return self.matchIndex(file_index)
return file_index.isValid()
'''
#property
def root_path(self):
return self._root_path
#root_path.setter
def root_path(self, p):
self._root_path = p
self.invalidateFilter()
def matchIndex(self, index):
return (self.sourceModel().isDir(index) or
super().filterAcceptsRow(index.row(), index.parent()))
def flags(self, index):
flags = super().flags(index)
if (self._disables and
not self.matchIndex(self.mapToSource(index))):
flags &= ~Qt.ItemIsEnabled
return flags
class Widget_Item_from_Proxy(QTreeWidgetItem):
def __init__(self, index_in_dirModel, parent = None):
super().__init__(parent)
self.setText(0, index_in_dirModel.data(QFileSystemModel.FileNameRole))
self.setText(1, index_in_dirModel.data(QFileSystemModel.FilePathRole))
if os.path.isfile(index_in_dirModel.data(QFileSystemModel.FilePathRole)):
self.setIcon(0,QApplication.style().standardIcon(QStyle.SP_FileIcon))
else:
self.setIcon(0,QApplication.style().standardIcon(QStyle.SP_DirIcon))
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
global treeWidget
treeWidget = QTreeWidget()
self.treeWidget = treeWidget
self.treeWidget.itemExpanded.connect(self.upon_expansion)
self.treeWidget.itemClicked.connect(self.tree_click)
#The following directories will be displayed on the tree.
self.add_path_to_tree_widget('C:/MyFolder')
self.add_path_to_tree_widget('C:/Users/r2d2w/OneDrive/Desktop')
self.add_path_to_tree_widget('C:/')
self.setCentralWidget(self.treeWidget)
def add_path_to_tree_widget(self,path):
dirModel = QFileSystemModel()
dirModel.setRootPath(QDir.rootPath())
dirModel.directoryLoaded.connect(lambda: self.once_loaded(path, dirModel))
def once_loaded(self, path, dirModel):
if dirModel.canFetchMore(dirModel.index(path)):
dirModel.fetchMore(dirModel.index(path))
return
root_index = dirModel.index(path).parent()
proxy = To_Display_Folder(disables = False, parent = dirModel)
proxy.setSourceModel(dirModel)
proxy.root_path = path
proxy_root_index = proxy.mapFromSource(root_index)
origin_in_proxy = proxy.index(0,0,parent = proxy_root_index)
root_item = Widget_Item_from_Proxy(
proxy.mapToSource(origin_in_proxy))
self.treeWidget.addTopLevelItem(root_item)
for row in range(0, proxy.rowCount(origin_in_proxy)):
proxy_index = proxy.index(row,0,parent = origin_in_proxy)
child = Widget_Item_from_Proxy(
proxy.mapToSource(proxy_index),
parent = self.treeWidget.topLevelItem(self.treeWidget.topLevelItemCount()-1))
dirModel.directoryLoaded.disconnect()
#pyqtSlot(QTreeWidgetItem)
def upon_expansion(self, treeitem):
for i in range(0, treeitem.childCount()):
if os.path.isdir(treeitem.child(i).text(1)):
self.add_child_path_to_tree_widget(treeitem.child(i))
def add_child_path_to_tree_widget(self,subfolder_item):
subfolder_path = subfolder_item.text(1)
dirModel = QFileSystemModel()
dirModel.setRootPath(QDir.rootPath())
dirModel.directoryLoaded.connect(lambda: self.child_once_loaded(subfolder_item, subfolder_path,dirModel))
def child_once_loaded(self, subfolder_item, subfolder_path, dirModel):
if dirModel.canFetchMore(dirModel.index(subfolder_path)):
dirModel.fetchMore(dirModel.index(subfolder_path))
return
root_index = dirModel.index(subfolder_path).parent()
proxy = To_Display_Folder(disables = False, parent = dirModel)
proxy.setSourceModel(dirModel)
proxy.root_path = subfolder_path
proxy_root_index = proxy.mapFromSource(root_index)
origin_in_proxy = proxy.index(0,0,parent = proxy_root_index)
root_item = Widget_Item_from_Proxy(
proxy.mapToSource(origin_in_proxy))
folder_item = subfolder_item.parent()
itemIndex = folder_item.indexOfChild(subfolder_item)
folder_item.removeChild(subfolder_item)
folder_item.insertChild(itemIndex, root_item)
for row in range(0, proxy.rowCount(origin_in_proxy)):
proxy_index = proxy.index(row,0,parent = origin_in_proxy)
child = Widget_Item_from_Proxy(
proxy.mapToSource(proxy_index),
parent = root_item)
dirModel.directoryLoaded.disconnect()
#pyqtSlot(QTreeWidgetItem)
def tree_click(self, item):
print(item.text(0))
print(item.text(1))
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
Since the bounty period is still not over, I will use the time to post two new questions:
Sometimes, especially when the line self.add_path_to_tree_widget('C:/') is present, the code does not give all directories when we click run. This problem is easily fixed by closing the window and clicking on run again. This problem occurs because the QFileSystemModel does not yet have enough time to traverse through the designated folder. If it has just a little bit more time, it will be able to. I wonder if there is a way to fix this programatically.
The function add_path_to_tree_widget is similar to add_child_path_to_tree_widget. The function once_loaded is similar to child_once_loaded. I wonder if there is a way to write these functions more succinctly.

While not impossible, it's quite difficult to create a unique and dynamic model that is able to access different QFileSystemModel structures.
An easier and simpler implementation, which would be more practical for static purposes, is to use a QTreeWidget and create items recursively.
class MultiBrowser(QTreeWidget):
def __init__(self, *pathList):
super().__init__()
self.iconProvider = QFileIconProvider()
self.setHeaderLabels(['Name'])
for path in pathList:
item = self.createFSItem(QFileInfo(path), self.invisibleRootItem())
self.expand(self.indexFromItem(item))
def createFSItem(self, info, parent):
item = QTreeWidgetItem(parent, [info.fileName()])
item.setIcon(0, self.iconProvider.icon(info))
if info.isDir():
infoList = QDir(info.absoluteFilePath()).entryInfoList(
filters=QDir.AllEntries | QDir.NoDotAndDotDot,
sort=QDir.DirsFirst
)
for childInfo in infoList:
self.createFSItem(childInfo, item)
return item
# ...
multiBrowser = MultiBrowser('path1', 'path2')
For obvious reasons, the depth of each path and their contents will freeze the UI from interaction until the whole structure has been crawled.
If you need a more dynamic approach, consider using the QFileSystemModel as a source for path crawling, along with its directoryLoaded signal, which will obviously require a more complex implementation.

Related

How to reimplement QTextDocument createObject?

How to reimplement QTextDocument.createObject?
this method plays a role in making QTextFrame, QTextList, QTextTable or other QTextObject.
According to woboq, I think my reimplementation is the same.
But kernel stops.
Why? What is my code short of?
from PySide2 import QtWidgets
from PySide2 import QtGui
from PySide2 import QtCore
import PySide2
import sys
import os
dirname = os.path.dirname(PySide2.__file__)
plugin_path = os.path.join(dirname, 'plugins', 'platforms')
os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = plugin_path
class TextEdit(QtWidgets.QTextEdit):
def __init__(self, parent=None):
super(TextEdit, self).__init__(parent=None)
document = TextDocument(self)
self.setDocument(document)
class TextDocument(QtGui.QTextDocument):
def __init__(self, parent=None):
super(TextDocument, self).__init__(parent=None)
self.setParent(parent)
def createObject(self, f):
obj = QtGui.QTextObject(self)
if f.isListFormat():
obj = QtGui.QTextList(self)
elif f.isTableFormat():
obj = QtGui.QTextTable(self)
elif f.isFrameFormat():
obj = QtGui.QTextFrame(self)
return obj
def main():
if QtWidgets.QApplication.instance() is not None:
app = QtWidgets.QApplication.instance()
else:
app = QtWidgets.QApplication([])
mainwindow = TextEdit()
mainwindow.show()
sys.exit(QtWidgets.QApplication.exec_())
if __name__ == "__main__":
main()
It seems to me that it is a bug (I have tested it with PyQt5 and it works correctly), the problem to be the life cycle of the QTextObject since as in C++ the life cycle is undefined since it is a pointer but being a child of QTextDocument So its life cycle is that of the QTextDocument, but in python it seems that it considers it an object of limited scope(local variable) not respecting the ownership that the QTextDocument has over this since it is its parent. A workaround seems to be making obj an member of the class:
def createObject(self, f):
self.obj = QtGui.QTextObject(self)
if f.isListFormat():
self.obj = QtGui.QTextList(self)
elif f.isTableFormat():
self.obj = QtGui.QTextTable(self)
elif f.isFrameFormat():
self.obj = QtGui.QTextFrame(self)
return self.obj
Or using a container that is a member of the class.
class TextDocument(QtGui.QTextDocument):
def __init__(self, parent=None):
super(TextDocument, self).__init__(parent)
self.objs = []
def createObject(self, f):
obj = QtGui.QTextObject(self)
if f.isListFormat():
obj = QtGui.QTextList(self)
elif f.isTableFormat():
obj = QtGui.QTextTable(self)
elif f.isFrameFormat():
obj = QtGui.QTextFrame(self)
self.objs.append(obj)
return obj
I prefer the second workaround since in the case of the first one you could generate problems if you create several QTextObject since the previous one would be deleted.
Finally I recommend reporting the bug.
The handling of the life cycle of some objects seems a persistent bug in PySide2.

Hide empty parent folders QTreeView/QFileSystemModel

So i have a tree viewas shown below;
#QTreeView widget
#Shows files in set directory
self.treeView = QtWidgets.QTreeView(self.centralWidget)
self.treeView.setSortingEnabled(True)
self.treeView.setObjectName("treeView")
self.horizontalLayout_4.addWidget(self.treeView)
self.file_model=QtWidgets.QFileSystemModel()
self.file_model.setRootPath('C:\My Stuff\Movies')
self.treeView.setModel(self.file_model)
self.treeView.setRootIndex(self.file_model.index('C:\My Stuff\Movies'))
self.treeView.setColumnWidth(0,275)
self.file_model.setNameFilters(self.filterList)
self.file_model.setNameFilterDisables(0)
As you can see i have a filter that hides items that dont pass the filter (e.g. *.mkv) however i have folders in my directory that contain a file that does not fit the filter requirements. The folder remains in my treeview even though it is empty, how do i remove these empty folders (Keep in mind i need to be able to show these folders when i apply a filter that allows for the file in the folder to be shown.
I am running PyQt5, Python 3.5, Windows 7.
We have the same question and this is the way I tried to solve the problem.
You need to subclass QSortFilterProxyModel and override the hasChildren and filterAcceptsRow.
Please take note that instead of calling the QFileSystemModel's setNameFilters you would need to call the subclassed QSortFilterProxyModel's nameFilters method.
This is the code I ended up basing on the implementation above:
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class DirProxy(QSortFilterProxyModel):
nameFilters = ''
def __init__(self):
super().__init__()
self.dirModel = QFileSystemModel()
self.dirModel.setFilter(QDir.NoDotAndDotDot | QDir.AllDirs | QDir.Files) # <- added QDir.Files to view all files
self.setSourceModel(self.dirModel)
def setNameFilters(self, filters):
if not isinstance(filters, (tuple, list)):
filters = [filters]
self.nameFilters = filters
self.invalidateFilter()
def hasChildren(self, parent):
sourceParent = self.mapToSource(parent)
if not self.dirModel.hasChildren(sourceParent):
return False
qdir = QDir(self.dirModel.filePath(sourceParent))
return bool(qdir.entryInfoList(qdir.NoDotAndDotDot|qdir.AllEntries|qdir.AllDirs))
def filterAcceptsRow(self, row, parent):
source = self.dirModel.index(row, 0, parent)
if source.isValid():
if self.dirModel.isDir(source):
qdir = QDir(self.dirModel.filePath(source))
if self.nameFilters:
qdir.setNameFilters(self.nameFilters)
return bool(qdir.entryInfoList(qdir.NoDotAndDotDot|qdir.AllEntries|qdir.AllDirs))
elif self.nameFilters: # <- index refers to a file
qdir = QDir(self.dirModel.filePath(source))
return qdir.match(self.nameFilters, self.dirModel.fileName(source)) # <- returns true if the file matches the nameFilters
return True
class Test(QWidget):
def __init__(self):
super().__init__()
self.dirProxy = DirProxy()
self.dirProxy.dirModel.directoryLoaded.connect(lambda : self.treeView.expandAll())
self.dirProxy.setNameFilters(['*.ai']) # <- filtering all files and folders with "*.ai"
self.dirProxy.dirModel.setRootPath(r"<Dir>")
self.treeView = QTreeView()
self.treeView.setModel(self.dirProxy)
root_index = self.dirProxy.dirModel.index(r"<Dir>")
proxy_index = self.dirProxy.mapFromSource(root_index)
self.treeView.setRootIndex(proxy_index)
self.treeView.show()
app = QApplication(sys.argv)
ex = Test()
sys.exit(app.exec_())
This is the testing I did and the result looks just fine to me:
Trial 1:
Trial 2:
Please read this question for more info.

Cannot connect PySide QTableView selectionChanged signal

I am designing a program composed of a 3D viewer and a table with Python 3.4 and PySide bindings.
I have created a TableView with this class:
from PySide import QtGui
from PySide.QtCore import Qt
class MyTableView(QtGui.QWidget):
def __init__(self, parent=None):
super(MyTableView, self).__init__()
self.parent = parent
self.title = "Results"
self.initUI()
def initUI(self):
self.grid = QtGui.QGridLayout(self)
self.table = QtGui.QTableView()
self.grid.addWidget(self.table, 0, 0)
# Configure table
self.table.verticalHeader().setVisible(False)
self.table.horizontalHeader().setDefaultAlignment(Qt.AlignLeft)
self.table.setSortingEnabled(True)
self.table.setAlternatingRowColors(True)
self.table.setShowGrid(False)
self.table.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.table.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
and the model with this other class:
class MyModel(QStandardItemModel):
def __init__(self, path, *args, **kwargs):
super(MyModel, self).__init__()
self.path = path
self.parse()
def parse(self):
with open(self.path) as f:
self.mydata = yaml.load(f)
self.setColumnCount(len(self.mydata['headers']) + 1)
self.setHorizontalHeaderLabels(
['ID'] + self.mydata['headers'])
row = 0
for ind, val in self.mydata['rows'].items():
col = 0
self.insertRow(row)
self.setItem(row, col, QStandardItem(ind))
for v in val:
col += 1
self.setItem(row, col, QStandardItem(str(v)))
row += 1
which are then tied together in this controller:
from PySide.QtCore import Qt
class MyController(object):
def __init__(self, model, view):
self.model = model
self.tableview = view.table
self.fill_table()
self.connect_signals()
def fill_table(self):
self.tableview.setModel(self.model)
self.tableview.sortByColumn(0, Qt.AscendingOrder)
def connect_signals(self):
selectionModel = self.tableview.selectionModel()
selectionModel.selectionChanged.connect(self.selection_changed)
def selection_changed(self, selected, deselected):
print("Selection changed.")
Then, the program is executed through this script:
def main():
app = QtGui.QApplication(sys.argv)
MyController(MyModel(sys.argv[1]), MyView())
sys.exit(app.exec_())
if __name__ == '__main__':
main()
(Note that I haven't posted the main window class, but you get the idea)
The table gets rendered OK, but I am not able to connect the selectionChanged signal to the handler (which should udpate the viewer, but for testing purposes it's only a print statement).
What am I doing wrong? Thanks!
[EDIT]
I have discovered that it works if I use a lambda function to call the handler method. Can someone explain why?!
selectionModel.selectionChanged.connect(lambda: self.selection_changed(selectionModel.selectedRows()))
I tried to implement what you wrote and it worked - so I can't be 100% sure of why you are having trouble. But I suspect it is because of what I had to fix to get it to work at all: I had to sort out some problems you have with garbage collection.
In the example code you give, you create a MyController, a MyModel and a MyView. But they will all then be garbage collected (in CPython) since you don't keep a reference to them. If you add a reference to MyController
my_controller = MyController(MyModel(sys.argv[1]), MyView())
you are almost there, but I think the MyTableView might also then be garbage collected since the controller only keeps a reference to the QTableVIew not the MyTableView.
Presumably using the lanbda function changes the references you are preserving - it preserves the controller and the selection model - and that may be why it is working in that case.
Generally it's a good idea to use the Qt parenting mechanism. If you simply parented all these objects on the main window (or their natural parent widget) that would have prevented most of these problems.

(PyQt) Uncheck pushbutton from outside button's class?

A QPushButton is set 'asCheckable'. Whence toggled, a class bool is changed.
This altered bool allows a method in a different class to proceed, and upon completion of this outside method I need to return the button to its initial state, 'setChecked(False)'.
While I am able to return the class housed bool to its default state at the end of this external method, I am unable to externally access a method which un-clicks the button.
I assume its due to the arguments in the classes init, but these are necessary - and I'm wondering if there is another means to achieve the described workflow.
Related code snips below:
(command in question is distinguished at bottom of 'Class 2')
Class 1:
class shapeCSVeditor(QtGui.QDialog, QtGui.QWidget):
valueShare = []
rowOverride = False# <<=== equivalent to 'override' in 'Class 2'
def __init__(self, iface, fileName, editorType, parent=None):
super(shapeCSVeditor, self).__init__(parent)
self.iface = iface
self.editorType = editorType
self.fileName = filename
self.pushButtonSetBase = QtGui.QPushButton(self)
self.pushButtonSetBase.setText("Set Base Shape")
self.pushButtonSetBase.setCheckable(True)
self.pushButtonSetBase.toggled.connect(self.on_pushButtonSetBase_toggled)
self.layoutHorizontal.addWidget(self.pushButtonSetBase)
#some other things here...
#QtCore.pyqtSlot()
def on_pushButtonSetBase_toggled(self):
shapeCSVeditor.rowOverride = True
pass
def on_BaseRow_Changed(self):
self.pushButtonSetBase.setChecked(False)
return
Class 2:
class CSVModel(QtCore.QAbstractTableModel):
# Establish inital settings and branch processes
def __init__(self, iface, fileName, editorType, parent=None):
super(CSVModel,self).__init__()
self.propertiesFile = r'some file'
self.areaStressFile = r'some other file'
self.iface = iface
self.rows = []
self.editorType = editorType
self.loadCSV()
self.iface.mapCanvas().selectionChanged.connect(self.addRow)
# add rows to the TableView based on object selection(s) in Qgis.mapCanvas
def addRow(self):
override = shapeCSVeditor.rowOverride
selectedFeatures = selectedLayer.selectedFeatures()
if override:
for feature in selectedFeatures:
self.rows.pop(0)
feat_Attributes = []
feat_Attributes.extend([self.iface.activeLayer().name()+'_'+str(feature.id())])
feat_Attributes.extend(['',]*(len(self.header)-1))
self.beginResetModel()
self.rows.insert(0,feat_Attributes)
shapeCSVeditor.rowOverride = False
self.endResetModel()
shapeCSVeditor.on_BaseRow_Changed# <<<=== wrong-diddily!
break
PS - if parentheticals are added to the 'shapeCSVeditor()' 3 arguments are requisite as referenced in the Button class, and if parentheticals are added to 'on_BaseRow_Changed', the return is;
TypeError: unbound method on_BaseRow_Changed() must be called with
shapeCSVeditor instance as first argument (got nothing instead)
What you are doing is strange.
In python, the first argument of a class method is always the object itself.
So, in your:
def on_BaseRow_Changed(self):
self.pushButtonSetBase.setChecked(False)
# return => This return is useless
if you don't provide an object then you can't access the pushbutton.
You didn't gave us all the code but I think you should provide your addRow with the shapeCSVeditor object that you want to update:
def addRow(self, shapeCSVObj):
override = shapeCSVObj.rowOverride
if override:
for feature in selectedFeatures:
self.rows.pop(0)
feat_Attributes = []
feat_Attributes.extend([self.iface.activeLayer().name()+'_'+str(feature.id())])
feat_Attributes.extend(['',]*(len(self.header)-1))
self.beginResetModel()
self.rows.insert(0,feat_Attributes)
shapeCSVObj.rowOverride = False
self.endResetModel()
shapeCSVObj.on_BaseRow_Changed()
break
Somewhere you must have a shapeCSVeditor that is created. You should provide it to you outside class.
Hope this helps.
class shapeCSVeditor(QtGui.QDialog, QtGui.QWidget):
valueShare = []
rowOverride = False
def __init__(self, iface, fileName, editorType, parent=None):
super(shapeCSVeditor, self).__init__(parent)
self.iface = iface
self.editorType = editorType
self.fileName = fileName
self.tableView = QtGui.QTableView(self)
self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
self.tableData = CSVModel(self,iface,fileName,editorType)
^^==not implementing 'self' (shapeCSVeditor object) was the problem!
self.tableView.setModel(self.tableData)
...
self.pushButtonSetBase = QtGui.QPushButton(self)
self.pushButtonSetBase.setText("Set Base Shape")
self.pushButtonSetBase.setCheckable(True)
self.pushButtonSetBase.clicked.connect(self.on_pushButtonSetBase_toggled)
...
#QtCore.pyqtSlot()
def on_pushButtonSetBase_toggled(self):
self.rowOverride = True
#QtCore.pyqtSlot()
def on_BaseRow_Changed(self):
self.rowOverride = False
self.pushButtonSetBase.setChecked(False)
///////////////////////////////////////////////////////////////////////////////////////
class CSVModel(QtCore.QAbstractTableModel):
def __init__(self, shapeCSVeditor, iface, fileName, editorType):
super(CSVModel,self).__init__()
self.propertiesFile = r'foo'
self.areaStressFile = r'bar'
self.tableView = shapeCSVeditor <<== proper passing of shapeCSVeditor object! (?)
self.iface = iface
self.rows = []
self.editorType = editorType
self.loadCSV()
self.iface.mapCanvas().selectionChanged.connect(self.addRow)
...
def addRow(self):
selectedFeatures = selectedLayer.selectedFeatures()
if self.tableView.rowOverride:
for feature in selectedFeatures:
self.rows.pop(0)
feat_Attributes = []
feat_Attributes.extend([self.iface.activeLayer().name()+'_'+str(feature.id())])
feat_Attributes.extend(['',]*(len(self.header)-1))
self.beginResetModel()
self.rows.insert(0,feat_Attributes)
self.endResetModel()
self.tableView.rowOverride = False
self.tableView.on_BaseRow_Changed()
Radical. Works for the current needs.
Now the question is if its proper to python 'standards'.
Quite new to writing, so its possible more needs fixed.
High thanks to Plouff for the clues.

PyQt - Set model of a proxymodel in QThread

In my project i noticed that the main dialog freezes when it is setting the model of some ProxyModel, so i decided to create a new thread for this task to provide the responsiveness of the window, but right now it keeps popping an error that say:
TypeError: QTableView.setModel(QAbstractItemModel): argument 1 has unexpected type 'tuple'
and i don't know why...
Here's my code:
This is the QThread for updating the proxyModel with the arguments i provide
class ThreadedProxyModel(QThread):
def __init__(self, contacts, contactsProxyModel, groups, groupsProxyModel,
chatSession, chatSessionProxyModel, msgs, msgsProxyModel):
QThread.__init__(self)
self.contacts = contacts
self.contactsProxyModel = contactsProxyModel
self.groups = groups
self.groupsProxyModel = groupsProxyModel
self.chatSession = chatSession
self.chatSessionProxyModel = chatSessionProxyModel
self.msgs = msgs
self.msgsProxyModel = msgsProxyModel
def run(self):
self.contactsProxyModel.setSourceModel(recordsTableModel(self.contacts))
self.contactsProxyModel.setFilterKeyColumn(-1)
self.contactsProxyModel.setFilterCaseSensitivity(Qt.CaseInsensitive)
self.groupsProxyModel.setSourceModel(recordsTableModel(self.groups))
self.groupsProxyModel.setFilterKeyColumn(-1)
self.groupsProxyModel.setFilterCaseSensitivity(Qt.CaseInsensitive)
self.chatSessionProxyModel.setSourceModel(recordsTableModel(self.chatSession))
self.chatSessionProxyModel.setFilterKeyColumn(-1)
self.chatSessionProxyModel.setFilterCaseSensitivity(Qt.CaseInsensitive)
self.msgsProxyModel.setSourceModel(recordsTableModel(self.msgs))
self.msgsProxyModel.setFilterKeyColumn(-1)
self.msgsProxyModel.setFilterCaseSensitivity(Qt.CaseInsensitive)
def getContactsProxyModel(self):
return self.contactsProxyModel,
def getGroupsProxyModel(self):
return self.groupsProxyModel
def getChatSessionProxyModel(self):
return self.chatSessionProxyModel
def getMsgsProxyModel(self):
return self.msgsProxyModel
And this is the method calling the setProxyModel thread in the dialog class. Notice that all the data (contacts, groups, chatsession...) is fine:
def setProxyModel(self):
progress = QProgressDialog("Initializing UI ...", "Abort", 0, 0, self)
progress.setWindowTitle("WhatsApp Browser ...")
progress.setWindowModality(Qt.WindowModal)
progress.setMinimumDuration(0)
progress.setCancelButton(None)
progress.show()
queryTh = ThreadedProxyModel(self.contacts, self.contactsProxyModel, self.groups, self.groupsProxyModel,
self.chatSession, self.chatSessionProxyModel, self.msgs, self.msgsProxyModel,)
queryTh.start()
while queryTh.isRunning():
QApplication.processEvents()
self.contactsProxyModel = queryTh.getContactsProxyModel()
self.groupsProxyModel = queryTh.getGroupsProxyModel()
self.chatSessionProxyModel = queryTh.getChatSessionProxyModel()
self.msgsProxyModel = queryTh.getMsgsProxyModel()
progress.close()
And this is in the init method in my dialog, i create the proxymodels and call the method for updating them in the Thread and then i set them up in various QTableView:
self.contactsProxyModel = QSortFilterProxyModel(self)
self.groupsProxyModel = QSortFilterProxyModel(self)
self.groupMembersProxyModel = QSortFilterProxyModel(self)
self.chatSessionProxyModel = QSortFilterProxyModel(self)
self.chatMsgsProxyModel = QSortFilterProxyModel(self)
self.msgsProxyModel = QSortFilterProxyModel(self)
self.setProxyModel()
self.contactsTableView.setModel(self.contactsProxyModel)
self.contactsTableView.resizeColumnsToContents()
self.groupsTableView.setModel(self.groupsProxyModel)
self.groupsTableView.resizeColumnsToContents()
self.chatSessionTableView.setModel(self.chatSessionProxyModel)
self.chatSessionTableView.resizeColumnsToContents()
self.chatSessionTableView.clicked.connect(self.setChatMsgsProxyModel)
self.chatMsgsTableView.resizeColumnsToContents()
self.groupsTableView.clicked.connect(self.setGroupMembersProxyModel)
self.groupMembersTableView.resizeColumnsToContents()
self.msgsTableView.setModel(self.msgsProxyModel)
self.msgsTableView.resizeColumnsToContents()
Thank you for any advice, i'm pretty stuck...
Not sure, but it seems that
def getContactsProxyModel(self):
return self.contactsProxyModel,
return a tuple, try to delete the comma

Categories