如何让 QLineEdit 和 QPushButton 在 PyQt5 的 tableview 中显示在列中并设置这样的样式?

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

我有3张三张图如下:

如何在PyQt5的tableview中让QLineEdit和QPushButton显示在一个列中并设置这样的样式?

我有如下三张图,

我想用 PyQt5 编写一个 GUI 来实现这些功能:

  1. 鼠标点击一次,会select这一行,同时高亮这一行1。就像数字1指向
  2. 几秒钟后,在'Click here to add a file'处再次点击鼠标一次,将进入编辑模式。 就像数字 2 指向的一样,QLineEdit 和 QPushButton '...' 将显示在第二列中。 如果我单击“...”,并弹出一个文件选择对话框,当我 select 一个文件时,它将用文件绝对路径替换 'Click here to add a file'。

    注意:不是双击鼠标进入编辑模式,应该是点击鼠标一次,几秒后,再次点击鼠标,会进入编辑模式。 当我 select 一个绝对路径非常非常长的文件时。我可以在 QPushButton '...' 后面看到一些字符显示, 看起来 QPushButton 重叠在 QLineEdit 的右边。

  3. 当第2步完成后,如果继续在其他行点击鼠标,第2步中的QLineEdit和QPushButton'...'将消失,如'VAR("myModelConer")

我研究了3个功能很多天,但无法得到我想要的风格。 我将在这里给出我的代码,例如,它是 2 行和 2 列。 任何人都可以帮助我修改和实现以上3个功能。

提前致谢

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_())

如果你想为编辑器使用一个复杂的小部件,你当然不应该在 createEditor 中使用 setIndexWidget(),因为你会失去对它的直接访问和控制。 Return 复杂的小部件,并确保 setModelData 和 setEditorData 都正常运行。

要检查 "delayed" 单击,您还需要覆盖 editorEvent() 以确保事件实际上是左键单击。
但这还不够,虽然:项目视图选择总是被事件循环的一个周期延迟,所以在点击后立即获得当前选择是不可靠的,因为它会在之后更新;您需要使用单发 QTimer 才能正确检查 table.

的选择和当前索引

最后,无需检查委托中的列,只需使用 setItemDelegateForColumn() 即可。

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)