QTreeView very slow sorting - qt

Sorting QTreeView with QSortFilterProxyModel is extremely slow (compared to basic QTreeWidget). What's wrong with the code? How can I speed-up?
# coding: utf-8
import sys
from PyQt5.QtWidgets import (QTreeView, QAbstractItemView,
QMainWindow, QApplication)
from PyQt5.QtSql import (QSqlDatabase, QSqlQuery, QSqlQueryModel)
from PyQt5.QtCore import QSortFilterProxyModel
class Database:
def __init__(self):
self.db = QSqlDatabase.addDatabase("QODBC")
self.db.setDatabaseName(
r'Driver={SQL Server Native Client 11.0};Server={(localdb)\v11.0};')
self.db.open()
class Model(QSqlQueryModel):
def __init__(self, parent=None):
super().__init__(parent)
query = QSqlQuery()
query.prepare('SELECT * FROM Companies WHERE Name LIKE ?')
query.bindValue(0, '%elektro%')
query.exec_()
self.setQuery(query)
class TreeView(QTreeView):
def edit(self, index, trigger, event):
if trigger == QAbstractItemView.DoubleClicked:
return False
return QTreeView.edit(self, index, trigger, event)
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.db = Database()
self.model = Model()
sortFilterModel = QSortFilterProxyModel()
sortFilterModel.setSourceModel(self.model)
treeView = TreeView(self)
treeView.setModel(sortFilterModel)
treeView.hideColumn(0)
treeView.hideColumn(9)
treeView.setIndentation(0)
treeView.setSelectionMode(QAbstractItemView.MultiSelection)
treeView.setAllColumnsShowFocus(True)
treeView.setSortingEnabled(True)
treeView.clicked.connect(self.row_id)
self.setCentralWidget(treeView)
def row_id(self, index):
if index.isValid():
id_ = index.sibling(index.row(), 0).data()
print(id_)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window()
window.show()
app.exec_()

Some tips (from a C++ perspective, so i could be missing something )
QTreeView is known for being slower than a QTableView and consume a lot of memory And you are using a plain table model anyways, so, try with a QTableview.
Also, in a real tree hierarchical model hiding column 0 would hide all child items.
Try using QSqlTableModel instead of a QSqlQueryModel, it has convenience setSort, setFilter methods and only fetchs visible rows. So i would be probably faster.

Related

Python listwidget click item button

First, you should know I'm very new to Python. I'm currently working on an app to increase my knowledge of this language.
I'm having a QListWidget which contains items based on a widget I created. This widget contains buttons like one to update or another to delete the item.
So I'm having troubles to do this action. For example, If I click on the update button, I want to open a window and load content by getting the item values.
How could I do that ?
This is my actual code
MainWindow
from PyQt5 import QtGui, QtWidgets, QtCore
from PyQt5.QtCore import QSize
from PyQt5.QtWidgets import QMainWindow, QHBoxLayout
from CustomWidgets.fb_line import fb_line
from Data.data_saver import data_saver
from MainWindows import Ui_MainWindow
from Windows.Ajout import Ajout
from Windows.Parametres import Parametres
from Windows.custom_fb_line import custom_fb_line
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.c_fb_l = custom_fb_line()
self.parametres = Parametres()
self.ajout = Ajout()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.ui.btn_parametre.clicked.connect(self.button_open_parametres)
self.ui.btn_ajouter.clicked.connect(self.button_open_ajout)
self.ui.btn_annuler.clicked.connect(self.button_annuler)
self.ui.btn_fermer.clicked.connect(self.close)
self.addLines()
def button_open_parametres(self):
self.parametres.show()
def button_open_ajout(self):
self.ajout.show()
def button_annuler(self):
self.ui.lw_dossier.clear()
self.addLines()
def addLines(self):
fbs = ds.read_data(data)
listWidget = self.ui.lw_dossier
listWidget.reset()
for fb in fbs:
item_widget = fb_line(fb)
item = QtWidgets.QListWidgetItem(listWidget)
item.setSizeHint(QSize(0, 50))
listWidget.addItem(item)
listWidget.setItemWidget(item, item_widget)
listWidget.show()
data = "./Data/data.pkl"
ds = data_saver()
Widget :
from PyQt5 import QtWidgets
from Objects.enum import enum
from custom_fb_line import Ui_custom_fb_line
class fb_line(QtWidgets.QWidget):
def __init__(self, fb, *args, **kwargs):
QtWidgets.QWidget.__init__(self, *args, **kwargs)
self.ui = Ui_custom_fb_line()
self.ui.setupUi(self)
self.ui.le_source.setText(fb.dossier_source)
self.ui.le_cible.setText(fb.dossier_cible)
self.ui.le_cron.setText("tous les " + str(fb.cron.number) + " " + enum().get_types()[fb.cron.type])
self.ui.btn_delete.clicked.connect(self.delete)
self.ui.btn_modifier.clicked.connect(self.modifier)
self.ui.btn_demarrer.clicked.connect(self.demarrer)
def delete(self):
print('delete ')
def modifier(self):
print('modifier')
def demarrer(self):
print('démarrer')
So print does actually show in console but I can't figure out how to achieve button click to update or delete items.
Never mind, I found a way, which is really simple in fact. Being new to python, I didn't know how to pass arguments to methods plugged to buttons. Here is how to do that for anyone who could have same issues
self.ui.btn_delete.clicked.connect(lambda: self.delete(fb))
def delete(self, fb):
print('delete ')
print(fb.dossier_source)

