Tutorials‎ > ‎

Qt Application using QML and C++ with MySQL Database

posted Dec 16, 2013, 10:48 PM by Muhammad Zullidar   [ updated Aug 15, 2016, 11:34 PM by Surya Wang ]

In this tutorial, i will show you how to make a qt application using QML and C++ with MySQL database. I assumed you are already know about Qt, QML, basic database query syntax, and C++. When making this application, i used Qt Creator 2.6.1, Qt SDK 5.0.0 32bit msvc2010, and Windows 7 Professional 32bit. So let's start making the application.

Introduction

This application is an application for making assistant teaching schedule. There are two main roles, Assistant Supervisor and Teaching Assistant. Assistant Supervisor role can show, update, delete, insert assistant's teaching schedule, whereas Teaching Assistant role can only show their teaching schedule.
This is the application flow:

  1. Login form is opened. User has to log in using their username.
  2. The application will check the user's role in database
  3. The application will show next window according to user's role

Others feature of the application:

  • Frameless window
  • Draggable window

We also make additional component to make our application more powerful. Here's the additional components:

  • CloseButton
  • This component is basically a Rectangle component. It has Text component and MouseArea component in it. Here's the code to make this component:
    import QtQuick 2.0
    
    Rectangle{
        width: 15
        height: 15
        anchors{
            right: parent.right
            rightMargin: 5
            top: parent.top
            topMargin: 5
        }
        border.color: "red"
        color: "tomato"
        radius: 3
        smooth: true
        Text{
            text: "x"
            color: "white"
            font{bold: true}
            anchors.centerIn: parent
        }
        MouseArea{
            anchors.fill: parent
            onClicked: Qt.quit();
        }
    }
    		
  • Combobox
  • Combobox is an Item component that wrapped Rectangle and ListView component. In main Rectangle, there are Text component and another Rectangle component. The Text is used to store the item's text choosen by user. That another Rectangle component is used as trigger to show the list of the items. The items is showed using ListView component. Here's the code to make this component:
    import QtQuick 2.0
    
    Item{
        id: root
        property alias text: combotext.text
        property variant mod
        Rectangle {
            id: combo
            width: parent.width
            height: parent.height
            color: "lightgrey"
            Text{
                id: combotext
                anchors{
                    left: parent.left
                    leftMargin: 10
                    verticalCenter: parent.verticalCenter
                }
            }
            Rectangle{
                width: parent.width/4
                height: parent.height
                color: "grey"
                radius: 3
                anchors{
                    right: parent.right
                    verticalCenter: parent.verticalCenter
                }
                MouseArea{
                    anchors.fill: parent
                    onClicked: {
                        if(content.model === emptymod){
                            root.z = 1;
                            content.model = mod;
                        }
                        else {
                            root.z = 0;
                            content.model = emptymod;
                        }
    
                    }
                }
            }
            border.color: "grey"
            radius: 3
        }
        ListView{
            id: content
            anchors{
                top: combo.bottom
                left: combo.left
                right: combo.right
            }
            height: content.count>0?contentHeight:0
            ListModel{
                id: emptymod
            }
            clip: true
            model: emptymod
            boundsBehavior: Flickable.StopAtBounds
            delegate:Rectangle{
                width: parent.width
                height: 20
                color: "lightgrey"
                Text{
                    text: modelData
                    anchors.centerIn: parent
                }
                MouseArea{
                    anchors.fill: parent
                    hoverEnabled: true
                    onClicked: {
                        root.z = 0;
                        combotext.text = modelData;
                        content.model = emptymod;
                    }
                    onEntered: parent.color = "gold"
                    onExited: parent.color = "lightgrey"
                }
            }
        }
    }
    		
  • Logo
  • Logo is basically an Image component. Here's the code:
    import QtQuick 2.0
    
    Image {
        source: "../../pictures/logo.jpg"
        width: 100
        height: 80
        fillMode: Image.PreserveAspectFit
    }
    
  • TextBox
  • TextBox is a Rectangle component that has additional properties. Some properties connect to another properties so when user changed the value, the connected property's value also change. Here's the code to make the TextBox:
    import QtQuick 2.0
    
    Rectangle{
        id:root
        property alias maxLength: textinput.maximumLength
        property bool isPassword: false
        property alias text: textinput.text
        property alias isFocus: textinput.focus
        property bool isOnlyDigit: false
        property bool isOnlyDigitAndAlphabet: false
        property bool isDate: false
        border{color:"black"}
        signal enter
        radius: 3
        TextInput{
            id: textinput
            font{
                bold: true
                pixelSize: 12
            }
            width: parent.width-10
            focus: isFocus
            anchors.centerIn: parent
            autoScroll: true
            echoMode: isPassword?TextInput.Password:TextInput.Normal
            maximumLength: 10
            validator: RegExpValidator{
                regExp: isOnlyDigit?/[0-9]*/:isOnlyDigitAndAlphabet?/([0-9a-zA-Z]*)/:isDate?/[1-9][0-9][0-9][0-9]\-[0-3][0-9]\-[0-1][0-9][ ][0-2][0-9]\:[0-5][0-9]/:/.*/
            }
            onAccepted: {
                root.enter();
            }
        }
    }
    
    Javascript is supported when you binding a value to a property. That allows you to make a logical property binding.
  • TabBar
  • TabBar is an Item-based component. It has three rectangles, two rectangles for the options, and the last rectangle as container. The content of the container could be changed dynamically by changing the source of the Loader. Here's the code:
    import QtQuick 2.0
    
    Item {
        Rectangle{
            id: tab1
            width: 100
            height: 30
            color: "gold"
            radius: 5
            Text{
                text: "Assistant Schedule"
                anchors{
                    top: parent.top
                    topMargin: 5
                    horizontalCenter: parent.horizontalCenter
                }
            }
            anchors{
                top: parent.top
                left: parent.left
            }
            MouseArea{
                anchors.fill: parent
                onClicked: {
                    parent.color = "gold";
                    tab2.color = "lightgrey";
                    loadcom.source = "ShowAssistantSchedule.qml";
                }
            }
        }
        Rectangle{
            id: tab2
            width: 100
            height: 30
            color: "lightgrey"
            radius: 5
            Text{
                text: "Insert Transaction"
                anchors{
                    top: parent.top
                    topMargin: 5
                    horizontalCenter: parent.horizontalCenter
                }
            }
            anchors{
                top: parent.top
                left: tab1.right
                leftMargin: 5
            }
            MouseArea{
                anchors.fill: parent
                onClicked: {
                    parent.color = "gold";
                    tab1.color = "lightgrey";
                    loadcom.source = "InsertTransaction.qml";
                }
            }
        }
        Rectangle{
            anchors{
                top: parent.top
                topMargin: 20
                bottom: parent.bottom
                left: parent.left
                right: parent.right
            }
            radius: 5
            gradient:Gradient{
                GradientStop{position: 0.0; color: "lightgrey"}
                GradientStop{position: 0.1; color: "white"}
                GradientStop{position: 0.9; color: "lightgrey"}
            }
            border.color: "grey"
            Loader{
                id: loadcom;
                source: "ShowAssistantSchedule.qml"
                anchors.fill: parent
            }
        }
    }
    
    This code is actually made for specific purpose, but you can change it as you need.
  • DragBox
  • DragBox component is a component that makes your window draggable. Here's the code of DragBox:
    import QtQuick 2.0
    import QtQuick.Window 2.0
    
    MouseArea{
        property variant clickPos: "1,1"
        z: -1
        anchors.fill: parent
        onPressed: {
            clickPos  = Qt.point(mouse.x,mouse.y)
        }
        onPositionChanged: {
            var delta = Qt.point(mouse.x-clickPos.x, mouse.y-clickPos.y);
            Mainwindow.x = Mainwindow.x+delta.x;
            Mainwindow.y =  Mainwindow.y+delta.y;
    
        }
    }
    
    We make new property called clickPos and set the default value to "1,1". Its type is variant, so that we can bind its property with every types that suitable. When user click the mouse, we get the coordinate of mouse pointer position. And when user hold an move the mouse pointer, it will change mainwindow's position.
