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.
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'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];
I'm using a Qt property browser widget. I'm creating all the values of the property grid automatically using the moc but I didn't find how to make the values entered in the property grid be set automatically in my class instance? is there any sginal where I can know that?
I creating the property grid like this:
QWidget *w = new QWidget();
Foo *o = new Foo();
QtBoolPropertyManager *boolManager = new QtBoolPropertyManager(w);
QtIntPropertyManager *intManager = new QtIntPropertyManager(w);
QtStringPropertyManager *stringManager = new QtStringPropertyManager(w);
QtGroupPropertyManager *groupManager = new QtGroupPropertyManager(w);
const QMetaObject *meta = o->metaObject();
const QString className = meta->className();
QMap<QString, PropManager*> dic;
QtProperty *root = groupManager->addProperty(className);
for(int i = meta->propertyOffset(); i < meta->propertyCount(); ++i)
{
QMetaProperty metaProp = meta->property(i);
const QString name = metaProp.name();
QVariant value = metaProp.read(o);
QVariant::Type t = metaProp.type();
QtProperty *prop = NULL;
PropManager *propMan = NULL;
switch(t)
{
case QVariant::Int:
{
prop = intManager->addProperty(name);
root->addSubProperty(prop);
intManager->setValue(prop, value.toInt());
propMan = new PropManager(prop, intManager, t);
break;
}
case QVariant::String:
{
prop = stringManager->addProperty(name);
root->addSubProperty(prop);
stringManager->setValue(prop, value.toString());
propMan = new PropManager(prop, stringManager, t);
break;
}
default:
throw std::invalid_argument("unknow type");
}
if(prop != NULL)
dic.insert(name, propMan);
}
QtCheckBoxFactory *checkBoxFactory = new QtCheckBoxFactory(w);
QtSpinBoxFactory *spinBoxFactory = new QtSpinBoxFactory(w);
QtLineEditFactory *lineEditFactory = new QtLineEditFactory(w);
QtAbstractPropertyBrowser *editor = new QtTreePropertyBrowser();
editor->setFactoryForManager(boolManager, checkBoxFactory);
editor->setFactoryForManager(intManager, spinBoxFactory);
editor->setFactoryForManager(stringManager, lineEditFactory);
editor->addProperty(root);
QGridLayout *layout = new QGridLayout(w);
layout->addWidget(editor, 1, 0);
w->show();
Foo.h is defined like this:
class Foo : public QObject
{
Q_OBJECT
public:
explicit Foo() { }
~Foo() { }
Q_PROPERTY(QString name READ getA WRITE setA)
Q_PROPERTY(int age READ getNum WRITE setNum)
QString name;
int age;
QString getA() const { return name; }
int getNum() const { return age; }
void setA(QString a) { this->name = a; }
void setNum(int n) { this->age = n; }
};
Partial solution:
I found propertyChanged() from QtAbstractPropertyManager but this does raise every time a single character is edited. I'd like to have an slot where this is fired when the user is done with the editing.
Here is my program:
#include <QStandardItemModel>
#include <QSortFilterProxyModel>
#include <QTreeView>
class MySortFilterProxyModel : public QSortFilterProxyModel
{
public:
MySortFilterProxyModel();
void updateFilter(int filterType);
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const;
bool lessThan(const QModelIndex &left,const QModelIndex &right) const;
private:
int _filterType;
};
MySortFilterProxyModel::MySortFilterProxyModel()
: _filterType(0)
{
}
bool MySortFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
QStandardItemModel* source = static_cast<QStandardItemModel*>(sourceModel());
QModelIndex modelIndex = source->index(sourceRow, 0, sourceParent);
QStandardItem* item = source->itemFromIndex(modelIndex);
QVariant v = item->data(Qt::UserRole);
int itemType = v.toInt();
if(itemType == _filterType)
return true;
return false;
}
bool MySortFilterProxyModel::lessThan(const QModelIndex &left,const QModelIndex &right) const
{
QVariant leftData = sourceModel()->data(left);
QVariant rightData = sourceModel()->data(right);
if(leftData.type() == QVariant::String && rightData.type() == QVariant::String)
{
QString leftString = leftData.toString();
QString rightString = rightData.toString();
return QString::localeAwareCompare(leftString, rightString) < 0;
}
return false;
}
void MySortFilterProxyModel::updateFilter(int filterType)
{
_filterType = filterType;
// how can i trigger filteracceptRows here ??
}
int main(int argc, char** argv)
{
QApplication qtApp(argc, argv);
MySortFilterProxyModel mySortFilterProxyModel;
QStandardItemModel standardModel;
QTreeView treeView;
mySortFilterProxyModel.setSourceModel(&standardModel);
treeView.setModel(&standardModel);
treeView.setSortingEnabled(true);
treeView.show();
return qtApp.exec();
}
Everytime i AppendRow to standardModel sort and filter works.
How can i trigger filtering without appending or removing something to standardModel?
I want to filter rows on QTreeView through right click but i couldn't find a way to triggger filterAcceptRows on my void MySortFilterProxyModel::updateFilter(int filterType) function.
Having multiple instances of MySortFilterProxyModel class for every possible filterType value and switching them according to filterType may work but is there a better solution?
calling invalidate() on updateFilter worked for me.
void MySortFilterProxyModel::updateFilter(int filterType)
{
_filterType = filterType;
invalidate();
}
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();
}