Qt Quick Controls - 文本编辑器

一个使用Qt Quick Controls 的富文本编辑器应用程序。

文本编辑器示例允许对 HTML、Markdown 或纯文本文件进行所见即所得的编辑。该应用程序有两种用户界面:一种适用于大屏幕,另一种是适用于小型触摸设备的简化用户界面。texteditor.cpp 包含main() 函数,该函数调用QFontDatabase::addApplicationFont() 添加图标字体。(FontLoader 是实现相同效果的另一种方法)。

桌面用户界面

桌面版是一个完整的文本编辑器,具有格式化文本、打开和保存 HTML、Markdown 和纯文本文件的功能。

模型-视图-控制(MVC)设计模式中,控制层包括可执行的操作集。在Qt Quick Controls 中,Action 类型用于封装单个操作或命令。因此,我们从一组 Action 对象开始:

    Action {
        id: openAction
        text: qsTr("&Open")
        shortcut: StandardKey.Open
        onTriggered: {
            if (textArea.textDocument.modified)
                discardDialog.open()
            else
                openDialog.open()
        }
    }

打开文件的Action 必须首先提示用户现有文档是否已更改,以避免丢失用户的更改。否则,它只是打开FileDialog ,该文件将在下文中进一步声明。

只有在有更改需要保存时,才会启用用于保存文件的Action

    Action {
        id: saveAction
        text: qsTr("&Save…")
        shortcut: StandardKey.Save
        enabled: textArea.textDocument.modified
        onTriggered: textArea.textDocument.save()
    }

只有选中某些文本时,才会启用用于复制选中文本的Action

    Action {
        id: copyAction
        text: qsTr("&Copy")
        shortcut: StandardKey.Copy
        enabled: textArea.selectedText
        onTriggered: textArea.copy()
    }

每个用于更改文本格式(如粗体、斜体和对齐方式)的操作都是checkable ,其布尔checked 状态与selected text 中的相关属性同步。由于声明式双向同步比较困难,我们使用onTriggered 脚本在操作激活时更改属性。cursorSelection 属性是 Qt XML 6.7 中的新特性,它使这一操作比原来简单得多。

    Action {
        id: boldAction
        text: qsTr("&Bold")
        shortcut: StandardKey.Bold
        checkable: true
        checked: textArea.cursorSelection.font.bold
        onTriggered: textArea.cursorSelection.font = Qt.font({ bold: checked })
    }

    Action {
        id: alignCenterAction
        text: qsTr("&Center")
        shortcut: "Ctrl+|"
        checkable: true
        checked: textArea.cursorSelection.alignment === Qt.AlignCenter
        onTriggered: textArea.cursorSelection.alignment = Qt.AlignCenter
    }