Now we learn about how to make main file for QML. This file is page controller. It control the flow of the program. Here's the code:
import QtQuick 2.0
import QtQuick.Window 2.0

Rectangle{
    id: container

    function changeWindowSize(mainwindow,width,height){
        mainwindow.setWidth(width);
        mainwindow.setHeight(height);
        mainwindow.setX((Screen.width-width)/2);
        mainwindow.setY((Screen.height-height)/2);
    }

    Loader{
        id: loader
        anchors{
            centerIn: parent
        }
    }
    states: [
        State{
            name: "Login"
            PropertyChanges {
                target: loader
                source: "Login.qml"
            }
            StateChangeScript{
                name: "changeMainWindow"
                script: changeWindowSize(Mainwindow,300,150);
            }
        },
        State{
            name: "MainMenu"
            PropertyChanges {
                target: loader
                source: "MainMenu.qml"
            }
            StateChangeScript{
                name: "changeMainWindow"
                script: changeWindowSize(Mainwindow,780,535);
            }
        },
        State{
            name: "AstSpv"
            PropertyChanges {
                target: loader
                source: "AstSpv.qml"
            }
            StateChangeScript{
                name: "changeMainWindow"
                script: changeWindowSize(Mainwindow,780,535);
            }
        }
    ]
    Component.onCompleted: container.state = "Login"
}

This file is actually a Rectangle that has a Loader. The loader deciding which file will be raised. To control it, we use State. We have three states, each state has two components, PropertyChanges and StateChangeScript. PropertyChanges handles source changes of the loader, and StateChangeScript handles running changeWindowSize() function. This function is used to change main window, so it will resize when the source of the loader was changed. When the component is completely load the all of the component from main.qml, we set the state to "Login" state using Component.onCompleted signal.

Now we take a look at Login.qml.

Login

Here's the code:
import QtQuick 2.0
import QtGraphicalEffects 1.0

