PDA

View Full Version : Getting an image from mysql database (as BLOB field) through subclassed QSqlQueryMode



MarkoSan
14th August 2015, 09:00
I am working on Qt/QML app, which connects to mysql database and fetches data from it. Now, how do I fetch image from database (as BLOB field) using QSqlQueryModel? Now, I know how to connect to database, I've written simple read only subclassed QSqlQueryModel, I do know how to get the value of some field, but how do I get image and show it in some QML ListView? Does anyone has some experience and/or example with QQuickImageProvider, is this right way to go?

anda_skoa
14th August 2015, 09:27
Your model needs to return some string id for each image when its data() method is called by the view.
The view's delegate then needs to use that id to construct an "image://" URL.
Your image provider then needs to convert the BLOB data for that id to a QImage.

Cheers,
_

MarkoSan
14th August 2015, 09:42
Your model needs to return some string id for each image when its data() method is called by the view.
The view's delegate then needs to use that id to construct an "image://" URL.
Your image provider then needs to convert the BLOB data for that id to a QImage.

Cheers,
_

Can you please provide me some example?

anda_skoa
14th August 2015, 10:34
QVariant MyModel::data(const QModelIndex & index, int role) const
{
if (role == ImageRole) {
return QString::number(index.row());
}

/// other data
}

QImage MyModel::image(const QString &id) const
{
int row = id.toInt();

// load BLOB for image on row "row", return
}




Image {
source: "image://mysqlimageprovider/" * model.image
}




QImage MyImageProvider::requestImage(const QString &d, QSize *size, const QSize &requestedSize, bool requestedAutoTransform)
{
QImage image = myModel->image(id);
*size = image.size();
return image;
}


You could even inherit your model from both the current base class and QQuickImageProvider.

Cheers,
_

MarkoSan
14th August 2015, 11:26
QVariant MyModel::data(const QModelIndex & index, int role) const
{
if (role == ImageRole) {
return QString::number(index.row());
}

/// other data
}

QImage MyModel::image(const QString &id) const
{
int row = id.toInt();

// load BLOB for image on row "row", return
}




Image {
source: "image://mysqlimageprovider/" * model.image
}




QImage MyImageProvider::requestImage(const QString &d, QSize *size, const QSize &requestedSize, bool requestedAutoTransform)
{
QImage image = myModel->image(id);
*size = image.size();
return image;
}


You could even inherit your model from both the current base class and QQuickImageProvider.

Cheers,
_

Wow, superb idea! Will try asap!

MarkoSan
16th August 2015, 10:29
Hmm, it seems I've forgot a little about Qt models, here is my code, first header file:

#ifndef UEPEOPLEMODEL_H
#define UEPEOPLEMODEL_H

#include <QImage>
#include <QVariant>
#include <QStringList>
#include <QDebug>
#include <QHash>
#include <QByteArray>
#include <QtSql/QSqlError>
#include <QtSql/QSqlQueryModel>
#include <QtSql/QSqlRecord>
#include <QModelIndex>
#include <QQuickImageProvider>

#include "../settings/uedefaults.h"

class UePeopleModel : public QSqlQueryModel, QQuickImageProvider
{
Q_OBJECT

/*
private:
QHash<int, QByteArray> m_ueRoleNames;

void ueGenerateRoleNames();
*/

public:
UePeopleModel(QObject *parent=0);
~UePeopleModel();

QVariant data(const QModelIndex &index,
int role) const Q_DECL_OVERRIDE;
// void ueRefresh();
/*
inline QHash<int, QByteArray> roleNames() const
{ return this->m_ueRoleNames; }
*/

public:
static const int UePersonNameRole=Qt::UserRole+1;
static const int UePersonImageRole=UePersonNameRole+1;
};

#endif // UEPEOPLEMODEL_H

and its implementation:

#include "uepeoplemodel.h"

UePeopleModel::UePeopleModel(QObject* parent)
: QSqlQueryModel(parent),
QQuickImageProvider(QQmlImageProviderBase::Image,
0)
{
QSqlDatabase db;

if(!QSqlDatabase::connectionNames().contains(UePos Database::UeDatabaseConnectionNames::DATABASE_CONN ECTION_NAME_PEOPLE,
Qt::CaseInsensitive))
{
db=QSqlDatabase::addDatabase(UePosDatabase::DATABA SE_DRIVER,
UePosDatabase::UeDatabaseConnectionNames::DATABASE _CONNECTION_NAME_PEOPLE);
} // if

db.setHostName(/*this->uePosSettings()->ueDbHostname()*/UePosDatabase::UeDatabaseConnectionParameters::DAT ABASE_HOSTNAME);
db.setDatabaseName(/*this->uePosSettings()->ueDbName()*/UePosDatabase::UeDatabaseConnectionParameters::DAT ABASE_NAME);
db.setUserName(/*this->uePosSettings()->ueDbUser()*/UePosDatabase::UeDatabaseConnectionParameters::DAT ABASE_USERNAME);
db.setPassword(/*this->uePosSettings()->ueDbPassword()*/UePosDatabase::UeDatabaseConnectionParameters::DAT ABASE_PASSWORD);

if(db.open())
{
this->setQuery(UePosDatabase::UeSqlQueries::UeTablePeopl e::SQL_QUERY_GET_ALL_PEOPLE,
db);
//this->ueGenerateRoleNames();
}
else
{
qDebug() << db.lastError().text();
}
} // default constructor

UePeopleModel::~UePeopleModel()
{
} // default destructor

