PDA

View Full Version : Populate Listview from Database



arcade
31st July 2016, 09:22
I want to populate listview ( a specific page in my app ) from a database .

Currently the page is static and look like this

image: https://s32.postimg.org/vrsdrl2p1/man.png

code :

import QtQuick 2.6
import QtQuick.Layouts 1.1
import QtQuick.Controls 2.0


Pane {
padding: 0

property var delegateComponentMap: {
"page": itemDelegateComponent

}

Component {
id: itemDelegateComponent

ItemDelegate {
text: labelText
width: parent.width
}
}






ColumnLayout {
id: column
spacing: 40
anchors.fill: parent
anchors.topMargin: 20

Label {
id: label1
Layout.fillWidth: true
wrapMode: Label.Wrap
horizontalAlignment: Qt.AlignHCenter
text: "Offline Pages "
}

ListView {
id: listView

Layout.fillWidth: true
Layout.fillHeight: true
clip: true
model: ListModel {
ListElement { type: "ItemDelegate"; labelText: "page1" }
ListElement { type: "ItemDelegate"; labelText: "page2" }
ListElement { type: "ItemDelegate"; labelText: "page3" }


}
spacing: 5

section.property: "type"

delegate: Component{

Item{
id: aItem
width: listView.width //rowLayout.width. We got width from children elements before, now get width from listView
height: 30

RowLayout{

id: rowLayout
anchors.fill: parent
spacing: 10

Label{
id:page_name
padding: 10
text: labelText
Layout.fillWidth: true // !!! to fill most part of row width

}


Button{

text: qsTr("Delete")

id: delete_button

}
Button{

text: qsTr("Update")
id: update_button
}



}
}
}
}









RowLayout{
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom

Button{
text:"Update All"


}
Button{
text:"Delete All"
}
}
}
}

my databse only has 2 entries ( looks like this ) . the databse is stored in genericelocation

db : https://s32.postimg.org/jb4nhmoxh/image.png

how should i approach this task ?

anda_skoa
31st July 2016, 11:26
If the database is an Sqlite file compatible with QML LocalStorage (http://doc.qt.io/qt-5/qtquick-localstorage-qmlmodule.html) then you can probably use that to add entries to the QML ListModel you have.

Alternatively you use a C++ list model for the ListView and expose either an instance of it to QML or register the model as a type an instantiate it from QML.

For the model there are a couple of approaches:

- derive from QAbstractListModel and do the database query inside of it

- derive from QSqlQueryModel and overwrite data() such that it maps QML roles to columns of the base class.

Cheers,
_

arcade
31st July 2016, 12:11
If the database is an Sqlite file compatible with QML LocalStorage (http://doc.qt.io/qt-5/qtquick-localstorage-qmlmodule.html) then you can probably use that to add entries to the QML ListModel you have.

Alternatively you use a C++ list model for the ListView and expose either an instance of it to QML or register the model as a type an instantiate it from QML.

For the model there are a couple of approaches:

- derive from QAbstractListModel and do the database query inside of it

- derive from QSqlQueryModel and overwrite data() such that it maps QML roles to columns of the base class.

Cheers,
_

Hello , thanks for answering my question :D . about the first approach using " QML Localstorage" , i do have a sqlite DB but it is stored in some genericLocation

i am storing the database at this path

QString path = QStandardPaths::writableLocation(QStandardPaths::G enericDataLocation);

Can i still open the database ?

for ex :

this is where the db is stored : /home/arcade/.local/share/WTL_appdata/wtl.db

but this path will be different for everyone right .

anda_skoa
31st July 2016, 14:37
No, I don't think LocalStorage can be configured for a different path than the one it is using by default.

You'll have to create a model yourself.
If you already have database access code then the simplest solution is probably to create your own list model.
http://doc.qt.io/qt-5/qtquick-modelviewsdata-cppmodels.html#qabstractitemmodel-subclass

Cheers,
_

arcade
31st July 2016, 15:03
Thanks !! . Just more thing is there any example which i can refer too ?

An example that fits my case ? :)

anda_skoa
31st July 2016, 15:35
The link in my previous comment is about an actual example.

Cheers,
_

arcade
4th August 2016, 14:42
Hello , i am still not sure how to create the model for my need :confused: . If possible can you break the task for me ??

My project goes like this :

1. all of my program logic is in dbmanager.cpp file
2. main.cpp is just used to instantiate some files and settings
3. main.qml holds main UI

code base : https://github.com/hackertron/W2L/tree/master/UI

anda_skoa
4th August 2016, 17:03
First you need a data structure that can hold all values for a single list entry, e.g. something like


struct ListEntry
{
QString label;
};

This is like the Animal class in the example.

Then you need a QAbstractListModel derived class that has a list or vector of that structure.


class MyModel : public QAbstractListModel
{

private:
QList<ListEntry> m_entries;
};

Then you need to implement rowCount(), data() and roleNames() like in the example.

Then you add methods that the database class can call to access the list of entries, so that it can fill it, update it, etc.
QAbstractItemModel defines a set of protected helper methods that need to be used if the model's content is changed after it has been passed to a view, e.g. calling beingInsertRows() and endInsertRows() when new entries need to be added to "m_entries"

Cheers,
_

arcade
5th August 2016, 14:59
Hello , thanks for the breaking up the steps :D

Below is what i have tried

model.h


#include <QAbstractListModel>
#include <QStringList>

//![0]
class list
{
public:
list(const QString &title, const QString &id);
//![0]
//type title size id animal list
QString title() const;
QString id() const;

private:
QString m_title;
QString m_id;
//![1]
};

class listmodel : public QAbstractListModel
{
Q_OBJECT
public:
enum listroles {
titlerole = Qt::UserRole + 1,
idrole
};

listmodel(QObject *parent = 0);


void addpages(const list &list);

int rowCount(const QModelIndex & parent = QModelIndex()) const;

QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;

protected:
QHash<int, QByteArray> roleNames() const;
private:
QList<list> m_list;

};





model.cpp



#include "model.h"

list::list(const QString &title, const QString &id)
: m_title(title), m_id(id)
{
}

QString list::title() const
{
return m_title;
}

QString list::id() const
{
return m_id;
}

listmodel::listmodel(QObject *parent)
: QAbstractListModel(parent)
{
}

void listmodel::addpages(const list &list)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_list << list;
endInsertRows();
}