Rectangle {
    id: login
    width: 300
    height: 150
    radius: 3
    gradient:Gradient{
        GradientStop{position: 0.05; color: "lightgrey"}
        GradientStop{position: 0.1; color: "white"}
        GradientStop{position: 0.9; color: "lightgrey"}
        GradientStop{position: 0.95; color: "grey"}
    }

    Logo{
        id: logo
        anchors{
            left: parent.left
            leftMargin: 10
            verticalCenter: parent.verticalCenter
        }
    }

    Text{
        id: username
        text: "Username"
        color: "grey"
        font{
            pixelSize: 12
            bold: true
        }
        anchors{
            top : logo.top
            left : logo.right
            leftMargin: 10
        }
    }

    TextBox{
        id: textUsername
        width: 150
        height: 20
        maxLength: 20
        anchors{
            left: logo.right
            leftMargin: 10
            top: username.bottom
            topMargin: 5
        }
        isFocus: true
    }

    Text{
        id: password
        text: "Password"
        color: "grey"
        font{
            pixelSize: 12
            bold: true
        }
        anchors{
            bottom : textPassword.top
            bottomMargin: 5
            left : logo.right
            leftMargin: 10
        }
    }

    TextBox{
        id: textPassword
        width: 150
        height: 20
        maxLength: 19
        anchors{
            left: logo.right
            leftMargin: 10
            bottom: logo.bottom
        }
        isPassword: true
        onEnter: {
            if(textPassword.text.trim()==="" || textUsername.text.trim()==="")
            {
                err.visible = true;
                err.text = "Username or password must be filled"
            }
            else if(Services.doLogin(textUsername.text,textPassword.text))
            {
                Session.username = textUsername.text;
                var userid = Services.getUserID(Session.username);
                if(Services.getUserRole(userid)==="Assistant Supervisor")
                {
                    container.state = "AstSpv";
                }
                else
                {
                    container.state = "MainMenu";
                }
            }
            else
            {
                err.visible = true;
                err.text = "Wrong Username or password"
            }
        }
    }

    Text {
        id: err
        color: "red"
        visible: false
        anchors{
            top: textPassword.bottom
            topMargin: 5
            horizontalCenter: parent.horizontalCenter
        }
    }
    DragBox{}

    CloseButton{}
    border.color: "grey"
    Keys.onTabPressed: {
        if(textUsername.isFocus)textPassword.isFocus = true;
        else textUsername.isFocus = true;
    }
}

This component is actually a Rectangle component. It has eight components, one Logo, three Texts, two TextBoxs, one DragBox and one CloseButton. When user press Enter on password Textbox, enter signal that we have created will be emitted. So, when the enter signal is emitted, then we simply do login logic. We check whether the textbox is empty or not. After that, we validate the username using Services.doLogin() function. Services.doLogin() is C++ function that has been integrated to QML (We will discuss about it later). That function returns bool value. So when its value is true, then we check the role of the user using Services.getUserRole(). If the role is Assistant Supervisor, then it will change the state to "AstSpv", otherwise it will change the state to "MainMenu". But, if there is no username found in database, then it would show an error message. After the login process succeeded, we store the user information to the Session. Session is simply C++ object that i have created before that contains user information.

MainWindow is one of the context property that we set in C++. It is actually the main viewer of the qml.

Now we continue to the next page, when the user's role is Teaching Assistant.

MainMenu

Here is the code of MainMenu.qml:
import QtQuick 2.0

Rectangle {
    width: 780
    height: 535
    border.width: 3
    border.color: "grey"
    gradient:Gradient{
        GradientStop{position: 0.05; color: "lightgrey"}
        GradientStop{position: 0.1; color: "white"}
        GradientStop{position: 0.9; color: "lightgrey"}
        GradientStop{position: 0.95; color: "grey"}
    }

    Logo{
        id: logo
        anchors{
            left: parent.left
            leftMargin: 10
            top: parent.top
            topMargin: 30
        }
    }

    Text{
        text: "Assistant Schedule"
        color: "darkblue"
        font{
            pixelSize: 30
            bold: true
            family: "Bauhaus 93"
        }
        anchors{
            verticalCenter: logo.verticalCenter
            left: logo.right
            leftMargin: 10
        }
        style: Text.Raised
    }

    Text{
        id: welcome
        text: "Welcome, "+Session.name+" | "
        color: "blue"
        font{
            pixelSize: 15
            bold: true
            family: "Arial"
        }
        anchors{
            bottom: logo.bottom
            left: logo.right
            leftMargin: 10
        }
    }

    Text{
        id:signout
        text: "Sign Out"
        color: "blue"
        font{
            pixelSize: 15
            bold: true
            family: "Arial"
        }
        anchors{
            bottom: logo.bottom
            left: welcome.right
        }
        MouseArea{
            anchors.fill: parent
            onClicked: container.state = "Login"
            hoverEnabled: true
            onEntered: {
                signout.color = "gold";
            }
            onExited: {
                signout.color = "blue";
            }
        }
    }

    AssistantScheduleView{
        anchors{
            top: logo.bottom
            topMargin: 10
            bottom: parent.bottom
            bottomMargin: 10
            left: parent.left
            leftMargin: 10
            right: parent.right
            rightMargin: 10
        }
    }
    DragBox{}
    CloseButton{}
    Component.onCompleted: Services.getTransactionData(Session.username);
}

