PyQT: adding to QAbstractItemModel using a button - pyqt

I'm trying to implement a TreeView using QAbstractItemModel. I want to set up an empty model and then add an item to the model using a button.
This appears to be a lot more complex than I'd realised! I've modified a custom instance of the QAbstractItemModel example found in the Lib\site-packages\PyQt4\examples\itemviews\simpletreemodel\simpletreemodel.pyw.
Rather than setting up the model all at once, I want o build it incrementally.
When I click the button, I get the message showing that the process method is executing, but the model isn't updated.
Any pointers as to why it's not working would be greatly appreciated!
from PyQt4 import QtGui, QtCore
import sys
class myWindow(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.resize(500, 400)
#Tree Widget
self.treeView = QtGui.QTreeView(self)
#Model
self.model = TreeModel()
self.treeView.setModel(self.model)
# Button
self.button = QtGui.QPushButton(self)
self.button.setText('Add Item')
self.button.clicked.connect(self.process)
# Add to Layout
self.gridLayout = QtGui.QGridLayout(self)
self.gridLayout.addWidget(self.button,0,0)
self.gridLayout.addWidget(self.treeView,1,0)
def process(self):
print "here"
newItem = TreeItem(["bob","bob","bob"],self.model.rootItem)
self.model.rootItem.appendChild(newItem)
class TreeItem(object):
def __init__(self, data, parent=None):
self.parentItem = parent
self.itemData = data
self.childItems = []
def appendChild(self, item):
self.childItems.append(item)
def child(self, row):
return self.childItems[row]
def childCount(self):
return len(self.childItems)
def columnCount(self):
return len(self.itemData)
def data(self, column):
try:
return self.itemData[column]
except IndexError:
return None
def parent(self):
return self.parentItem
def row(self):
if self.parentItem:
return self.parentItem.childItems.index(self)
return 0
class TreeModel(QtCore.QAbstractItemModel):
def __init__(self, parent=None):
super(TreeModel, self).__init__(parent)
self.rootItem = TreeItem(("Model", "Status","Location"))
def columnCount(self, parent):
if parent.isValid():
return parent.internalPointer().columnCount()
else:
return self.rootItem.columnCount()
def data(self, index, role):
if not index.isValid():
return None
if role != QtCore.Qt.DisplayRole:
return None
item = index.internalPointer()
return item.data(index.column())
def flags(self, index):
if not index.isValid():
return QtCore.Qt.NoItemFlags
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
def headerData(self, section, orientation, role):
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return self.rootItem.data(section)
return None
def index(self, row, column, parent):
if not self.hasIndex(row, column, parent):
return QtCore.QModelIndex()
if not parent.isValid():
parentItem = self.rootItem
else:
parentItem = parent.internalPointer()
childItem = parentItem.child(row)
if childItem:
return self.createIndex(row, column, childItem)
else:
return QtCore.QModelIndex()
def parent(self, index):
if not index.isValid():
return QtCore.QModelIndex()
childItem = index.internalPointer()
parentItem = childItem.parent()
if parentItem == self.rootItem:
return QtCore.QModelIndex()
return self.createIndex(parentItem.row(), 0, parentItem)
def rowCount(self, parent):
if parent.column() > 0:
return 0
if not parent.isValid():
parentItem = self.rootItem
else:
parentItem = parent.internalPointer()
return parentItem.childCount()
if __name__ == "__main__":
app = QtGui.QApplication.instance() # checks if QApplication already exists
if not app: # create QApplication if it doesnt exist
app = QtGui.QApplication(sys.argv)
# Window
window = myWindow()
window.show()
sys.exit(app.exec_())

Using a QStandardItemModel has worked a treat!
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class Window(QWidget):
def __init__(self):
QWidget.__init__(self)
self.resize(500, 400)
#Tree Widget
self.treeView = QTreeView()
#Model
self.model = QStandardItemModel()
self.treeView.setModel(self.model)
# Button
self.button = QPushButton()
self.button.setText('Add Item')
self.button.clicked.connect(self.addChildClick)
# Add to Layout
self.gridLayout = QGridLayout(self)
self.gridLayout.addWidget(self.button,0,0)
self.gridLayout.addWidget(self.treeView,1,0)
def addChildClick(self):
selection = self.treeView.selectedIndexes()
text = "bob"
item = QStandardItem(text)
# if nothing selected parent is model
if selection == []:
parent = self.model
else: # Otherwise parent is what is selected
s = selection[0] # Handling multiple selectons
parent = self.model.itemFromIndex(s)
parent.appendRow(item)
#cleanup
self.treeView.expandAll()
self.treeView.clearSelection()
if __name__ == "__main__":
app = QApplication.instance() # checks if QApplication already exists
if not app: # create QApplication if it doesnt exist
app = QApplication(sys.argv)
# Window
window = Window()
window.show()
app.exec_()

Related

KeyErrror: "parameter"

So I'm trying to implement a GUI in PyQT using a table, but for some reason I keep getting the error KeyError:"Parameter",
The dictionary uses variables from a decoded bin file that has data i would like to access an use
I'm very confused as i see no way forward, any help would be appreciated.
Below is my code
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import *
class DictionaryTableModel(QtCore.QAbstractTableModel):
def __init__(self, data, headers):
super(DictionaryTableModel, self).__init__()
self._data = data
self._headers = headers
def data(self, index, role):
if role == Qt.DisplayRole:
# Look up the key by header index.
column = index.column()
column_key = self._headers[column]
return self._data[index.row()][column_key]
def rowCount(self, index):
# The length of the outer list.
return len(self._data)
def columnCount(self, index):
# The length of our headers.
return len(self._headers)
def headerData(self, section, orientation, role):
# section is the index of the column/row.
if role == Qt.DisplayRole:
if orientation == Qt.Horizontal:
return str(self._headers[section])
if orientation == Qt.Vertical:
return str(section)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.table = QtWidgets.QTableView()
with open("C:\\Ptests\\asdf\\bikeparametereditor\\uploaddomainobject.bin", 'rb') as file:
bytes2 = file.read()
# Read & Assign to variables
serialNumber = bytes2[0x1b:0x1b + 15].decode("utf-8")
frameNumber = bytes2[0x2a:0x3b].decode("utf-8")
productionDate = int.from_bytes(bytes2[0x3b:0x3e], byteorder="little")
hardwareVersion = bytes2[0x3f:0x49].decode("utf-8")
ssDO_KEY_LOCK = int.from_bytes(bytes2[0x49:0x49], byteorder="little")
coreDataVersion = int.from_bytes(bytes2[0x00:0x02], byteorder="little")
coreCRC = int.from_bytes(bytes2[0x03:0x06], byteorder="little")
coreTimeStamp = int.from_bytes(bytes2[0x07:0x09], byteorder="little")
coreAddrIndex = int.from_bytes(bytes2[0x09:0x11], byteorder="little")
coreSize = int.from_bytes(bytes2[0x20:0x21], byteorder="little")
coreWriteCounter = int.from_bytes(bytes2[0x21:0x22], byteorder="little")
corefailCounter = int.from_bytes(bytes2[0x22:0x23], byteorder="little")
data = [
{"serialNumber": serialNumber,
"frameNumber": frameNumber,
"productionDate": productionDate,
"hardwareVersion": hardwareVersion,
"coreDataVersion": coreDataVersion,
"coreCRC": coreCRC,
"coreTimeStamp": coreTimeStamp,
"coreAddrIndex": coreAddrIndex,
"coreSize": coreSize,
"coreWriteCounter": coreWriteCounter,
"corefailCounter": corefailCounter,
"ssDO_KEY_LOCK": ssDO_KEY_LOCK}
]
headers = ["parameter", "value"]
self.model = DictionaryTableModel(data, headers)
self.table.setModel(self.model)
self.setCentralWidget(self.table)
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()

Removing rows of a QSortFilterProxyModel behaves badly

I have an application which contains a QTableView for which I would like to have the possibility to sort its contents but also to remove one or more rows. Below is an example code that implements this.
from PyQt5 import QtCore, QtWidgets
class NeXuSFilesModel(QtCore.QAbstractTableModel):
fields = ['col1','col2']
def __init__(self,parent):
super(NeXuSFilesModel,self).__init__(parent)
self._nexus_contents = [['aaa','111'],['bbb','222'],['ccc','333']]
def columnCount(self, parent=None):
return 2
def data(self,index,role):
if not index.isValid():
return QtCore.QVariant()
row = index.row()
col = index.column()
if role == QtCore.Qt.DisplayRole:
return str(self._nexus_contents[row][col])
else:
return QtCore.QVariant()
def headerData(self, index, orientation, role):
if role == QtCore.Qt.DisplayRole:
if orientation == QtCore.Qt.Horizontal:
return NeXuSFilesModel.fields[index]
else:
return index + 1
return None
def removeRow(self, row, parent):
self.beginRemoveRows(QtCore.QModelIndex(),row,row+1)
del self._nexus_contents[row]
self.endRemoveRows()
return True
def rowCount(self, parent=None):
return len(self._nexus_contents)
class NeXuSDataTableView(QtWidgets.QTableView):
def __init__(self,parent):
super(NeXuSDataTableView,self).__init__(parent)
self.horizontalHeader().setStretchLastSection(True)
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_Delete:
model = self.model()
selected_indexes = self.selectionModel().selectedRows()
source_indexes_rows = sorted([model.mapToSource(index).row() for index in selected_indexes],reverse=True)
for row in source_indexes_rows:
model.sourceModel().removeRow(row,QtCore.QModelIndex())
super(NeXuSDataTableView, self).keyPressEvent(event)
class MainWindow(QtWidgets.QWidget):
def __init__(self):
super(MainWindow,self).__init__()
self._table = NeXuSDataTableView(self)
model = NeXuSFilesModel(self)
proxy_model = QtCore.QSortFilterProxyModel()
proxy_model.setSourceModel(model)
self._table.setModel(proxy_model)
self._table.setSortingEnabled(True)
mainLayout = QtWidgets.QVBoxLayout()
mainLayout.addWidget(self._table)
self.setLayout(mainLayout)
self.show()
if __name__ == '__main__':
app = QtWidgets.QApplication([])
_ = MainWindow()
app.exec_()
When I run that code, I fall into several problems for which I could not find the solution or could not understand the explanations given by the various sources I could find.
When the program starts, the data is showed initially in the wrong order. Indeed, it is displayed in descending order whereas I would like to display it in ascending order
When I remove one item, it removes actually two items !
Would you have any idea about what is wrong with my implementation ?
About both your questions:
proxy_model.sort(0, QtCore.Qt.AscendingOrder) after self._table.sortSortingEnabled(True) results in an ascending order:
self._table.setSortingEnabled(True)
proxy_model.sort(0, QtCore.Qt.AscendingOrder)
mainLayout = QtWidgets.QVBoxLayout()
mainLayout.addWidget(self._table)
self.setLayout(mainLayout)
self.show()
Using self.beginRemoveRows(QtCore.QModelIndex(),row,row) will remove only one row.

Completion with QLineEdit and QItemDelegate

I'm trying to implement the QCompleter example we can find in the widgets/tools/customcompleter sources.
What I want is when I'm typing some text, if there is some matches, the first completion line available is selected so if I hit the return key (and only if I hit it), the completion is done.
But in my code here, the line is never selected. I think my problem is located in the keyPressEvent but I don't know where. It is a minimal example, written in pyqt5 (5.10) and Python 3.5.2.
Any help is very appreciated :)
Best regards
import sys
from PyQt5.QtCore import QAbstractTableModel, Qt, QVariant
from PyQt5.QtWidgets import (QApplication, QCompleter, QItemDelegate,
QLineEdit, QMainWindow, QTableView)
class MyLineEdit(QLineEdit):
def __init__(self, parent=None, completer=None):
super().__init__(parent)
if completer:
self.setCompleter(completer)
def setCompleter(self, completer):
if completer:
completer.setWidget(self)
completer.setCompletionMode(QCompleter.PopupCompletion)
completer.setCaseSensitivity(Qt.CaseInsensitive)
completer.setModelSorting(
QCompleter.CaseSensitivelySortedModel)
completer.setMaxVisibleItems(15)
completer.activated.connect(self.insertCompletion)
super().setCompleter(completer)
def insertCompletion(self, completion):
completer = self.completer()
if completer and completer.widget() == self:
completer.widget().setText(completion)
def keyPressEvent(self, event):
completer = self.completer()
if event.key() in (Qt.Key_Return, Qt.Key_Enter,
Qt.Key_Tab, Qt.Key_Backtab):
self.returnPressed.emit()
if completer and completer.popup().isHidden():
return
super().keyPressEvent(event)
input_text = self.text()
if completer:
if not event.text():
completer.popup().hide()
return
if input_text and input_text != completer.completionPrefix():
completer.setCompletionPrefix(input_text)
completer.popup().setCurrentIndex(
completer.completionModel().index(0, 0))
class MyDelegate(QItemDelegate):
def __init__(self, parent):
super().__init__(parent)
def createEditor(self, parent, option, index):
strings = ('tata', 'tete', 'titi', 'toto', 'tutu')
completer = QCompleter(strings)
editor = MyLineEdit(parent)
editor.setCompleter(completer)
editor.editingFinished.connect(self.commitAndCloseEditor)
editor.returnPressed.connect(self.commitAndCloseEditor)
return editor
def commitAndCloseEditor(self):
editor = self.sender()
self.commitData.emit(editor)
self.closeEditor.emit(editor)
def setEditorData(self, editor, index):
if editor:
editor.setText(index.model().data[0])
def setModelData(self, editor, model, index):
if editor:
model.setData(index, editor.text(), Qt.EditRole)
class Model(QAbstractTableModel):
def __init__(self):
super().__init__()
self.data = ['hello']
def rowCount(self, parent=None):
return 1
def columnCount(self, parent=None):
return 1
def data(self, index, role):
if not index.isValid():
return QVariant()
if role in (Qt.DisplayRole, Qt.EditRole):
return self.data[0]
return QVariant()
def setData(self, index, value, role):
if role == Qt.EditRole:
self.data[0] = value
top_left = self.index(0, 0)
bottom_right = self.index(
self.rowCount() + 1, self.columnCount())
self.dataChanged.emit(top_left, bottom_right,
[Qt.DisplayRole])
return True
return False
def flags(self, index):
return Qt.ItemIsEditable | super().flags(index)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.model = Model()
self.table = QTableView()
self.table.setModel(self.model)
delegate = MyDelegate(self.table)
self.table.setItemDelegateForColumn(0, delegate)
def initUI(self):
self.show()
self.setCentralWidget(self.table)
if __name__ == '__main__':
app = QApplication(sys.argv)
mw = MainWindow()
mw.initUI()
sys.exit(app.exec_())
Try it:
import sys
from PyQt5 import QtCore, QtWidgets
class LineEdit(QtWidgets.QLineEdit):
def __init__(self, *args, **kwargs):
QtWidgets.QLineEdit.__init__(self, *args, **kwargs)
self.multipleCompleter = None
def keyPressEvent(self, event):
QtWidgets.QLineEdit.keyPressEvent(self, event)
if not self.multipleCompleter:
return
c = self.multipleCompleter
if self.text() == "":
return
c.setCompletionPrefix(self.cursorWord(self.text()))
if len(c.completionPrefix()) < 1:
c.popup().hide()
return
c.complete()
def cursorWord(self, sentence):
p = sentence.rfind(" ")
if p == -1:
return sentence
return sentence[p + 1:]
def insertCompletion(self, text):
p = self.text().rfind(" ")
if p == -1:
self.setText(text)
else:
self.setText(self.text()[:p+1]+ text)
def setMultipleCompleter(self, completer):
self.multipleCompleter = completer
self.multipleCompleter.setWidget(self)
completer.activated.connect(self.insertCompletion)
def main():
app = QtWidgets.QApplication(sys.argv)
w = LineEdit()
completer = QtWidgets.QCompleter(['tata', 'tete', 'titi', 'toto', 'tutu'])
completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
w.setMultipleCompleter(completer)
w.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Finally found a solution. I used a QTextEdit, it does not work with QLineEdit, I don't know why. I still have a small problem, I can't close the completion popup when I hit the return key, so I must hit return key, twice. Not a big problem.
import sys
from PyQt5.QtCore import QAbstractTableModel, Qt, QVariant, pyqtSignal
from PyQt5.QtGui import QTextCursor, QTextOption
from PyQt5.QtWidgets import (QAbstractItemDelegate, QApplication, QCompleter,
QItemDelegate, QMainWindow, QTableView, QTextEdit)
class MyLineEdit(QTextEdit):
returnPressed = pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
self.setAcceptRichText(False)
self.setWordWrapMode(QTextOption.NoWrap)
self.setUndoRedoEnabled(False)
self.setTabChangesFocus(True)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.completer = None
self.textChanged.connect(self.textHasChanged)
def setCompleter(self, completer):
if completer:
completer.setWidget(self)
completer.setCompletionMode(QCompleter.PopupCompletion)
completer.setCaseSensitivity(Qt.CaseInsensitive)
completer.setModelSorting(
QCompleter.CaseSensitivelySortedModel)
completer.setMaxVisibleItems(15)
completer.activated.connect(self.insertCompletion)
self.completer = completer
def insertCompletion(self, completion):
print('>>> insertCompletion')
if self.completer and self.completer.widget() == self:
self.completer.widget().setPlainText(completion)
self.completer.widget().moveCursor(QTextCursor.EndOfLine)
self.completer.widget().ensureCursorVisible()
def focusInEvent(self, event):
print('>>> focusInEvent')
if self.completer:
self.completer.setWidget(self)
super().focusInEvent(event)
def keyPressEvent(self, event):
print('>>> keyPressEvent')
if self.completer and self.completer.popup().isVisible():
if event.key() in (Qt.Key_Return, Qt.Key_Enter,
Qt.Key_Tab, Qt.Key_Backtab, Qt.Key_Escape):
event.ignore()
return
else:
if event.key() in(Qt.Key_Return, Qt.Key_Enter):
self.returnPressed.emit()
return
super().keyPressEvent(event)
if not self.toPlainText():
self.completer.popup().hide()
return
self.completer.setCompletionPrefix(self.toPlainText())
self.completer.popup().setCurrentIndex(
self.completer.completionModel().index(0, 0))
self.completer.complete()
def textHasChanged(self):
# remove new lines and strip left blank characters
self.blockSignals(True)
cursor = self.textCursor()
self.setPlainText(' '.join(self.toPlainText().splitlines()).lstrip())
self.setTextCursor(cursor)
self.ensureCursorVisible()
self.blockSignals(False)
class MyDelegate(QItemDelegate):
def __init__(self, parent):
super().__init__(parent)
def createEditor(self, parent, option, index):
strings = ('tata', 'tada', 'tadam', 'tete', 'titi', 'toto', 'tutu')
completer = QCompleter(strings)
editor = MyLineEdit(parent)
editor.setCompleter(completer)
editor.returnPressed.connect(self.commitAndCloseEditor)
return editor
def commitAndCloseEditor(self):
print('>>> commitAndCloseEditor')
editor = self.sender()
self.commitData.emit(editor)
self.closeEditor.emit(editor)
def setEditorData(self, editor, index):
if editor:
editor.setText(index.model().data[0])
editor.selectAll()
def setModelData(self, editor, model, index):
if editor:
model.setData(index, editor.toPlainText(), Qt.EditRole)
class Model(QAbstractTableModel):
def __init__(self):
super().__init__()
self.data = ['hello']
def rowCount(self, parent=None):
return 1
def columnCount(self, parent=None):
return 1
def data(self, index, role):
if not index.isValid():
return QVariant()
if role in (Qt.DisplayRole, Qt.EditRole):
return self.data[0]
return QVariant()
def setData(self, index, value, role):
if role == Qt.EditRole:
self.data[0] = value
top_left = self.index(0, 0)
bottom_right = self.index(
self.rowCount() + 1, self.columnCount())
self.dataChanged.emit(top_left, bottom_right,
[Qt.DisplayRole])
return True
return False
def flags(self, index):
return Qt.ItemIsEditable | super().flags(index)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.model = Model()
self.table = QTableView()
self.table.setModel(self.model)
delegate = MyDelegate(self.table)
self.table.setItemDelegateForColumn(0, delegate)
def initUI(self):
self.show()
self.setCentralWidget(self.table)
if __name__ == '__main__':
app = QApplication(sys.argv)
mw = MainWindow()
mw.initUI()
sys.exit(app.exec_())

