[QT]example5-QML-database(SQLite)

๐Ÿ‘‰๊ธฐ๋Šฅ์€ ๋ฒ„ํŠผ ํด๋ฆญํ•˜๋ฉด ํ…์ŠคํŠธ ์ž…๋ ฅ 2๊ฐœ์— ์ž…๋ ฅ๋œ ์ •๋ณด๋ฅผ ๋กœ์ปฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅํ•˜๊ณ  ๋ฆฌ์ŠคํŠธ๋ทฐ์— ํ‘œ์‹œํ•ด์ค๋‹ˆ๋‹ค.
The function saves the information entered in the two text inputs to a local database when the button is clicked and displays it in a list view.

๐Ÿ‘‰ QML์ž์ฒด๊ฐ€ ๋กœ์ปฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๋™๊ธฐ๋Šฅ์ด ์žˆ์Šต๋‹ˆ๋‹ค.
QML itself has a local database connection function.

๐Ÿ‘‰ํ”„๋กœ์ ํŠธ์ƒ์„ฑ ํ›„ ์ˆ˜์ •ํ•ด์•ผํ• ๊ฑด Main.qml๊ณผ Screen01.qml,CMakeLists.txt์ž…๋‹ˆ๋‹ค.
After creating the project, the files that need to be modified are Main.qml, Screen01.qml, and CMakeLists.txt.

๐Ÿ‘‰CMakeLists.txtํŒŒ์ผ์—์„œ Screen01.qml์„ ์ธ์‹ํ•˜๋„๋ก ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
Set the CMakeLists.txt file to recognize Screen01.qml.

๐Ÿ‘‰Main.qml์€ Screen01.qml์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์—ญํ• ๋งŒ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
Main.qml only performs the role of loading Screen01.qml.

๐Ÿ‘‰๋ชจ๋“  ๋ฐ์ดํ„ฐ์ฒ˜๋ฆฌ์™€ ์•ฑ๋””์ž์ธ์€ Screen01.qml์—์„œ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
All data processing and app design is performed in Screen01.qml.

1.CMakeLists.txt

โœ”๏ธ ํ”„๋กœ์ ํŠธ ๋””๋ ‰ํ† ๋ฆฌ๋‚ด์— qml/Screen01.qml ํŒŒ์ผ์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
Create a file named qml/Screen01.qml in the project directory.

โœ”๏ธ qml/Screen01.qml ์ด ์ฝ”๋“œ๋งŒ ์ถ”๊ฐ€ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.
Just add this code to qml/Screen01.qml.


cmake_minimum_required(VERSION 3.16)