In this file, we can AssistantScheduleView. This is basically a custom component made from ListView component. It shows Assistant's teaching schedule. Here's the code:

import QtQuick 2.0

ListView{
    id: root
    property bool isDeletable: false
    signal selected(var id, var desc, var start, var end, var stat)
    signal deleted(var id)
    clip: true
    model: Transaction
    boundsBehavior: Flickable.StopAtBounds
    header:Item{
        width: parent.width
        height: 20
        Component{
            id: rectheader
            Rectangle{
                width: headerWidth
                height: 20
                color: "grey"
                Text{
                    id: nameText
                    color: "white"
                    text: qsTr(headername)
                    anchors.centerIn: parent
                    wrapMode: Text.Wrap
                }
            }
        }
        Loader{
            id: headerDesc;
            property int headerWidth: parent.width*0.5
            property string headername: "Description";
            sourceComponent: rectheader;
            anchors{left: parent.left}
        }
        Loader{
            id: headerStart;
            property int headerWidth: parent.width*0.2
            property string headername: "Start Time";
            sourceComponent: rectheader;
            anchors{left: headerDesc.right}
        }
        Loader{
            id: headerEnd;
            property int headerWidth: parent.width*0.2
            property string headername: "End Time";
            sourceComponent: rectheader;
            anchors{left: headerStart.right}
        }
        Loader{
            id: headerStatus;
            property int headerWidth: parent.width*0.1
            property string headername: "Status";
            sourceComponent: rectheader;
            anchors{left: headerEnd.right}
        }
    }
    delegate:Item{
        id: del
        width: parent.width
        height: 20
        MouseArea{
            anchors.fill: parent
            onClicked: {
                root.selected(id,desc,starttime,endtime,stat);
            }
        }

        Component{
            id: rectview
            Rectangle{
                Text{
                    text: "X"
                    font.bold: true
                    color: "red"
                    anchors{
                        left: parent.left
                        leftMargin: 5
                        verticalCenter: parent.verticalCenter
                    }
                    visible: isShow
                    MouseArea{
                        anchors.fill: parent
                        onClicked: {
                            root.deleted(id);
                        }
                    }
                }

                width: loadWidth
                height: del.height
                color: "lightgrey"
                border.color: "grey"
                Text{
                    id: nameText
                    color: "grey"
                    text: name
                    anchors.centerIn: parent
                    wrapMode: Text.Wrap
                }
            }
        }
        Loader{
            id: loadDesc;
            property int loadWidth: del.width*0.5
            property string name: desc;
            property bool isShow: isDeletable;
            sourceComponent: rectview;
            anchors{left: parent.left}
        }
        Loader{
            id: loadStart;
            property int loadWidth: del.width*0.2
            property string name: starttime;
            property bool isShow: false;
            sourceComponent: rectview;
            anchors{left: loadDesc.right}
        }
        Loader{
            id: loadEnd;
            property int loadWidth: del.width*0.2
            property string name: endtime;
            property bool isShow: false;
            sourceComponent: rectview;
            anchors{left: loadStart.right}
        }
        Loader{
            id: loadStatus;
            property int loadWidth: del.width*0.1
            property string name: stat;
            property bool isShow: false;
            sourceComponent: rectview;
            anchors{left: loadEnd.right}
        }
    }
}

This ListView has one boolean property called isDeletable. It will determine whether user can deletes item on the list. It also has two signals, selected and deleted. selected signal will be emitted when user click one of the item on the list. It returns description, starttime, endtime, and status of assistant's teaching schedule. deleted signal will be emitted when user click cross on the list if isDeletable value was true. It also will send the id of the item that has been chosen by user. We set clip property to true, so the ListView will be rendered only on its scope. We set its model to Transaction. Transaction is C++ object list that contains teaching schedule information. For header and delegate, we use Loader to load same component, but with additional property.

Now we discuss about Assistant Supervisor page.

AstSpv-AssistantSchedule AstSpv-InsertTransaction
This page contains two tabs, "Show Assistant Schedule" and "Insert Transaction". In "Show Assistant Schedule" tab, Supervisor can see teaching schedule per assistant. It can also delete and update selected schedule. In "Insert Transaction" tab, Supervisor can insert new teaching schedule for specific assistant.
Here's the code:
import QtQuick 2.0

