I'm trying to use the QCompleter class for the QLineEdit to give auto completion suggestions while typing, and update the suggestions after the user enters a new text. But when I try to update the Completer with text that starts with something that is already at the completer list, it just crashes the entire app with no visible exception! Even try-except doesn't catch this error, and I can't understand what I am doing wrong...
Below is a simpler example of my code: it is a simple "echo" console application that gets commands from QLineEdit (input text box) and writing it to QTextBrowser (output text box). When entering entirely new "command" (text) it works fine, and being added to the completer, so next time I can see it. But if the new text starts similar to other words in the completer list, choosing it crashes the entire GUI app with no exception visible, not even when I'm running in debug mode...
Please see my example below, and try writing at the upper text box options like: a, aa, aaa (that start similar to completer already word: aaa1)
What am I doing wrong??
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow, QVBoxLayout, QLineEdit, QTextBrowser, QCompleter
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setWindowTitle('console')
self.setGeometry(10, 50, 500, 800)
# Create text box for input
self.consoleCommandLineEdit = QLineEdit(self)
self.consoleCommandLineEdit.setFixedHeight(25)
self.consoleCommandLineEdit.editingFinished.connect(self.gotConsoleCommand)
self.completerCommands = ['aaa1','aaa2','aaa3'] # initial completer list
completer = QCompleter(self.completerCommands)
self.consoleCommandLineEdit.setCompleter(completer)
# Create text box for output
self.consoleViewer = QTextBrowser(self)
self.consoleViewer.setLineWrapMode(QTextBrowser.NoWrap)
widget = QWidget(self)
self.setCentralWidget(widget)
self.vlay = QVBoxLayout(widget)
self.vlay.addWidget(self.consoleCommandLineEdit)
self.vlay.addWidget(self.consoleViewer)
def gotConsoleCommand(self):
cmd = self.consoleCommandLineEdit.text()
self.consoleCommandLineEdit.setText('')
self.sendCommandToConsole(cmd)
def sendCommandToConsole(self,cmd):
self.consoleViewer.append(cmd) # add cmd to output box
if cmd not in self.completerCommands: # if the command is new, add it to the completer
self.completerCommands.append(cmd) # 1. add the new text to the list we have
completer = QCompleter(self.completerCommands) # 2. create a new completer object
self.consoleCommandLineEdit.setCompleter(completer) # 3. set the new completer as the LineEdit completer
if __name__ == "__main__":
app = QApplication(sys.argv)
mainWin = MainWindow()
mainWin.show()
sys.exit(app.exec_())
I have not yet found the cause of the problem but a better solution than creating a new QCompleter every time you need to add a new text. In this case it is better to use a model to store the texts that is the basis of the QCompleter information.
import sys
from PyQt5.QtGui import QStandardItem, QStandardItemModel
from PyQt5.QtWidgets import (
QApplication,
QWidget,
QMainWindow,
QVBoxLayout,
QLineEdit,
QTextBrowser,
QCompleter,
)
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setWindowTitle("console")
self.setGeometry(10, 50, 500, 800)
# Create text box for input
self.consoleCommandLineEdit = QLineEdit()
self.consoleCommandLineEdit.setFixedHeight(25)
self.consoleCommandLineEdit.editingFinished.connect(self.gotConsoleCommand)
self.model = QStandardItemModel()
self.model.appendRow([QStandardItem(text) for text in ("aaa1", "aaa2", "aaa3")])
completer = QCompleter(self.model, self)
self.consoleCommandLineEdit.setCompleter(completer)
# Create text box for output
self.consoleViewer = QTextBrowser(lineWrapMode=QTextBrowser.NoWrap)
widget = QWidget()
self.setCentralWidget(widget)
vlay = QVBoxLayout(widget)
vlay.addWidget(self.consoleCommandLineEdit)
vlay.addWidget(self.consoleViewer)
def gotConsoleCommand(self):
cmd = self.consoleCommandLineEdit.text()
self.consoleCommandLineEdit.clear()
self.sendCommandToConsole(cmd)
def sendCommandToConsole(self, cmd):
self.consoleViewer.append(cmd) # add cmd to output box
if not self.model.findItems(cmd):
self.model.appendRow(QStandardItem(cmd))
if __name__ == "__main__":
app = QApplication(sys.argv)
mainWin = MainWindow()
mainWin.show()
sys.exit(app.exec_())
Perhaps something that helps us understand the problem is that if you add a parent to QCompleter the problem does not happen:
completer = QCompleter(self.completerCommands, self)
but I stress: the best solution is to use a model.