Introduction / Purpose
This class prevents the need of deriving a QAbstractListModel
class to service a simple list in memory like a std::vector
or QList
derived instance and through a few signals/callback functions the list can be displayed and controlled.
This class can only service a single QTreeView
instance.
Usage
Note: Assuming the QTreeView
is already available.
When the parent
passed at the constructor of sf::ListModel
is a QTreeView
derived instance, it attaches itself so a call to sf::ListModel::setTreeView
can be omitted.
The Data structure & ListModel
The following code declares the needed instances and pointers.
struct Data
{
QString name;
int image{0};
double amount{0.0};
QString description;
bool selected{false};
};
QList<Data> _data;
ListModel* _listModel;
Setup Columns
This code creates and configures the columns.
int index = 0;
for (auto str: {"#", "Name", "Amount", "Description"})
{
auto col = _listModel->getColumns().add();
switch (index)
{
case 0:
col->flags = Qt::ItemFlag::ItemIsUserCheckable;
col->alignment = Qt::AlignRight | Qt::AlignVCenter;
break;
case 2:
col->flags = Qt::ItemFlag::ItemIsEditable;
col->alignment = Qt::AlignRight | Qt::AlignVCenter;
break;
case 3:
col->flags = Qt::ItemFlag::ItemIsEditable;
default:
break;
}
col->name = str;
col->tag.index = index++;
}
Connecting & Handling of Signals
Connection of only 4 signals.
connect(_listModel, &ListModel::getRowData, this, &ListViewWindow::getListData);
connect(_listModel, &ListModel::setRowData, this, &ListViewWindow::setListData);
connect(_listModel, &ListModel::contextMenuRequested, this, &ListViewWindow::popupMenu);
connect(_listModel, &ListModel::currentRowChanged, this, &ListViewWindow::rowChanged);
Getting and Setting Data
The signal to get data is implemented like this.
void ListViewWindow::getListData(const QModelIndex& index, int role, QVariant& data)
{
switch (role)
{
case CommonItemDelegate::TypeRole:
if (index.column() == 2)
data = CommonItemDelegate::etDoubleSpinBox;
break;
case Qt::ItemDataRole::EditRole:
{
switch (index.column())
{
case 2:
data = _data.at(index.row()).amount;
break;
case 3:
data = _data.at(index.row()).description;
break;
default:;
}
}
break;
case Qt::ItemDataRole::DisplayRole:
{
switch (index.column())
{
case 0:
data = index.row() + 1;
return;
case 1:
data = _data.at(index.row()).name;
break;
case 2:
data = _data.at(index.row()).amount;
break;
case 3:
data = _data.at(index.row()).description;
break;
default:;
}
}
break;
case Qt::ItemDataRole::DecorationRole:
if (index.column() > 0)
{
if (index.column() == 2)
{
data = Resource::getSvgIcon(
Resource::getSvgIconResource(Resource::Icon::Warning), _data.at(index.row()).amount >= 0.0 ? QColorConstants::Green : QColorConstants::Red
);
}
else
{
std::array<Resource::Icon, 3> icons{Resource::Icon::Form, Resource::Icon::Application, Resource::Icon::FloppyDisk};
data = Resource::getSvgIcon(Resource::getSvgIconResource(icons[index.column() % icons.size()]), QPalette::ColorRole::WindowText);
}
}
break;
case Qt::ItemDataRole::CheckStateRole:
if (index.row() < _data.count())
data = _data.at(index.row()).selected ? Qt::Checked : Qt::Unchecked;
break;
default:
break;
}
}
The signal to set data is implemented like this and is used to set the checkbox (selected flag).
void ListViewWindow::setListData(const QModelIndex& index, int role, const QVariant& data)
{
if (data.isValid())
{
switch (role)
{
case Qt::ItemDataRole::CheckStateRole:
_data[index.row()].selected = data.toInt() == Qt::Checked;
break;
case Qt::ItemDataRole::EditRole:
if (index.column() == 2)
_data[index.row()].amount = data.toDouble();
else if (index.column() == 3)
_data[index.row()].description = data.toString();
break;
default:
break;
}
}
}
The properties on the QTreeView are set when attached to use a request for a context menu signal.
void ListViewWindow::popupMenu(const QModelIndex& index, const QPoint& pos)
{
QMenu contextMenu(this);
contextMenu.addAction(ui->actionMoveUp);
contextMenu.addAction(ui->actionMoveDown);
contextMenu.addAction(ui->actionInsertRow);
contextMenu.addAction(ui->actionRemoveRow);
contextMenu.exec(_listModel->getTreeView()->mapToGlobal(pos));
}
Inserting & Removing Rows
This shows how a row is inserted and is scrolled in to the visible part of the list.
void ListViewWindow::insertRow()
{
auto index = _listModel->currentIndex();
int row = index.isValid() ? index.row() + 1 : 0;
_listModel->insertRows(row, 1);
if (_dataSource.count())
{
_data.insert(row, std::move(_dataSource.takeAt(row)));
index = _listModel->index(row);
_listModel->scrollTo(index);
_listModel->setCurrentIndex(index);
}
}
This shows how a row is removed.
void ListViewWindow::removeRow()
{
auto index = _listModel->currentIndex();
if (index.isValid())
{
_data.removeAt(index.row());
_listModel->removeRow(index);
}
}
Moving Rows
To move a row up and keep it selected.
void ListViewWindow::moveRowUp()
{
auto index = _listModel->currentIndex();
if (index.isValid())
{
auto row = index.row();
if (_data.count() > 1 && row > 0)
{
_listModel->moveRow(row, row - 1);
_data.move(row, row - 1);
_listModel->scrollTo(index);
}
}
}
To move a row down and keep it selected.
void ListViewWindow::moveRowDown()
{
auto index = _listModel->currentIndex();
if (index.isValid())
{
auto row = index.row();
if (_data.count() > 1 && row + 1 < _data.count())
{
_listModel->moveRow(row, row + 1);
_data.move(row, row + 1);
_listModel->scrollTo(index);
}
}
}