QVariant UePeopleModel::data(const QModelIndex &index,
int role) const
{
// QVariant value;

// if(role<Qt::UserRole)
// {
// value=QSqlQueryModel::data(index,
// role);
// }
// else
// {
// int iColumnIndex=role-Qt::UserRole-1;
// QModelIndex modelIndex=this->index(index.row(),
// iColumnIndex);

// value=QSqlQueryModel::data(modelIndex,
// Qt::DisplayRole);
// } // if

// return value;

QVariant value=QSqlQueryModel::data(index,
role);

if(value.isValid()&&role==Qt::DisplayRole)
{
switch(index.column())
{
case UePosDatabase::UeTableIndexes::UeTablePeople::INDE X_ID:
return value.toInt();

case UePosDatabase::UeTableIndexes::UeTablePeople::INDE X_NAME:
return value.toString();

case UePosDatabase::UeTableIndexes::UeTablePeople::INDE X_APPPASSWORD:
return value.toString();

case UePosDatabase::UeTableIndexes::UeTablePeople::INDE X_CARD:
return value.toString();

case UePosDatabase::UeTableIndexes::UeTablePeople::INDE X_IMAGE:
{
QImage image;

image.loadFromData(value.toByteArray());

return image;
} // case

default:
return value;
} // switch
} // if

return QVariant();
} // data

/*
void UePeopleModel::ueRefresh()
{
this->setQuery(UePosDatabase::UeSqlQueries::UeTablePeopl e::SQL_QUERY_GET_ALL_PEOPLE);
} // ueRefresh
*/

//void UePeopleModel::ueGenerateRoleNames()
//{
// //this->roleNames().clear();
// m_ueRoleNames.clear();
// for(int iIndex=0; iIndex<this->record().count(); iIndex++)
// {
///*
// this->roleNames().insert(Qt::UserRole+1+iIndex,
// this->record().fieldName(iIndex).toUtf8());
//*/
// m_ueRoleNames.insert(Qt::UserRole+1+iIndex,
// this->record().fieldName(iIndex).toUtf8());
// } // for
//} // ueGenerateRoleNames

Am I using Qt::DisplayRole in right way or should I implement custom roles? And how do I then use this data()method in qml?

anda_skoa
16th August 2015, 11:32
You can use Qt::DisplayRole, it is mapped by default to "model.display" in QML.

However, you need to keep in mind that the QtQuick views, e.g. ListView or TableView, do not deal with model columns, only rows, so you will need custom roles anyway.

I would suggest to use custom roles for all columns and not use Qt::DisplayRole other than maybe for debugging (e.g. with a QWidget based table view).

So in your data() method you first need to map the role to a column and then proceed with quering the data from the base class.

Cheers,
_

MarkoSan
17th August 2015, 11:53
Ok, I am a little lost now. I've done this:

#ifndef UEPEOPLEMODEL_H
#define UEPEOPLEMODEL_H

#include <QImage>
#include <QVariant>
#include <QStringList>
#include <QDebug>
#include <QHash>
#include <QByteArray>
#include <QtSql/QSqlError>
#include <QtSql/QSqlQueryModel>
#include <QtSql/QSqlRecord>
#include <QModelIndex>
#include <QQuickImageProvider>

#include "../settings/uedefaults.h"

class UePeopleModel : public QSqlQueryModel, QQuickImageProvider
{
Q_OBJECT

public:
UePeopleModel(QObject *parent=0);
~UePeopleModel();

QVariant data(const QModelIndex &index,
int role) const Q_DECL_OVERRIDE;
QImage image(const QString &id) const;
QImage requestImage(const QString &id,
QSize *size,
const QSize &requestedSize);

public:
static const int UePersonNameRole=Qt::UserRole+1;
static const int UePersonImageRole=UePersonNameRole+1;
};

#endif // UEPEOPLEMODEL_H
and implementation:

#include "uepeoplemodel.h"

UePeopleModel::UePeopleModel(QObject* parent)
: QSqlQueryModel(parent),
QQuickImageProvider(QQmlImageProviderBase::Image,
QQmlImageProviderBase::ForceAsynchronousImageLoadi ng)
{
QSqlDatabase db;

if(!QSqlDatabase::connectionNames().contains(UePos Database::UeDatabaseConnectionNames::DATABASE_CONN ECTION_NAME_PEOPLE,
Qt::CaseInsensitive))
{
db=QSqlDatabase::addDatabase(UePosDatabase::DATABA SE_DRIVER,
UePosDatabase::UeDatabaseConnectionNames::DATABASE _CONNECTION_NAME_PEOPLE);
} // if

db.setHostName(/*this->uePosSettings()->ueDbHostname()*/UePosDatabase::UeDatabaseConnectionParameters::DAT ABASE_HOSTNAME);
db.setDatabaseName(/*this->uePosSettings()->ueDbName()*/UePosDatabase::UeDatabaseConnectionParameters::DAT ABASE_NAME);
db.setUserName(/*this->uePosSettings()->ueDbUser()*/UePosDatabase::UeDatabaseConnectionParameters::DAT ABASE_USERNAME);
db.setPassword(/*this->uePosSettings()->ueDbPassword()*/UePosDatabase::UeDatabaseConnectionParameters::DAT ABASE_PASSWORD);

if(db.open())
{
this->setQuery(UePosDatabase::UeSqlQueries::UeTablePeopl e::SQL_QUERY_GET_ALL_PEOPLE,
db);
//this->ueGenerateRoleNames();
}
else
{
qDebug() << db.lastError().text();
}
} // default constructor

