user1102185
user1102185

Reputation: 33

How to let QLineEdit and QPushButton show in a column and style like this in a tableview in PyQt5?

I have 3 three pictures shown in below:

picture1

enter image description here

enter image description here

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:

  1. when click mouse one time, it will select this line, and also highlight this line1. just like digital 1 point to
  2. 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.

  3. when step 2 is done, if continue to click mouse in other line, QLineEdit and QPushButton '...' in step 2 will disapper, like line 'VAR("myModelConer")

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

Answers (1)

musicamante
musicamante

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

Related Questions