What is the right way to color-code rows in a QTableView?
I'm developing a spreadsheet application that should color-code its rows based on a specific value set in one of the columns. I use QSqlRelationalTableModel and QSqlRelationalDelegate; because, the value that should determine the color is a foreign key.
Why can't it be as simple as the following? Any ideas?
model->setData( model->index( index.row(), index.column() ),
QBrush(Qt::red),
Qt::BackgroundRole );
You should overwrite the data function of QSqlRelationalTableModel and when you get the Qt :: BackgroundRole role to filter according to your case and return the appropriate QBrush, in the following example filter by the foreign field and check that it is equal to Lima:
Example:
sqlrelationaltablemodel.h
#ifndef SQLRELATIONALTABLEMODEL_H
#define SQLRELATIONALTABLEMODEL_H
#include <QSqlRelationalTableModel>
class SqlRelationalTableModel : public QSqlRelationalTableModel
{
Q_OBJECT
public:
SqlRelationalTableModel(QObject * parent = 0, QSqlDatabase db = QSqlDatabase());
QVariant data(const QModelIndex & item, int role = Qt::DisplayRole) const;
};
#endif // SQLRELATIONALTABLEMODEL_H
sqlrelationaltablemodel.cpp
#include "sqlrelationaltablemodel.h"
#include <QBrush>
SqlRelationalTableModel::SqlRelationalTableModel(QObject *parent, QSqlDatabase db)
:QSqlRelationalTableModel(parent, db)
{
}
QVariant SqlRelationalTableModel::data(const QModelIndex &item, int role) const
{
if(role == Qt::BackgroundRole)
if(QSqlRelationalTableModel::data(index(item.row(), 2), Qt::DisplayRole).toString().trimmed() == "Lima")
return QVariant(QBrush(Qt::red));
return QSqlRelationalTableModel::data(item, role);
}
Output:
The complete example can be found here.
I'm coming from c++ and I'm having difficulties making my classes work the way I want them to. The program I am building is rather simple. The user creates an album or two and can fill that album with cards. Those albums are created dynamically on run time.
Here is my initial class diagram for the back-end c++ stuff:
class diagram
And here is how the UI looks like:
QML UI
The crux of the problem:
A set of changes to the classes are necessary to make them available to QML.
Inheritance: Classes need to inherit from QObject or QAbstractListModel depending on their role.
Containers: Dynamically instantiated objects within an object, through pointers or not, need to be placed in a QList or something similar.
Root context property: The model needs to be registered so that it can be made available in a Listview for example.
I have had some success setting an instance of an Album class, with some changes made to it, as a root context property. The model worked correctly and the cards in the album displayed correctly in a ListView.
The problem here is that I need to step out of the picture another level, I should set a class that contains the Albums as the root context property and let the user create, and add, Albums to it during run time. I will also need to also display the albums and the cards inside them through different views.
I am wondering if I should implement a table/tree data structure for the albums and the cards and somehow pass the columns and rows to the models or If there is a tidier approach that I am not aware of.
Here are my classes so far:
album.h
#ifndef ALBUM_H
#define ALBUM_H
#include <QObject>
#include <QString>
#include <QAbstractListModel>
#include <QQmlListProperty>
#include "card.h"
class Album : public QAbstractListModel
{
Q_OBJECT
public:
enum AlbumRoles {
CardIDRole = Qt::UserRole + 1,
NameRole,
ImageURLRole,
SubtypeRole,
SupertypeRole,
NumberRole,
ArtistRole,
RarityRole,
SeriesRole,
SetRole,
SetCodeRole,
ConditionRole,
StatusRole
};
Album(QObject *parent = 0);
QString name() const;
void addCard(const Card &card);
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<Card> m_cards;
QString m_name;
};
album.cpp
#include "album.h"
Album::Album(QObject *parent)
: QAbstractListModel(parent)
{
}
QString Album::name() const
{
return m_name;
}
void Album::addCard(const Card &card)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_cards << card;
endInsertRows();
}
int Album::rowCount(const QModelIndex & parent) const {
Q_UNUSED(parent);
return m_cards.count();
}
QVariant Album::data(const QModelIndex & index, int role) const {
if (index.row() < 0 || index.row() >= m_cards.count())
return QVariant();
const Card &card = m_cards[index.row()];
if (role == CardIDRole)
return card.cardID();
else if (role == NameRole)
return card.name();
else if (role == ImageURLRole)
return card.imageURL();
else if (role == SubtypeRole)
return card.subtype();
else if (role == SupertypeRole)
return card.supertype();
else if (role == NumberRole)
return card.number();
else if (role == ArtistRole)
return card.artist();
else if (role == RarityRole)
return card.rarity();
else if (role == SeriesRole)
return card.series();
else if (role == SetRole)
return card.set();
else if (role == SetCodeRole)
return card.setCode();
else if (role == ConditionRole)
return card.condition();
else if (role == StatusRole)
return card.status();
return QVariant();
}
QHash<int, QByteArray> Album::roleNames() const {
QHash<int, QByteArray> roles;
roles[CardIDRole] = "cardID";
roles[NameRole] = "name";
roles[ImageURLRole] = "imageURL";
roles[SubtypeRole] = "subtype";
roles[SupertypeRole] = "supertype";
roles[NumberRole] = "number";
roles[ArtistRole] = "artist";
roles[RarityRole] = "rarity";
roles[SeriesRole] = "series";
roles[SetRole] = "set";
roles[SetCodeRole] = "setCode";
roles[ConditionRole] = "condition";
roles[StatusRole] = "status";
return roles;
}
card.h
#ifndef CARD_H
#define CARD_H
#include <QString>
class Card
{
public:
// Standard Qt constructor with parent for memory management
Card(const QString &cardID, const QString &name, const QString &imageURL, const QString &subtype, const QString &supertype, const int &number, const QString &artist, const QString &rarity, const QString &series, const QString &set, const QString &setCode, const QString &condition, const QString &status);
QString cardID() const;
QString name() const;
QString imageURL() const;
QString subtype() const;
QString supertype() const;
int number() const;
QString artist() const;
QString rarity() const;
QString series() const;
QString set() const;
QString setCode() const;
QString condition() const;
QString status() const;
private:
// private members
QString m_cardID;
QString m_name;
QString m_imageURL;
QString m_subtype;
QString m_supertype;
int m_number;
QString m_artist;
QString m_rarity;
QString m_series;
QString m_set;
QString m_setCode;
QString m_condition;
QString m_status;
};
#endif //CARD_H
card.cpp
#include "card.h"
// Standard Qt constructor with parent for memory management
Card::Card(const QString &cardID, const QString &name, const QString &imageURL, const QString &subtype, const QString &supertype, const int &number, const QString &artist, const QString &rarity, const QString &series, const QString &set, const QString &setCode, const QString &condition, const QString &status): m_cardID(cardID), m_name(name), m_imageURL(imageURL), m_subtype(subtype), m_supertype(supertype), m_number(number), m_artist(artist), m_rarity(rarity), m_series(series), m_set(set), m_setCode(setCode), m_condition(condition), m_status(status)
{
}
QString Card::cardID() const
{
return m_cardID;
}
QString Card::name() const
{
return m_name;
}
QString Card::imageURL() const
{
return m_imageURL;
}
QString Card::subtype() const
{
return m_subtype;
}
QString Card::supertype() const
{
return m_supertype;
}
int Card::number() const
{
return m_number;
}
QString Card::artist() const
{
return m_artist;
}
QString Card::rarity() const
{
return m_rarity;
}
QString Card::series() const
{
return m_series;
}
QString Card::set() const
{
return m_set;
}
QString Card::setCode() const
{
return m_setCode;
}
QString Card::condition() const
{
return m_condition;
}
QString Card::status() const
{
return m_status;
}
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include "app.h"
#include "album.h"
#include "card.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
qmlRegisterType<Album>("Classes.PokemonApp", 1, 0, "Album");
qmlRegisterType<Card>("Classes.PokemonApp", 1, 0, "Card");
App pokeApp;
Album myAlbumModel;
Card cardOne("xy7-2","Gloom","http://s3.amazonaws.com/pokemontcg/xy7/2.png","Stage 1","Pokémon",2,"Masakazu Fukuda","Uncommon","XY","Ancient Origins","xy7","Mint","In my collection");
Card cardTwo("xy7-7","Sceptile-EX","https://s3.amazonaws.com/pokemontcg/xy7/7.png","EX","Pokémon",7,"Eske Yoshinob","Rare Holo EX","XY","Ancient Origins","xy7","Used","Duplicate");
myAlbumModel.addCard(cardOne);
myAlbumModel.addCard(cardTwo);
pokeApp.addAlbum(myAlbumModel);
QQmlApplicationEngine engine;
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
QQmlContext *ctxt = engine.rootContext();
ctxt->setContextProperty("pokeApp", &pokeApp);
return app.exec();
}
CardsView.qml
import QtQuick 2.0
import Classes.PokemonApp 1.0
// Cards Deligate
ListView {
width: 200; height: 250
model: myAlbumModel
delegate: Text { text: "Card:"
+ "\n" + "ID: " + cardID
+ "\n" + "Name: " + name
+ "\n" + "Image URL: " + imageURL
+ "\n" + "Subtype: " + subtype
+ "\n" + "Supertype: " + supertype
+ "\n" + "Number: " + number
+ "\n" + "Artist: " + artist
+ "\n" + "Rarity: " + rarity
+ "\n" + "Series: " + series
+ "\n" + "Set: " + set
+ "\n" + "Set code: " + setCode
+ "\n" + "Condition: " + condition
+ "\n" + "Status: " + status }
}
So to be clear, this is what I need help with:
Give the user the ability to create albums during run time, let the user add cards to albums and have models display the albums and the cards in them in different views.
Assuming that your App class has a list of Album objects, accessible as a property, adding an album would simply be a slot or invokable method in that class.
Something like
public slots:
void addAlbum(const QString &name);
That would create an instance of Album, put it in the list and emit the notify signal for the property.
For adding a Card you could add a similar slot to the Album class, something like
pulic slots:
void addCard(const QString &cardID, const QString &name, const QString &imageURL, const QString &subtype, const QString &supertype, const int &number, const QString &artist, const QString &rarity, const QString &series, const QString &set, const QString &setCode, const QString &condition, const QString &status);
which would simply create a Card add then call the addCard overload.
I am now finished with the project and I have accomplished all the goals I had and overcome the problems that I have asked for help here and a few other ones that showed up later on.
For those who might find this page while searching for solutions for similar problems they have, you can find all the source code for my complete project on my Github page here: https://github.com/Nizars/PokeApp
I would like to point out a few changes that I have made:
I have switched to an SQL method of storing the cards, the albums and the card images as well.
I have used different models for different views. A proxy model for relational queries used by the table view and two sql models for other functionalities.
I also use a custom made image provider for checking for images requested from the qml side in the database first before requesting the images from an API through https and storing it in the database if it wasn't found there at first.
I have also switched to a borderless window to get rid of the windows default window border and created my own close, minimize and maximize buttons. I have also used a mouse position provider to make dragging the window around possible and seamless.
I have also added a working RSS feed to the homepage and used a page swipe view for the different pages in the app, I used connections to signal messages between the different qml documents so that data can be sent between them.
Final note: You need to include your own OpenSSL library includes for the network requests to work and create the sql tables in your local server as well as connect the app to it in pokeapp.cpp.
Feel free to ask me any questions if you need any help.
I am trying to write a QML Gui for a large dynamic C/Fortran simulation. The data I want to display is stored in Fortran Common blocks and updated on fixed time steps. My problem is that QML ListView does not refresh when the dataChanged signal is emitted after each time step, although the signal is received by the Gui (test is in the code below).
I am probably missing out something really obvious because when I flick my ListView down and up again, the displayed data is updated and correct (I guess because the QML engine re-renders elements when they get "out of sight" and back in again). So the only thing that does not work is that the ListView gets updated every time the dataChanged signal is received and not only when it is re-rendered. Below is a more detailed description of my approach and the relevant code parts.
Each simulation entity has several attributes (alive, position...), so I decided to create a ListModel containing a DataObject for each entity. This is the corresponding header file (the actual simulation data is declared as extern structs in "interface.h", so I can access it via pointer):
"acdata.h"
#include <QtCore>
#include <QObject>
#include <QtGui>
extern "C" {
#include "interface.h"
}
class AcDataObject : public QObject
{
Q_OBJECT
public:
explicit AcDataObject(int id_, int *pac_live, double *pac_pos_x, QObject *parent = 0) :
QObject(parent)
{
entity_id = id_;
ac_live = pac_live;
ac_pos_x = pac_pos_x;
}
int entity_id;
int *ac_live;
double *ac_pos_x;
};
class AcDataModel : public QAbstractListModel
{
Q_OBJECT
public:
enum RoleNames {
IdRole = Qt::UserRole,
LiveRole = Qt::UserRole + 1,
PosXRole = Qt::UserRole + 2
};
explicit AcDataModel(QObject *parent = 0);
virtual int rowCount(const QModelIndex &parent) const;
virtual QVariant data(const QModelIndex &index, int role) const;
Q_INVOKABLE Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE;
void do_update();
protected:
virtual QHash<int, QByteArray> roleNames() const;
private:
QList<AcDataObject*> data_list;
QHash<int, QByteArray> m_roleNames;
QModelIndex start_index;
QModelIndex end_index;
signals:
void dataChanged(const QModelIndex &start_index, const QModelIndex &end_index);
};
Like the header, the .cpp file is also adapted from what you can find in the Qt5 Cadaques Book here, except that my constructor iterates over all simulation entities to set the pointers. Additionally, there is the do_update function that emits the dataChanged signal for the whole list.
"acdata.cpp"
#include "acdata.h"
AcDataModel::AcDataModel(QObject *parent) :
QAbstractListModel(parent)
{
m_roleNames[IdRole] = "entity_id";
m_roleNames[LiveRole] = "ac_live";
m_roleNames[PosXRole] = "ac_pos_x";
for (int i = 0; i < MAX_ENTITIES; i++) // MAX_ENTITIES is defined in interface.h
{
AcDataObject *data_object = new AcDataObject( i,
&fdata_ac_.ac_live[i], // fdata_ac_ is the C struct/Fortran common block defined in interface.h
&fdata_ac_.ac_pos_x[i] );
data_list.append(data_object);
}
}
int AcDataModel::rowCount(const QModelIndex &parent) const {
Q_UNUSED(parent);
return data_list.count();
}
QVariant AcDataModel::data(const QModelIndex &index, int role) const
{
int row = index.row();
if(row < 0 || row >= data_list.count()) {
return QVariant();
}
const AcDataObject *data_object = data_list.at(row);
switch(role) {
case IdRole: return data_object->entity_id;
case LiveRole: return *(data_object->ac_live);
case PosXRole: return *(data_object->ac_pos_x);
}
return QVariant();
}
QHash<int, QByteArray> AcDataModel::roleNames() const
{
return m_roleNames;
}
void AcDataModel::do_update() {
start_index = createIndex(0, 0);
end_index = createIndex((data_list.count() - 1), 0);
dataChanged(start_index, end_index);
}
Qt::ItemFlags AcDataModel::flags(const QModelIndex &index) const
{
if (!index.isValid()) {return 0;}
return Qt::ItemIsEditable | QAbstractItemModel::flags(index);
}
When the simulation is running, do_update() is called every second. I have created a test Gui with a ListView and exposed my model to it with:
Excerpt from "threadcontrol.cpp"
acdata = new AcDataModel();
viewer = new QtQuick2ApplicationViewer();
viewer->rootContext()->setContextProperty("acdata", acdata);
viewer->setMainQmlFile(QStringLiteral("../lib/qml_gui/main.qml"));
viewer->showExpanded();
(This code is part of a larger file that controls the different threads. I am quite sure the rest is not relevant to the actual problem and this question is getting really long...)
So finally there is main.qml. It contains a list with MAX_ENTITIES elements and each elements holds text fields to display my data. I have also added a Connections element to check if the dataChanged signal is received by the Gui.
"main.qml"
ListView {
id: listviewer
model: acdata
delegate: Rectangle {
/* ... some formatting stuff like height etc ... */
Row {
anchors.fill: parent
Text {
/* ... formatting stuff ... */
text: model.entity_id
}
Text {
/* ... formatting stuff ... */
text: model.ac_live
}
Text {
/* ... formatting stuff ... */
text: model.ac_pos_x
}
}
}
Connections {
target: listviewer.model // EDIT: I drew the wrong conclusions here, see text below!
onDataChanged: {
console.log("DataChanged received")
}
}
}
When running the simulation, the "DataChanged received" message is printed every second.
Edit: I was connecting to the ListModel and not to the ListView here, although the ListView has to receive the dataChanged signal. As the console log does not work when connecting to listviewer, I am probably missing the connection between listView and dataChanged signal. However, I think this should work automatically when implementing the dataChanged signal?
Additional information: I have found a similar problem here with Qt Map and it actually seemed to be a bug that was fixed in Qt 5.6. However, running qmake with Qt 5.7 did not fix my problem.
You mustn't declare the dataChanged() signal in your class, because you want to emit the signal AbstractItemModel::dataChanged(). If you re-declare it you add a comleptely new and different Signal that is not connected anywhere. If you remove the declaration in acdata.h everything should work fine.
I am trying to write custom delegate and custom model as a part of learning Qt.
I made a simple custom model based on QAbstractTableModel. I did not do anything complicated. It only generates the data in its constructor as well as minimally implement the pure virtual function.
I made a custom delegate which display numerical data in terms of bars. I also implemented a spin box as an editor to edit data.
The program works well. I can view, edit and modify data through a QTableView with the delegate set.
But there is a small problem. When I call the editor, the data bar persists, which means I see the data bar at the background and the spin box on top.
Initially, I think it is because the Qt::EditRole in the QAbstractTableModel::data() has not been set properly. But, surprisingly, I find that the Qt::EditRole has never been called.
So, there are two question:
How to remove the data bar when I am having the spin box editor?
Why is the EditRole never been called in my custom model?
Here is part of my code:
My Custom Model:
MyModel::MyModel(QObject* parent):QAbstractTableModel(parent)
{
for (int i = 0; i < 10; ++i)
localData.push_back(i*i);
}
QVariant MyModel::data(const QModelIndex &index, int role) const
{
switch(role)
{
case Qt::EditRole:
qDebug() << "EditRole"; //Never Print Out
return 0;
case Qt::DisplayRole :
if (index.column() == 0)
return (index.row());
if (index.column() == 1)
return (localData.at(index.row()));
default:
return QVariant();
}
}
My Custom Delegate:
void MyDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
{
painter->save();
painter->setPen(Qt::red);
painter->setBrush(Qt::red);
double factor = 0;
if (index.data().toDouble() > 100)
factor = 1;
else
factor = index.data().toDouble() / (double) (100.0);
painter->drawRect(option.rect.x()+5, option.rect.y()+3, (option.rect.width()-10)*factor, option.rect.height()-6);
painter->restore();
}
QWidget* MyDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QSpinBox* box = new QSpinBox(parent);
box->setMinimum(0);
box->setMaximum(100);
return box;
}
Try to set setAutoFillBackground(true) for your view
Editrole is not called because your custom editor does not query the model for that data. You don not set any value for your spin box. Try to set it as:
box->setValue(model.data(Qt::EditRole));
in the MyDelegate::createEditor() function.
I've been working on a simple QTreeView of a local directory. The goal is allow the user to browse to his/her directory and select the correct csv file.
I've created a QFileSystemModel and displayed it with a QTreeView. I'm confused how to get the filename from the currently selected node.
I've read through the documentation and I've found the following signal/slot pairing:
connect(tree, SIGNAL(clicked(QModelIndex)), this, SLOT(handleTreeWidgetEvent(QModelIndex)));
But I'm not sure what to do with the QModelIndex once activated. I know you're supossed to index the QTreeView with this index, but I'm not sure how.
Any help is greatly appreciated.
EDIT: Adding code so people can see what I'm doing.
QFileSystemModel *model = new QFileSystemModel;
model->setRootPath("/");
tree = new QTreeView;
tree->setModel(model);
tree->setRootIndex(model->index("/home/Missions/"));
tree->setColumnWidth(0, 350);
connect(tree, SIGNAL(clicked(QModelIndex)), this, SLOT(handleTreeWidgetEvent(QModelIndex)));
WhatEverClassInheritingQObject::handleTreeWidgetEvent(const QModelIndex& index)
{
const QString valuablePathAskedFor(fileSystemModel->fileName(index));
...
}
you can retrieve the path as a QString in your setData method via filePath() method based just on the QModelIndex, so it will be called each time user has checked (or unchecked) some directory or file in your model being displayed, and then you need to store all these paths in some conatainer and implement method to return this:
bool MyQFileSystemModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.column() != 0 || role != Qt::CheckStateRole)
return QFileSystemModel::setData(index, value, role);
int newCheckState = value.toInt();
QString filePath = filePath(index);
if (newCheckState == Qt::Checked || newCheckState == Qt::PartiallyChecked )
checkedPaths.insert(filePath);
else
checkedPaths.remove(filePath);
emit dataChanged(index, index.child(index.row(),0));
return true;
}
class MyQFileSystemModel : public QFileSystemModel
{
Q_OBJECT
public:
//...
QSet<QString> getChecked() const { return checkedPaths; }
private:
QSet<QString> chackedPaths;
//...
};