UePeopleModel::~UePeopleModel()
{
} // default destructor

QVariant UePeopleModel::data(const QModelIndex &index,
int role) const
{
QVariant value=QSqlQueryModel::data(index,
role);
if(value.isValid())
{
switch(role)
{
case UePeopleModel::UePersonNameRole:
{
return value.toString();
} // case

case UePeopleModel::UePersonImageRole:
{
return QString::number(index.row());
} // case

default:
value=QVariant();
} // switch
} // if

return value;
} // data

QImage UePeopleModel::image(const QString &id) const
{
int iRow=id.toInt();
} // image

QImage UePeopleModel::requestImage(const QString &id,
QSize *size,
const QSize &requestedSize)
{
QImage image=this->image(id);

*size = image.size();

return image;
} // requestImage
Now what?

anda_skoa
17th August 2015, 12:27
Couple of things:

- in order to be able to use this class as a QQuickImageProvider, it needs to have a "is-a" relation ship, i.e. publically inherit from QQuickImageProvider
- in data() it doesn't make sense to use the role as given to query the base class, after all these roles will most often be your custom roles and have no meaning for the base class
- you are missing the roleNames() override that returns the mapping of role names (used in QML) to role numbers (using in the C++ API).

Cheers,
_

MarkoSan
20th August 2015, 07:33
Couple of things:

- in order to be able to use this class as a QQuickImageProvider, it needs to have a "is-a" relation ship, i.e. publically inherit from QQuickImageProvider
- in data() it doesn't make sense to use the role as given to query the base class, after all these roles will most often be your custom roles and have no meaning for the base class
- you are missing the roleNames() override that returns the mapping of role names (used in QML) to role numbers (using in the C++ API).

Cheers,
_

Well, class is publicly inherited from QQuickImageProvider:
class UePeopleModel : public QSqlQueryModel, QQuickImageProvider
I've upgraded function data(), is now ok:


QVariant UePeopleModel::data(const QModelIndex &index,
int role) const
{
QVariant value=QSqlQueryModel::data(index,
role);
if(value.isValid())
{
switch(role)
{
case ueRoleName:
{
return value.toString();
} // case

case ueRoleImage:
{
return QString::number(index.row());
} // case

default:
value=QVariant();
} // switch
} // if

return value;
} // data

But what do I have to put inside method UePeopleModel::image?

Sincerely yours,
Marko

anda_skoa
20th August 2015, 09:37
Well, class is publicly inherited from QQuickImageProvider:
class UePeopleModel : public QSqlQueryModel, QQuickImageProvider

Nope. Unless your code looks different in reality, this is a private inheritance



I've upgraded function data(), is now ok:

Nope, see my last comment.


But what do I have to put inside method UePeopleModel::image?

You get the BLOB from the base class for the given row and then load it into a QImage.
If the BLOB is data in a format known to Qt, then you can probably use QImage::loadFromData().

Cheers,
_

MarkoSan
20th August 2015, 12:34
Nope. Unless your code looks different in reality, this is a private inheritance


Nope, see my last comment.


You get the BLOB from the base class for the given row and then load it into a QImage.
If the BLOB is data in a format known to Qt, then you can probably use QImage::loadFromData().

Cheers,
_
Do you have some example regarding this problem, particulary passing text and Image to qml, because I do not understand you regarding data() method?

Added after 50 minutes:

Ok, now I've upgraded method image:
QImage UePeopleModel::image(const QString &id) const
{
return QImage::fromData(this->record(id.toInt()).value(UePosDatabase::UeTableInd exes::UeTablePeople::INDEX_IMAGE).toByteArray(),
"PNG");
} // imageIs this ok now?
Secondly, the header of class is now:
class UePeopleModel : public QSqlQueryModel,
public QQuickImageProviderto get as-is relationship. Is this ok?
I still do not know what is wrong with data() method:
QVariant UePeopleModel::data(const QModelIndex &index,
int role) const
{
QVariant value=QSqlQueryModel::data(index,
role);
if(value.isValid())
{
switch(role)
{
case ueRoleName:
{
return value.toString();
} // case

case ueRoleImage:
{
return QString::number(index.row());
} // case

default:
value=QVariant();
} // switch
} // if

return value;
} // dataCan someone please help me?!

anda_skoa
20th August 2015, 12:41
You data() method is still wrong because you do not handle the role correctly.

As I wrote before, the roles you have specified mean nothing to the data() method of the base class.
So calling the base implementation with the arguments you are getting is not going to work at all.

In comment #7 I explained that you need to map the roles used by you to the columns used by the base class.
A QtQuick view will always call with column 0 and one of the roles you have specified and used by name.
The base class implementation expects column value > 0 for fields other than the first and one of the Qt standard item roles.

So you first need to switch() on the role to determine the column of the data you are supposed to deliver.
Then call the base class' data() with that row/column and e.g. Qt::DisplayRole.

Unless of course the initial role is the image role, in which case you return the identifier that you later on can use to provide the image.

Cheers,
_

MarkoSan
21st August 2015, 06:55
You data() method is still wrong because you do not handle the role correctly.

As I wrote before, the roles you have specified mean nothing to the data() method of the base class.
So calling the base implementation with the arguments you are getting is not going to work at all.

In comment #7 I explained that you need to map the roles used by you to the columns used by the base class.
A QtQuick view will always call with column 0 and one of the roles you have specified and used by name.
The base class implementation expects column value > 0 for fields other than the first and one of the Qt standard item roles.