Fading on Tab Change in pyqt

i have a page containing two tabs.i want to add a fadeIn effect when i change the tabs.Is that possible?
import sys
from PyQt4.QtCore import QTimeLine
from PyQt4.QtGui import *
class FaderWidget(QWidget):
def __init__(self, old_widget, new_widget):
QWidget.__init__(self, new_widget)
self.old_pixmap = QPixmap(new_widget.size())
old_widget.render(self.old_pixmap)
self.pixmap_opacity = 1.0
self.timeline = QTimeLine()
self.timeline.valueChanged.connect(self.animate)
self.timeline.finished.connect(self.close)
self.timeline.setDuration(333)
self.timeline.start()
self.resize(new_widget.size())
self.show()
def paintEvent(self, event):
painter = QPainter()
painter.begin(self)
painter.setOpacity(self.pixmap_opacity)
painter.drawPixmap(0, 0, self.old_pixmap)
painter.end()
def animate(self, value):
self.pixmap_opacity = 1.0 - value
self.repaint()
class StackedWidget(QStackedWidget):
def __init__(self, parent = None):
QStackedWidget.__init__(self, parent)
def setCurrentIndex(self, index):
self.fader_widget = FaderWidget(self.currentWidget(), self.widget(index))
QStackedWidget.setCurrentIndex(self, index)
def setPage1(self):
self.setCurrentIndex(0)
def setPage2(self):
self.setCurrentIndex(1)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = QWidget()
stack = StackedWidget()
stack.addWidget(QCalendarWidget())
editor = QTextEdit()
editor.setPlainText("Hello world! "*100)
stack.addWidget(editor)
page1Button = QPushButton("Page 1")
page2Button = QPushButton("Page 2")
page1Button.clicked.connect(stack.setPage1)
page2Button.clicked.connect(stack.setPage2)
layout = QGridLayout(window)
layout.addWidget(stack, 0, 0, 1, 2)
layout.addWidget(page1Button, 1, 0)
layout.addWidget(page2Button, 1, 1)
window.show()
sys.exit(app.exec_())
this is code that shows some fade effect but i m getting nothing from it and how it works and how to implement on tabs. it will be really appreciable if someone could help me implement it on tabs as well.
thanks in advance.
With the same logic as the code you show, each widget will be placed inside a QStackedWidget, where one of them will be the widget that will be displayed and the other will be the FaderWidget.
class FaderWidget(QWidget):
def __init__(self, *args, **kwargs):
QWidget.__init__(self, *args, **kwargs)
self.pixmap_opacity = None
self.timeline = QTimeLine(333, self)
self.timeline.valueChanged.connect(self.animate)
self.timeline.finished.connect(self.close)
def start(self, old_widget, new_widget):
self.pixmap_opacity = 1.0
self.old_pixmap = QPixmap(new_widget.size())
old_widget.render(self.old_pixmap)
self.timeline.start()
self.resize(new_widget.size())
self.show()
def paintEvent(self, event):
if self.pixmap_opacity:
QWidget.paintEvent(self, event)
painter = QPainter(self)
painter.setOpacity(self.pixmap_opacity)
painter.drawPixmap(0, 0, self.old_pixmap)
def animate(self, value):
self.pixmap_opacity = 1.0 - value
self.update()
class FaderTabWidget(QTabWidget):
def __init__(self, parent=None):
QTabWidget.__init__(self, parent)
self.currentChanged.connect(self.onCurrentIndex)
self.last = -1
self.current = self.currentIndex()
def onCurrentIndex(self, index):
self.last = self.current
self.current = self.currentIndex()
if self.widget(self.last):
self.widget(self.last).setCurrentIndex(1)
old_widget = self.widget(self.last).widget(0)
current_widget = self.widget(self.current).widget(0)
fade = self.widget(self.current).widget(1)
fade.start(old_widget, current_widget)
def addTab(self, widget, text):
stack = QStackedWidget(self)
stack.addWidget(widget)
fade = FaderWidget(self)
fade.timeline.finished.connect(lambda: stack.setCurrentIndex(0))
stack.addWidget(fade)
stack.setCurrentIndex(0 if self.currentIndex() == -1 else 1)
QTabWidget.addTab(self, stack, text)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = QWidget()
tabWidget = FaderTabWidget()
tabWidget.addTab(QCalendarWidget(), "Tab1")
editor = QTextEdit()
editor.setPlainText("Hello world! " * 100)
tabWidget.addTab(editor, "Tab2")
layout = QVBoxLayout(window)
layout.addWidget(tabWidget)
window.show()
sys.exit(app.exec_())