Rectangle {
    width: 780
    height: 535
    border.width: 3
    border.color: "grey"
    gradient:Gradient{
        GradientStop{position: 0.05; color: "lightgrey"}
        GradientStop{position: 0.1; color: "white"}
        GradientStop{position: 0.9; color: "lightgrey"}
        GradientStop{position: 0.95; color: "grey"}
    }

    Logo{
        id: logo
        anchors{
            left: parent.left
            leftMargin: 10
            top: parent.top
            topMargin: 30
        }
    }

    Text{
        text: "Assistant Supervisor"
        color: "darkblue"
        font{
            pixelSize: 30
            bold: true
            family: "Bauhaus 93"
        }
        anchors{
            verticalCenter: logo.verticalCenter
            left: logo.right
            leftMargin: 10
        }
        style: Text.Raised
    }

    Text{
        id: welcome
        text: "Welcome, "+Session.name+" | "
        color: "blue"
        font{
            pixelSize: 15
            bold: true
            family: "Arial"
        }
        anchors{
            bottom: logo.bottom
            left: logo.right
            leftMargin: 10
        }
    }

    Text{
        id:signout
        text: "Sign Out"
        color: "blue"
        font{
            pixelSize: 15
            bold: true
            family: "Arial"
        }
        anchors{
            bottom: logo.bottom
            left: welcome.right
        }
        MouseArea{
            anchors.fill: parent
            onClicked: container.state = "Login"
            hoverEnabled: true
            onEntered: {
                signout.color = "gold";
            }
            onExited: {
                signout.color = "blue";
            }
        }
    }

    TabBar{
        anchors{
            top: logo.bottom
            topMargin: 10
            bottom: parent.bottom
            bottomMargin: 10
            left: parent.left
            leftMargin: 10
            right: parent.right
            rightMargin: 10
        }
    }
    DragBox{}
    CloseButton{}
}
As you can see from the code above, There is a TabBar component. You can see from the code that i have shared before, that this component has Loader. The loader would changes according what user want to see. So when user click "Show Assistant Schedule", the Loader's source would be set to ShowAssistantSchedule.qml. When user click "Insert Transaction", the Loader's source would be set to "InsertTransaction.qml". Here's the code of ShowAssistantSchedule.qml:
 import QtQuick 2.0

Item{
    AssistantScheduleView{
        id:astschview
        isDeletable: true
        visible: false
        anchors{
            top: cmbast.bottom
            topMargin: 5
            left: parent.left
            leftMargin: 5
            right: parent.right
            rightMargin: 5
            bottom: parent.bottom
            bottomMargin: 80
        }
        onSelected: {
            update.description = desc;
            update.starttime = start;
            update.endtime = end;
            update.status = stat;
            update.transactionId = id;
        }
        onDeleted: {
            Services.deleteTransaction(id);
            Services.getTransactionData(cmbast.text);
        }
    }

    UpdateTeachingSchedule{
        id: update
        visible: false
        anchors{
            top: astschview.bottom
            topMargin: 10
            bottom: parent.bottom
            bottomMargin: 10
            left: parent.left
            leftMargin: 10
            right: parent.right
            rightMargin: 10
        }
        onUpdate: {
            doUpdate(cmbast.text);
        }

    }

    Text{
        id: asttext
        text: "Assistant : "
        anchors{
            left: parent.left
            leftMargin: 10
            top: parent.top
            topMargin: 10
        }
    }
    Combobox{
        id: cmbast
        width: 150
        height: 20
        text: "Choose Assistant"
        mod: Services.getTeachingAssistantUsernames()
        anchors{
            left: asttext.right
            verticalCenter: asttext.verticalCenter
        }
    }
    Rectangle{
        width: 30
        height: 20
        radius: 3
        Text{
            text: "Show"
            anchors.centerIn: parent
        }
        anchors{
            left: cmbast.right
            leftMargin: 10
            verticalCenter: cmbast.verticalCenter
        }
        gradient:Gradient{
            GradientStop{position: 0.0; color: "white";}
            GradientStop{position: 0.3; color: "lightgrey";}
            GradientStop{position: 0.7; color: "grey";}
        }
        MouseArea{
            anchors.fill: parent
            onClicked: {
                Services.getTransactionData(cmbast.text,"All");
                astschview.visible = true;
                update.visible = true;
            }
        }
    }
}
 

As you can see from the code above, this Item has AssistantScheduleView component and UpdateTeachingSchedule component. We have already discuss about AssistantScheduleView component before. We set its isDeletable property to true. when selected signal was emitted, onSelected slot will be called. In onSelected slot, we get the value from the ListView and show it on UpdateTeachingSchedule. When we click to the red cross in ListView, it will emit deleted signal. So, in onDeleted slot, we delete the transaction chosen by user and refresh the model. Now we discuss about UpdateTeachingSchedule. This component is used to update selected item. Here's the code:

 import QtQuick 2.0