So you first need to switch() on the role to determine the column of the data you are supposed to deliver.
Then call the base class' data() with that row/column and e.g. Qt::DisplayRole.

Unless of course the initial role is the image role, in which case you return the identifier that you later on can use to provide the image.

Cheers,
_

Ok, will try, but where do I get identifier that I can later use to provide the image? And am I getting closer with data()method:

QVariant UePeopleModel::data(const QModelIndex &index,
int role) const
{
QVariant value=QVariant();

switch(role)
{
case UePeopleModel::ueRoleImage:
{
value=this->record(index.row()).value(UePosDatabase::UeTableIn dexes::UeTablePeople::INDEX_IMAGE).toString();
} // case

case UePeopleModel::ueRoleName:
{
value=this->record(index.row()).value(UePosDatabase::UeTableIn dexes::UeTablePeople::INDEX_NAME).toString();
} // case

default:
return value;
} // switch

return value;
} // data

anda_skoa
21st August 2015, 10:36
Ok, will try, but where do I get identifier that I can later use to provide the image?

I am not sure what you mean, we had that already several comments ago.
The simplest way is to just encode the row as a string.




And am I getting closer with data()method:

Yes, that looks better.
Obviously the handling of the image role is wrong, but the name looks fine.
You wouldn't need to call toString() though.

Cheers,
_

MarkoSan
21st August 2015, 12:08
Ok, now I am getting QML runtime errros:

qrc:/gui/delegates/UePeopleItemDelegate.qml:55:9: QML Image: Cannot open: qrc:/gui/delegates/nan
qrc:/gui/delegates/UePeopleItemDelegate.qml:55:9: QML Image: Cannot open: qrc:/gui/delegates/nan
qrc:/gui/delegates/UePeopleItemDelegate.qml:55:9: QML Image: Cannot open: qrc:/gui/delegates/nan
qrc:/gui/delegates/UePeopleItemDelegate.qml:55:9: QML Image: Cannot open: qrc:/gui/delegates/nan
qrc:/gui/delegates/UePeopleItemDelegate.qml:55:9: QML Image: Cannot open: qrc:/gui/delegates/nan
Here is QML ListView error is generated in:

ListView
{
id: uePeopleListView
boundsBehavior: Flickable.DragAndOvershootBounds
snapMode: ListView.SnapToItem
highlightRangeMode: ListView.ApplyRange
anchors.right: parent.right
anchors.rightMargin: 0
anchors.bottom: parent.top
anchors.bottomMargin: -128
anchors.left: parent.left
anchors.leftMargin: 0
anchors.top: parent.top
anchors.topMargin: 0
orientation: ListView.Horizontal
flickableDirection: Flickable.HorizontalFlick
antialiasing: true
spacing: 16
delegate: UePeopleItemDelegate
{
id: uePersonDelegate

ueParamPersonImage: "//image://UePeopleModel/"*uePeopleModel.ueImage
ueParamPersonName: "Test"//UePersonNameRole
}
model: uePeopleModel
}and here is ListView's delegate:
import QtQuick 2.3

Item
{
id: uePeopleItemDelegate

property string ueParamPersonImage
property string ueParamPersonName

width: 256
height: 256
antialiasing: true

clip: true

Rectangle
{
id: ueRectangleMain
color: "#000000"

radius: 16
anchors.fill: parent
antialiasing: true
border.color: "#ffffff"
border.width: 4
clip: true
//opacity: 0.7

Grid
{
antialiasing: true
anchors.rightMargin: 12
anchors.leftMargin: 12
anchors.bottomMargin: 12
anchors.topMargin: 12
anchors.fill: parent
spacing: 4
rows: 2
columns: 1
}

Row
{
id: ueRowImage
anchors.rightMargin: 12
anchors.leftMargin: 12
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.left: parent.left
anchors.top: parent.top
anchors.bottomMargin: 48
anchors.topMargin: 12
}

Image
{
id: uePersonImage
x: 12
y: 12
width: 232
height: 196
antialiasing: true
fillMode: Image.PreserveAspectFit
source: ueParamPersonImage
}

Column
{
id: ueColumnPeopleInfo
x: 12
y: 214
width: 232
height: 30
spacing: 0
}

Text
{
id: ueTextPersonName
x: 12
y: 214
width: 232
height: 30
color: "#ffffff"
text: ueParamPersonName
font.bold: true
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
font.pixelSize: 16
}
}
}The number of erros is consistent with number of records that I wish to display. What is now wrong?!

anda_skoa
21st August 2015, 13:20
The image URL must be something like
"image://providername/imageid"

providername is what you register your image provider with, imageid is what your model returns for the image role in data().



delegate: UePeopleItemDelegate
{
id: uePersonDelegate

ueParamPersonImage: "image://UePeopleModel/" + model.uePeopleModel.ueImage
}

Assuming that your roleNames() method has a mapping of "ueImage" to the numerical UePeopleModel::ueRoleImage

From the log output I would guess that you are not using an image URL, thus the value returned from the model is considered a relative filename, your delegate is inside the Qt resource system under "delegates" and the value you pass back from then model is "nan".

Cheers,
_

MarkoSan
21st August 2015, 13:34
The image URL must be something like
"image://providername/imageid"

providername is what you register your image provider with, imageid is what your model returns for the image role in data().



delegate: UePeopleItemDelegate
{
id: uePersonDelegate

ueParamPersonImage: "image://UePeopleModel/" + model.uePeopleModel.ueImage
}

Assuming that your roleNames() method has a mapping of "ueImage" to the numerical UePeopleModel::ueRoleImage