I can't see QLabel and QLineEdit widgets in my QMainWindow in Python2

I wrote this code and I don't understand why widgets QLabel and QLineEdit don't show up? Do I have to put them in another class? It's Python2.7 and PySide.
This is how a window looks like when I run the code:
#!/usr/bin/env python
# coding: utf-8
import sys
import crypt
from PySide import QtGui
class MyApp(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MyApp, self).__init__(parent)
self.initui()
def initui(self):
# main window size, title and icon
self.setMinimumSize(500, 350)
self.setWindowTitle("Calculate a password hash in Linux")
# lines for entering data
self.saltLabel = QtGui.QLabel("Salt:")
self.saltLine = QtGui.QLineEdit()
self.saltLine.setPlaceholderText("e.g. $6$xxxxxxxx")
# set layout
grid = QtGui.QGridLayout()
grid.addWidget(self.saltLabel, 0, 0)
grid.addWidget(self.saltLine, 1, 0)
self.setLayout(grid)
# show a widget
self.show()
def main():
app = QtGui.QApplication(sys.argv)
instance = MyApp()
instance.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
How about using a QWidget as centralWidget
widget = QWidget()
widget.setLayout(grid)
#add your widgets and...
self.setCentralWidget(widget)
and you don't need to call show() since you do in your __main__
It's up to the owner but i would recommend sublassing a QWidget and leave your QMainWindow instance as concise as possible. An implementation could be:
class MyWidget(QtGui.QWidget):
def __init__(self, *args):
QtGui.QWidget.__init__(self, *args)
grid = QtGui.QGridLayout()
#and so on...
and use this as widget in your QMainWindow instance. This increases readability and maintainability and reusability a lot :)

ProTip: Very Important thing to remember when using QScrollArea

