Disable sorting of child items in QTreeView - qt

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

Related

qt: qlayout do not correctly add widget

I want do dynamically change the layout in Qt. For example, I want to change the QHBoxLayout to QVBoxLayout through a button. My test code is:
from PyQt5.QtWidgets import *
import sys
class SubWidget(QWidget):
def __init__(self):
super().__init__()
self.lay = QHBoxLayout()
self.label1 = QLabel('left')
self.label2 = QLabel('right')
self.lay.addWidget(self.label1)
self.lay.addWidget(self.label2)
self.setLayout(self.lay)
def change(self):
self.lay.removeWidget(self.label1)
self.lay.removeWidget(self.label2)
self.lay = QVBoxLayout()
self.setLayout(self.lay)
self.lay.addWidget(self.label2)
self.lay.addWidget(self.label1)
class Widget(QWidget):
def __init__(self):
super().__init__()
lay = QVBoxLayout()
self.btn = QPushButton('change layout')
self.btn.clicked.connect(self.btnClick)
self.subWidget = SubWidget()
lay.addWidget(self.btn)
lay.addWidget(self.subWidget)
self.setLayout(lay)
def btnClick(self, check=False):
self.subWidget.change()
if __name__ == '__main__':
app = QApplication(sys.argv)
win = Widget()
win.show()
app.exec_()
The code output GUI is:
And I hope it change to the following picture after click change layout button:
Any suggestion is appreciated~~~
The point of the solution is
Make sure old layout is deleted, included both python wrapping reference and core Qt object, that's for deleteLater() is used.
Make sure new layout is assigned strictly after old was deleted, that's for need to use destroyed()-switchlayout() signal-slot chain.
I reproduced example with PySide6 (don't forget to switch on your version of PyQt or PySide package):
# from PyQt5.QtWidgets import *
from PySide6.QtWidgets import *
import sys
class SubWidget(QWidget):
def __init__(self):
super().__init__()
self.lay = QVBoxLayout()
self.label1 = QLabel('left')
self.label2 = QLabel('right')
self.lay.addWidget(self.label1)
self.lay.addWidget(self.label2)
self.setLayout(self.lay)
def change(self):
self.lay.removeWidget(self.label1)
self.lay.removeWidget(self.label2)
self.lay.deleteLater()
self.lay.destroyed.connect(self.switchlayout)
def switchlayout(self):
# print("***destroyed")
self.lay = QHBoxLayout()
self.lay.addWidget(self.label2)
self.lay.addWidget(self.label1)
self.setLayout(self.lay)
self.adjustSize()
class Widget(QWidget):
def __init__(self):
super().__init__()
lay = QVBoxLayout()
self.btn = QPushButton('change layout')
self.btn.clicked.connect(self.btnClick)
self.subWidget = SubWidget()
lay.addWidget(self.btn)
lay.addWidget(self.subWidget)
self.setLayout(lay)
def btnClick(self, check=False):
self.subWidget.change()
if __name__ == '__main__':
app = QApplication(sys.argv)
win = Widget()
win.show()
app.exec_()

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 :)

Painting FocusRect with QStylePainter

I am trying to make my own widget that uses QStyle options in order to give it a "native" look and feel.
Step 1 would be drawing a simple FocusRect, which I tried to accomplish like that:
import sys
from PyQt5 import QtWidgets, QtGui
class MyWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
def paintEvent(self, event):
painter = QtWidgets.QStylePainter(self)
option = QtWidgets.QStyleOptionFocusRect()
option.initFrom(self)
option.backgroundColor = self.palette().color(QtGui.QPalette.Background)
painter.drawPrimitive(QtWidgets.QStyle.PE_FrameFocusRect, option)
if __name__ == '__main__':
qApp = QtWidgets.QApplication(sys.argv)
qApp.setStyle('fusion')
window = QtWidgets.QMainWindow()
window.widget = MyWidget(window)
window.widget.resize(100, 100)
window.widget.move(50, 50)
window.setFixedSize(200, 200)
window.show()
sys.exit(qApp.exec_())
Unfortunately this only gives me an empty window. What am I missing?
Turns out that the FrameFocusRect is only painted, when it actually has the focus.
Adding option.state |= QtWidgets.QStyle.State_KeyboardFocusChange in the paintEvent method solves the problem.