From the log output I would guess that you are not using an image URL, thus the value returned from the model is considered a relative filename, your delegate is inside the Qt resource system under "delegates" and the value you pass back from then model is "nan".

Cheers,
_
I've changed delegate params to:


ListView
{
id: uePeopleListView
boundsBehavior: Flickable.DragAndOvershootBounds
snapMode: ListView.SnapToItem
highlightRangeMode: ListView.ApplyRange
anchors.right: parent.right
anchors.rightMargin: 0
anchors.bottom: parent.top
anchors.bottomMargin: -128
anchors.left: parent.left
anchors.leftMargin: 0
anchors.top: parent.top
anchors.topMargin: 0
orientation: ListView.Horizontal
flickableDirection: Flickable.HorizontalFlick
antialiasing: true
spacing: 16
delegate: UePeopleItemDelegate
{
id: uePersonDelegate

ueParamPersonImage: "image://UePeopleModel/" + model.uePeopleModel.ueRoleImage
ueParamPersonName: model.uePeopleModel.ueRoleName
}
model: uePeopleModel
}

Now I am getting following errors:


qrc:/main.qml:52: TypeError: Cannot read property 'UePersonNameRole' of undefined
qrc:/main.qml:51: TypeError: Cannot read property 'ueRoleImage' of undefined
qrc:/main.qml:52: TypeError: Cannot read property 'UePersonNameRole' of undefined
qrc:/main.qml:51: TypeError: Cannot read property 'ueRoleImage' of undefined
qrc:/main.qml:52: TypeError: Cannot read property 'UePersonNameRole' of undefined
qrc:/main.qml:51: TypeError: Cannot read property 'ueRoleImage' of undefined
qrc:/main.qml:52: TypeError: Cannot read property 'UePersonNameRole' of undefined
qrc:/main.qml:51: TypeError: Cannot read property 'ueRoleImage' of undefined
qrc:/main.qml:52: TypeError: Cannot read property 'UePersonNameRole' of undefined
qrc:/main.qml:51: TypeError: Cannot read property 'ueRoleImage' of undefined

Here is again UePeopleModel class header, which are ueRoleName and ueRoleImage declared in:


#ifndef UEPEOPLEMODEL_H
#define UEPEOPLEMODEL_H

#include <QImage>
#include <QVariant>
#include <QStringList>
#include <QDebug>
#include <QHash>
#include <QByteArray>
#include <QtSql/QSqlError>
#include <QtSql/QSqlQueryModel>
#include <QtSql/QSqlRecord>
#include <QModelIndex>
#include <QQuickImageProvider>
#include <QByteArray>
#include <QSqlRecord>
#include <QDebug>

#include "../settings/uedefaults.h"
#include "../settings/uetypes.h"

class UePeopleModel : public QSqlQueryModel,
public QQuickImageProvider
{
Q_OBJECT

public:
UePeopleModel(QObject *parent=0);
~UePeopleModel();

QVariant data(const QModelIndex &index,
int role) const Q_DECL_OVERRIDE;
QImage ueImage(const QString &id) const;
QImage requestImage(const QString &id,
QSize *size,
const QSize &requestedSize);
UeTypeRoles roleNames() const;

public:
static const int ueRoleName=Qt::UserRole+1;
static const int ueRoleImage=Qt::UserRole+2;
};

#endif // UEPEOPLEMODEL_H

and here is roleNames()method:


UeTypeRoles UePeopleModel::roleNames() const
{
UeTypeRoles roles;
const int iRoleName=UePeopleModel::ueRoleName;
const int iRoleImage=UePeopleModel::ueRoleImage;

roles.insert(iRoleName,
"ueRoleName");
roles.insert(iRoleImage,
"ueRoleImage");

return roles;
} // roleNames
What am I still missing? I am getting furious!!!!

anda_skoa
21st August 2015, 14:53
What is UeTypeRoles?

Why is there "uePeopleModel" between "model" and "ueRoleImage" in QML?

Cheers,
_

MarkoSan
24th August 2015, 06:45
What is UeTypeRoles?

Why is there "uePeopleModel" between "model" and "ueRoleImage" in QML?

Cheers,
_

I've corrected QML model statement into:


ListView
{
id: uePeopleListView
boundsBehavior: Flickable.DragAndOvershootBounds
snapMode: ListView.SnapToItem
highlightRangeMode: ListView.ApplyRange
anchors.right: parent.right
anchors.rightMargin: 0
anchors.bottom: parent.top
anchors.bottomMargin: -128
anchors.left: parent.left
anchors.leftMargin: 0
anchors.top: parent.top
anchors.topMargin: 0
orientation: ListView.Horizontal
flickableDirection: Flickable.HorizontalFlick
antialiasing: true
spacing: 16
delegate: UePeopleItemDelegate
{
id: uePersonDelegate

ueParamPersonImage: "image://uePeopleModel/"+model.ueRoleImage
ueParamPersonName: model.ueRoleName
}
model: uePeopleModel
}

and UeTypeRoles is declared in uetypes.h as:


#ifndef UETYPES
#define UETYPES

#include <QHash>
#include <QByteArray>

typedef QHash<int, QByteArray> UeTypeRoles;

#endif // UETYPES

and now am I getting following runtime QML errors:


qrc:/gui/delegates/UePeopleItemDelegate.qml:55:9: QML Image: Invalid image provider: image://uepeoplemodel/undefined
qrc:/main.qml:52:32: Unable to assign [undefined] to QString
qrc:/gui/delegates/UePeopleItemDelegate.qml:55:9: QML Image: Invalid image provider: image://uepeoplemodel/undefined
qrc:/main.qml:52:32: Unable to assign [undefined] to QString
qrc:/gui/delegates/UePeopleItemDelegate.qml:55:9: QML Image: Invalid image provider: image://uepeoplemodel/undefined
qrc:/main.qml:52:32: Unable to assign [undefined] to QString
qrc:/gui/delegates/UePeopleItemDelegate.qml:55:9: QML Image: Invalid image provider: image://uepeoplemodel/undefined
qrc:/main.qml:52:32: Unable to assign [undefined] to QString
qrc:/gui/delegates/UePeopleItemDelegate.qml:55:9: QML Image: Invalid image provider: image://uepeoplemodel/undefined
qrc:/main.qml:52:32: Unable to assign [undefined] to QString

Now, I've done step-by-step debugging of method data(), the database IS open and connection to it IS established (checked 5 times), and I've addes some debug code int data() method:


QVariant UePeopleModel::data(const QModelIndex &index,
int role) const
{
QVariant value=QVariant();

switch(role)
{
case ueRoleImage:
{
for(int iIndex=0; iIndex<this->record().count(); iIndex++)
{
qDebug() << this->record().fieldName(iIndex) << " ";
qDebug() << this->record().value(iIndex) << " ";
} // for
//value=this->record(index.row()).value(UePosDatabase::UeTableIn dexes::UeTablePeople::INDEX_IMAGE).toByteArray();
value=this->record(index.row()).value("IMAGE").toByteArray();
int i=0;
} // case

case ueRoleName:
{
value=this->record(index.row()).value(UePosDatabase::UeTableIn dexes::UeTablePeople::INDEX_NAME).toString();
int i=0;
} // case

default:
return value;
} // switch

qDebug() << value;

return value;
} // data

Here are outputs if qDebug():


"ID"
QVariant(QString, "")
"NAME"
QVariant(QString, "")
"APPPASSWORD"
QVariant(QString, "")
"CARD"
QVariant(QString, "")
"ROLE"
QVariant(QString, "")
"VISIBLE"
QVariant(QString, "")
"IMAGE"
QVariant(QByteArray, "")

Why am I getting empty values from db?!
P.S.: int i=0; was added for debug purpose, to set breakpoint in this line.

anda_skoa
25th August 2015, 08:10
I am not sure what you are trying to do right now.

Why all that code in the case for ueImageRole?
Why not return the image Id as previously discussed?
Are you attempting a different approach?

Cheers,
_

MarkoSan
3rd September 2015, 10:04
I've finally managed to get rid of the bug. Here is updated code:


#ifndef UEPEOPLEMODEL_H
#define UEPEOPLEMODEL_H

#include <QImage>
#include <QVariant>
#include <QStringList>
#include <QDebug>
#include <QHash>
#include <QByteArray>
#include <QSqlError>
#include <QSqlQueryModel>
#include <QSqlRecord>
#include <QModelIndex>
#include <QQuickImageProvider>
#include <QByteArray>
#include <QSqlRecord>
#include <QDebug>
#include <QSqlQuery>

#include "../settings/uedefaults.h"
#include "../settings/uetypes.h"

class UePeopleModel : public QSqlQueryModel,
public QQuickImageProvider
{
Q_OBJECT

private:
QSqlDatabase m_ueDb;

private:
QSqlDatabase ueDatabase() const
{ return this->m_ueDb; }
void ueSetDatabase(const QSqlDatabase& database)
{ this->m_ueDb=database; }
QImage ueImage(const QString& id) const;

public:
UePeopleModel(QObject *parent=0);
~UePeopleModel();

QVariant data(const QModelIndex &index,
int role) const Q_DECL_OVERRIDE;
QImage requestImage(const QString &id,
QSize *size,
const QSize &requestedSize);
UeTypeRoles roleNames() const;

public:
static const int ueRoleName=Qt::UserRole+1;
static const int ueRoleImage=Qt::UserRole+2;
static const int ueRolePassword=Qt::UserRole+3;
};

#endif // UEPEOPLEMODEL_H

and implementation:


#include "uepeoplemodel.h"

UePeopleModel::UePeopleModel(QObject* parent)
: QSqlQueryModel(parent),
QQuickImageProvider(QQmlImageProviderBase::Image,
QQmlImageProviderBase::ForceAsynchronousImageLoadi ng)
{
if(!QSqlDatabase::connectionNames().contains(UePos Database::UeDatabaseConnectionNames::DATABASE_CONN ECTION_NAME_PEOPLE,
Qt::CaseInsensitive))
{
this->ueSetDatabase(QSqlDatabase::addDatabase(UePosDatab ase::DATABASE_DRIVER,
UePosDatabase::UeDatabaseConnectionNames::DATABASE _CONNECTION_NAME_PEOPLE));
} // if

this->ueDatabase().setHostName(/*this->uePosSettings()->ueDbHostname()*/UePosDatabase::UeDatabaseConnectionParameters::DAT ABASE_HOSTNAME);
this->ueDatabase().setDatabaseName(/*this->uePosSettings()->ueDbName()*/UePosDatabase::UeDatabaseConnectionParameters::DAT ABASE_NAME);
this->ueDatabase().setUserName(/*this->uePosSettings()->ueDbUser()*/UePosDatabase::UeDatabaseConnectionParameters::DAT ABASE_USERNAME);
this->ueDatabase().setPassword(/*this->uePosSettings()->ueDbPassword()*/UePosDatabase::UeDatabaseConnectionParameters::DAT ABASE_PASSWORD);

if(this->ueDatabase().open())
{
this->setQuery(UePosDatabase::UeSqlQueries::UeTablePeopl e::SQL_QUERY_GET_ALL_PEOPLE,
this->ueDatabase());
if(this->lastError().isValid())
qDebug() << this->lastError();
}
else
{
qDebug() << this->ueDatabase().lastError();
}

qDebug() << this->ueDatabase().connectionNames();
} // default constructor