Today I spent 3 hours in figuring why my QScrollArea was not working. And finally found that the reason was the components which I was dynamically adding to the ScrollArea didn't have any Layout so it was not able to compute the minimum size of it while adding.
So If anyone is adding elements dynamically to the QScrollArea, make sure your widgets have some area. I saw lot of posts where people where facing the same issue and there was no direct answer that layout is the cause so this is like a post notifying to the all users to make sure you first have a layout in all of your widgets you want to append to QScrollArea.
Thank you
An example would be
import sys
from PyQt4 import QtCore, QtGui, uic
mainBase, mainForm = uic.loadUiType("main.ui")
labelBase, labelForm = uic.loadUiType("labelsForScroll.ui")
dataBase, dataForm = uic.loadUiType("data.ui")
emptyBase, emptyForm = uic.loadUiType("empty.ui")
class Data(dataBase, dataForm):
def __init__(self, parent=None):
super(dataBase, self).__init__(parent)
self.setupUi(self)
def setSl(self, num):
self.sl.setText(str(num))
class Label(labelBase, labelForm):
def __init__(self, parent=None):
super(labelBase, self).__init__(parent)
self.setupUi(self)
class Empty(emptyBase, emptyForm):
def __init__(self, parent=None):
super(emptyBase, self).__init__(parent)
self.setupUi(self)
class Main(mainBase, mainForm):
def __init__(self, parent=None):
super(mainBase, self).__init__(parent)
self.setupUi(self)
self.__l = Label()
#self.vert.addWidget(l)
self.__maxSlNum = 0
self.__datLis =[]
#self.addField()
self.__e = Empty()
self.__e.layout().addWidget(self.__l)
self.scrollArea.setWidgetResizable(True)
self.scrollArea.setWidget(self.__e)
QtCore.QObject.connect(self.add, QtCore.SIGNAL("clicked()"), self.addField)
def addField(self):
d = Data()
self.__maxSlNum+=1
d.setSl(self.__maxSlNum)
self.__datLis.append(d)
lay = self.__e.layout()
lay.addWidget( d)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
app.setStyle("cleanlooks")
m = Main()
m.show()
sys.exit(app.exec_())
links for the ui file can be found here links

PyQt-QtableView Header. How to add top-right small button to hide/show column with checked/unchecked Checkboxes?