int listmodel::rowCount(const QModelIndex & parent) const {
Q_UNUSED(parent);
return m_list.count();
}

QVariant listmodel::data(const QModelIndex & index, int role) const {
if (index.row() < 0 || index.row() >= m_list.count())
return QVariant();

const list &list = m_list[index.row()];
if (role == titlerole)
return list.title();
else if (role == idrole)
return list.id();
return QVariant();
}

//![0]
QHash<int, QByteArray> listmodel::roleNames() const {
QHash<int, QByteArray> roles;
roles[titlerole] = "title";
roles[idrole] = "id";
return roles;
}
//![0]




main.cpp snippet



listmodel mod;


if(!db.open())
{
qDebug() <<"error in opening DB";
}
else
{
qDebug() <<"connected to DB" ;

}

QVector<QString> page_id;
QVector<QString> rev_id;

QSqlQuery quer("SELECT page_ID , page_revision FROM Pages");
while (quer.next()) {
QString i = quer.value(0).toString();
page_id.push_back(i);
QString r = quer.value(1).toString();
rev_id.push_back(r);


}
for (int i = 0; i < page_id.size(); ++i)
{
qDebug() << page_id.at(i).toLocal8Bit().constData() << endl;
qDebug() << rev_id.at(i).toLocal8Bit().constData() << endl;
mod.addpages(list(page_id.at(i) , rev_id.at(i)));




}





QQmlContext *ctxt = engine.rootContext();
ctxt->setContextProperty("myModel", &mod);


qml



import QtQuick 2.6
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.3
import QtQuick.Window 2.2
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.1
import QtQuick.Controls 2.0
import Qt.labs.folderlistmodel 2.1

Pane{

ListView {
width: 200; height: 250

model: myModel
delegate: Text { text: "pages: " + title + ", " + id }

}
}



OUTPUT :
12061

As you can see it's not even close to my original view : https://s32.postimg.org/vrsdrl2p1/man.png

now i need to add buttons to it but how ?

anda_skoa
5th August 2016, 15:34
You just changed your model, so you can just keep using your already working QML code.

Your original QML code just needs to use the new role names or you change your model so that is uses the original role names.

Cheers,
_

arcade
6th August 2016, 09:52
Yeah , i got it :D

See this : 12062 ( still need to do some work on it , but it's fine for now )

Now how to actually make it show/hide item dynamically :confused:

ex : when i save a new page it should show that page too . If i remove the page it should remove it from the view too .


what should i do ?

anda_skoa
6th August 2016, 10:34
Look at the code you have in listmodel::addpages()

That adds (in this case appends) a row to the model.
Removing a row is almost the same, just with beginRemoveRows() and endRemoveRows()

Cheers,
_

arcade
6th August 2016, 19:58
hello :) . I am trying something like this

scene : when someone clicks on delete button , i handle it like this



