Reputation: 33
I have 3 three pictures shown in below:
How to let QLineEdit and QPushButton show in a column and style like this in a tableview in PyQt5?
I have the following three pictures shown in below,
I want to write a GUI which fulfill these feature by PyQt5:
after some seconds, at 'Click here to add a file' click mouse again one time, it will enter edit mode. just like digital 2 point to, a QLineEdit and a QPushButton '...' will display in the 2nd column. if I click '...', and pop up a File Selection Dialog, when I select a file, it will replace 'Click here to add a file' by file absolute path.
be careful: not double-click mouse enter into edit mode, it should be click mouse one time, some seconds later, click mouse again, will enter into edit mode. when I select a file which absolute path is very very long. I can see some char show behind QPushButton '...', it looks like QPushButton overlap on the right of QLineEdit.
I have research 3 features many days, but cannot get my desire style. I will give my code in here, as a example, it is 2 rows and 2 columns. anybody could help me to modify and fullfil above 3 function.
Thanks in advance
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class Delegate(QStyledItemDelegate):
def __init__(self, parent=None):
super(Delegate, self).__init__(parent)
def createEditor(self, parent, option, index):
if index.column() == 0:
lineedit = QLineEdit("$woroot/1.scs",parent)
pushbutton = QPushButton("...", parent)
#lineedit = QLineEdit("..",self.parent())
#pushbutton = QPushButton("...", self.parent())
lineedit.index = [index.row(), index.column()]
pushbutton.index = [index.row(), index.column()]
h_box_layout = QHBoxLayout()
h_box_layout.addWidget(lineedit)
h_box_layout.addWidget(pushbutton)
h_box_layout.setContentsMargins(0, 0, 0, 0)
h_box_layout.setAlignment(Qt.AlignCenter)
widget = QWidget()
widget.setLayout(h_box_layout)
self.parent().setIndexWidget(
index,
widget
)
elif index.column() == 1:
combobox = QComboBox(parent)
combobox.addItems(section_list)
combobox.setEditable(True)
#combobox.editTextChanged.connect(self.commitAndCloseEditor)
return combobox
def setEditorData(self, editor, index):
text = index.model().data(index, Qt.DisplayRole)
print "setEditorData, text=", text
text = str(text)
i = editor.findText(text)
print "i=", i
if i == -1:
i = 0
editor.setCurrentIndex(i)
def setModelData(self, editor, model, index):
text = editor.currentText()
if len(text) >= 1:
model.setData(index, text)
def updateEditorGeometry(self, editor, option, index):
editor.setGeometry(option.rect)
def commitAndCloseEditor(self):
editor = self.sender()
if isinstance(editor, (QTextEdit, QLineEdit,QSpinBox,QComboBox)):
self.commitData[QWidget].emit(editor)
self.closeEditor[QWidget].emit(editor)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
model = QStandardItemModel(4, 2)
tableView = QTableView()
tableView.setModel(model)
delegate = Delegate(tableView)
tableView.setItemDelegate(delegate)
section_list = ['w','c','h']
for row in range(4):
for column in range(2):
index = model.index(row, column, QModelIndex())
model.setData(index, (row + 1) * (column + 1))
tableView.setWindowTitle("Spin Box Delegate")
tableView.show()
sys.exit(app.exec_())
Upvotes: 0
Views: 1382
Reputation: 48260
If you want to use a complex widget for an editor, you certainly should not use setIndexWidget()
within createEditor
, because you will loose direct access to and control over it. Return the complex widget instead, and ensure that both setModelData and setEditorData act properly.
To check for the "delayed" click, you also need to override editorEvent()
to ensure that the event is actually a left button click.
This only won't be enough, though: item view selections are always delayed by a cycle of the event loop, so getting the current selection just after a click is not reliable, as it will updated afterwards; you need to use a single shot QTimer in order to correctly check the selection and current index of the table.
Finally, there's no need to check for the column in the delegate, just use setItemDelegateForColumn()
instead.
class ClickDelegate(QtWidgets.QStyledItemDelegate):
blankText = '<Click here to add path>'
def openFileDialog(self, lineEdit):
if not self.blankText.startswith(lineEdit.text()):
currentPath = lineEdit.text()
else:
currentPath = ''
path, _ = QtWidgets.QFileDialog.getOpenFileName(lineEdit.window(),
'Select file', currentPath)
if path:
lineEdit.setText(path)
def createEditor(self, parent, option, index):
editor = QtWidgets.QWidget(parent)
layout = QtWidgets.QHBoxLayout(editor)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
editor.lineEdit = QtWidgets.QLineEdit(self.blankText)
layout.addWidget(editor.lineEdit)
# set the line edit as focus proxy so that it correctly handles focus
editor.setFocusProxy(editor.lineEdit)
# install an event filter on the line edit, because we'll need to filter
# mouse and keyboard events
editor.lineEdit.installEventFilter(self)
button = QtWidgets.QToolButton(text='...')
layout.addWidget(button)
button.setFocusPolicy(QtCore.Qt.NoFocus)
button.clicked.connect(lambda: self.openFileDialog(editor.lineEdit))
return editor
def setEditorData(self, editor, index):
if index.data():
editor.lineEdit.setText(index.data())
editor.lineEdit.selectAll()
def setModelData(self, editor, model, index):
# if there is no text, the data is cleared
if not editor.lineEdit.text():
model.setData(index, None)
# if there is text and is not the "blank" default, set the data accordingly
elif not self.blankText.startswith(editor.lineEdit.text()):
model.setData(index, editor.lineEdit.text())
def initStyleOption(self, option, index):
super().initStyleOption(option, index)
if not option.text:
option.text = self.blankText
def eventFilter(self, source, event):
if isinstance(source, QtWidgets.QLineEdit):
if (event.type() == QtCore.QEvent.MouseButtonPress and
source.hasSelectedText() and
self.blankText.startswith(source.text())):
res = super().eventFilter(source, event)
# clear the text if it's the "Click here..."
source.clear()
return res
elif event.type() == QtCore.QEvent.KeyPress and event.key() in (
QtCore.Qt.Key_Escape, QtCore.Qt.Key_Tab, QtCore.Qt.Key_Backtab):
# ignore some key events so that they're correctly filtered as
# they are emitted by actual editor (the QWidget)
return False
return super().eventFilter(source, event)
def checkIndex(self, table, index):
if index in table.selectedIndexes() and index == table.currentIndex():
table.edit(index)
def editorEvent(self, event, model, option, index):
if (event.type() == QtCore.QEvent.MouseButtonPress and
event.button() == QtCore.Qt.LeftButton and
index in option.widget.selectedIndexes()):
# the index is already selected, we'll delay the (possible)
# editing but we MUST store the direct reference to the table for
# the lambda function, since the option object is going to be
# destroyed; this is very important: if you use "option.widget"
# in the lambda the program will probably hang or crash
table = option.widget
QtCore.QTimer.singleShot(0, lambda: self.checkIndex(table, index))
return super().editorEvent(event, model, option, index)
Upvotes: 1