我们有一个包含Menus 和 MenuItems 层次结构的MenuBar 。每个MenuItem 只需绑定相关的action ,它封装了 UI 表示和实现。

    menuBar: MenuBar {
        Menu {
            title: qsTr("&File")

            MenuItem {
                action: openAction
            }
            MenuItem {
                action: saveAction
            }
            MenuItem {
                action: saveAsAction
            }
            MenuItem {
                action: quitAction
            }
        }

        Menu {
            title: qsTr("&Edit")

            MenuItem {
                action: copyAction
            }
        ...

ToolBar 中重复使用了相同的Action 对象;但在这里,我们覆盖了每个 Action 的text 属性,以便从我们的图标字体中选择一个文本图标:

    header: ToolBar {
        leftPadding: 8

        Flow {
            id: flow
            width: parent.width

            Row {
                id: fileRow
                ToolButton {
                    id: openButton
                    text: "\uF115" // icon-folder-open-empty
                    font.family: "fontello"
                    action: openAction
                    focusPolicy: Qt.TabFocus
                }
                ToolButton {
                    id: saveButton
                    text: "\uE80A" // icon-floppy-disk
                    font.family: "fontello"
                    action: saveAction
                    focusPolicy: Qt.TabFocus
                }
                ToolSeparator {
                    contentItem.visible: fileRow.y === editRow.y
                }
            }

            Row {
                id: editRow
                ToolButton {
                    id: copyButton
                    text: "\uF0C5" // icon-docs
                    font.family: "fontello"
                    focusPolicy: Qt.TabFocus
                    action: copyAction
                }
            ...

文本编辑器的主要部分是TextArea ,位于Flickable 内:

    Flickable {
        id: flickable
        flickableDirection: Flickable.VerticalFlick
        anchors.fill: parent

        ScrollBar.vertical: ScrollBar {}

        TextArea.flickable: TextArea {
            id: textArea
            textFormat: Qt.AutoText
            wrapMode: TextArea.Wrap
            focus: true
            selectByMouse: true
            persistentSelection: true
            ...

纵轴上有一个ScrollBar 。由于通过wrapMode 启用了单词包,因此我们不需要水平ScrollBar

使用TextArea.flickable 附加属性,当文本光标移出视口时(例如通过方向键或输入大量文本),TextArea 会滚动Flickable 以保持光标可见。

有一个上下文菜单;我们使用TapHandler 来检测右键单击并打开它:

            TapHandler {
                acceptedButtons: Qt.RightButton
                onTapped: contextMenu.popup()
            }

上下文Menu 包含MenuItems ,它重复使用与主MenuBarToolBar 相同的Action 对象。和以前一样,只需将action 与表示要执行操作的可重用操作绑定即可。不过,我们会覆盖每个菜单项的text ,以省略上下文菜单中的下划线助记符。

    Menu {
        id: contextMenu

        MenuItem {
            text: qsTr("Copy")
            action: copyAction
        }
        ...

我们一直使用qsTr() 函数来翻译用户界面文本,这样无论最终用户的母语是什么,应用程序都能理解。

我们使用多种dialogs

    FileDialog {
        id: openDialog
        fileMode: FileDialog.OpenFile
        selectedNameFilter.index: 1
        nameFilters: ["Text files (*.txt)", "HTML files (*.html *.htm)", "Markdown files (*.md *.markdown)"]
        currentFolder: StandardPaths.writableLocation(StandardPaths.DocumentsLocation)
        onAccepted: {
            textArea.textDocument.modified = false // we asked earlier, if necessary
            textArea.textDocument.source = selectedFile
        }
    }

    FileDialog {
        id: saveDialog
        fileMode: FileDialog.SaveFile
        nameFilters: openDialog.nameFilters
        currentFolder: StandardPaths.writableLocation(StandardPaths.DocumentsLocation)
        onAccepted: textArea.textDocument.saveAs(selectedFile)
    }

    FontDialog {
        id: fontDialog
        onAccepted: textArea.cursorSelection.font = selectedFont
    }

    ColorDialog {
        id: colorDialog
        selectedColor: "black"
        onAccepted: textArea.cursorSelection.color = selectedColor
    }

    MessageDialog {
        title: qsTr("Error")
        id: errorDialog
    }

    MessageDialog {
        id : quitDialog
        title: qsTr("Quit?")
        text: qsTr("The file has been modified. Quit anyway?")
        buttons: MessageDialog.Yes | MessageDialog.No
        onButtonClicked: function (button, role) {
            if (role === MessageDialog.YesRole) {
                textArea.textDocument.modified = false
                Qt.quit()
            }
        }
    }

    MessageDialog {
        id : discardDialog
        title: qsTr("Discard changes?")
        text: qsTr("The file has been modified. Open a new file anyway?")
        buttons: MessageDialog.Yes | MessageDialog.No
        onButtonClicked: function (button, role) {
            if (role === MessageDialog.YesRole)
                openDialog.open()
        }
    }

一般来说,为每种用途声明单独的实例会更容易。我们有两个FileDialog 实例,分别用于打开和保存文件。在 Qt 6.7 中,通过TextDocument 中的新功能,这变得更加容易。

FontDialogColorDialog 允许更改文本格式。(在 Markdown 格式中,没有表示特定字体和颜色选择的语法;但粗体、斜体和monospace 等字体特征会被保存)。在 HTML 格式中,所有格式都会被保存)。

我们有一个用于显示错误信息的MessageDialog ,还有两个用于在文件被修改时提示用户如何操作。

触摸用户界面

触摸用户界面是文本编辑器的简化版。它适用于屏幕尺寸有限的触摸设备。该示例使用文件选择器自动加载相应的用户界面。

运行示例

要从 Qt Creator,打开Welcome 模式,然后从Examples 中选择示例。更多信息,请参阅Qt Creator: 教程:构建并运行

示例项目 @ code.qt.io

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.