PyQt5 QTextEdit自动补全,QTextEdit使用QCompleter

提示:文章干货满满,耗费作者的一番心力,留个关注收藏鼓励一下吧


前言

在Qt中QCompleter这个小部件可以说是十分有用,联合QLineEdit等等的自动补全十分好用 , 但可惜的是Qt并没有为QTextEdit提供setCompleter这个方法 .作者写文章时是想用QTextEdit做一个代码补全的功能 , 但发现并不直接支持setCompleter , 然而限于PyQt本身资料较少 , 在网上也没有找到直接有用的信息 , 花了很多事件研究C语言原生的Qt , 恰巧又找到"https://stackoverflow.com/questions/60451045/pyqt5-adding-qcompleter-to-existing-qtextedit"的一篇文章 , 琢磨许久完成了这个可以支持setCompleter的CodeTextEdit类 , 现在放出来供大家便捷使用 , 可以直接创建示例使用.


一、CodeTextEdit使用

CodeTextEdit类代码(可复制使用)

class CodeTextEdit(QTextEdit):
    def __init__(self, searchWords_dict=dict, matchWords_list=list, parent=None):
        super(CodeTextEdit, self).__init__(parent)

        self.textChangedSign = True
        self.textChanged.connect(self.dealTextChanged)
        self._completer = None
        self.searchWords_dict = searchWords_dict
        self.matchWords_list = matchWords_list
        # print(list(searchWords_dict.keys()))
        # 将传入的搜索词语匹配词整合 , 成为自动匹配选词列表
        self.initAutoCompleteWords()
        # 用户已经完成的前缀
        self.completion_prefix = ''

    def initAutoCompleteWords(self):
        '''
        处理autoCompleteWords_list,以及specialCursorDict
        '''
        self.autoCompleteWords_list = []
        self.specialCursorDict = {}  # 记录待选值的特殊光标位置
        for i in self.searchWords_dict:
            if "$CURSON$" in self.searchWords_dict[i]:
                cursorPosition = len(self.searchWords_dict[i]) - len("$CURSON$") - self.searchWords_dict[i].find(
                    "$CURSON$")
                self.searchWords_dict[i] = self.searchWords_dict[i].replace("$CURSON$", '')
                self.specialCursorDict[i] = cursorPosition
        for i in self.matchWords_list:
            if "$CURSON$" in i:
                cursorPosition = len(i) - len("$CURSON$") - i.find("$CURSON$")
                self.matchWords_list[self.matchWords_list.index(i)] = i.replace("$CURSON$", '')
                self.specialCursorDict[i.replace("$CURSON$", '')] = cursorPosition

        self.autoCompleteWords_list = list(self.searchWords_dict.keys()) + self.matchWords_list

    def setCompleter(self, c):
        self._completer = c
        c.setWidget(self)  # 设置Qcomplete 要关联的窗口小部件。在QLineEdit上设置QCompleter时,会自动调用此函数。在为自定义小部件提供Qcomplete时,需要手动调用。
        # 更改样式
        c.popup().setStyleSheet('''
        QListView {
            color: #9C9C9C;
            background-color: #4F4F4F;
        }
        QListView::item:selected //选中项
        {
            background-color: #9C9C9C;
            border: 20px solid #9C9C9C;
        }
        QListView::item:selected:active //选中并处于激活状态时
        {
            background-color: #9C9C9C;
            border: 20px solid #9C9C9C;
        }

        QListView::item {
            color: red;
            padding-top: 5px;
            padding-bottom: 5px;
        }

        QListView::item:hover {
            background-color: #9C9C9C;
        }
        QScrollBar::handle:vertical{ //滑块属性设置
            background:#4F4F4F;
            width:2px;
            height:9px;
            border: 0px;
            border-radius:100px;
            }
        QScrollBar::handle:vertical:normal{
            background-color:#4F4F4F;
            width:2px;
            height:9px;
            border: 0px;
            border-radius:100px;
            }
        QScrollBar::handle:vertical:hover{
            background:#E6E6E6;
            width:2px;
            height:9px;
            border: 0px solid #E5E5E5;
            border-radius:100px;
            }
        QScrollBar::handle:vertical:pressed{
            background:#CCCCCC;
            width:2px;
            height:9px;
            border: 0px solid #E5E5E5;
            border-radius:100px;
            }
        ''')

        '''下面是completer的一些属性设置,可以自行修改'''
        # setModelSorting此方法指定模型中项目的排序方式。值为枚举值
        # 分别为QCompleter.UnsortedModel QCompleter.CaseSensitivelySortedModel QCompleter.CaseInsensitivelySortedModel
        # 内容                                      值     描述
        # QCompleter.UnsortedModel                 0  该模型是未排序的。
        # QCompleter.CaseSensitivelySortedModel    1  该模型是大小写敏感的。
        # QCompleter.CaseInsensitivelySortedModel  2  模型不区分大小写。
        self._completer.setModelSorting(QCompleter.CaseSensitivelySortedModel)
        # self.completer.setCaseSensitivity 和 上述 self.completer.setModelSorting 一样 ,同时设置且不对应时setCaseSensitivity不生效
        # Qt.CaseSensitivity 该属性保持匹配的大小写敏感性。
        # self.completer.setCaseSensitivity(Qt.CaseInsensitive)

        # 如果filterMode设置为Qt.MatchStartsWith,则只会显示以类型化字符开头的条目。 Qt.MatchContains将显示包含类型字符的条目,并且Qt.MatchEnds以类型字符结尾的条目显示。
        # 目前,只有这三种模式得以实施。 将filterMode设置为任何其他Qt :: MatchFlag将发出警告,并且不会执行任何操作。
        # 默认模式是Qt :: MatchStartsWith。
        # 这个属性是在Qt 5.2中引入的。
        self._completer.setFilterMode(Qt.MatchContains)

        # 此属性在导航项目时包含完成项。默认True 这个属性是在Qt 4.3中引入的。
        self._completer.setWrapAround(False)

        # 设置补全模式  有三种枚举值: QCompleter.PopupCompletion(默认)  QCompleter.InlineCompletion   QCompleter.UnfilteredPopupCompletion
        # QCompleter.PopupCompletion(默认) 当前完成显示在一个弹出窗口中。
        # QCompleter.InlineCompletion  完成内联显示(作为选定的文本)。
        # QCompleter.UnfilteredPopupCompletion 所有可能的完成都显示在弹出窗口中,最有可能的建议显示为当前。
        c.setCompletionMode(QCompleter.PopupCompletion)

        # QCompleter.setModel(QAbstractItemModel *model)
        # 设置为模型提供QCompleter的模型。 该模型可以是列表模型或树模型。 如果一个模型已经被预先设置好了,并且它有QCompleter作为它的父项,它将被删除。
        # self.completer.setModel(completer_model)
        self._completer.setModel(QStringListModel(self.autoCompleteWords_list, self._completer))

        '''
        当用户激活popup()中的项目时发送此信号。 (通过点击或按回车)
        QCompleter.activated;如果文本框的当前项目发生更改,则会发出两个信号currentIndexChanged()
        和activated()。无论以编程方式或通过用户交互完成更改,currentIndexChanged()
        总是被发射,而只有当更改是由用户交互引起时才activated()

        注意: 这里的文本框项目发生改变是指QCompleter的项目添加到文本框造成的改变
        '''
        c.activated.connect(self.insertCompletion)  # 为 用户更改项目文档事件 绑定 函数

    def insertCompletion(self, completion):
        '''
        当用户激活popup()中的项目时调用。(通过点击或按回车)
        @competion::添加 QCompleter
        '''

        if self._completer.widget() is not self:  # 如果没有绑定completer跳过此事件
            return
        '''
        俩种补全方式
        1. 搜索添加 在searchWords_dict键中的对应项,删除用户输入的匹配项,自动补全其键值
        2. 匹配添加 在
        '''
        tc = self.textCursor()
        # 判断是搜索添加 还是 直接匹配添加
        if completion in self.searchWords_dict:  # 搜索添加
            # 删除输入的搜索词
            for i in self._completer.completionPrefix():
                tc.deletePreviousChar()
            # 让光标移到删除后的位置
            self.setTextCursor(tc)

            # 插入对应的键值
            insertText = self.searchWords_dict[completion]
            tc.insertText(insertText)
            # 关闭自动补全选项面板
            self._completer.popup().hide()

        else:  # 直接匹配添加
            # 计算用户输入和匹配项差值,并自动补全
            extra = len(completion) - len(
                self._completer.completionPrefix())  # self._completer.completionPrefix() 为 当前匹配项中,用户已输入的值 , 如输入 ab 匹配到 ABORT 则返回 ab

            # 判断用户输入单词 与 需要补全内容是否一致,一致就不用操作
            if not self.completion_prefix == completion[-extra:]:
                # 自动补全
                tc.insertText(completion[-extra:])
                # 关闭自动补全选项面板
                self._completer.popup().hide()
        '''更改光标位置'''
        # 移动光标 ,光标位置枚举值
        # cursor_pos = QTextCursor.NoMove #光标不移动
        # cursor_pos = QTextCursor.Start #文档开头
        # cursor_pos = QTextCursor.End #文档结尾
        # cursor_pos = QTextCursor.Up #上一行
        # cursor_pos = QTextCursor.Down #下一行
        # cursor_pos = QTextCursor.Left #向左移动一字符
        # cursor_pos = QTextCursor.Right #向右移动一字符
        # cursor_pos = QTextCursor.StartOfLine  # 行首
        # cursor_pos = QTextCursor.StartOfBlock #段首
        # cursor_pos = QTextCursor.StartOfWord #单词首
        # cursor_pos = QTextCursor.EndOfLine #行末
        # cursor_pos = QTextCursor.EndOfBlock #段末
        # cursor_pos = QTextCursor.EndOfWord #单词末
        # cursor_pos = QTextCursor.PreviousCharacter #上一个字符
        # cursor_pos = QTextCursor.PreviousBlock #上一个段落
        # cursor_pos = QTextCursor.PreviousWord #上一个单词
        # cursor_pos = QTextCursor.NextCharacter #下一个字符
        # cursor_pos = QTextCursor.NextBlock #下一个段落
        # cursor_pos = QTextCursor.NextWord #下一个单词

        # 判断自动补全后是否需要更改特定光标位置
        tc.movePosition(QTextCursor.EndOfWord)  # 先移动到单词尾部,避免错误
        if completion in self.specialCursorDict.keys():  # 存在特殊位置
            for i in range(self.specialCursorDict[completion]):
                tc.movePosition(QTextCursor.PreviousCharacter)
            self.setTextCursor(tc)
        else:  # 不存在特殊位置,移动到单词末尾
            tc.movePosition(QTextCursor.EndOfWord)
            self.setTextCursor(tc)

    def focusInEvent(self, e):
        # 当edit获取焦点时激活completer
        # Open the widget where you are at in the edit
        if self._completer is not None:
            self._completer.setWidget(self)
        super(CodeTextEdit, self).focusInEvent(e)

    def dealTextChanged(self):
        '''
        内容改变处理信号处理
        '''

        '''下面是对keyPressEvent面对中文输入法输入时没反应的补充'''
        connect = self.toPlainText()

        class QKeyEvent:
            def key(self=None):
                return 0

            def text(self=None):
                return connect.split('\n')[-1].split(' ')[-1]

            def modifiers(self=None):
                return Qt.AltModifier

        self.keyPressEvent(type('QKeyEvent', (QKeyEvent,), {}))

    def getLastPhrase(self):
        '''
        获取最后一个词组(先以行分割,然后按空格分割词组)
        '''
        # 获取全部文本
        connect = self.toPlainText()
        # print(connect.split('\n')[-1].split(' '))
        lastPhrase = connect.split('\n')[-1].split(' ')[-1]
        return lastPhrase

    def keyPressEvent(self, e):
        '''
        按键按下事件
        @e::<PyQt5.QtGui.QKeyEvent object at 0x000001577FAE8048>
        e.text()输入的文本 , 像换行,tab这样的键是没有文本的
        e.key(),键盘上每个键都对应一个编码 如 换行 16777220,j 74
        '''
        isShortcut = False  # 判断是否是快捷键的标志

        # self._completer.popup().isVisible()) 判断 completer 是否弹出
        if self._completer is not None and self._completer.popup().isVisible():
            # print('Popup is up')
            # The following keys are forwarded by the completer to the widget.
            # 如果键入的是特殊键则忽略这次事件
            if e.key() in (Qt.Key_Enter, Qt.Key_Return, Qt.Key_Escape, Qt.Key_Tab, Qt.Key_Backtab):
                e.ignore()
                return

        # Ctrl + e 快捷键
        if e.key() == Qt.Key_E and e.modifiers() == Qt.ControlModifier:
            words = self.autoCompleteWords_list
            self._completer.setModel(QStringListModel(words))  # 设置数据
            isShortcut = True
        '''
        if e.key() == Qt.Key_Period:
            #This is how I will do the lookup functionality. Show when period is his, open the list of options.
            self.textCursor().insertText('.')
            self.moveCursor(QtGui.QTextCursor.PreviousWord)
            self.moveCursor(QtGui.QTextCursor.PreviousWord, QtGui.QTextCursor.KeepAnchor)
            dict_key = self.textCursor().selectedText().upper()
            #print('Dict Key' , dict_key)
            self.moveCursor(QtGui.QTextCursor.NextWord)
            self.moveCursor(QtGui.QTextCursor.NextWord)

            #print(dict_key)
            words = self.searchWords_dict[dict_key]
            self._completer.setModel(QStringListModel(words, self._completer))
            isShortcut = True
        '''
        # 当不存在关联的 completer 以及 当前键不是快捷键的时候执行父操作
        if (self._completer is None or not isShortcut) and e.key() != 0:
            # Do not process the shortcut when we have a completer.
            super(CodeTextEdit, self).keyPressEvent(e)

        # 当当不存在关联的 completer 或者 有修饰符(ctrl或shift)和输入字符为空时 , 直接返回不进行任何操作
        ctrlOrShift = e.modifiers() & (Qt.ControlModifier | Qt.ShiftModifier)  # 是ctrl或shift这样的修饰词
        if self._completer is None or (ctrlOrShift and len(e.text()) == 0):
            return

        # eow = "~!@#$%^&*()_+{}|:\"<>?,./;'[]\\-="
        eow = ''
        # 有修饰符 但 不是ctrl 或者 shift,例如alt出现 hasModifier 就为True
        hasModifier = (e.modifiers() != Qt.NoModifier) and not ctrlOrShift  # 判断是否有ctrl与shift外的修饰符

        lastPhrase = self.getLastPhrase()  # 当前出现的单词
        self.completion_prefix = lastPhrase

        # 限制最少输入俩个字符后才进行匹配 , 且不满足条件自动关闭界面
        # 不是快捷键,同时满足(没有修饰符 或者 文本为空 或者 输入字符少于2 或者 输入文本最后以eow中一个字符结尾)
        if not isShortcut and (len(e.text()) == 0 or len(lastPhrase) < 2):
            self._completer.popup().hide()
            return
        # not isShortcut 确保快捷键可以操作显示自动补全窗口
        # if not isShortcut and (hasModifier or len(e.text()) == 0 or len(lastPhrase) < 2 or e.text()[-1] in eow):
        # self._completer.popup().hide()
        # return

        # 选中第一项
        if lastPhrase != self._completer.completionPrefix():
            # Puts the Prefix of the word youre typing into the Prefix
            self._completer.setCompletionPrefix(lastPhrase)
            self._completer.popup().setCurrentIndex(  # QCompleter.popup().setCurrentIndex 设置选中项
                self._completer.completionModel().index(0,
                                                        0))  # QCompleter.completionModel() 返回QCompleter模型。 QCompleter模型是一个只读列表模型,它包含当前QCompleter前缀的所有可能匹配项。

        cr = self.cursorRect()
        # 设置 completer 尺寸
        cr.setWidth(self._completer.popup().sizeHintForColumn(
            0) + self._completer.popup().verticalScrollBar().sizeHint().width())
        self._completer.complete(cr)

