概要
因为最近在练习怎么写一个单词翻译app,所以想写一个能实现单词自动补全的文本框,就像图中这种用灰色字体显示可能想要的单词,然后按Tab键补全的功能
浏览了一下论坛里,看到有大佬的实现了一个类似python idle提示器的效果
用QCompleter的重写来实现
解决方案
改写textedit的tab键功能
class MyTextEdit(QTextEdit):
ACTabsignal = pyqtSignal(bool)
def __init__(self, parent=None):
super().__init__(parent)
def keyPressEvent(self, event):
if event.key() == Qt.Key_Tab:
self.ACTabsignal.emit(True)
else:
# 传递其他按键事件给父类处理
super().keyPressEvent(event)
双层edit叠加
用两个textedit,一个透明的书写edit覆盖一个提示edit,再用绝对位置重叠来达到上图的效果
# 创建第一个 QTextEdit 部件
textEdit1 = self.MyTextEdit(self)
textEdit1.setReadOnly(True)
textEdit1.setStyleSheet("border: 1px solid white; border-radius: 5px;color: gray;")
textEdit1.resize(400, 300)
textEdit1.move(0, 0) # 设置部件的位置
# 创建第二个 QTextEdit 部件
textEdit2 = self.MyTextEdit(self)
textEdit2.setStyleSheet("background: transparent;border: 1px solid white;border-radius: 5px;")
textEdit2.textChanged.connect(lambda: createAutoCompleter(textEdit2.toPlainText()))
textEdit2.ACTabsignal.connect(lambda: Edit2ToEdit1())
textEdit2.resize(400, 300)
textEdit2.move(0, 0) # 设置部件的位置,与第一个部件重叠
单词补全功能
接着就是处理补全逻辑,因为涉及到耗时的查找单词补全功能,所以要添加子线程
class AutoCompleter(QThread):
SetText = pyqtSignal(str)
def __init__(self, Editdata,parent=None):
super().__init__(parent)
self.Editdata = Editdata
self.completerList = ["Apple", "Banana",'baby',"Orange", "Pineapple", "Grapes"]
def run(self):
# 如果输入为空,则不进行补全
if not self.Editdata or self.Editdata.isspace():
self.SetText.emit("")
return
# 截取最后一个单词
web = self.Editdata.split()[-1]
# 如果不是英文单词,则不进行补全
if not web.isalpha():
self.SetText.emit("")
return
# 如果末尾单词长度小于 2 则不进行补全
if len(web) < 2:
self.SetText.emit("")
return
# 遍历所有单词,找到以输入字符开头的单词
for word in self.completerList:
if word.lower().startswith(web.lower()):
# 保持原有输入字符,并在后面添加补全单词
self.SetText.emit(self.Editdata + word[len(web):])
return
# 如果没有找到,则不进行补全
self.SetText.emit("")
最后别忘了加上信号槽
textEdit2.textChanged.connect(lambda: createAutoCompleter(textEdit2.toPlainText()))
textEdit2.ACTabsignal.connect(lambda: Edit2ToEdit1())
完整代码
import sys
from PyQt5.QtGui import QTextCursor
from PyQt5.QtWidgets import QApplication, QWidget, QTextEdit, QVBoxLayout
from PyQt5.QtCore import QThread, pyqtSignal, Qt
class AutoCompleterEdit(QWidget):
def __init__(self):
super().__init__()
self.initUI()
class AutoCompleter(QThread):
SetText = pyqtSignal(str)
def __init__(self, Editdata,parent=None):
super().__init__(parent)
# 截取最后一个单词
self.Editdata = Editdata
self.completerList = ["Apple", "Banana",'baby',"Orange", "Pineapple", "Grapes"]
def run(self):
# 如果输入为空,则不进行补全
if not self.Editdata or self.Editdata.isspace():
self.SetText.emit("")
return
web = self.Editdata.split()[-1]
# 如果不是英文单词,则不进行补全
if not web.isalpha():
self.SetText.emit("")
return
# 如果末尾单词长度小于 2 则不进行补全
if len(web) < 2:
self.SetText.emit("")
return
# 遍历所有单词,找到以输入字符开头的单词
for word in self.completerList:
if word.lower().startswith(web.lower()):
# 保持原有输入字符,并在后面添加补全单词
self.SetText.emit(self.Editdata + word[len(web):])
return
# 如果没有找到,则不进行补全
self.SetText.emit("")
class MyTextEdit(QTextEdit):
ACTabsignal = pyqtSignal(bool)
def __init__(self, parent=None):
super().__init__(parent)
def keyPressEvent(self, event):
if event.key() == Qt.Key_Tab:
self.ACTabsignal.emit(True)
else:
# 传递其他按键事件给父类处理
super().keyPressEvent(event)
def initUI(self):
# 创建自动补全线程
def createAutoCompleter(text):
self.ACThread = self.AutoCompleter(text)
self.ACThread.SetText.connect(textEdit1.setText)
self.ACThread.start()
def Edit2ToEdit1():
textEdit2.setText(textEdit1.toPlainText())
# 重置第二个编辑框的光标位置
cursor = textEdit2.textCursor()
cursor.movePosition(QTextCursor.End)
textEdit2.setTextCursor(cursor)
# 创建第一个 QTextEdit 部件
textEdit1 = self.MyTextEdit(self)
textEdit1.setReadOnly(True)
textEdit1.setStyleSheet("border: 1px solid white; border-radius: 5px;color: gray;")
textEdit1.resize(400, 300)
textEdit1.move(0, 0) # 设置部件的位置
# 创建第二个 QTextEdit 部件
textEdit2 = self.MyTextEdit(self)
textEdit2.setStyleSheet("background: transparent;border: 1px solid white;border-radius: 5px;")
textEdit2.textChanged.connect(lambda: createAutoCompleter(textEdit2.toPlainText()))
textEdit2.ACTabsignal.connect(lambda: Edit2ToEdit1())
textEdit2.resize(400, 300)
textEdit2.move(0, 0) # 设置部件的位置,与第一个部件重叠
class example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setAutoFillBackground(True)
self.setWindowTitle("AutoCompleterEdit")
self.resize(400, 300)
self.vBox = QVBoxLayout()
ACEdit = AutoCompleterEdit()
self.vBox.addWidget(ACEdit)
self.setLayout(self.vBox)
self.show()
if __name__ == '__main__':
QApplication.setHighDpiScaleFactorRoundingPolicy(Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
app = QApplication(sys.argv)
ex = example()
sys.exit(app.exec_())
效果展示
小结
这样的话,只要提供相应词库就能补全末尾单词。但美中不足的是,如果要回到文中修改单词就没办法提供自动补全了,存在一些瑕疵。不过应该可以通过重写处理逻辑,比如对光标位置的判断来提供不同的补全方案进行优化。
最后,如果这篇文章对你有帮助,请帮忙收个藏点个赞吧。