PyQt QWidget in QAbstractListModel gets deleted with QSortFilterProxyModel

I need to populate a listview with widgets, then have a custom proxyfilter work with it.
Without the filter it works great, when active it seems to delete the widgets attach to the model.
It shows up fine showing all items, filtering works but when erasing the filter, when hidden widgets should be shown again following error gets thrown:
custom_widget.setGeometry(option.rect)
RuntimeError: underlying C/C++ object has been deleted
Tried not using QVariant and going the internalPointer route but breaks at the same spot.
Thanks for having a look!
Setup:
def __init__(self, *args):
QtGui.QWidget.__init__(self, *args)
# create temp data
self.list_data = []
for x in xrange(500):
widget = ListItemWidget(text=str(x), parent=self)
self.list_data.append((str(x), widget)) # testing to put in inmut tuple
# create listviewmodel
self.lm = ListViewModel(parent=self)
# create listview widget
self.lv = QtGui.QListView()
# create filter proxy
self.proxy_model = ListViewFilterProxyModel()
self.proxy_model.setFilterPattern('')
self.proxy_model.setSourceModel(self.lm)
# set model of listview to filter proxy
self.lv.setModel(self.proxy_model)
# set delegate for column 0
self.lv.setItemDelegateForColumn(0, CustomWidgetDelegate(self.lv))
self.lm.updateData(self.list_data)
self.proxy_model.invalidate()
self.connect(self.filter_edit, QtCore.SIGNAL("textChanged(QString)"), self.update_filter)
def update_filter(self, pattern):
self.proxy_model.setFilterPattern(pattern)
self.proxy_model.invalidate()
Custom widget
class ListItemWidget(QtGui.QWidget):
def __init__(self, text=None, parent=None):
QtGui.QWidget.__init__(self)
self.text = text
#QtCore.pyqtProperty(QtCore.QString)
def text(self):
return self.__text
#text.setter
def text(self, value):
self.__text = value
Delegate for painting the view
class CustomWidgetDelegate(QtGui.QItemDelegate):
def __init__(self, parent=None):
super(CustomWidgetDelegate, self).__init__(parent)
def paint(self, painter, option, index):
custom_widget = index.model().data(index, QtCore.Qt.DisplayRole).toPyObject()[1]
>>>>>> custom_widget.setGeometry(option.rect)
if not self.parent().indexWidget(index):
self.parent().setIndexWidget(index, custom_widget)
List view model:
class ListViewModel(QtCore.QAbstractListModel):
def __init__(self, parent=None, *args):
QtCore.QAbstractListModel.__init__(self, parent, *args)
self.listdata = []
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.listdata)
def data(self, index, role):
if role == QtCore.Qt.SizeHintRole:
return QtCore.QSize(80, 80)
if index.isValid() and role == QtCore.Qt.DisplayRole:
return QtCore.QVariant(self.listdata[index.row()]).toPyObject()
return QtCore.QVariant()
def updateData(self, listdata):
self.listdata = listdata
index = len(self.listdata)
return True
Finally the filter proxy model:
class ListViewFilterProxyModel(QtGui.QSortFilterProxyModel):
def __init__(self, parent=None):
self.filter_str = None
QtGui.QSortFilterProxyModel.__init__(self, parent)
def setFilterPattern(self, pattern):
self.filter_str = QtCore.QString(pattern)
def filterAcceptsRow(self, sourceRow, sourceParent):
if self.filter_str is None:
return True
index = self.sourceModel().index(sourceRow, 0, sourceParent)
# just testing on the str here...
text = index.data().toPyObject()[0]
if not str(self.filter_str) in text:
return False
return True

Resources