UePeopleModel::~UePeopleModel()
{
} // default destructor

QVariant UePeopleModel::data(const QModelIndex &index,
int role) const
{
switch(role)
{
case ueRoleImage:
{
//qDebug() << this->record(index.row()).value(UePosDatabase::UeTableIn dexes::UeTablePeople::INDEX_IMAGE).toByteArray().s ize();

return this->record(index.row()).value(UePosDatabase::UeTableIn dexes::UeTablePeople::INDEX_IMAGE).toByteArray();
} break; // case

case ueRoleName:
{
//qDebug() << this->record(index.row()).value(UePosDatabase::UeTableIn dexes::UeTablePeople::INDEX_NAME).toString();

return this->record(index.row()).value(UePosDatabase::UeTableIn dexes::UeTablePeople::INDEX_NAME).toString();
} break; // case

case ueRolePassword:
{
//qDebug() << this->record(index.row()).value(UePosDatabase::UeTableIn dexes::UeTablePeople::INDEX_APPPASSWORD).toString( );

return this->record(index.row()).value(UePosDatabase::UeTableIn dexes::UeTablePeople::INDEX_APPPASSWORD).toString( );
} break; // case

default:
{
return QSqlQueryModel::data(index,
role);
} break; // default
} // switch

return QVariant();
} // data

QImage UePeopleModel::ueImage(const QString &id) const
{
qDebug() << this->record(id.toInt()).value(UePosDatabase::UeTableInd exes::UeTablePeople::INDEX_IMAGE).toByteArray().si ze();

return QImage::fromData(this->record(id.toInt()).value(UePosDatabase::UeTableInd exes::UeTablePeople::INDEX_IMAGE).toByteArray(),
"PNG");
} // image

QImage UePeopleModel::requestImage(const QString &id,
QSize *size,
const QSize &requestedSize)
{
if(size)
{
*size=QSize(UeDefaults::UeGraphics::PEOPLE_IMAGE_W IDTH,
UeDefaults::UeGraphics::PEOPLE_IMAGE_HEIGHT);
} // if

QImage image(requestedSize.width()>0?requestedSize.width():UeDefaults::UeGraphics::PE OPLE_IMAGE_WIDTH,
requestedSize.height()>0?requestedSize.height():UeDefaults::UeGraphics::P EOPLE_IMAGE_HEIGHT,
QImage::Format_ARGB32);

image=this->ueImage(id);
image=image.scaled(UeDefaults::UeGraphics::PEOPLE_ IMAGE_WIDTH,
UeDefaults::UeGraphics::PEOPLE_IMAGE_HEIGHT,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);

return image;
} // requestImage

UeTypeRoles UePeopleModel::roleNames() const
{
UeTypeRoles roles;

const int iRoleName=UePeopleModel::ueRoleName;
const int iRoleImage=UePeopleModel::ueRoleImage;
const int iRolePassword=UePeopleModel::ueRolePassword;

roles.insert(iRoleName,
"ueRoleName");
roles.insert(iRoleImage,
"ueRoleImage");
roles.insert(iRolePassword,
"ueRolePassword");

return roles;
} // roleNames

In the UePeopleModel::data() method, break statements were placed wrong and inside default statement now I do master's class

return QSqlQueryModel::data(index,
role);

But now I have a new problem:
My UePeopleModel fetches users from database (for now: Name, Image and Password). Names and password are fetch ok, but qml shows for every user my Image (which is first fetched. In UePeopleModel::data() method images are fetched correctly (I've done qDebug() of image sizes and compared with images in database). Here is screenshot:
11347
Why is QML showing only first image fetched from db?

anda_skoa
3rd September 2015, 11:11
Your data() method does not return an id for the image, but the image data itself.
I've already asked you before why you changed that.

If you don't return an id that contains the row number, then the id you get in image() is also not a row number.
So id.toInt() returns 0.

Also in your requestImage() method, why do you set "size" to a fixed value but then used requestedSize?
Set "size" at the end, when you actually know which size you returned.

Also, why create an image an immediately discard it?
Creating and deleting an image buffer are not cheap operations (heap memory allocations).

So

1) Fix data() so that it returns an id for the image again (and maybe thing about why you changed to to something that does not work without any need of doing so)
2) in requestImage() set "size" at the end, from the size() vaule of the "image"
3) Do not create an empty image that you then don't need (lines 93-95), the image() method returns the image that you need.

Cheers,
_

MarkoSan
3rd September 2015, 11:32
Ok, is this better (I think it is because now works! ;))?
UePeopleModel implementation:


#include "uepeoplemodel.h"

