警告

本节包含从 C++ 自动翻译到 Python 的代码片段,可能包含错误。

完成示例#

完成示例展示了如何根据模型提供的数据为输入小部件提供字符串补全功能。

../_images/completer-example.png

本示例使用自定义项模型 FileSystemModelQCompleter 对象。 QCompleter 是一个基于项模型提供补全功能的类。可以使用组合框选择模型类型、补全模式和大小写敏感度。

资源文件#

完成示例需要资源文件来存储 countries.txtwords.txt。资源文件包含以下代码

<Code snippet "/data/qt5-full-670/6.7.0/Src/qtbase/tools/completer/completer.qrc" not found>

FileSystemModel 类定义#

FileSystemModel 类是 QFileSystemModel 的子类,提供本地文件系统的数据模型。

class FileSystemModel(QFileSystemModel):

# public
    FileSystemModel(QObject parent = None)
    QVariant data(QModelIndex index, int role = Qt.DisplayRole) override

此类仅有一个构造函数和一个 data() 函数,因为它只被创建来使 data() 能够返回显示角色的整个文件路径,而 QFileSystemModel 的 data() 函数仅返回文件夹,而不包括驱动器标签。这在与 FileSystemModel 的实现部分中进一步解释。

FileSystemModel 类实现#

FileSystemModel 类的构造函数用于将 parent 传递给 QFileSystemModel。

def __init__(self, parent):
    super().__init__(parent)

如前所述,重新实现了 data() 函数,使其返回显示角色的完整文件路径。例如,使用 QFileSystemModel,您将在视图中看到“Program Files”。然而,在 FileSystemModel 中,您将看到“C:\Program Files”。

def data(self, QModelIndex index, int role):

    if role == Qt.DisplayRole and index.column() == 0:
        path = QDir.toNativeSeparators(filePath(index))
        if path.endsWith(QDir.separator()):
            path.chop(1)
        return path

    return QFileSystemModel.data(index, role)

Qt::EditRole,它是 QCompleter 用于查找匹配项的role,保持不变。

MainWindow 类定义#

MainWindow 类是 QMainWindow 的子类,并实现了五个私有槽 —— about()changeCase()changeMode()changeModel()changeMaxVisible()

class MainWindow(QMainWindow):

    Q_OBJECT
# public
    MainWindow(QWidget parent = None)
# private slots
    def about():
    def changeCase(int):
    def changeMode(int):
    def changeModel():
    def changeMaxVisible(int):

MainWindow 类中,我们有两个私有函数: createMenu()modelFromFile()。我们同时还声明了所需的私有小部件 - 三个 QComboBox 对象,一个 QCheckBox ,一个 QCompleter ,一个 QLabel ,和一个 QLineEdit

# private
    def createMenu():
    modelFromFile = QAbstractItemModel(QString fileName)
    caseCombo = None
    modeCombo = None
    modelCombo = None
    maxVisibleSpinBox = None
    wrapCheckBox = None
    completer = None
    contentsLabel = None
    lineEdit = None

MainWindow 类实现#

MainWindow 的构造函数创建一个带有父小部件的 MainWindow 并初始化私有成员。然后调用 createMenu() 函数。

我们设置了三个 QComboBox 对象,modelCombmodeCombocaseCombo。默认情况下,modelComb 设置为 QFileSystemModel,modeCombo 设置为“过滤弹出”,caseCombo 设置为“忽略大小写”。

def __init__(self, parent):
    super().__init__(parent)

    createMenu()
    centralWidget = QWidget()
    modelLabel = QLabel()
    modelLabel.setText(tr("Model"))
    modelCombo = QComboBox()
    modelCombo.addItem(tr("QFileSystemModel"))
    modelCombo.addItem(tr("QFileSystemModel that shows full path"))
    modelCombo.addItem(tr("Country list"))
    modelCombo.addItem(tr("Word list"))
    modelCombo.setCurrentIndex(0)
    modeLabel = QLabel()
    modeLabel.setText(tr("Completion Mode"))
    modeCombo = QComboBox()
    modeCombo.addItem(tr("Inline"))
    modeCombo.addItem(tr("Filtered Popup"))
    modeCombo.addItem(tr("Unfiltered Popup"))
    modeCombo.setCurrentIndex(1)
    caseLabel = QLabel()
    caseLabel.setText(tr("Case Sensitivity"))
    caseCombo = QComboBox()
    caseCombo.addItem(tr("Case Insensitive"))
    caseCombo.addItem(tr("Case Sensitive"))
    caseCombo.setCurrentIndex(0)

创建了 maxVisibleSpinBox 并确定了补全器中可见项的数量。

然后设置了 wrapCheckBox。这个复选框决定是否启用或禁用 completersetWrapAround() 属性。

maxVisibleLabel = QLabel()
maxVisibleLabel.setText(tr("Max Visible Items"))
maxVisibleSpinBox = QSpinBox()
maxVisibleSpinBox.setRange(3,25)
maxVisibleSpinBox.setValue(10)
wrapCheckBox = QCheckBox()
wrapCheckBox.setText(tr("Wrap around completions"))
wrapCheckBox.setChecked(True)

实例化了 contentsLabel 并将其大小策略设置为 fixed。然后将选项框的 activated() 信号连接到对应的槽。

contentsLabel = QLabel()
contentsLabel.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
modelCombo.activated.connect(
        self.changeModel)
modeCombo.activated.connect(
        self.changeMode)
caseCombo.activated.connect(
        self.changeCase)
maxVisibleSpinBox.valueChanged.connect(
        self.changeMaxVisible)

设置 lineEdit,然后使用一个 QGridLayout 布局来排列所有的控件。调用 changeModel() 函数以初始化 completer

lineEdit = QLineEdit()
layout = QGridLayout()
layout.addWidget(modelLabel, 0, 0); layout.addWidget(modelCombo, 0, 1)
layout.addWidget(modeLabel, 1, 0); layout.addWidget(modeCombo, 1, 1)
layout.addWidget(caseLabel, 2, 0); layout.addWidget(caseCombo, 2, 1)
layout.addWidget(maxVisibleLabel, 3, 0); layout.addWidget(maxVisibleSpinBox, 3, 1)
layout.addWidget(wrapCheckBox, 4, 0)
layout.addWidget(contentsLabel, 5, 0, 1, 2)
layout.addWidget(lineEdit, 6, 0, 1, 2)
centralWidget.setLayout(layout)
setCentralWidget(centralWidget)
changeModel()
setWindowTitle(tr("Completer"))
lineEdit.setFocus()

使用 createMenu() 函数来创建填充 fileMenuhelpMenu 所需的 QAction 对象。将这些动作的 triggered() 信号连接到各自的槽。

def createMenu(self):

    exitAction = QAction(tr("Exit"), self)
    aboutAct = QAction(tr("About"), self)
    aboutQtAct = QAction(tr("About Qt"), self)
    exitAction.triggered.connect(qApp.quit)
    aboutAct.triggered.connect(self.about)
    aboutQtAct.triggered.connect(qApp.aboutQt)
    fileMenu = menuBar().addMenu(tr("File"))
    fileMenu.addAction(exitAction)
    helpMenu = menuBar().addMenu(tr("About"))
    helpMenu.addAction(aboutAct)
    helpMenu.addAction(aboutQtAct)

modelFromFile() 函数接受一个文件的 fileName,并根据其内容进行处理。

首先验证 file 是否可以以 QFile::ReadOnly 模式打开,如果失败,则函数返回一个空的 QStringListModel。

QAbstractItemModel MainWindow.modelFromFile(QString fileName)

    file = QFile(fileName)
    if not file.open(QFile.ReadOnly):
        return QStringListModel(completer)

在用文件内容填充 QStringList 对象 words 之前,我们将鼠标光标覆盖为 Qt::WaitCursor。完成此操作后,我们恢复鼠标光标。

#ifndef QT_NO_CURSOR
    QGuiApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
#endif
    words = QStringList()
    while not file.atEnd():
        line = file.readLine()
        if not line.isEmpty():
            words << QString.fromUtf8(line.trimmed())

#ifndef QT_NO_CURSOR
    QGuiApplication.restoreOverrideCursor()
#endif

如前所述,资源文件包含两个文件 - countries.txtwords.txt。如果读取的文件是 words.txt,则返回一个以 words 作为它的 QStringList,并且 completer 作为其父代的 QStringListModel。

if not fileName.contains("countries.txt"):
    return QStringListModel(words, completer)

如果读取的文件是 countries.txt,那么我们需要一个具有 words.count() 行,2 列,并且以 completer 作为其父代的 QStandardItemModel。

m = QStandardItemModel(words.count(), 2, completer)

countries.txt 的标准行是

挪威 NO

因此,为了填充 QStandardItemModel 对象 m,我们必须分割国家和它的符号。完成此操作后,返回 m

for i in range(0, words.count()):
    countryIdx = m.index(i, 0)
    symbolIdx = m.index(i, 1)
    country = words.at(i).mid(0, words[i].length() - 2).trimmed()
    symbol = words.at(i).right(2)
    m.setData(countryIdx, country)
    m.setData(symbolIdx, symbol)

return m

changeMode() 函数根据 index 的值设置 completer 的模式。

def changeMode(self, index):

    QCompleter.CompletionMode mode
    if index == 0:
        mode = QCompleter.InlineCompletion
    elif index == 1:
        mode = QCompleter.PopupCompletion
else:
        mode = QCompleter.UnfilteredPopupCompletion
    completer.setCompletionMode(mode)

changeModel() 函数根据用户选择的模型来更改所用的项模型。

使用 switch 语句根据 modelCombo 的索引来更改项模型。如果为 case 0,我们使用未排序的 QFileSystemModel,提供不带驱动器标签的文件路径。

def changeModel(self):

    del completer
    completer = QCompleter(self)
    completer.setMaxVisibleItems(maxVisibleSpinBox.value())
    switch (modelCombo.currentIndex()) {
    else:
    elif role == 0:
        { // Unsorted QFileSystemModel
            fsModel = QFileSystemModel(completer)
            fsModel.setRootPath(QString())
            completer.setModel(fsModel)
            contentsLabel.setText(tr("Enter file path"))

        break

请注意,我们以 completer 作为父代创建模型,因为这允许我们用新模型替换旧模型。当将新模型分配给它时,completer 将确保删除旧模型。

如果 case 是 1,我们使用我们之前定义的 DirModel,结果生成文件的完整路径。

elif role == 1:
    { // FileSystemModel that shows full paths
        fsModel = FileSystemModel(completer)
        completer.setModel(fsModel)
        fsModel.setRootPath(QString())
        contentsLabel.setText(tr("Enter file path"))

    break

case 为 2 时,我们尝试完成国家名称。这需要一个 QTreeView 对象, treeView。国家名称从 countries.txt 中提取,并将用于显示补全内容的弹出窗口设置为 treeView

elif role == 2:
    { // Country List
        completer.setModel(modelFromFile(":/resources/countries.txt"))
        treeView = QTreeView()
        completer.setPopup(treeView)
        treeView.setRootIsDecorated(False)
        treeView.header().hide()
        treeView.header().setStretchLastSection(False)
        treeView.header().setSectionResizeMode(0, QHeaderView.Stretch)
        treeView.header().setSectionResizeMode(1, QHeaderView.ResizeToContents)
        contentsLabel.setText(tr("Enter name of your country"))

    break

下面的截图显示了具有国家列表模型的补全器。

../_images/completer-example-country.png

如果 case 为 3,我们尝试完成单词。这是通过使用包含从 words.txt 提取的数据的 QStringListModel 来完成的。该模型不区分大小写进行排序 case insensitively

下面的截图显示了具有单词列表模型的补全器。

../_images/completer-example-word.png

一旦选择了模型类型,我们就调用 changeMode() 函数和 changeCase() 函数,并相应地设置 wrap 选项。将 wrapCheckBoxclicked() 信号连接到 completersetWrapAround() 穿槽。

elif role == 3:
    { // Word list
        completer.setModel(modelFromFile(":/resources/wordlist.txt"))
        completer.setModelSorting(QCompleter.CaseInsensitivelySortedModel)
        contentsLabel.setText(tr("Enter a word"))

    break

changeMode(modeCombo.currentIndex())
changeCase(caseCombo.currentIndex())
completer.setWrapAround(wrapCheckBox.isChecked())
lineEdit.setCompleter(completer)
wrapCheckBox.clicked.connect(completer.setWrapAround)

changeMaxVisible() 更新补全器中可见项的最大数量。

def changeMaxVisible(self, max):

    completer.setMaxVisibleItems(max)

about() 函数提供了关于该示例的简要描述。

def about(self):

    QMessageBox.about(self, tr("About"), tr("This example demonstrates the "
        "different features of the QCompleter class."))

`` main()``

函数#

main() 函数实例化了 QApplicationMainWindow,并调用 show() 函数。

if __name__ == "__main__":

    app = QApplication([])
    window = MainWindow()
    window.show()
    sys.exit(app.exec())

示例项目 @ code.qt.io