Item{
    id: root
    property int transactionId: -1
    property alias description: txtBoxDesc.text
    property alias starttime: txtBoxStartTime.text
    property alias endtime: txtBoxEndTime.text
    property alias status: cmbBoxStatus.text
    signal update

    function doUpdate(username){
        if(transactionId === -1)
        {
            errMsg.color = "red";
            errMsg.text = "You have to choose one item from the list";
            errMsg.visible = true;
        }
        else if(description.trim() === "" || starttime.trim() ===""
                || endtime.trim() === "" || status.trim() === "")
        {
            errMsg.color = "red";
            errMsg.text = "All field must be filled";
            errMsg.visible = true;
        }
        else{
            var result = Services.updateTransaction(transactionId,description,starttime,endtime,status);
            if(result){
                errMsg.color = "green";
                errMsg.text = "Transaction updated successfully";
                errMsg.visible = true;
                Services.getTransactionData(username);
            }else
            {
                errMsg.color = "red";
                errMsg.text = "Failed to update transaction ";
                errMsg.visible = true;
            }
        }
    }

    Text{
        text: "Update Teaching Schedule"
        anchors{
            bottom: con.top
            bottomMargin: 5
            left: con.left
        }
        color: "grey"
    }

    Rectangle {
        id: con
        width: parent.width
        height: parent.height
        border.color: "grey"
        color: "lightgrey"
        Item{
            width: parent.width-20
            height: parent.height
            anchors.centerIn: con
            Text{
                id: txtDesc
                text: "Description"
                color: "grey"
                anchors{
                    top: parent.top
                    horizontalCenter: txtBoxDesc.horizontalCenter
                }
            }
            TextBox{
                id: txtBoxDesc
                height: 20
                width: parent.width/1.9
                maxLength: 100
                anchors{
                    top: txtDesc.bottom
                    topMargin: 3
                    left: parent.left
                }
            }
            Text{
                id: txtStartTime
                text: "StartTime"
                color: "grey"
                anchors{
                    top: parent.top
                    horizontalCenter: txtBoxStartTime.horizontalCenter
                }
            }
            TextBox{
                id: txtBoxStartTime
                width: parent.width/6
                isDate: true
                maxLength: 20
                height: 20
                anchors{
                    top: txtStartTime.bottom
                    topMargin: 3
                    left: txtBoxDesc.right
                    leftMargin: 3
                }
            }
            Text{
                id: txtEndTime
                text: "EndTime"
                color: "grey"
                anchors{
                    top: parent.top
                    horizontalCenter: txtBoxEndTime.horizontalCenter
                }
            }
            TextBox{
                id: txtBoxEndTime
                width: parent.width/6
                height: 20
                maxLength: 20
                isDate: true
                anchors{
                    top: txtEndTime.bottom
                    topMargin: 3
                    left: txtBoxStartTime.right
                    leftMargin: 3
                }
            }
            Text{
                id: txtStatus
                text: "Status"
                color: "grey"
                anchors{
                    top: parent.top
                    horizontalCenter: cmbBoxStatus.horizontalCenter
                }
            }
            Combobox{
                id: cmbBoxStatus
                width: parent.width/8
                height: 20
                mod:ListModel{
                    ListElement{modelData: "Not Done"}
                    ListElement{modelData: "Done"}
                }
                anchors{
                    top: txtStatus.bottom
                    topMargin: 3
                    left: txtBoxEndTime.right
                    leftMargin: 3
                }
            }
        }
        Rectangle{
            id: btnUpdate
            width: parent.width/10
            height: 20
            radius: 3
            Text{
                anchors.centerIn: parent
                text: "Update"
            }
            gradient:Gradient{
                GradientStop{position: 0.0; color: "white";}
                GradientStop{position: 0.3; color: "lightgrey";}
                GradientStop{position: 0.7; color: "grey";}
            }
            anchors{
                bottom: parent.bottom
                bottomMargin: 5
                horizontalCenter: parent.horizontalCenter
            }
            MouseArea{
                anchors.fill: parent
                onClicked: root.update()
            }
        }
        Text{
            id: errMsg
            color: "green"
            visible: false
            anchors{
                left: btnUpdate.right
                leftMargin: 5
                verticalCenter: btnUpdate.verticalCenter
            }
        }
    }
}
 

This file has javascript function called doUpdate(). This function is used to validate whether the data is valid or not. If the data is valid, then it will update the data and refresh the data. To bind the data, we use alias property. So we set the data from another file using these properties.

C++ Part

We have already make the design, now we take a look at C++ code side. We make two model classes, a service class, a session class, and a class to connect to database. First we take a look at database class. Here's the code:

// connecttodatabase.h

#ifndef CONNECTTODATABASE_H
#define CONNECTTODATABASE_H
#include <QtSql/QSqlDatabase>

class ConnectToDatabase
{
public:
    ConnectToDatabase();
    QSqlDatabase con() const;
    void doConnect(QString driver, QString hostname, QString databasename, QString username, QString password);
private:
    QSqlDatabase m_db;
};

#endif // CONNECTTODATABASE_H
 
// connecttodatabase.cpp
#include "connecttodatabase.h"

ConnectToDatabase::ConnectToDatabase()
{
}

void ConnectToDatabase::doConnect(QString driver, QString hostname, QString databasename, QString username, QString password)
{
    m_db = QSqlDatabase::addDatabase(driver);
    m_db.setHostName(hostname);
    m_db.setDatabaseName(databasename);
    m_db.setUserName(username);
    m_db.setPassword(password);
    m_db.open();
}


QSqlDatabase ConnectToDatabase::con() const
{
    return m_db;
}
 