UePeopleModel::UePeopleModel(QObject* parent)
: QSqlQueryModel(parent),
QQuickImageProvider(QQmlImageProviderBase::Image,
QQmlImageProviderBase::ForceAsynchronousImageLoadi ng)
{
if(!QSqlDatabase::connectionNames().contains(UePos Database::UeDatabaseConnectionNames::DATABASE_CONN ECTION_NAME_PEOPLE,
Qt::CaseInsensitive))
{
this->ueSetDatabase(QSqlDatabase::addDatabase(UePosDatab ase::DATABASE_DRIVER,
UePosDatabase::UeDatabaseConnectionNames::DATABASE _CONNECTION_NAME_PEOPLE));
} // if

this->ueDatabase().setHostName(/*this->uePosSettings()->ueDbHostname()*/UePosDatabase::UeDatabaseConnectionParameters::DAT ABASE_HOSTNAME);
this->ueDatabase().setDatabaseName(/*this->uePosSettings()->ueDbName()*/UePosDatabase::UeDatabaseConnectionParameters::DAT ABASE_NAME);
this->ueDatabase().setUserName(/*this->uePosSettings()->ueDbUser()*/UePosDatabase::UeDatabaseConnectionParameters::DAT ABASE_USERNAME);
this->ueDatabase().setPassword(/*this->uePosSettings()->ueDbPassword()*/UePosDatabase::UeDatabaseConnectionParameters::DAT ABASE_PASSWORD);

if(this->ueDatabase().open())
{
this->setQuery(UePosDatabase::UeSqlQueries::UeTablePeopl e::SQL_QUERY_GET_ALL_PEOPLE,
this->ueDatabase());
if(this->lastError().isValid())
qDebug() << this->lastError();
}
else
{
qDebug() << this->ueDatabase().lastError();
}

// qDebug() << this->ueDatabase().connectionNames();
} // default constructor

UePeopleModel::~UePeopleModel()
{
} // default destructor

QVariant UePeopleModel::data(const QModelIndex &index,
int role) const
{
switch(role)
{
case ueRoleImage:
{
//return this->record(index.row()).value(UePosDatabase::UeTableIn dexes::UeTablePeople::INDEX_IMAGE).toByteArray();
return QString::number(index.row());
} break; // case

case ueRoleName:
{
return this->record(index.row()).value(UePosDatabase::UeTableIn dexes::UeTablePeople::INDEX_NAME).toString();
} break; // case

case ueRolePassword:
{
return this->record(index.row()).value(UePosDatabase::UeTableIn dexes::UeTablePeople::INDEX_APPPASSWORD).toString( );
} break; // case

default:
{
return QSqlQueryModel::data(index,
role);
} break; // default
} // switch

return QVariant();
} // data

QImage UePeopleModel::ueImage(const QString &id) const
{
return QImage::fromData(this->record(id.toInt()).value(UePosDatabase::UeTableInd exes::UeTablePeople::INDEX_IMAGE).toByteArray(),
"PNG").scaled(UeDefaults::UeGraphics::PEOPLE_IMAGE_WIDT H,
UeDefaults::UeGraphics::PEOPLE_IMAGE_HEIGHT,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
} // image

QImage UePeopleModel::requestImage(const QString &id,
QSize *size,
const QSize &requestedSize)
{
Q_UNUSED(requestedSize);

if(size)
{
*size=QSize(UeDefaults::UeGraphics::PEOPLE_IMAGE_W IDTH,
UeDefaults::UeGraphics::PEOPLE_IMAGE_HEIGHT);
} // if

return this->ueImage(id);
} // requestImage

UeTypeRoles UePeopleModel::roleNames() const
{
UeTypeRoles roles;

const int iRoleName=UePeopleModel::ueRoleName;
const int iRoleImage=UePeopleModel::ueRoleImage;
const int iRolePassword=UePeopleModel::ueRolePassword;

roles.insert(iRoleName,
"ueRoleName");
roles.insert(iRoleImage,
"ueRoleImage");
roles.insert(iRolePassword,
"ueRolePassword");

return roles;
} // roleNames

Now, I still do not understand something:
In method QImage UePeopleModel::requestImage(const QString &id, QSize *size, const QSize &requestedSize):


QImage UePeopleModel::ueImage(const QString &id) const
{
return QImage::fromData(this->record(id.toInt()).value(UePosDatabase::UeTableInd exes::UeTablePeople::INDEX_IMAGE).toByteArray(),
"PNG").scaled(UeDefaults::UeGraphics::PEOPLE_IMAGE_WIDT H,
UeDefaults::UeGraphics::PEOPLE_IMAGE_HEIGHT,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
} // image

QImage UePeopleModel::requestImage(const QString &id,
QSize *size,
const QSize &requestedSize)
{
Q_UNUSED(requestedSize);

if(size)
{
*size=QSize(UeDefaults::UeGraphics::PEOPLE_IMAGE_W IDTH,
UeDefaults::UeGraphics::PEOPLE_IMAGE_HEIGHT);
} // if

return this->ueImage(id);
} // requestImage

How do I use requestedSize in QImage UePeopleModel::requestImage(const QString &id, QSize *size, const QSize &requestedSize) since I already get now scaled image from method QImage UePeopleModel::ueImage(const QString &id) const?

anda_skoa
3rd September 2015, 13:36
Ok, is this better (I think it is because now works! ;))?

Yes, data() looks good now.



Now, I still do not understand something:
In method QImage UePeopleModel::requestImage(const QString &id, QSize *size, const QSize &requestedSize):
How do I use requestedSize in QImage UePeopleModel::requestImage(const QString &id, QSize *size, const QSize &requestedSize) since I already get now scaled image from method QImage UePeopleModel::ueImage(const QString &id) const?

Just remove the image() method and do its code inside requestImage().

The initial suggestion for image() was based on the idea of a separate image provider object.
Since your class is now a model and the image provider in one, this is no longer necessary.

Alternatively, pass the requestedSize as an additional argument to image().

Cheers,
_