With PyQt4, I am using a QtableView with more than 10 columns. The user must have the choice of showing/hiding a column.
This is generally done by adding a small button in the top-right of the table's header. The button shows a menu with checked/unchecked Checkboxes allowing to hide/show columns.
This is an example from Sqlite-Manager Table.
So, I wonder how can I do the same with PyQt's QtableView?
Thanks,
Thank you Kitsune Meyoko, it was a great Idea.. ;)
I found another solution pretty much like yours by using QMenu with Checkable QActions instead of a QPushButton: Let's Go:
import sys
import string
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class Header(QHeaderView):
def __init__(self, parent=None):
super(Header, self).__init__(Qt.Horizontal, parent)
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.ctxMenu)
self.setup()
#pyqtSlot(bool)
def printID(self, i):
print("id")
if i == False:
self.hideSection(0)
else:
self.showSection(0)
#pyqtSlot(bool)
def printNAME(self, i):
print("name")
if i == False:
self.hideSection(1)
else:
self.showSection(1)
#pyqtSlot(bool)
def printUSERNAME(self, i):
print("username")
if i == False:
self.hideSection(2)
else:
self.showSection(2)
def setup(self):
self.id = QAction("id",self)
self.id.setCheckable(True)
self.id.setChecked(True)
self.connect(self.id, SIGNAL("triggered(bool)"), self, SLOT("printID(bool)"))
self.name = QAction("name",self)
self.name.setCheckable(True)
self.name.setChecked(True)
self.connect(self.name, SIGNAL("triggered(bool)"), self, SLOT("printNAME(bool)"))
self.username = QAction("username",self)
self.username.setCheckable(True)
self.username.setChecked(True)
self.connect(self.username, SIGNAL("triggered(bool)"), self, SLOT("printUSERNAME(bool)"))
def ctxMenu(self, point):
menu = QMenu(self)
self.currentSection = self.logicalIndexAt(point)
menu.addAction(self.id)
menu.addAction(self.name)
menu.addAction(self.username)
menu.exec_(self.mapToGlobal(point))
class Table(QTableWidget):
def __init__(self, parent=None):
super(Table, self).__init__(parent)
self.setHorizontalHeader(Header(self))
self.setColumnCount(3)
self.setHorizontalHeaderLabels(['id', 'name', 'username'])
self.populate()
def populate(self):
self.setRowCount(10)
for i in range(10):
for j,l in enumerate(string.ascii_letters[:3]):
self.setItem(i, j, QTableWidgetItem(l))
if __name__ == '__main__':
app = QApplication(sys.argv)
t = Table()
t.show()
app.exec_()
sys.exit()
In QTableView not have kind of button just like "Sqlite-Manager Table". But your can custom widget by using QtGui.QPushButton and work with QtGui.QMenu together to get column from user. And use QTableView.hideColumn (self, int column) & QTableView.showColumn (self, int column) to hide show your column;
Full example;
import sys
import random
from functools import partial
from PyQt4 import QtGui
class QCustomTableViewWidget (QtGui.QWidget):
def __init__ (self, myQStandardItemModel, *args, **kwargs):
super(QCustomTableViewWidget, self).__init__(*args, **kwargs)
# Layout setup
self.localQTableView = QtGui.QTableView()
self.rightCornerQPushButton = QtGui.QPushButton()
menuQHBoxLayout = QtGui.QHBoxLayout()
menuQHBoxLayout.addStretch(1)
menuQHBoxLayout.addWidget(self.rightCornerQPushButton)
allQVBoxLayout = QtGui.QVBoxLayout()
allQVBoxLayout.addLayout(menuQHBoxLayout)
allQVBoxLayout.addWidget(self.localQTableView)
self.setLayout(allQVBoxLayout)
# Object setup
self.localQTableView.setModel(myQStandardItemModel)
self.rightCornerQPushButton.setText('Show column')
currentQMenu = QtGui.QMenu()
for column in range(myQStandardItemModel.columnCount()):
currentQAction = QtGui.QAction('Column %d' % (column + 1), currentQMenu)
currentQAction.setCheckable(True)
currentQAction.setChecked(True)
currentQAction.toggled.connect(partial(self.setColumnVisible, column))
currentQMenu.addAction(currentQAction)
self.rightCornerQPushButton.setMenu(currentQMenu)
def setColumnVisible (self, column, isChecked):
if isChecked:
self.localQTableView.showColumn(column)
else:
self.localQTableView.hideColumn(column)
def tableView (self):
return self.localQTableView
# Simulate data
myQStandardItemModel = QtGui.QStandardItemModel()
for _ in range(10):
myQStandardItemModel.appendRow([QtGui.QStandardItem('%d' % random.randint(100, 999)), QtGui.QStandardItem('%d' % random.randint(100, 999)), QtGui.QStandardItem('%d' % random.randint(100, 999))])
# Main application
myQApplication = QtGui.QApplication(sys.argv)
myQCustomTableViewWidget = QCustomTableViewWidget(myQStandardItemModel)
myQCustomTableViewWidget.show()
sys.exit(myQApplication.exec_())

Disable sorting of child items in QTreeView

I am using pyQt. How can I disable child items sorting in QTreeView/StandardItemModel?
You could use a QSortFilterProxyModel and reimplement its lessThan method.
Alternatively, create a subclass of QStandardItem and reimplement its less than operator.
Here's a simple example that demonstrates the latter approach:
from random import sample
from PyQt4 import QtGui, QtCore
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.view = QtGui.QTreeView(self)
self.view.setHeaderHidden(True)
self.model = QtGui.QStandardItemModel(self.view)
self.view.setModel(self.model)
parent = self.model.invisibleRootItem()
keys = range(65, 91)
for key in sample(keys, 10):
item = StandardItem('Item %s' % chr(key), False)
parent.appendRow(item)
for key in sample(keys, 10):
item.appendRow(StandardItem('Child %s' % chr(key)))
self.view.sortByColumn(0, QtCore.Qt.AscendingOrder)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.view)
class StandardItem(QtGui.QStandardItem):
def __init__(self, text, sortable=True):
QtGui.QStandardItem.__init__(self, text)
self.sortable = sortable
def __lt__(self, other):
if getattr(self.parent(), 'sortable', True):
return QtGui.QStandardItem.__lt__(self, other)
return False
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
Call setSortingEnabled(bool) on your QTreeView instance. Here is the corresponding docu for c++ and here is the link to pyqt api docu for this function

Resources