Qt Quick Controls - 텍스트 편집기

Qt Quick Controls 을 사용하는 서식 있는 텍스트 편집기 앱입니다.

텍스트 편집기 예제를 사용하면 HTML, 마크다운 또는 일반 텍스트 파일을 WYSIWYG로 편집할 수 있습니다. 이 애플리케이션에는 두 가지 사용자 인터페이스가 제공됩니다. 하나는 큰 화면용이고 다른 하나는 작은 터치 기반 기기를 위한 간소화된 UI입니다. texteditor.cpp 에는 QFontDatabase::addApplicationFont()를 호출하여 아이콘 글꼴을 추가하는 main() 함수가 포함되어 있습니다. (FontLoader 을 사용하면 동일한 결과를 얻을 수 있습니다.)

데스크톱 사용자 인터페이스

데스크톱 버전은 텍스트 서식을 지정하고 HTML, 마크다운 및 일반 텍스트 파일을 열고 저장할 수 있는 기능을 갖춘 완전한 텍스트 편집기입니다.

MVC(모델-뷰-제어) 디자인 패턴에서 제어 계층에는 수행할 수 있는 일련의 작업이 포함됩니다. Qt Quick Controls 에서 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 = 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 은 UI 표현과 구현을 캡슐화하는 관련 action 을 바인딩하기만 하면 됩니다.

    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 에서 재사용되지만 여기서는 각 액션의 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 첨부 속성은 텍스트 커서가 뷰포트 밖으로 이동하면(예: 화살표 키를 사용하거나 많은 텍스트를 입력하여) TextAreaFlickable 을 스크롤하여 커서를 계속 표시하도록 하는 데 사용됩니다.

컨텍스트 메뉴가 있으며 TapHandler 을 사용하여 마우스 오른쪽 클릭을 감지하여 엽니다:

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

Menu 컨텍스트에는 기본 MenuBarToolBar 에서 사용하는 것과 동일한 Action 객체를 재사용하는 MenuItems 이 포함되어 있습니다. 이전과 마찬가지로 수행하려는 작업을 나타내는 재사용 가능한 액션에 action 을 바인딩하면 충분합니다. 그러나 각 메뉴 항목의 text 을 재정의하여 컨텍스트 메뉴에서 밑줄이 그어진 니모닉을 생략합니다.

    Menu {
        id: contextMenu

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

최종 사용자의 모국어에 관계없이 애플리케이션을 이해할 수 있도록 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 에서는 텍스트 서식을 변경할 수 있습니다. (마크다운 형식에서는 특정 글꼴 및 색상 선택을 나타내는 구문이 없지만 굵게, 이탤릭체 및 모노스페이스와 같은 글꼴 특성은 저장됩니다. HTML 형식에서는 모든 서식이 저장됩니다.)

오류 메시지를 표시하는 MessageDialog, 파일이 수정되었을 때 사용자에게 수행할 작업을 묻는 메시지가 두 개 더 있습니다.

터치 사용자 인터페이스

터치 사용자 인터페이스는 텍스트 편집기의 간소화된 버전입니다. 화면 크기가 제한된 터치 장치에 적합합니다. 이 예에서는 파일 선택기를 사용하여 적절한 사용자 인터페이스를 자동으로 로드합니다.

예제 실행하기

에서 예제를 실행하려면 Qt Creator에서 Welcome 모드를 열고 Examples 에서 예제를 선택합니다. 자세한 내용은 예제 빌드 및 실행하기를 참조하세요.

예제 프로젝트 @ 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.