0

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_())
  • Mh. There are some issues with your concept (besides the fact that you're using the `print` statement as it was used on Python 2, which is now out of support). First of all, you shouldn't use `setIndexWidget` inside a `createEditor`, because you'll lose control over its behavior (what if the user clicks on another item?). If you want that kind of control over the delegate (which I don't believe it would be that accurate, by the way), why don't you do it within [`editorEvent()`](https://doc.qt.io/qt-5/qstyleditemdelegate.html#editorEvent)? – musicamante Mar 31 '20 at 21:58
  • Hi Dennis. Thanks very much. unfortunately, I don't know how to contact with you in this forum. Could you send a example code to my email 305966188@qq.com about above three point feautre. I am appreciate for your help. Thanks again. – user1102185 Apr 01 '20 at 16:25
  • @musicamante, could you help to give an example for me? I have see a post in https://stackoverflow.com/questions/47849366/pyqt-qpushbutton-delegate-in-column-of-a-treeview, it has a little to my case, but not fully exactly same. I am a newbies for python and Qt. – user1102185 Apr 02 '20 at 11:37

1 Answers1

1

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)
musicamante
  • 41,230
  • 6
  • 33
  • 58
  • @musicamnete. of course, I will accept your help. and I will do it. but when I test. there are still some errors: initStyleOption(self, option, index): super(), return super().eventFilter(source, event), return super().editorEvent. it says super takes at least 1 argument(0 given). and when I select a file, it can put in QLineEdit, but when I click other line, it is blank, cannot display my selection file absoulte path, Could you help to check agian? thanks – user1102185 Apr 02 '20 at 16:14
  • @user1102185 you should always specify when you're on python 2, because nowadays it's generally implied you're on python 3 since version 2 is already considered obsolete. Add the class name and `self` to the arguments of each `super` call. For example, in my code it would be `super(ClickDelegate, self).initStyleOption(option, index)`, etc. If you don't know what it is, read the docs about [super](https://docs.python.org/2/library/functions.html#super) (but you really, *really*, **really** should know it, as it's one of the most important basics of python). – musicamante Apr 02 '20 at 16:20
  • @musicamnete. after your suggestion, I have changed to super(ClickDelegate, self), now, everything is work very very well. it is wonderful. I am very very admire your strong ability and kindness. you are a very very nice man. Actually, I am a EDA engineer, I am graduate from Microelectronic, in my daily work, I need to use 20-30 EDA tools, like Cadence, synopsys, mentor and so on. in order to improve my work efficiency, I am intersted in program. but my program level is some a bit low. So, Thanks for your patient and passioin and nice help again. – user1102185 Apr 02 '20 at 16:33