调用实例

调用分为简单几步

  1. 创建QCompleter对象self.completer = QCompleter(self)
  2. 创建搜索自动补全Dict
    self.searchWords_dict = { "插入图片": "![$CURSON$]()", "插入链接": "[]()", }
  3. 创建匹配自动补全Listself.matchWords_list = [ 'ABORT', 'CALL']
  4. 创建CodeTextEdit对象self.codeTextEdit = CodeTextEdit(searchWords_dict=self.searchWords_dict, matchWords_list=self.matchWords_list, parent=self)
  5. 传入QCompleter对象self.codeTextEdit.setCompleter(self.completer)

通过上面简单的5步,我们就可以创建一个带有自动补全功能的QTextEdit了,下面有一些使用时的注意事项

使用说明

  1. CodeTextEdit中限制至少输入2个字符才开始提示你(可以在后面自行更改)
  2. searchWords_dict参数需要一个字典对象 , 它的作用是为CodeTextEdit添加快捷输入搜索词 . 例如上述示例中插入图片": "![$CURSON$]()即代表输入插入则会出现提示![]()供选择 , 那么$CURSON$的作用呢?它提供一个光标的位置信息,则代表用户选择![]()后光标会移动到[]的中间 , $CURSON$并不是一个必选值 , 如果没有那么光标会出现在最后 .
  3. self.matchWords_list 需要一个list对象 , 和searchWords_dict相似 , 'ABORT'的作用是输入AB时自动提示ABORT , 与之对应$CURSON$的作用和用法也相同

具体示例

class TestWindow(QMainWindow):
    def __init__(self):
        super(TestWindow, self).__init__(None)
        self.resize(500,400)
        self.completer = QCompleter(self)

        self.searchWords_dict = {
            "插入图片": "![$CURSON$]()",
            "插入链接": "[]()",
        }
        self.matchWords_list = [
            'ABORT',
            'CALL',
            "#######",
            "![]($CURSON$)",
            "[]()",
            'super(sqlWindow, self).__init__(parent)'

        ]
        self.codeTextEdit = CodeTextEdit(searchWords_dict=self.searchWords_dict,
                                           matchWords_list=self.matchWords_list,
                                           parent=self)
        self.codeTextEdit.setCompleter(self.completer)
        self.codeTextEdit.resize(200,300)

if __name__ == "__main__":
    def except_hook(cls, exception, traceback):
        sys.__excepthook__(cls, exception, traceback)


    cgitb.enable(format='text')
    sys.excepthook = except_hook
    App = QApplication(sys.argv)  # 创建QApplication对象,作为GUI主程序入口
    mainWindow = TestWindow()
    mainWindow.show()
    sys.exit(App.exec_())  # 循环中等待退出程序

二、代码剖析


总结

提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

盧瞳

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值