project(example5 VERSION 0.1 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(Qt6 REQUIRED COMPONENTS Quick)
#--
find_package(Qt6 6.5 COMPONENTS Quick REQUIRED)

qt_standard_project_setup(REQUIRES 6.8)

qt_add_executable(appexample5
    main.cpp
)

qt_add_qml_module(appexample5
    URI example5
    VERSION 1.0
    QML_FILES
        Main.qml
        qml/Screen01.qml
)

# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
# If you are developing for iOS or macOS you should consider setting an
# explicit, fixed bundle identifier manually though.
set_target_properties(appexample5 PROPERTIES
#    MACOSX_BUNDLE_GUI_IDENTIFIER com.example.appexample5
    MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
    MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
    MACOSX_BUNDLE TRUE
    WIN32_EXECUTABLE TRUE
)

target_link_libraries(appexample5
    PRIVATE Qt6::Quick
)

include(GNUInstallDirs)
install(TARGETS appexample5
    BUNDLE DESTINATION .
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

2.Main.qml

โœ”๏ธ ๋ณผ๋“œ์ฒด ์„ค์ • ๋œ ๋ถ€๋ถ„์˜ ์ฝ”๋“œ๋งŒ ์ž…๋ ฅํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.
You only need to enter the code for the bolded part.

import QtQuick
import QtQuick.Controls
import QtQuick.Window

Window {
    width: 800
    height: 600
    visible: true
    title: qsTr("QML LocalStorage Example")

    Loader {
        anchors.fill: parent
        source: "qml/Screen01.qml"  // ๋กœ์ปฌ QML ํŒŒ์ผ ๋กœ๋“œ / Local QML file load
    }
}

3.qml/Screen01.qml

โœ”๏ธ์—ฌ๊ธฐ์„œ ๋ฐ์ดํ„ฐ ์ž…๋ ฅ, ์ˆ˜์ •, ์‚ญ์ œ๋ฅผ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
This is where you enter, edit, and delete data.

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.LocalStorage

Rectangle {
    id: root
    width: 800
    height: 600
    color: "#EAEAEA"

    property var db
    property int selectedId: -1  // ์„ ํƒ๋œ ๋ ˆ์ฝ”๋“œ ID / Seclected Record ID

    Component.onCompleted: {
        db = LocalStorage.openDatabaseSync("ExampleLocalDB", "1.0", "Local DB Example", 1000000)
        db.transaction(function(tx) {
            tx.executeSql("CREATE TABLE IF NOT EXISTS records(id INTEGER PRIMARY KEY AUTOINCREMENT, text1 TEXT, text2 TEXT)")
        })
        loadData()
    }

    // DB์—์„œ ๋ฐ์ดํ„ฐ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ
    // Retrieving data from DB
    function loadData() {
        db.transaction(function(tx) {
            const rs = tx.executeSql("SELECT * FROM records ORDER BY id DESC")
            listModel.clear()
            for (let i = 0; i < rs.rows.length; i++) {
                listModel.append({
                    id: rs.rows.item(i).id,
                    text1: rs.rows.item(i).text1,
                    text2: rs.rows.item(i).text2
                })
            }
            selectedId = -1
        })
    }

    // DB์— ์ƒˆ ๋ฐ์ดํ„ฐ ์ถ”๊ฐ€
    // Add new data to DB
    function insertData(t1, t2) {
        if (t1.trim() === "" || t2.trim() === "") {
            console.log("์ž…๋ ฅ๊ฐ’์ด ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค.")
            return
        }
        db.transaction(function(tx) {
            tx.executeSql("INSERT INTO records (text1, text2) VALUES (?, ?)", [t1, t2])
        })
        input1.text = ""
        input2.text = ""
        loadData()
    }

    // ์„ ํƒ๋œ ๋ ˆ์ฝ”๋“œ ์‚ญ์ œ
    // Delete selected record
    function deleteSelected() {
        if (selectedId === -1) return
        db.transaction(function(tx) {
            tx.executeSql("DELETE FROM records WHERE id = ?", [selectedId])
        })
        loadData()
    }


    // DB์— ๋ชจ๋“  ๋ฐ์ดํ„ฐ ์‚ญ์ œ
    // Delete all data in DB
    function deleteAllData() {
        db.transaction(function(tx) {
            tx.executeSql("DELETE FROM records")
        })
        loadData()
    }

    ColumnLayout {
        anchors.centerIn: parent
        spacing: 12
        width: 300


        // ๋ฆฌ์ŠคํŠธ ๋ทฐ / List View
        Rectangle {
            Layout.fillWidth: true
            height: 250
            color: "white"
            border.color: "#B0B0B0"
            radius: 4

            ListView {
                id: listView
                // ๋ถ€๋ชจ Reactangleํฌ๊ธฐ์™€ ๊ฐ™๊ฒŒ ์„ค์ •
                // Set the size to be the same as the parent Reactangle
                anchors.fill: parent
                // ๋ถ€๋ชจ Rectangle์˜ ๊ฒฝ๊ณ„ ์•ˆ์ชฝ์œผ๋กœ 4px ์—ฌ์œ 
                // Rectangleํ…Œ๋‘๋ฆฌ๊ฐ€ ์•ˆ๋ณด์ด๊ธฐ๋•Œ๋ฌธ์—
                // 4px margin inside the parent Rectangle's border
                // Because the Rectangle border is not visibl
                anchors.margins: 4
                clip: true
                model: ListModel { id: listModel }
                interactive: true
                highlightFollowsCurrentItem: true
                currentIndex: -1

                delegate: Rectangle {
                    id: delegateRect
                    height: 36
                    width: listView.width
                    //anchors.fill: parent
                    //anchors.margins: 6
                    //anchors.left: parent.left
                    //anchors.right: parent.right

                    // ๋ ˆ์ฝ”๋“œ์ƒ‰ ๋ณ€๊ฒฝ / Change record color
                    color: listView.currentIndex === index ? "#D0E6FF" : (index % 2 === 0 ? "#FFFFFF" : "#FFFFFF")

                    // -- ์ค„๋ฌด๋Šฌ ํ˜•ํƒœ ์ ์šฉ / Apply striped shape
                    //color: listView.currentIndex === index ? "#D0E6FF" : (index % 2 === 0 ? "#F9F9F9" : "#FFFFFF")

                    Row {
                        anchors.fill: parent
                        anchors.margins: 6
                        spacing: 10
                        Text { text: text1; font.pixelSize: 14; color: "black" }
                        Text { text: text2; font.pixelSize: 14; color: "gray" }
                    }

                    MouseArea {
                        anchors.fill: parent
                        onClicked: {
                            listView.currentIndex = index
                            root.selectedId = id
                        }
                    }
                }
            }
        }

        // ์ž…๋ ฅ ํ•„๋“œ / input field
        TextField { id: input1; Layout.fillWidth: true; placeholderText: "ํ…์ŠคํŠธ1 ์ž…๋ ฅ/text1 input" }
        TextField { id: input2; Layout.fillWidth: true; placeholderText: "ํ…์ŠคํŠธ2 ์ž…๋ ฅ/text2 input" }

        // ๋ฒ„ํŠผ ์˜์—ญ / button area
        RowLayout {
            Layout.fillWidth: true
            spacing: 10

            Button {
                text: "์ €์žฅ/Save"
                Layout.fillWidth: true
                onClicked: insertData(input1.text, input2.text)
            }

            Button {
                text: "์‚ญ์ œ/Delete"
                Layout.fillWidth: true
                enabled: selectedId !== -1
                onClicked: deleteSelected()
            }
            Button {
                text: "๋ชจ๋‘์‚ญ์ œ/DeleteAll"
                Layout.fillWidth: true
                onClicked: deleteAllData()
            }
        }
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *