I'm new to Qt, so I'm not sure what to use.
I thought I would have a QStringList to give to my QStringListModel and display it in a ListView.
Now, however, I need to divide QStringList in the values of 2 types. So, I need to have string + some typeId, not just one string, but QStringList is for one-dimensional list only.
Anyone could give an advice on what is the best way to try to implement this?
The solution is to use QAbstractListModel subclass as a Qt Quick model. An example of base class for a models (I use it for convenience):
// abstractobjectlistmodel.h
#pragma once
#include <QtCore>
struct AbstractObjectListModel
: public QAbstractListModel
{
explicit AbstractObjectListModel(QObject * const parent = Q_NULLPTR)
: QAbstractListModel{parent}
{ ; }
int rowCount(QModelIndex const & parent = {}) const Q_DECL_OVERRIDE Q_DECL_FINAL
{
Q_UNUSED(parent);
return items.count();
}
QVariant data(QModelIndex const & index, const int role = Qt::DisplayRole) const Q_DECL_OVERRIDE Q_DECL_FINAL
{
if (!index.isValid()) {
return {};
}
switch (role) {
case Qt::UserRole : {
return QVariant::fromValue(items[index.row()].data());
}
default : {
return {};
}
}
}
QHash< int, QByteArray > roleNames() const Q_DECL_OVERRIDE Q_DECL_FINAL
{
auto roleNames = QAbstractListModel::roleNames();
roleNames.insert(Qt::UserRole, "modelData");
return roleNames;
}
Q_INVOKABLE
virtual
QObject * get(int row) const
{
if (row < 0) {
return {};
}
if (row >= rowCount()) {
return {};
}
return items[row];
}
void remove(int row, int count = 1)
{
Q_ASSERT(count > 0);
Q_ASSERT(row >= 0);
Q_ASSERT(row + count <= rowCount());
beginRemoveRows({}, row, row + count - 1);
while (0 < count) {
items.takeAt(row)->deleteLater();
--count;
}
endRemoveRows();
}
void clear()
{
if (!items.isEmpty()) {
remove(0, rowCount());
}
}
protected :
~AbstractObjectListModel() Q_DECL_OVERRIDE Q_DECL_EQ_DEFAULT; // derived classes should not meant to be manipulated polymorphically
QList< QPointer< QObject > > items;
void insert(int row, QObject * const item)
{
item->setParent(this);
beginInsertRows({}, row, row);
items.insert(row, item);
endInsertRows();
}
void append(QObject * const item)
{
insert(rowCount(), item);
}
};
But one need to override get to access items' Q_PROPERTY properties (in addition to dynamic ones):
// type of element
class Project
: public QObject
{
Q_OBJECT
Q_PROPERTY(QString name MEMBER name NOTIFY nameChanged)
Q_PROPERTY(QString path MEMBER path NOTIFY pathChanged)
public :
Project(QString name, QString path,
QObject * const parent = Q_NULLPTR)
: QObject{parent}
, name{name}
, path{path}
{ ; }
Q_SIGNALS :
void nameChanged(QString name);
void pathChanged(QString path);
private :
QString name;
QString path;
};
// custom model
class ProjectsListModel
: public AbstractObjectListModel
{
Q_OBJECT
public :
explicit ProjectsListModel(QObject * const parent = Q_NULLPTR)
: AbstractObjectListModel{parent}
{ ; }
void appendProject(QString name, QString path)
{
AbstractObjectListModel::append(::new Project{name, path});
}
Q_INVOKABLE
Project *
get(int row) const Q_DECL_OVERRIDE
{
return qobject_cast< Project * >(AbstractObjectListModel::get(row));
}
};
Before use one need to register concrete model with qmlRegisterType< ProjectsListModel >();. Properties of Project class are avaliable in delegate and highlight by means of members of modelData.
Another example:
struct TimeZoneModel Q_DECL_FINAL
: public QAbstractListModel
{
Q_OBJECT
public :
explicit TimeZoneModel(QObject * const parent = Q_NULLPTR)
: QAbstractListModel{parent}
{ ; }
int rowCount(QModelIndex const & parent = {}) const Q_DECL_OVERRIDE
{
Q_UNUSED(parent);
return timeZoneIds.count();
}
QVariant data(QModelIndex const & index, const int role = Qt::DisplayRole) const Q_DECL_OVERRIDE
{
if (!index.isValid() || (role > Qt::UserRole + 4)) {
return {};
}
QTimeZone timeZone{timeZoneIds[index.row()]};
if (!timeZone.isValid()) {
return {};
}
return roleData(timeZone, role);
}
QHash< int, QByteArray > roleNames() const Q_DECL_OVERRIDE
{
auto roleNames = QAbstractListModel::roleNames();
int i = Qt::UserRole;
for (const auto role : {"modelData", "id", "comment", "name", "country"}) {
roleNames.insert(i++, role);
}
return roleNames;
}
Q_INVOKABLE
QByteArray get(int row) const
{
if (row < 0) {
return {};
}
if (row >= rowCount()) {
return {};
}
return timeZoneIds[row];
}
private :
QVariant roleData(QTimeZone const & timeZone, int role = Qt::UserRole) const
{
switch (role) {
case Qt::UserRole : {
QVariantMap modelData;
const auto names = roleNames();
while (++role < Qt::UserRole + 5) {
modelData.insert(QString::fromUtf8(names[role]), roleData(timeZone, role));
}
return modelData;
}
case Qt::UserRole + 1: {
return QString::fromUtf8(timeZone.id());
}
case Qt::UserRole + 2 : {
return timeZone.comment();
}
case Qt::UserRole + 3 : {
return timeZone.displayName(QTimeZone::StandardTime);
}
case Qt::UserRole + 4 : {
return QLocale::countryToString(timeZone.country());
}
default : {
return {};
}
}
}
const QByteArrayList timeZoneIds = QTimeZone::availableTimeZoneIds();
};
In addition to access via modelData's fields (modelData.id, modelData.comment etc) all the symbols are accessible directly (i.e. id, comment etc) in delegate and highlight contexts of the ListView.
Model TimeZoneModel is const and can be injected into global scope directly without any performance drawbacks:
QQmlApplicationEngine engine;
TimeZoneModel timeZoneModel;
Q_SET_OBJECT_NAME(timeZoneModel);
qmlRegisterType< TimeZoneModel >();
const auto rootContext = engine.rootContext();
rootContext->setContextProperty(timeZoneModel.objectName(), &timeZoneModel);
If you need dictionary that contains QString and any other type I suggest you to use
QMap<QString, YourType> myMap;
Here you have some example of usage:
QMap<int, QString> myMap;
myMap.insert(1,"A");
myMap.insert(2,"B");
myMap[3] = "C";
foreach(int i, myMap.keys()) qDebug() << myMap[i];
Related
I am currently working on a ticker client, that polls data from a web api, hands it to a ListModel, which is then used to display the data in a qml ListView.
class TickerClient : public QObject
{
Q_OBJECT
public:
explicit TickerClient(QObject *parent = nullptr);
QList<QVariantMap> items() const;
protected:
void registerErrorHandlers(QNetworkReply *reply);
signals:
void statusChanged(QNetworkAccessManager::NetworkAccessibility acc);
void dataChanged();
void preItemRefresh();
void postItemRefresh();
public slots:
void fetch(int start = 0, int limit = 100);
protected slots:
void onReceive(QNetworkReply *reply);
protected:
QSharedPointer<QNetworkAccessManager> mNetMan;
QList<QVariantMap> mItems;
};
class TickerModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(TickerClient *client READ client WRITE setClient)
public:
explicit TickerModel(QObject *parent = nullptr);
enum {
IdRole = Qt::UserRole + 1,
NameRole,
SymbolRole,
...
};
// Basic functionality:
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
TickerClient *client() const;
void setClient(TickerClient *client);
private:
TickerClient *mClient;
};
The ticker client does not only fetch, but also handle the fetched data and expose it to the surrounding ListModel as a list of QVariantMaps.
void TickerClient::onReceive(QNetworkReply *reply)
{
if (!reply)
return;
if(reply->error()) {
qCritical() << "Error: "
<< reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toString();
return;
}
// Read all data as json document.
QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll());
if(!jsonDoc.isArray()) {
qCritical() << "Error: Expected array";
return;
}
emit preItemRefresh();
mItems.clear();
QJsonArray currencies = jsonDoc.array();
for(int i = 0; i < currencies.count(); i++) {
QJsonObject currency = currencies[i].toObject();
mItems.append(currency.toVariantMap());
}
emit postItemRefresh();
reply->deleteLater();
}
Both, the TickerClient and the TickerModel are exposed to qml:
qmlRegisterType<TickerModel>("Ticker", 1, 0, "TickerModel");
qmlRegisterUncreatableType<TickerClient>("Ticker", 1, 0, "TickerClient",
QStringLiteral("MarketCapProvider should not be created in QML"));
TickerClient tickerClient;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("tickerClient", &tickerClient);
The exposed client is then handed to the model and refreshed every 5 seconds:
ListView {
id: page
Timer {
interval: 5000
running: true
repeat: true
triggeredOnStart: true
onTriggered: {
var pos = scrollBar.position
tickerClient.fetch()
scrollBar.position = pos
}
}
ScrollBar.vertical: ScrollBar {
id: scrollBar
}
model: TickerModel {
client: tickerClient
}
delegate: RowLayout {
width: parent.width
spacing: 10
Label {
text: "#" + model.rank
padding: 5
}
Label {
text: model.name
padding: 5
}
}
}
However, the fetching of the data doesn't seem to work well as the wrong data is returned. So if I ask for model.name, I might end of with model.rank. The following code is used to fetch the entry for a given pair of index and role.
QVariant TickerModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || !mClient)
return QVariant();
auto it = roleNames().find(role);
if(it != roleNames().end())
return mClient->items().at(index.row())[it.value()];
else
qDebug() << "Didn't match a role";
return QVariant();
}
QHash<int, QByteArray> TickerModel::roleNames() const
{
static const QHash<int, QByteArray> names{
{ IdRole, "id" },
{ NameRole, "name" },
...
};
return names;
}
TickerClient *TickerModel::client() const
{
return mClient;
}
void TickerModel::setClient(TickerClient *client)
{
beginResetModel();
if(mClient) {
mClient->disconnect(this);
}
mClient = client;
if(mClient) {
connect(mClient, &TickerClient::preItemRefresh, this, [=]() {
beginResetModel();
});
connect(mClient, &TickerClient::postItemRefresh, this, [=]() {
endResetModel();
});
}
endResetModel();
}
What, am I doing wrong and how can I extend this solution for more complex Json objects?
You are clearing mItems and then appending new items to it in onReceive method. You are not showing if model is listening to postItemRefresh signal. Anyway, you should call beginResetModel and endResetModel.
E.g. add following slot to your TickerModel and connect it to postItemRefresh signal to get your model synched witht the new data:
void TickerModel::reset()
{
beginResetModel();
endResetModel();
}
Edit:
You should add prints or go through with the debugger with what parameters your model data() is called when things get screwed. Also, make sure beginResetModel and endResetModel are called when items are updated.
Is your rowCount returning mClient->items().count() ?
To handle more complex json objects, instead of:
QJsonArray currencies = jsonDoc.array();
for(int i = 0; i < currencies.count(); i++) {
QJsonObject currency = currencies[i].toObject();
mItems.append(currency.toVariantMap());
}
You don't handle QVariantMap but parse more complex json objects to your own class which provides getters for data:
QJsonArray currencies = jsonDoc.array();
for(int i = 0; i < currencies.count(); i++) {
QJsonObject currency = currencies[i].toObject();
ComplexCurrency c;
c.read(currency);
mItems.append(c);
}
Your class:
class ComplexCurrency
{
public:
int id() const;
QString name() const;
bool enabled() const;
QList<QVariantMap> items();
void read(const QJsonObject &json);
private:
int m_id;
QString m_name;
bool m_enabled;
QList<QVariantMap> m_items;
...
};
void ComplexCurrency::read(const QJsonObject &json)
{
m_id = json["id"].toInt();
m_name = json["name"].toString();
m_enabled = json["enabled"].toBool();
// parse items array here
}
And then in model data():
QVariant TickerModel::data(const QModelIndex &index, int role) const
{
if ((index.row() < 0 || index.row() >= mClient->items().count()) || !mClient)
return QVariant();
const ComplexCurrency &c = mClient->items().at(index.row());
if (role == NameRole)
return c.name();
else if (role == SomethingRole)
return c.something();
return QVariant();
}
I've implemented QIdentityProxyModel like this:
class ProxyModel : public QIdentityProxyModel
{
Q_OBJECT
public:
ProxyModel(QObject *parent)
{
entriesPerPage = 3;
page = 1;
}
inline int getEntriesPerPage() const
{ return entriesPerPage; }
inline void setEntriesPerPage(int value)
{ entriesPerPage = value; }
inline qint64 getPage() const
{ return page; }
inline void setPage(const qint64 &value)
{ page = value; }
inline int rowCount(const QModelIndex &parent = QModelIndex()) const override{
Q_UNUSED(parent)
if(!sourceModel())
return 0;
return entriesPerPage * page <= sourceModel()->rowCount()
? entriesPerPage
: sourceModel()->rowCount() - entriesPerPage * (page - 1);
}
inline int columnCount(const QModelIndex &parent = QModelIndex()) const override{
Q_UNUSED(parent);
return 6;
}
QModelIndex ProxyModel::mapFromSource(const QModelIndex &sourceIndex) const
{
if(!sourceModel() && !proxyIndex.isValid())
return QModelIndex();
return sourceIndex.isValid()
? createIndex(sourceIndex.row() % entriesPerPage, sourceIndex.column(), sourceIndex.internalPointer())
: QModelIndex();
}
QModelIndex ProxyModel::mapToSource(const QModelIndex &proxyIndex) const
{
if(!sourceModel() && !proxyIndex.isValid())
return QModelIndex();
QModelIndex remapped = createIndex(proxyIndex.row() ,
proxyIndex.column(),
proxyIndex.internalPointer());
return QIdentityProxyModel::mapToSource(remapped);
}
private:
int entriesPerPage;
qint64 page;
};
When I insert a row in the sourceModel() with index more then entriesPerPage, view displays empty rows, so that row number is more then entriesPerPage, although rowCount() return number equal to entriesPerPage.
How can I get rid of empty rows?
First. It is a bad practice to override rowCount/mapFromSource for QIdentityProxyModel. I propose you to use QAbstractProxyModel to have more clear code.
Main. Your problem is in getEntriesPerPage/setPage methods. You need to call beginResetModel/endResetModel after updating such data.
inline void setPage(const qint64 &value)
{
beginResetModel();
page = value;
endResetModel();
}
Offtopic: it is pretty cool, that you have to code with Qt in BSUIR. Who is your teacher?
I have an ini file as shown
[Profiles]
hh\Ta=20
hh\Te=48
hh\Texp=38
hh\lim1=0
hh\lim2=0
hh\offset=0
hh\profilename=hh
tt\Ta=20
tt\Te=48
tt\Texp=38
tt\lim1=0
tt\lim2=0
tt\offset=0
tt\profilename=hh
I'm trying to load it, but I get all the data as zeros, like Ta Te Texp,..etc
The file is saved correctly but loading the values back is not working.
Is it a problem with setting the group,..etc ?
here is my load and save mechanism
void Method::loadFrom(Settings &set, bool ownGroup)
{
set.beginGroup("Profiles");
foreach (const QString &group, set.childGroups()) {
set.beginGroup(group);
Profile *profile = new Profile();
profile->setObjectName(group);
profile->loadFrom(set);
set.endGroup();
m_currentProfile->m_Profiles << profile;
}
set.endGroup();
EraObject::staticLoadFrom(set, this, ownGroup);
}
void Method::saveTo(Settings &set, bool ownGroup, bool force)
{
set.beginGroup("Profiles");
foreach(Profile * profile, m_currentProfile->m_Profiles) {
profile->saveTo(set);
set.endGroup();
}
set.sync();
EraObject::staticSaveTo(set, this, ownGroup, force);
}
class Settings : public QSettings
{
Q_OBJECT
public:
explicit Settings(QString fileName);
~Settings();
private:
Q_DISABLE_COPY(Settings)
};
void EraObject::staticLoadFrom(Settings &set, QObject *object, const bool &ownGroup)
{
Q_ASSERT(object);
bool modified;
const QMetaObject *superMeta = object->metaObject()->superClass();
const QMetaObject *meta = object->metaObject();
const QString &group = object->objectName().isEmpty() ? meta->className() : object->objectName();
if (ownGroup)
set.beginGroup(group);
qCDebug(loadLog) << group;
for (int i = superMeta->propertyOffset(); i < meta->propertyCount(); i++) {
modified = false;
const QMetaProperty &prop = meta->property(i);
QMetaType::Type type = (QMetaType::Type)prop.type();
if (prop.isUser()) {
switch (type) {
case QMetaType::QDateTime:
modified = setOrReset(set, object, prop, QDateTime::fromString(set.value(prop.name(), "").toString(), "yyyyMMddhhmmsszzz"));
break;
case QMetaType::QString: { //check if there is an localized string first (eg.: name#de=test)
const QVariant value = set.value(QString("%1#%2").arg(prop.name()).arg(QLocale::system().bcp47Name()),
set.value(prop.name(), ""));
modified = setOrReset(set, object, prop, value);
break;
}
case QVariant::UserType: {
QObject *obj = prop.read(object).value<QObject *>();
Q_ASSERT_X(obj, "staticLoadFrom", "Custom class NOT known to QMetaType, register it with qmlRegisterType()!");
bool check = QMetaObject::invokeMethod(obj,
"loadFrom",
Qt::DirectConnection,
Q_ARG(Settings &, set)); //we invoke cause it can be just QObject(e.g. QAbstractItemModel)
Q_ASSERT_X(check, "staticLoadFrom", "Could not invoke. Be sure you subclassed EraObject or defined method!: " + obj->objectName().toLatin1());
break;
}
default:
modified = setOrReset(set, object, prop, set.value(prop.name(), ""));
}
qCDebug(loadLog) << "modified:" << modified << "Property:" << prop.name();
}
}
if (ownGroup)
set.endGroup();
object->setProperty("dirty", NotDirty);
}
void EraObject::staticSaveTo(Settings &set, QObject *object, const bool &ownGroup, const bool &force)
{
Q_ASSERT(object);
if (force || object->property("dirty").toInt() == RamDirty) {
const QMetaObject *superMeta = object->metaObject()->superClass();
const QMetaObject *meta = object->metaObject();
const QString &group = object->objectName().isEmpty() ? meta->className() : object->objectName();
if (ownGroup)
set.beginGroup(group);
qCDebug(saveLog) << group;
for (int i = superMeta->propertyOffset(); i < meta->propertyCount(); i++) {
const QMetaProperty &prop = meta->property(i);
if (prop.isUser()) {
QMetaType::Type type = (QMetaType::Type)prop.type();
switch (type) {
case QMetaType::QDateTime:
set.setValue(prop.name(), prop.read(object).toDateTime().toString("yyyyMMddhhmmsszzz"));
break;
case QMetaType::Float:
case QMetaType::Double:
set.setValue(prop.name(), QString::number(prop.read(object).toDouble(), 'g', MAX_SAVED_DECIMALS));
break;
case QMetaType::User: {
QObject *obj = prop.read(object).value<QObject *>();
Q_ASSERT_X(obj, "staticLoadFrom", "Custom class NOT known to QMetaType, register it with qmlRegisterType()!");
bool check = QMetaObject::invokeMethod(obj,
"saveTo",
Qt::DirectConnection,
Q_ARG(Settings &, set),
Q_ARG(bool, true),
Q_ARG(bool, force));
Q_ASSERT_X(check, "saveTo", "Could not invoke. Be sure you subclassed EraObject or defined method!: " + obj->objectName().toLatin1());
break;
}
default:
if (!prop.isConstant())
set.setValue(prop.name(), prop.read(object));
}
}
}
if (ownGroup)
set.endGroup();
object->setProperty("dirty", NotDirty);
set.sync();
}
}
I inherited QAbstractItemModel class for my model. To easily insert new items to the model I wrote next method:
void addItem(MyData *parent, MyData *children) {
QModelIndex idx = createIndex(parent->row(), 0, parent);
if (!idx.isValid()) {
return;
}
int childCount = parent->getChildCount();
beginInsertRows(idx, childCount, childCount);
parent->addChild(children);
endInsertRows();
emit layoutChanged(QList<QPersistentModelIndex>{idx});
}
It works well with QListView, but QML's TreeView does not updates values after it once showed it:
int main(int argc, char ** argv) {
Q_INIT_RESOURCE(ui);
QApplication application(argc, argv);
MyModel model;
for (int i = 0; i < 10; ++ i) {
MyData *firstLevelItem = new MyData(i);
for (int k = 0; k < 3; ++ k) {
MyData *secondLevelItem = new MyData(i);
model.addItem(firstLevelItem, secondLevelItem);
}
model.addItem(model.getRootItem(), firstLevelItem);
}
QQuickView view;
QQmlContext *context = view.rootContext();
context->setContextProperty("MyModel", &model);
view.setSource(QUrl("qrc:///ui/form.qml"));
view.show();
QTreeView t;
t.setModel(&model);
t.show();
MyData *data = new MyData(2281488);
model.addItem(model.getRootItem(), data);
// t displays changes, view - not
return application.exec();
}
MyData class:
class MyModel;
class MyData: public QObject {
Q_OBJECT
public:
explicit MyData() :
QObject() {
_parent = nullptr;
}
~MyData() {
qDeleteAll(_data);
}
// getters / setters
MyData *getChildItem(int index) const {
if (index < 0 || index >= _data.size()) {
return nullptr;
}
return _data[index];
}
int getChildCount() const {
return _data.size();
}
MyData *parent() const {
return _parent;
}
int row() const {
if (_parent) {
return _parent->_data.indexOf(const_cast<MyData *>(this));
} else {
return 0;
}
}
private:
void addChild(MyData *data) {
if (data) {
if (data->_parent) {
_parent->removeChild(data);
}
data->_parent = this;
_data << data;
}
}
void removeChild(MyData *data) {
_data.removeAll(data);
}
// some private fields
MyData *_parent;
QVector<MyData *> _data;
friend class MyModel;
};
QML's ListView is not suited to display tree-like structures like a directory tree, for example. It only works with list models typically inherited from QAbstractListModel. If you derive your model from QAbstractItemModel, you must specialise it such that it behaves like a list.
Since Qt 5.5 there is a QML TreeView componenent in the module QtQuick.Controls (v1.4). You may want to try this.
I have the QVector cars that I want to filter basing on the car's registration number. I want to create a new filtered vector. I don't think that this is ok because i'm iterating 2 vectors, copying from the first one to the second one. Am I doing this right?
void MainWindow::on_actionBy_registration_number_triggered()
{
myDialog = new MyDialog(this);
myDialog->exec();
QString toSearchString = myDialog->getRegistrationNumber();
QVector<Vehicle> founded;
QVectorIterator<Vehicle> iterator(cars);
while(iterator.hasNext()){
Vehicle car = iterator.next();
QString num = car.getRegistration().getRegistrationNumber();
if(num.contains(toSearchString,Qt::CaseInsensitive)){
founded.append(car);
}
}
model = new QStandardItemModel(founded.size(),5,this);
//create header
createHeader(model);
int j = 0; //row
QVectorIterator<Vehicle> iter(founded);
while(iter.hasNext()){
Vehicle output = iter.next();
//set car
QString makeAndModel = output.getGeneralData().getMake() + output.getGeneralData().getModel();
QStandardItem *mAndM = new QStandardItem(QString(makeAndModel));
model->setItem(j,0,mAndM);
//set type
QStandardItem *type = new QStandardItem(QString(output.getGeneralData().getType()));
model->setItem(j,1,type);
//set mileage
QString mileageString = QString::number(output.getGeneralData().getMileage());
QStandardItem *mileage = new QStandardItem(QString(mileageString));
model->setItem(j,2,mileage);
//set year
QString yearString = QString::number(output.getGeneralData().getYear());
QStandardItem *year = new QStandardItem(QString(yearString));
model->setItem(j,3,year);
//set registration
QString regString = VehicleHelper::convertBoolToString(output.getRegistration().isRegistered());
QStandardItem *regDate = new QStandardItem(QString(regString));
model->setItem(j,4,regDate);
j++;
}
ui->tableView->setModel(model);
ui->tableView->setEnabled(false);
}
This can be done neatly using a proxy filter model. Below is a self-contained example that runs on both Qt 4 and 5.
// https://github.com/KubaO/stackoverflown/tree/master/questions/filter-18964377
#include <QtGui>
#if QT_VERSION_MAJOR > 4
#include <QtWidgets>
#endif
class Vehicle {
QString m_make, m_model, m_registrationNumber;
public:
Vehicle(const QString & make, const QString & model, const QString & registrationNumber) :
m_make{make}, m_model{model}, m_registrationNumber{registrationNumber} {}
QString make() const { return m_make; }
QString model() const { return m_model; }
QString registrationNumber() const { return m_registrationNumber; }
bool isRegistered() const { return !m_registrationNumber.isEmpty(); }
};
class VehicleModel : public QAbstractTableModel {
QList<Vehicle> m_data;
public:
VehicleModel(QObject * parent = {}) : QAbstractTableModel{parent} {}
int rowCount(const QModelIndex &) const override { return m_data.count(); }
int columnCount(const QModelIndex &) const override { return 3; }
QVariant data(const QModelIndex &index, int role) const override {
if (role != Qt::DisplayRole && role != Qt::EditRole) return {};
const auto & vehicle = m_data[index.row()];
switch (index.column()) {
case 0: return vehicle.make();
case 1: return vehicle.model();
case 2: return vehicle.registrationNumber();
default: return {};
};
}
QVariant headerData(int section, Qt::Orientation orientation, int role) const override {
if (orientation != Qt::Horizontal || role != Qt::DisplayRole) return {};
switch (section) {
case 0: return "Make";
case 1: return "Model";
case 2: return "Reg.#";
default: return {};
}
}
void append(const Vehicle & vehicle) {
beginInsertRows({}, m_data.count(), m_data.count());
m_data.append(vehicle);
endInsertRows();
}
};
class Widget : public QWidget {
QGridLayout m_layout{this};
QTableView m_view;
QPushButton m_button{"Filter"};
VehicleModel m_model;
QSortFilterProxyModel m_proxy;
QInputDialog m_dialog;
public:
Widget() {
m_layout.addWidget(&m_view, 0, 0, 1, 1);
m_layout.addWidget(&m_button, 1, 0, 1, 1);
connect(&m_button, SIGNAL(clicked()), &m_dialog, SLOT(open()));
m_model.append({"Volvo", "240", "SQL8941"});
m_model.append({"Volvo", "850", {}});
m_model.append({"Volvo", "940", "QRZ1321"});
m_model.append({"Volvo", "960", "QRZ1628"});
m_proxy.setSourceModel(&m_model);
m_proxy.setFilterKeyColumn(2);
m_view.setModel(&m_proxy);
m_dialog.setLabelText("Enter registration number fragment to filter on. Leave empty to clear filter.");
m_dialog.setInputMode(QInputDialog::TextInput);
connect(&m_dialog, SIGNAL(textValueSelected(QString)),
&m_proxy, SLOT(setFilterFixedString(QString)));
}
};
int main(int argc, char *argv[])
{
QApplication a{argc, argv};
Widget w;
w.show();
return a.exec();
}