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:
- Login form is opened. User has to log in using their username.
- The application will check the user's role in database
- 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(); } }
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" } } } }
import QtQuick 2.0 Image { source: "../../pictures/logo.jpg" width: 100 height: 80 fillMode: Image.PreserveAspectFit }
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.
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.
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.

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.

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.


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.