I have recently started to use PyQt4 for making GUI applications. I was trying to make an application in which I used the QTableWidget. It's is 2-column table where the first column was set to QLineEdit and second column was set to QListWidget using delegates (sorry if "set to" is not the correct terminology"). The entries in the QListWidget column are coming from the list output of a QFileDialog
import sys
from PyQt4.QtGui import *
from PyQt4 import uic
from PyQt4.QtCore import *
qtCreatorFile = "app_name.ui" # UI File designed using QtDesigner
Ui_MainWindow, QtBaseClass = uic.loadUiType(qtCreatorFile)
class DelegateLEdit(QStyledItemDelegate):
def createEditor(self, parent, option, index):
line_edit = QLineEdit(parent)
return line_edit
class DelegateLWidget(QStyledItemDelegate):
def createEditor(self, parent, option, index):
list_widget = QListWidget(parent)
list_widget.setSelectionMode(QAbstractItemView.ExtendedSelection)
return list_widget
Everything seems to work fine except for one thing. Whenever I double click on any cell in the QListWidget column, the contents of that particular cell are set to -1. I have not handled the "doubleClicked" signal. Even when I did (I made it print the content of the cell on a textBrowser), double clicking the cell set the contents to -1. Can anyone please help me through this? I have tried reading the QTableWidget documentation but did not find any success. Can someone please point out what am I missing?
Here is the init function of my Form class
def __init__(self):
QMainWindow.__init__(self)
Ui_MainWindow.__init__(self)
self.setupUi(self)
self.setWindowFlags(Qt.WindowMinimizeButtonHint)
self.list_widget = QListWidget()
self.add_button.clicked.connect(self.add_to_list)
# self.table.itemDoubleClicked.connect(self.print_cell_content)
self.table.setColumnCount(2)
first_header = "Option"
second_header = "Directories"
header = first_header + ";" + second_header
self.table.setHorizontalHeaderLabels(header.split(";"))
header = self.table.horizontalHeader()
header.setResizeMode(1, QHeaderView.ResizeToContents)
self.table.resizeColumnsToContents()
# self.table.setShowGrid(False)
self.delegate_lw = DelegateLWidget(self)
self.delegate_le = DelegateLEdit(self)
self.table.setItemDelegateForColumn(0, self.delegate_le)
self.table.setItemDelegateForColumn(1, self.delegate_lw)
EDIT:
handler for add button that adds files to the second column of table
def add_to_list(self):
file_dialog = QFileDialog(self)
file_dialog.saveState()
file_dialog.setOption(QFileDialog.DontUseNativeDialog, True)
file_list = file_dialog.getOpenFileNames()
self.dir_list.extend(file_list)
self.dir_list = list(set(self.dir_list))
self.add_items(self.dir_list)
def add_items(self, list_items):
list_items.sort()
column = 1
self.row_count = 0
for item in list_items:
self.table.insertRow(self.row_count)
self.table.setItem(self.row_count, column, QTableWidgetItem(item))
self.row_count += 1
Thank You
You need to implement setEditorData and setModelData in your delegate, in order to be able to store and retrieve the model's data. For the list-widget column, the default behaviour is to display the current index, which will be -1 for an empty list.
Here is what your delegate classes should look like:
class DelegateLEdit(QStyledItemDelegate):
def createEditor(self, parent, option, index):
line_edit = QLineEdit(parent)
return line_edit
def setEditorData(self, editor, index):
editor.setText(index.data())
def setModelData(self, editor, model, index):
model.setData(index, editor.text())
class DelegateLWidget(QStyledItemDelegate):
def createEditor(self, parent, option, index):
list_widget = QListWidget(parent)
list_widget.setSelectionMode(QAbstractItemView.ExtendedSelection)
list_widget.addItems('One Two Three Four'.split()) # or whatever
return list_widget
def setEditorData(self, editor, index):
for line in index.data().splitlines():
for item in editor.findItems(line, Qt.MatchExactly):
item.setSelected(True)
def setModelData(self, editor, model, index):
text = '\n'.join(item.text() for item in editor.selectedItems())
model.setData(index, text)
Related
I'm trying to design a table widget in pyqt that gets the value in the first column of a row when navigating the table with the arrow keys. I'm able to do that using clicked.connect() on my table class using the pointer, but when using the arrow keys to navigate the table I can't manage to figure out a way to connect my function. I'm just getting my bearings in pyqt and have tried figuring this out from the docs but it doesn't seem any of the signal methods for QAbstractItemModel work. Not sure if I even tried the right thing. I tried adding a KeyPressEvent definition to my QAbstractTableView class but couldn't get that to work - also tried subclassing QTableView to no avail. Off course not sure any of those attempts were down properly. Here is my basic code that makes a table that highlights rows and prints the value in the first column of the selected row when that row is selected via a pointer click, but if you navigate with the arrow keys obviously nothing prints because the method to print the value isn't called.
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt, QAbstractTableModel, QVariant
test_data = [[i,j,k] for i in range(2) for j in range(2) for k in range(2)]
class TableStaticModel(QAbstractTableModel):
def __init__(self, header, data):
super(TableStaticModel, self).__init__()
self._data = data
self.header = header
def data(self, index, role=Qt.DisplayRole):
if role==Qt.DisplayRole:
return self._data[index.row()][index.column()]
if role==Qt.TextAlignmentRole:
value = self._data[index.row()][index.column()]
return Qt.AlignCenter
def rowCount(self, index):
return len(self._data)
def columnCount(self,index):
return len(self._data[0])
def headerData(self, col, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return QVariant(self.header[col])
return QVariant()
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.table = QtWidgets.QTableView()
model = TableStaticModel(['A','B','C'],test_data)
self.table.setModel(model)
self.table.clicked.connect(self.get_table_row_value)
self.table.setSelectionBehavior(self.table.SelectRows)
self.table.resizeRowsToContents()
self.table.setColumnWidth(0,83)
self.table.setColumnWidth(1,85)
self.table.setColumnWidth(2,83)
self.setCentralWidget(self.table)
def get_table_row_value(self):
index=self.table.selectionModel().currentIndex()
value=index.sibling(index.row(),0).data()
print(value)
app=QtWidgets.QApplication(sys.argv)
window=MainWindow()
window.show()
app.exec_()
If you only want to select one row then you must set the selectionModel to SingleSelection. On the other hand you must use the selectionChanged signal of the selectionModel:
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.table = QtWidgets.QTableView(
selectionBehavior=QtWidgets.QTableView.SelectRows,
selectionMode=QtWidgets.QTableView.SingleSelection,
)
model = TableStaticModel(["A", "B", "C"], test_data)
self.table.setModel(model)
self.table.selectionModel().selectionChanged.connect(self.get_table_row_value)
self.table.resizeRowsToContents()
self.table.setColumnWidth(0, 83)
self.table.setColumnWidth(1, 85)
self.table.setColumnWidth(2, 83)
self.setCentralWidget(self.table)
def get_table_row_value(self):
rows = set()
for index in self.table.selectedIndexes():
rows.add(index.row())
for row in rows:
ix = self.table.model().index(row, 0)
print(ix.data())
So i have a Item Completer class which i call in tableWidget.setItemDelegate, and when I write something in the tableWidget cell it starts to suggest it. But I want it to display all the things in the array so the user can chose from that.
self.tableWidget.setItemDelegate(TableItemCompleter())
class TableItemCompleter(QStyledItemDelegate):
def __init__(self, parent = None):
super(TableItemCompleter, self).__init__(parent)
def createEditor(self, parent, styleOption, index):
editor = QLineEdit(parent)
nevjegyzek_df = pd.read_csv(mypath+'/Adatok/nevjegyzek.csv',sep=';',encoding='utf-8')
nevjegyzek_df = pd.DataFrame(nevjegyzek_df, columns=['Név'])
completion_ls = list(nevjegyzek_df['Név'])
autoComplete = QCompleter(completion_ls)
editor.setCompleter(autoComplete)
return editor
I have a QCompleter on a QTableWidget column. As soon as the user starts editing I would like the completer to pop up, not waiting for them to enter text first. I subclassed the setEditorData function of the QStyledItemDelegate to do this which seems to make the most sense to me, however when I call completer.complete() nothing happens until I finish editing (at which point the popup fires).
Here is my code for the delegate:
class CompleterItemDelegate(QtGui.QStyledItemDelegate):
def createEditor(self, parent, option, index):
completer = QtGui.QCompleter(['test', 'test2'])
completer.setCompletionMode(completer.UnfilteredPopupCompletion)
edit = QtGui.QLineEdit(parent)
edit.setCompleter(completer)
return edit
def setEditorData(self, editor, index):
completer = editor.completer()
completer.complete() # does not fire until after editing is done
completer.popup().show() # no luck here either
print("setting editor data") # this however does work as expected...
super().setEditorData(editor, index)
You have to call complete() when the widget is displayed and for this you can use the showEvent() method:
from PyQt4 import QtCore, QtGui
class LineEdit(QtGui.QLineEdit):
def showEvent(self, event):
if self.completer() is not None:
QtCore.QTimer.singleShot(0, self.completer().complete)
super().showEvent(event)
class CompleterItemDelegate(QtGui.QStyledItemDelegate):
def createEditor(self, parent, option, index):
completer = QtGui.QCompleter(["test", "test2"])
completer.setCompletionMode(QtGui.QCompleter.UnfilteredPopupCompletion)
edit = LineEdit(parent)
edit.setCompleter(completer)
return edit
def main(args):
app = QtGui.QApplication(args)
w = QtGui.QTableWidget(4, 4)
delegate = CompleterItemDelegate(w)
w.setItemDelegate(delegate)
w.show()
ret = app.exec_()
return ret
if __name__ == "__main__":
import sys
sys.exit(main(sys.argv))
I want to have a QListView which displays custom widgets. I guess the best way to do this would be a QItemDelegate. Unfortunately I don't quite understand how to subclass it correctly and how to implement the paint() method, which seems to be the most important one. I couldn't find anything about using a delegate to create another widget.
I already tried to implement something similar without a delegate, but that didn't work out that well, because QListView is not supposed to display widgets.
import sys
from PyQt4 import QtCore
from PyQt4 import QtGui
class Model(QtCore.QAbstractListModel):
def __init__(self, parent=None):
super(QtCore.QAbstractListModel, self).__init__(parent)
self._widgets = []
def headerData(self, section, orientation, role):
""" Returns header for columns """
return "Header"
def rowCount(self, parentIndex=QtCore.QModelIndex()):
""" Returns number of interfaces """
return len(self._widgets)
def data(self, index, role):
""" Returns the data to be displayed """
if role == QtCore.Qt.DisplayRole:
row = index.row()
return self._widgets[row]
def insertRow(self, widget, parentIndex=QtCore.QModelIndex()):
""" Inserts a row into the model """
self.beginInsertRows(parentIndex, 0, 1)
self._widgets.append(widget)
self.endInsertRows()
class Widget(QtGui.QWidget):
def __init__(self, parent=None, name="None"):
super(QtGui.QWidget, self).__init__(parent)
self.layout = QtGui.QHBoxLayout()
self.setLayout(self.layout)
self.checkbox = QtGui.QCheckBox()
self.button = QtGui.QPushButton(self)
self.label = QtGui.QLabel(self)
self.label.setText(name)
self.layout.addWidget(self.checkbox)
self.layout.addWidget(self.button)
self.layout.addWidget(self.label)
class Window(QtGui.QMainWindow):
def __init__(self, parent=None):
super(QtGui.QMainWindow, self).__init__(parent)
self.view = QtGui.QListView(self)
self.model = Model()
self.view.setModel(self.model)
self.setCentralWidget(self.view)
self.model.insertRow(
widget=Widget(self)
)
self.model.insertRow(
widget=Widget(self)
)
self.model.insertRow(
widget=Widget(self)
)
self.model.insertRow(
widget=Widget(self)
)
self.show()
app = QtGui.QApplication(sys.argv)
window = Window()
sys.exit(app.exec_())
So, how would I need to implement a delegate in order to do what I want?
Here's an example of a QTableWidget with a button and text on each row. I defined an add_item method to add a whole row at once: insert a new row, put a button in column 0, put a regular item in column 1.
import sys
from PyQt4 import QtGui,QtCore
class myTable(QtGui.QTableWidget):
def __init__(self,parent=None):
super(myTable,self).__init__(parent)
self.setColumnCount(2)
def add_item(self,name):
#new row
row=self.rowCount()
self.insertRow(row)
#button in column 0
button=QtGui.QPushButton(name)
button.setProperty("name",name)
button.clicked.connect(self.on_click)
self.setCellWidget(row,0,button)
#text in column 1
self.setItem(row,1,QtGui.QTableWidgetItem(name))
def on_click(self):
# find the item with the same name to get the row
text=self.sender().property("name")
item=self.findItems(text,QtCore.Qt.MatchExactly)[0]
print("Button click at row:",item.row())
if __name__=='__main__':
app = QtGui.QApplication(sys.argv)
widget = myTable()
widget.add_item("kitten")
widget.add_item("unicorn")
widget.show()
sys.exit(app.exec_())
Bonus: how to know on which button did the user clicked ? A button doesn't have a row property, but we can create one when we instantiate the buttons, like so:
button.setProperty("row",row)
Problem is, if you sort your table or delete a row, the row numbers will not match any more. So instead we set a "name" property, same as the text of the item in column 1. Then we can use findItems to get the row (see on_click).
I've been trying to get a tabeView to display one of its columns as comboBoxes. In order to do this, I've written the code for a custom delegate:
class comboBoxDelegate(QStyledItemDelegate):
def __init__(self, model, parent=None):
super(comboBoxDelegate, self).__init__(parent)
self.parent= parent
self.model= model
def createEditor(self, parent, option, index):
if not index.isValid():
return False
self.currentIndex=index
self.comboBox = QComboBox(parent)
self.comboBox.setModel(self.model)
value = index.data(Qt.DisplayRole)
self.comboBox.setCurrentIndex(value)
return self.comboBox
def setEditorData(self, editor, index):
value = index.data(Qt.DisplayRole)
editor.setCurrentIndex(value)
def setModelData(self, editor, model, index):
if not index.isValid():
return False
index.model().setData(index, editor.currentIndex(), Qt.EditRole)
def paint(self, painter, option, index):
currentIndex= index.data(Qt.DisplayRole)
opt= QStyleOptionComboBox()
opt.rect= option.rect
currentComboIndex= self.model.createIndex(currentIndex,0)
opt.currentText= self.model.data(currentComboIndex, Qt.DisplayRole)
QApplication.style().drawComplexControl(QStyle.CC_ComboBox, opt, painter)
The problem is that when I try it the comboBox doesn't show any text at first (only once you've clicked on it). It seems the currentText property isn't working. Any help will be appreciated.
I know this is old, but you really don't have to deal with the painting at all. The combobox doesn't show a value because the combobox current index was likely set as a string instead of an int.
class ComboBoxDelegate(QtGui.QStyledItemDelegate):
"""ComboBox view inside of a Table. It only shows the ComboBox when it is
being edited.
"""
def __init__(self, model, itemlist=None):
super().__init__(model)
self.model = model
self.itemlist = None
# end Constructor
def createEditor(self, parent, option, index):
"""Create the ComboBox editor view."""
if self.itemlist is None:
self.itemlist = self.model.getItemList(index)
editor = QtGui.QComboBox(parent)
editor.addItems(self.itemlist)
editor.setCurrentIndex(0)
editor.installEventFilter(self)
return editor
# end createEditor
def setEditorData(self, editor, index):
"""Set the ComboBox's current index."""
value = index.data(QtCore.Qt.DisplayRole)
i = editor.findText(value)
if i == -1:
i = 0
editor.setCurrentIndex(i)
# end setEditorData
def setModelData(self, editor, model, index):
"""Set the table's model's data when finished editing."""
value = editor.currentText()
model.setData(index, value)
# end setModelData
# end class ComboBoxDelegate
This delegate will only show the combo box when the item is being edited otherwise it shows a normal text item delegate.
You can override QStyledItemDelegate.displayText() method to make your delegate display text without reimplementing paint(). Something like
class comboBoxDelegate(QStyledItemDelegate):
...
def displayText(self, value, locale=None):
return get_appropriate_text_representation_for_value(value)
I think you should call parent class paint() method. Add:
QStyledItemDelegate.paint(self, painter, option, index)
at the end of the paint method in your class, after the call to drawComplexControl