PySide crashing when replacing rows in QFormLayout

Using the following code example PySide segfaults when pushing "Add", "Add", "Remove", "Add" and due to some other sequences of interaction.
Python: 2.7.6
PySide: 1.2.1
QtCore: 4.8.5
Code:
from PySide.QtGui import *
from PySide.QtCore import *
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setObjectName('MainWindow')
self.baseLayout = QWidget(self)
self.v_layout = QVBoxLayout(self.baseLayout)
self.setCentralWidget(self.baseLayout)
self.form_layout = QFormLayout(self.baseLayout)
self.v_layout.addLayout(self.form_layout)
self.button_add = QPushButton(self.baseLayout)
self.button_add.setText("Add")
self.v_layout.addWidget(self.button_add)
self.button_del = QPushButton(self.baseLayout)
self.button_del.setText("Remove")
self.v_layout.addWidget(self.button_del)
self.button_add.clicked.connect(self.add)
self.button_del.clicked.connect(self.remove)
self.fields = []
def add_item(self):
layout = QHBoxLayout(self.parent())
line = QLineEdit(self.parent())
slider = QSlider(self.parent())
layout.addWidget(line)
layout.addWidget(slider)
self.fields.append((layout, line, slider))
self.form_layout.addRow("Test", layout)
def add(self):
for i in range(15):
self.add_item()
def remove(self):
for (layout, line, slider) in self.fields:
line.deleteLater()
slider.deleteLater()
while self.form_layout.itemAt(0):
child = self.form_layout.takeAt(0)
if child.widget():
child.widget().deleteLater()
self.form_layout.update()
self.fields = []
def main():
import sys
app = QApplication(sys.argv)
frame = MainWindow()
frame.show()
app.exec_()
if __name__ == '__main__':
main()
Is this the correct way of adding compound widgets (in this case a QLineEdit and a QSlider within a QVBoxLayout) to a form layout? What am I doing wrong?
The correct way of adding "compound widgets" to a QFormLayout is to create a QWidget that will be the parent to that layout.
add_item() should rather look something like this:
def add_item(self):
widget = QWidget(self.parent())
layout = QHBoxLayout(widget)
line = QLineEdit(widget)
slider = QSlider(widget)
layout.addWidget(line)
layout.addWidget(slider)
self.fields.append((layout, widget, line, slider))
self.form_layout.addRow("Test", widget)
(And the widget also has to be removed when deleting the fields).
I think you are not creating the layouts in the right way, for example you are trying to set the layout of base_layout two times. Also you can check for count() on a QLayout to see if it has children:
from PySide.QtGui import *
from PySide.QtCore import *
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.baseLayout = QWidget(self)
self.v_layout = QVBoxLayout(self.baseLayout)
self.setCentralWidget(self.baseLayout)
self.form_layout = QFormLayout()
self.v_layout.addLayout(self.form_layout)
self.button_add = QPushButton()
self.button_add.setText("Add")
self.v_layout.addWidget(self.button_add)
self.button_del = QPushButton()
self.button_del.setText("Remove")
self.v_layout.addWidget(self.button_del)
self.button_add.clicked.connect(self.add)
self.button_del.clicked.connect(self.remove)
self.fields = []
def add_item(self):
layout = QHBoxLayout()
line = QLineEdit()
slider = QSlider()
layout.addWidget(line)
layout.addWidget(slider)
self.fields.append((layout, line, slider))
self.form_layout.addRow("Test", layout)
def add(self):
for i in range(15):
self.add_item()
def remove(self):
while self.form_layout.count() > 0:
child = self.form_layout.takeAt(0)
widget = child.widget()
if widget:
widget.deleteLater()
self.form_layout.update()
self.fields = []
def main():
import sys
app = QApplication(sys.argv)
frame = MainWindow()
frame.show()
app.exec_()
if __name__ == '__main__':
main()

QTreeView very slow sorting

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.

Resources