[PyQt] Suppress departure from cell in QTableView

Maurizio Berti maurizio.berti at gmail.com
Sat Feb 23 02:19:54 GMT 2019


Il giorno ven 22 feb 2019 alle ore 02:37 John F Sturtz <john at sturtz.org> ha
scritto:

> This is a surprisingly hot-button issue! I posted a question to
> stackoverflow that involved this issue, and someone grilled me quite
> extensively on my use of QLabel in this way.
>

Well, that's understandable: as I pointed out (which is probably what that
user told you about), that's not the purpose of a label. While it "can"
work for simple scenarios, it's not intuitive from the user perspective,
and can be an issue whenever the size of the cell doesn't allow to show the
complete text the user is typing.


It may be that I could make it work fine using a QLineEdit. I haven't
> played with it much, and I probably should. There are two things I am doing
> with cell input that I think would be complicated using a QLineEdit:
>
> Validating cell input character by character. Each column in the table
> view has regular expression validation. I catch a character when it is
> typed, determine what the cell contents would be with that character added,
> and disallow that input right at that moment if it would be invalid.
> (I think this might be workable with a QLineEdit; I haven't messed with it
> ...)
>
> There are cases where I want to have different text style for different
> parts of the cell contents (for example, some part of the text in italic
> and the rest normal).  This is quite easy to do with a QLabel because it
> can contain rich text -- e.g.,
> setText('foo<span style="font-style: italic;">bar</span>')
> This doesn't appear to work for the contents of a QLineEdit widget.
> I did find this stackoverflow post regarding styling of text within a
> QLineEdit:
>
> https://stackoverflow.com/questions/14417333/how-can-i-change-color-of-part-of-the-text-in-qlineedit
>

Since you need inline text formatting, implementing QLineEdit can be an
issue as you'd probably need to do all the painting by yourself.

I'd suggest another approach instead: use a QTextEdit. It's implementation
is a bit harder to get, but allows a better way to do what you need, while
giving the user a standard and much more comfortable text editing UX.

Here's a small example I made which also uses QSyntaxHighlighter to do the
formatting (in this case, it makes all occurrences of the word "red" in
italic red).
It doesn't count in the "unallowed" text input you wrote about, but that
can be done inside the keyPressEvent() method (but the release could need
some checking too); you could also validate the input by connecting the
textChanged() signal, to inhibit invalid clipboard pasting (like new line
characters).


class MyHighlighter(QtGui.QSyntaxHighlighter):
    def __init__(self, *args, **kwargs):
        QtGui.QSyntaxHighlighter.__init__(self, *args, **kwargs)
        self.myFormat = QtGui.QTextCharFormat()
        self.myFormat.setFontItalic(True)
        self.myFormat.setForeground(QtCore.Qt.red)
        self.re = re.compile(r'(red)+')

    def highlightBlock(self, text):
        for match in self.re.finditer(text):
            self.setFormat(match.start(), match.end() - match.start(),
self.myFormat)


class MyEditor(QtWidgets.QTextEdit):
    submit = QtCore.pyqtSignal()

    def __init__(self, *args, **kwargs):
        QtWidgets.QTextEdit.__init__(self, *args, **kwargs)
        self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.setWordWrapMode(QtGui.QTextOption.NoWrap)
        self.highlighter = MyHighlighter(self.document())

    def keyPressEvent(self, event):
        if event.key() in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return):
            self.submit.emit()
        elif event.key() == QtCore.Qt.Key_Tab:
            # Ignore tab by letting the parent(s) handle it, as you are
            # probably not interested in the tab character.
            # Since you also want to prevent leaving the cell editing
            # if the content is not valid, here you can also choose to
            # accept the event, inhibiting cell departure.
            # Please note that the shift-tab is intercepted by the view.
            event.ignore()
        else:
            QtWidgets.QTextEdit.keyPressEvent(self, event)


class MyDelegate(QtWidgets.QStyledItemDelegate):
    def createEditor(self, parent, option, index):
        editor = MyEditor(parent)
        editor.setPlainText(index.data())
        editor.submit.connect(lambda: self.submit(editor, index))
        return editor

    def submit(self, editor, index):
        # You can also check for contents before proceeding with this
        self.setModelData(editor, index.model(), index)
        self.closeEditor.emit(editor, self.EditNextItem)

    def setModelData(self, editor, model, index):
        # This is required, otherwise you'll get the rich text contents as
XHTML
        model.setData(index, editor.toPlainText(), QtCore.Qt.DisplayRole)


Now you only have to setItemDelegate() on your view, and eventually set the
highlighting [validation] by checking the index.column() argument in the
createEditor() method according to your needs (or just create different
delegates for each column).

Cheers!

Maurizio

-- 
È difficile avere una convinzione precisa quando si parla delle ragioni del
cuore. - "Sostiene Pereira", Antonio Tabucchi
http://www.jidesk.net
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://www.riverbankcomputing.com/pipermail/pyqt/attachments/20190223/aee25468/attachment.html>


More information about the PyQt mailing list