En esta página

Qt Quick Controls - Editor de texto

Una aplicación de editor de texto enriquecido que utiliza Qt Quick Controls.

Text Editor Example permite la edición WYSIWYG de un archivo HTML, Markdown o de texto plano. La aplicación incluye dos interfaces de usuario: una para pantallas grandes y otra simplificada para dispositivos táctiles pequeños. Ambas son QML "puro". texteditor.cpp contiene la función main(), que llama a QFontDatabase::addApplicationFont() para añadir un icono de fuente. (FontLoader sería una forma alternativa de conseguir el mismo resultado).

Interfaz de usuario de escritorio

Editor de texto con barra de menús, barra de herramientas y contenido de documento formateado

La versión de escritorio es un completo editor de texto con capacidades para formatear texto, y abrir y guardar archivos HTML, Markdown y texto plano.

En el patrón de diseño modelo-vista-control (MVC), la capa de control incluye el conjunto de operaciones que se pueden realizar. En Qt Quick Controls, el tipo Action se utiliza para encapsular una única operación o comando. En consecuencia, comenzamos con un conjunto de objetos Action:

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

El Action para abrir un archivo debe primero preguntar al usuario si el documento existente ha sido modificado, para evitar perder los cambios del usuario. En caso contrario, simplemente abre el FileDialog que se declara más adelante.

El Action para guardar el archivo sólo se activa si hay cambios que guardar:

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

Action para copiar el texto seleccionado sólo se activa si hay texto seleccionado:

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

Cada Acción para cambiar el formato del texto (como negrita, cursiva y alineación) es checkable, y su estado booleano checked está sincronizado con la propiedad correspondiente en selected text. Como la sincronización bidireccional declarativa es difícil, utilizamos un script onTriggered para cambiar la propiedad cuando se activa la Acción. La propiedad cursorSelection es nueva en Qt 6.7 y hace esto mucho más fácil de lo que era.

    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
    }

Tenemos un MenuBar que contiene la jerarquía de Menus y MenuItems. Cada MenuItem simplemente necesita enlazar el action relevante, que encapsula la representación UI y la implementación.

    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
            }
        ...

Los mismos objetos Action se reutilizan en ToolBar; pero aquí anulamos la propiedad text de cada Acción para elegir un icono textual de nuestra fuente de iconos:

    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
                }
            ...

La parte principal del editor de texto es un TextArea dentro de un 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
            ...

Un ScrollBar está unido al eje vertical. Dado que el word-wrapping se activa a través de wrapMode, no necesitamos un ScrollBar horizontal.

La propiedad adjunta TextArea.flickable se utiliza para que cuando el cursor de texto se mueva fuera de la ventana gráfica (por ejemplo mediante las teclas de flecha, o escribiendo mucho texto), TextArea desplace el Flickable para mantener el cursor visible.

Tomamos las mismas acciones que declaramos en MenuBar y ToolBar y las añadimos a los elementos existentes en el estándar context menu proporcionado por TextArea:

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

Utilizamos sistemáticamente la función qsTr() para permitir la traducción del texto de la interfaz de usuario, de modo que la aplicación tenga sentido independientemente del idioma nativo del usuario final.

Utilizamos varios tipos de 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()
        }
    }

Generalmente es más fácil declarar instancias separadas para cada propósito. Tenemos dos instancias de FileDialog, para abrir y guardar archivos respectivamente. Esto se hizo más fácil en Qt 6.7, con las nuevas características de TextDocument.

Un FontDialog y un ColorDialog permiten cambiar el formato del texto. (En formato Markdown, no hay sintaxis para representar opciones específicas de fuente y color; pero se guardan características de fuente como negrita, cursiva y monospace. En formato HTML, se guarda todo el formato).

Tenemos un MessageDialog para mostrar mensajes de error, y dos más para indicar al usuario qué hacer cuando se ha modificado un archivo.

Interfaz de usuario táctil

Interfaz de editor de texto para dispositivos táctiles con barra de herramientas de formato

La interfaz de usuario táctil es una versión simplificada del editor de texto. Es adecuada para dispositivos táctiles con pantalla de tamaño limitado. El ejemplo utiliza selectores de archivo para cargar automáticamente la interfaz de usuario apropiada.

Ejecutar el ejemplo

Para ejecutar el ejemplo desde Qt Creator, abra el modo Welcome y seleccione el ejemplo de Examples. Para más información, consulte Qt Creator: Tutorial: Construir y ejecutar.

Proyecto de ejemplo @ 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.