本页内容

Qt Quick Controls - 文本编辑器

一款基于Qt Quick Controls 的富文本编辑器应用。

“文本编辑器”示例支持对HTML、Markdown或纯文本文件进行所见即所得(WYSIWYG)编辑。 该应用程序提供两种用户界面:一种适用于大屏幕,另一种是针对小型触控设备的简化界面。两者均为“纯”QML实现。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 6.7中的新特性,使得这一操作比以前简单得多。

    Action {
        id: boldAction
        text: qsTr("&Bold")
        shortcut: StandardKey.Bold
        checkable: true
        checked: textArea.cursorSelection.font.bold
        onTriggered: textArea.cursorSelection.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
    }

我们有一个MenuBar ,其中包含Menus 和MenuItems的层级结构。每个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
            }
        ...

这些Action 对象在ToolBar 中被复用;但在此处,我们重写了每个Action的text 属性,以便从图标字体中选择一个文本图标:

    header: ToolBar {
        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
                }
            ...

文本编辑器的主体是一个位于Flickable 中的TextArea :

    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 以保持光标可见。

我们将MenuBar 和ToolBar 中声明的操作,追加到TextArea 提供的标准context menu 中的现有项中:

                const menu = textArea.ContextMenu.menu
                menu.addItem(menuSeparatorComponent.createObject(menu.contentItem))
                menu.addAction(fontDialogAction)
                menu.addAction(colorDialogAction)

我们始终使用qsTr() 函数来启用 UI 文本的翻译,从而确保无论最终用户的母语是什么,应用程序都能正常运行。

我们使用了多种类型的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: textArea.cursorSelection.color
        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 中的新功能,这一操作变得更加简单。

一个 `FontDialog ` 和一个 `ColorDialog ` 可用于更改文本格式。(在 Markdown 格式中,没有专门表示特定字体和颜色的语法;但粗体、斜体和等宽字体等字体特征会被保存。在 HTML 格式中,所有格式都会被保存。)

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

触控用户界面

适用于触控设备的文本编辑器界面,带格式设置工具栏

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

运行示例

您可以通过以下方式运行示例:

示例项目 @ code.qt.io

© 2026 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.