NB: Because we want to use database, don't forget to add QT += sql to .pro file.
Take a look at the code. We make one function to connect to database, one getter to get QSqlDatabase object, so we can use the object to interact with database. For the driver, we use QMYSQL driver. It means that you are using MySQL as the DBMS. We set the object using QSqlDatabase::addDatabase(drivername). If QMYSQL driver is unavailable, you can go to this page to find out how to get it. After we set the object, we just simply set the hostname,databasename, username and password to access your database. After that, open the database by using QSqlDatabase::open() function. So when user want to interact with the database, just called ConnectToDatabase::con() getter.
Now We go to the Services class. Here's the code:

 
// services.h

#ifndef SERVICES_H
#define SERVICES_H

#include <QObject>
#include "connecttodatabase.h"
#include "session.h"
#include <QStringList>
#include <QQmlContext>

class QString;
class Services : public QObject
{
    Q_OBJECT
public:
    Services(QQmlContext *ctxt, Session *sess);
    Q_INVOKABLE bool doLogin(QString username, QString password);
    Q_INVOKABLE void getTransactionData(QString username);
    Q_INVOKABLE QString getUserRole(int userID);
    Q_INVOKABLE QStringList getTeachingAssistantUsernames();
    Q_INVOKABLE QStringList getCourseOutlines();
    Q_INVOKABLE bool updateTransaction(int id, QString desc, QString starttime, QString endtime, QString status);
    Q_INVOKABLE bool insertTransaction(QString username, QString desc, QString starttime, QString endtime, QString status = "Not Done");
    Q_INVOKABLE bool deleteTransaction(int id);
    Q_INVOKABLE int getUserID(QString username);

public slots:
    void changeName(QString username);
private:
    ConnectToDatabase m_con;
    QQmlContext *m_ctxt;
    Session *m_ses;
};

#endif // SERVICES_H
 
// services.cpp
#include "services.h"
#include "model_transaction.h"
#include <QSqlQuery>
#include <QVariant>
#include <QDebug>
#include <QCryptographicHash>
#include <QDateTime>

Services::Services(QQmlContext *ctxt, Session *sess)
{
    m_ctxt = ctxt;
    m_ses = sess;
    m_con.doConnect("QMYSQL","localhost","CPPQMLDatabase","root","");
}

bool Services::doLogin(QString username, QString password)
{
    QSqlQuery query(m_con.con());
    query.prepare("Select * from users where username = ? and password = ?");
    query.bindValue(0,username);
    query.bindValue(1,QCryptographicHash::hash(password.toLocal8Bit(),QCryptographicHash::Md5).toHex());
    query.exec();
    return query.next();
}

void Services::getTransactionData(QString username)
{
    QList<QObject *> data;
    QSqlQuery query(m_con.con());
    int userID = getUserID(username);
    if(getUserRole(userID)=="Teaching Assistant")
    {
        query.prepare("Select TransactionID,Description,StartTime,EndTime,Status from transaction where userid = ?");
        query.bindValue(0,userID);
        query.exec();
        while(query.next())
        {
            data.append(new Model_Transaction(query.value("TransactionID").toInt(),query.value("Description").toString(),query.value("StartTime").toDateTime().toString("yyyy-MM-dd hh:mm:ss"),query.value("EndTime").toDateTime().toString("yyyy-MM-dd hh:mm:ss"),query.value("Status").toString()));
        }
    }
    m_ctxt->setContextProperty("Transaction",QVariant::fromValue(data));
}

void Services::changeName(QString username)
{
    QSqlQuery query(m_con.con());
    query.prepare("Select Name from Users where username = ?");
    query.bindValue(0,username);
    query.exec();
    if(query.next())m_ses->setName(query.value("Name").toString());
}

int Services::getUserID(QString username)
{
    QSqlQuery query(m_con.con());
    query.prepare("Select UserID from Users where Username = ?");
    query.bindValue(0,username);
    query.exec();
    if(query.next())
    {
        return query.value(0).toInt();
    }
    return 0;
}

QString Services::getUserRole(int userID)
{
    QSqlQuery query(m_con.con());
    query.prepare("Select RoleName from Roles a join UserInRole b on a.RoleID = b.RoleID where b.UserID = ?");
    query.bindValue(0,userID);
    query.exec();
    bool res = query.next();
    if(res)
    {
        return query.value(0).toString();
    }
    else return "";
}

QStringList Services::getTeachingAssistantUsernames()
{
    QStringList data;
    QSqlQuery query(m_con.con());
    query.exec("Select Username from Users a join UserInRole b on a.Userid = b.Userid join Roles c on b.RoleID = c.RoleID where c.RoleName = 'Teaching Assistant'");
    while(query.next())
    {
        data<<query.value(0).toString();
    }
    return data;
}

QStringList Services::getCourseOutlines()
{
    QStringList data;
    QSqlQuery query(m_con.con());
    query.exec("Select Name from CourseOutline");
    while(query.next())
    {
        data<<query.value(0).toString();
    }
    return data;
}

bool Services::updateTransaction(int id, QString desc, QString starttime, QString endtime, QString status)
{
    QSqlQuery query(m_con.con());
    query.prepare("Update Transaction set Description = ?, StartTime = ?, EndTime = ?, Status = ? where TransactionID = ?");
    query.bindValue(0,desc);
    query.bindValue(1,starttime);
    query.bindValue(2,endtime);
    query.bindValue(3,status);
    query.bindValue(4,id);
    return query.exec();
}