Button{
text: "delete"
onClicked: {
dbman.del(id) // this one actually deletes file from db and from system , works fine
mod.deletepages(id) // this is what i am trying to implement
}


so what mod.deletepages(id) do is get the id of the page and send it to here



void listmodel::deletepages(QString pageid)
{


qDebug() << pageid;
beginRemoveRows(QModelIndex(),remove,remove);
endInsertRows();
}


now what i have is the value not the index . how to get the index of that value .

i have tried to use m_list.indexOf(pageid) ;

but i get error " error: no matching function for call to 'QList<list>::indexOf(QString&)'
int remove = m_list.indexOf(pageid); "
^

so how to get the index

anda_skoa
6th August 2016, 23:53
The easiest way is to pass the row index, since the view and the model have the same concept of what a row is

In your delegate


mod.deletepages(model.index)


In your model


void listmodel::deletepages(int row)
{
beginRemoveRows(QModelIndex(), row, row);
m_list.remove(row);
endRemoveRows();
}


Ideally you don't do two calls from QML though, but have one C++ method that modifies the model and the database.
Otherwise you could forget one and model and database could get out of sync.

Cheers,
_

arcade
7th August 2016, 10:58
Hello :) i got the index but i am not able to remove the item from m_list .

ERROR that i am getting is : 12063

Added after 8 minutes:

Yeah i got it :D . it was removeAt() instead of remove()

Thanks !!

arcade
7th August 2016, 15:43
hey now if i add / save a page offline , i want it to show it in the view too .


ex : i opened my app click on save button to save page . it should show that saved page in the view too .

i tried to call the addpages function along with my save page function , but it is not adding or showing it in view .

anda_skoa
7th August 2016, 15:48
Hmm your addpages() method should append an entry to the list model.

How do you call it?

Cheers,
_

arcade
7th August 2016, 16:41
Like this



save_file(text , pageid , revid , page_title);
listmodel mod;
mod.addpages(list(page_title,QString(pageid)));

anda_skoa
7th August 2016, 18:18
So you are creating a new model and adding the page there?

Don't you want to add the "list" entry to the existing model?

Cheers,
_

arcade
7th August 2016, 19:24
So you are creating a new model and adding the page there?

Don't you want to add the "list" entry to the existing model?

Cheers,
_

yes i want to add it in the same model !!

anda_skoa
8th August 2016, 01:21
yes i want to add it in the same model !!

Then you need to call addpages() on that model instance, not create a new instance.
I.e. your code snippet looks like you are creating a new instance called "mod" on which you call addpages().

Cheers,
_

arcade
13th August 2016, 16:36
Then you need to call addpages() on that model instance, not create a new instance.
I.e. your code snippet looks like you are creating a new instance called "mod" on which you call addpages().

Cheers,
_

Hello , i understand what you are saying .

My code goes like this .

1. Main.cpp : populate the views




if(!db.open())
{
qDebug() <<"error in opening DB";
}
else
{
qDebug() <<"connected to DB" ;

}

QVector<QString> page_id;
QVector<QString> page_title;

QSqlQuery quer("SELECT page_ID , page_title FROM Pages");
while (quer.next()) {
QString i = quer.value(0).toString();
page_id.push_back(i);
QString p = quer.value(1).toString();
page_title.push_back(p);


}
for (int i = 0; i < page_id.size(); ++i)
{
qDebug() << page_id.at(i).toLocal8Bit().constData() << endl;
qDebug() << page_title.at(i).toLocal8Bit().constData() << endl;
mod.addpages(list(page_title.at(i) , page_id.at(i)));




}





QQmlContext *ctxt = engine.rootContext();
ctxt->setContextProperty("myModel", &mod);



2. now this is how dbmanager.cpp calls the addpages()




listmodel mod ; // declared on global scope

mod.addpages(list(page_title,QString(pageid)));



i get the problem i.e. like you said creating new instance of listmodel ( first in main.cpp and other here in dbmanager.cpp )

so i need to pass the same instance in main.cpp to dbmanager right ?? Can i use pointers for that ?

anda_skoa
13th August 2016, 18:30
so i need to pass the same instance in main.cpp to dbmanager right ?? Can i use pointers for that ?

Yes.
Or you do the initial load also in dbmanager.

Btw, your loading loop doesn't need local vectors, you can directly call addpages




while (quer.next()) {
QString i = quer.value(0).toString();
QString p = quer.value(1).toString();
mod.addpages(p, i);
}





mod.addpages(list(page_title,QString(pageid)));

You also might want to use QString::number(pageid), assuming pageid is a numerical value and you want that formatted into a string.

Cheers,
_

arcade
14th August 2016, 10:52
Thanks !! It's done :cool: