Qt Quick 控件 - 文本编辑器

使用 Qt Quick 控件的富文本编辑器应用。

《文本编辑示例》允许对 HTML、Markdown 或纯文本文件进行所见即所得编辑。应用程序提供了两个用户界面:一个用于大屏幕,另一个用于小型触控设备。两者都是“纯”QML。《texteditor.cpp》中包含的`main()`函数调用`QFontDatabase::addApplicationFont()`来添加图标字体。(使用 FontLoader 也会是达到相同结果的一种方法。)

桌面用户界面

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

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

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

用于打开文件的 Action 必须首先提示用户如果现有的文档已更改,以避免丢失用户的更改。否则它将简单地打开下面声明的外部FileDialog

仅当有更改需要保存时,保存文件的 Action 才被启用

    Action {
        id: saveAction
        shortcut: StandardKey.Save
        enabled: textArea.textDocument.modified
        onTriggered: textArea.textDocument.save()
    }

仅当选择了一些文本时,复制所选文本的 Action 才被启用

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

每个更改文本格式(如加粗、斜体和对齐)的 Action 都是可检查的,其布尔`checked`状态与selected text中的相关属性同步。由于声明性双向同步比较困难,我们使用`onTriggered`脚本来在 Action 被激活时更改属性。`cursorSelection`属性是 Qt 6.7 中的新属性,这使得实现变得容易得多。

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

    Action {
        id: alignCenterAction
        shortcut: "Ctrl+|"
        checkable: true
        checked: textArea.cursorSelection.alignment === Qt.AlignCenter
        onTriggered: textArea.cursorSelection.alignment = Qt.AlignCenter
    }

我们有一个包含MenuBar的层次结构和Menus和 MenuItems 的MenuBar。`Platform.MenuItem`没有`action`属性,因此至少需要设置`text`并实现`onTriggered`。

注意:在 Qt Quick Controls 中,每个 MenuItem 可以简单地绑定相应的 action。在 Qt 的未来版本中,Qt.labs.platform 将会过时,MenuBar 将适用于所有平台。除非你需要在 Qt 6.7 及旧版本中使用本地菜单栏(仅在提供该功能的平台上),应避免导入 Qt.labs.platformQtQuick.Controls 更具移植性。

    Platform.MenuBar {
        Platform.Menu {
            title: qsTr("&File")

            Platform.MenuItem {
                text: qsTr("&Open")
                onTriggered: openAction.trigger()
            }
            Platform.MenuItem {
                text: qsTr("&Save…")
                onTriggered: saveAction.trigger()
            }
            Platform.MenuItem {
                text: qsTr("Save &As…")
                onTriggered: saveAsAction.trigger()
            }
            Platform.MenuItem {
                text: qsTr("&Quit")
                onTriggered: quitAction.trigger()
            }
        }

        Platform.Menu {
            title: qsTr("&Edit")

            Platform.MenuItem {
                text: qsTr("&Copy")
                enabled: copyAction.enabled
                onTriggered: copyAction.trigger()
            }
        ...

现有的 Action 对象在 ToolBar 中重用;但在这里,我们覆盖了每个 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
                }
            ...

文本编辑器的主要部分是一个位于 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 来保持光标可见。

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

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

上下文 Menu 包含 MenuItems

    Platform.Menu {
        id: contextMenu

        Platform.MenuItem {
            text: qsTr("Copy")
            enabled: copyAction.enabled
            onTriggered: copyAction.trigger()
        }
        ...

我们始终使用 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: "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 格式中,没有语法来表示特定的字体和颜色选择;但字体特征(如粗体、斜体和等宽)会被保存。在 HTML 格式中,所有格式都会被保存。)

我们有一个 MessageDialog 来显示错误消息,另外两个用于提示用户在文件已被修改时应该做什么。

触摸用户界面

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

运行示例

要从 Qt Creator 运行示例,打开 欢迎 模式,并从 示例 中选择示例。更多信息,请访问 构建和运行示例

示例项目 @ code.qt.io

© 2024Qt公司有限公司。本文件中包含的文档贡献者的版权。所提供的文档遵循由自由软件基金会发布的GNU自由文档许可协议第1.3版的条款。Qt及其相关标志是芬兰和/或其他国家的The Qt Company Ltd.的商标。商标。所有其他商标均为其各自所有者的财产。