bool Services::insertTransaction(QString username, QString desc, QString starttime, QString endtime, QString status)
{
    QSqlQuery query(m_con.con());
    int id = getUserID(username);
    query.prepare("Insert into Transaction (UserID,Description,StartTime,EndTime,Status) Values(?,?,?,?,?)");
    query.bindValue(0,id);
    query.bindValue(1,desc);
    query.bindValue(2,starttime);
    query.bindValue(3,endtime);
    query.bindValue(4,status);
    return query.exec();
}

bool Services::deleteTransaction(int id)
{
    QSqlQuery query(m_con.con());
    query.prepare("Delete from Transaction where TransactionID = ?");
    query.bindValue(0,id);
    return query.exec();
}
 

This class has serveral functions that integrate to QML. All of this functions has Q_INVOKABLE macro. When we make an object from this class, it automatically call QSqlDatabase::doConnect() function. Fill the paramater with correct argument. We use QSqlQuery to do query on database. We pass QSqlDatabase object to the constructor of QSqlquery. We do the query by using QSqlQuery::prepare() function. To bind the data to the query, we use QSqlQuery::bindValue() function. If you look at the query, there are question marks on the query, it means that you have to bind the data according to the sequence of the question marks. If the id field is on the first sequence, then you bind the value by calling QSqlQuery::bindValue(0,id) and soon. Call QSqlQuery::exec() to execute the query. It return boolean value, so you can detect whether the query is working or not.
Now we discuss about Session class. Here's the code:

//session.h 

#ifndef SESSION_H
#define SESSION_H

#include <QObject>

class Session : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString username READ username WRITE setUsername NOTIFY usernameChanged)
    Q_PROPERTY(QString name READ name NOTIFY nameChanged)
public:
    Session();
    QString username() const;
    void setUsername(const QString &newusername);
    QString name() const;
    void setName(const QString &newname);

signals:
    void usernameChanged(QString username);
    void nameChanged();

private:
    QString m_username;
    QString m_name;
};

#endif // SESSION_H
 
 //session.cpp
 #include "session.h"

Session::Session()
{
}

QString Session::username() const
{
    return m_username;
}

void Session::setUsername(const QString &newusername)
{
    if(m_username != newusername)
    {
        m_username = newusername;
        emit usernameChanged(m_username);
    }
}

QString Session::name() const
{
    return m_name;
}

void Session::setName(const QString &newname)
{
    if(m_name != newname)
    {
        m_name = newname;
    }
}
 

This class is used to store user basic information when they do login. This class is also accessible from QML. So when user login successfully, we set the username and name so the application would be know who logged in.
Now we take a look model class, Model_Transaction. Here's the code:

 //model_transaction.h
 #ifndef MODEL_TRANSACTION_H
#define MODEL_TRANSACTION_H

#include 

class Model_Transaction : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int id READ id NOTIFY idChanged)
    Q_PROPERTY(QString desc READ desc NOTIFY descChanged)
    Q_PROPERTY(QString starttime READ starttime NOTIFY starttimeChanged)
    Q_PROPERTY(QString endtime READ endtime NOTIFY endtimeChanged)
    Q_PROPERTY(QString stat READ stat NOTIFY statChanged)
public:
    Model_Transaction(int id, QString desc, QString starttime, QString endtime, QString stat);
    QString desc() const;
    QString starttime() const;
    QString endtime() const;
    QString stat() const;
    int id() const;
    /*void setType( const QString &newtype);
    void setDesc( const QString &newdesc);
    void setStartTime( const QString &newstarttime);
    void setEndTime( const QString &newendtime);
    void setStat( const QString &newstat);*/
signals:
    void idChanged();
    void descChanged();
    void starttimeChanged();
    void endtimeChanged();
    void statChanged();
private:
    int m_id;
    QString m_desc;
    QString m_starttime;
    QString m_endtime;
    QString m_stat;
};

#endif // MODEL_TRANSACTION_H
 
//model_transaction.cpp
 
#include "model_transaction.h"

Model_Transaction::Model_Transaction(int id, QString desc, QString starttime, QString endtime, QString stat)
{
    m_id = id;
    m_desc = desc;
    m_starttime = starttime;
    m_endtime = endtime;
    m_stat = stat;
}

QString Model_Transaction::desc() const
{
    return m_desc;
}

QString Model_Transaction::starttime() const
{
    return m_starttime;
}

QString Model_Transaction::endtime() const
{
    return m_endtime;
}

QString Model_Transaction::stat() const
{
    return m_stat;
}

int Model_Transaction::id() const
{
    return m_id;
}
 

This model is used to store transaction information from database. When we create AssistantScheduleView Component, we bind its model property to this class's object. All of this class's properties are accessible from QML.

That's all for the tutorial, i hope you find this tutorial useful for your project. Thank you.

ċ
CPPQMLDatabase_Final.7z
(1196k)
Muhammad Zullidar,
Dec 16, 2013, 10:48 PM