QTableView + QAbstractTableModel: Move rows via drag'n'drop - c++

I have a simple QAbstractTableModel-based model and a QTableView for it.
My aim is simple as well: allow to move/reorder rows via drag'n'drop. Notes:
D'n'd changes inside QTableView should be reflected in my model;
D'n'd supposed to be internal - movement should be performed only inside my view, no external MIME exports;
I want to drag and drop whole row. Separate items should not be dragged or dropped;
Dragging horizontal header is not a suitable solution for me because I want headers to be hided and because I want to let user to grab row at any place to drag it;
I'm reeeally close to my aim. But still it doesn't work as I expect. Now I can drag rows, but seems that any cell can accept a drop, although I've specified Qt::ItemIsDropEnabled only for a global table's parent and do not specify this flag for actual table items because I do not want to drop to them, I want somehow to drop "between the rows", just to perform row movement. Because table items for some reason can accept drops I get curious behaviour: if a drop to first cell of any row, I achieve exactly what I want: my row moves correctly. But if I drop to nonfirst cell of any row, it goes totally wrong. But it's better to show a pic of what happens here:
My code (minimal sample that has exactly my problem):
main.cpp
void setupView(QTableView &t)
{
t.verticalHeader()->hide();
t.horizontalHeader()->hide();
t.horizontalHeader()->setStretchLastSection(true);
t.setSelectionBehavior(QAbstractItemView::SelectRows);
t.setSelectionMode(QAbstractItemView::SingleSelection);
t.setDragEnabled(true);
t.setDropIndicatorShown(true);
t.setAcceptDrops(true);
t.viewport()->setAcceptDrops(true);
t.setDefaultDropAction(Qt::MoveAction);
t.setDragDropMode(QTableView::InternalMove);
t.setDragDropOverwriteMode(false);
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QMainWindow w;
QTableView *table = new QTableView(&w);
setupView(*table);
table->setModel(new TableModel);
w.setCentralWidget(table);
w.show();
return a.exec();
}
tablemodel.cpp
#include "tablemodel.h"
TableModel::TableModel()
{
// m_data is a QList<QStringList>
m_data = {
{"Name", "Kelly"},
{"Age", "19"},
{"Gender", "Female"},
};
}
int TableModel::rowCount(const QModelIndex &parent) const {
return m_data.size();
}
int TableModel::columnCount(const QModelIndex &parent) const {
return 2;
}
QVariant TableModel::data(const QModelIndex &i, int r) const
{
return (r == Qt::DisplayRole) ? m_data[i.row()][i.column()] : QVariant();
}
QVariant TableModel::headerData(int section, Qt::Orientation orientation, int r) const
{
return QVariant();
}
Qt::ItemFlags TableModel::flags(const QModelIndex &index) const
{
Qt::ItemFlags f = Qt::ItemIsEnabled | Qt::ItemIsSelectable
| Qt::ItemIsEditable | Qt::ItemIsDragEnabled;
if(!index.isValid()) {
f |= Qt::ItemIsDropEnabled;
}
return f;
}
Qt::DropActions TableModel::supportedDropActions() const
{
return Qt::MoveAction | Qt::CopyAction;
}
bool TableModel::setData(const QModelIndex &i, const QVariant &v, int r)
{
if(r == Qt::EditRole || r == Qt::DisplayRole) {
m_data[i.row()][i.column()] = v.toString();
return true;
}
return false;
}
bool TableModel::setItemData(const QModelIndex &i, const QMap<int, QVariant> &roles)
{
if(!roles.contains(Qt::EditRole) && !roles.contains(Qt::DisplayRole)) {
return false;
}
m_data[i.row()][i.column()] = roles[Qt::DisplayRole].toString();
return true;
}
bool TableModel::insertRows(int row, int count, const QModelIndex &parent)
{
beginInsertRows(QModelIndex(), row, row + count - 1);
for(int i = 0; i<count; ++i) {
m_data.insert(row, QStringList({"", ""}));
}
endInsertRows();
return true;
}
bool TableModel::removeRows(int row, int count, const QModelIndex &parent)
{
beginRemoveRows(QModelIndex(), row, row + count - 1);
for(int i = 0; i<count; ++i) {
m_data.removeAt(row);
}
endRemoveRows();
return true;
}
bool TableModel::moveRows(const QModelIndex &srcParent, int srcRow, int count,
const QModelIndex &dstParent, int dstChild)
{
beginMoveRows(QModelIndex(), srcRow, srcRow + count - 1, QModelIndex(), dstChild);
for(int i = 0; i<count; ++i) {
m_data.insert(dstChild + i, m_data[srcRow]);
int removeIndex = dstChild > srcRow ? srcRow : srcRow+1;
m_data.removeAt(removeIndex);
}
endMoveRows();
return true;
}
Please, give me some hint, what is wrong with model or view setup now.
UPD
For those who is interested in the solution:
bool TableModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
Q_UNUSED(parent);
Q_UNUSED(column);
if(row == -1) {
row = rowCount();
}
return QAbstractTableModel::dropMimeData(data, action, row, 0, parent);
}

You should add dropMimeData method to your model and implement it properly. If the drop on the first column works fine for you, you could probably simply call QAbstractItemModel::dropMimeData from inside your model's dropMimeData with column parameter equal to 0 regardless of which column the drop was actually made on.

Nitpick: These two lines are not necessary:
t.setAcceptDrops(true);
t.viewport()->setAcceptDrops(true);

Related

How to create a QTreeView that can handle very large amount of data?

I'm adding data loaded form a file on disk to a QTreeView , the file contains more than 60.000 rows and new 'rows'/data will be constantly added to it while its being executed, so each time the GUI is reloaded/reopened the file will contain more data.
I tried to load this data using another thread just to figure out that QAbstractItemModel is not thread safe.
Then I tried to created a 'lazy load' subclass of a QAbstractItemModel , that 'process' the items as they get visible in the
QTreeview.
At the beginning it worked great, as it doesn't freeze the entire GUI for like 10~ seconds at runtime anymore.
However when i scroll the QTreeView it get frozen for many seconds, i think its 'loading' the data.
I was reading the documentation of QAbstractItemModel and see that it have these two functions: fetchMore() and canFetchMore(), to control how the data is loaded, but i didn't understand how to use it and if they could help in this case.
How I could dynamically load/unload the data as it get visible in the QTreeView? and free the memory of the rows that are not visible/not visible anymore, instead of having the entire file loaded into the memory.
Here is what I already did, using Qt 6.4.
Reproducible example:
#include "treeview.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindowClass())
{
ui->setupUi(this);
QGridLayout* layout = new QGridLayout(this);
ui->centralWidget->setLayout(layout);
auto treeView = new TreeView(this);
layout->addWidget(treeView);
QShortcut *shortcut = new QShortcut(QKeySequence(Qt::Key_F2), this);
connect(shortcut, &QShortcut::activated, [=] {
treeView->setInfo();
qDebug() << "rowCount: " << treeView->model->rowCount();
});
}
treeview.h
class LazyLoadingModel : public QAbstractItemModel
{
Q_OBJECT
public:
LazyLoadingModel(QObject *parent = nullptr) : QAbstractItemModel(parent) {}
int rowCount(const QModelIndex &parent = QModelIndex()) const override
{
return m_rows.count();
}
int columnCount(const QModelIndex &parent = QModelIndex()) const override
{
return m_columns.count();
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
{
if (!index.isValid())
return QVariant();
int row = index.row();
int column = index.column();
if (row >= m_rows.count() || column >= m_columns.count())
return QVariant();
if (role == Qt::DisplayRole)
{
return m_rows[row][column];
}
return QVariant();
}
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override
{
if (!index.isValid())
return false;
int row = index.row();
int column = index.column();
if (row >= m_rows.count() || column >= m_columns.count())
return false;
if (role == Qt::EditRole)
{
m_rows[row][column] = value.toString();
emit dataChanged(index, index);
return true;
}
return false;
}
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override
{
if (!parent.isValid())
return createIndex(row, column);
else
return QModelIndex();
}
QModelIndex parent(const QModelIndex &index) const override
{
return QModelIndex();
}
void setColumns(const QStringList &columns)
{
m_columns = columns;
}
void addData(const QVector<QStringList> &row_info)
{
beginInsertRows(QModelIndex(), m_rows.count(), m_rows.count() + row_info.count() - 1);
for (const auto &row : row_info)
m_rows.append(row);
endInsertRows();
}
private:
QStringList m_columns;
QList<QStringList> m_rows;
};
class TreeView : public QTreeView {
Q_OBJECT
public:
LazyLoadingModel* model = new LazyLoadingModel();
TreeView(QWidget* parent = 0) : QTreeView(parent) {
setModel(model);
}
void setInfo() {
QElapsedTimer timer;
timer.start();
model->setColumns({ "Column 0", "Column 1", "Column 2", "Column 3" });
//
// Load the data from disk and parse it into a qvector
// or:
// Create some random data, just to debug the model:
QVector<QStringList> info{};
QStringList list;
for (size_t i = 0; i < 60000; i++) {
list = {};
QString str = QString::number(i);
for (size_t i = 0; i < 4; i++)
list << str;
info.emplace_back(list);
}
model->addData(info);
qint64 elapsed = timer.elapsed();
qDebug() << "[T] Elapsed time (ms):" << elapsed;
}
};

Qt 5.15 - QSortFilterProxyModel with QAbstractTableModel crashing on dataChanged signal

I have implemented a custom QAbstractTableModel and I have run it through the QAbstractItemModelTester and there are no more issues in my model. However, I am now trying to implement sorting through a QSortFilterProxyModel and I can't seem to get anything working at all.
void RDMSensorModels::UpdateDevice(ArtNet::ArtRdmDevice* rdmDev, const RDM::RDMProcessor::RDMDeviceModel& model, int pid) {
if (s_RequiredPIDs.contains(pid)) {
for (int i = 0; i < m_RDMDevices.size(); i++) {
if (m_RDMDevices[i] == rdmDev) {
emit dataChanged(createIndex(i, 0), createIndex(i, columnCount() - 1));
return;
}
}
}
}
This is the function, which emits the models dataChanged signal and I dont think there is a problem here, but after this signal is emitted the program crashes inside QSortFilterProxyModels internal dataChanged handler
As I can't embed pictures in my questions yet, here is a link to where the debugger breaks inside QSortFilterProxyModel
The weirdest thing about this is, that no matter what I pass to the dataChanged signal, the proxy_columns inside QSortFilterProxyModel is always empty.
Here you can see in the debugger, that the container is empty
If it's any help, here is my QSortFilterProxyModel implementation, its completely empty basically.
class RDMSensorSortFilterProxyModel final : public QSortFilterProxyModel {
enum SortValue {
MANUFACTUER_MODEL,
UNIVERSE_DMXADDRESS,
};
public:
RDMSensorSortFilterProxyModel(RDMSensorModels *sourceModel, QObject *parent = nullptr) : QSortFilterProxyModel(parent) {
setSourceModel(sourceModel);
}
int SortIndex();
void SetSortIndex(int value);
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
private:
SortValue m_SortValue = MANUFACTUER_MODEL;
};
int RDMSensorSortFilterProxyModel::SortIndex() { return m_SortValue; }
void RDMSensorSortFilterProxyModel::SetSortIndex(int value) {
m_SortValue = static_cast<SortValue>(value);
invalidate();
}
bool RDMSensorSortFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const { return true; }
bool RDMSensorSortFilterProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right) const {
auto leftDeviceManufacturer = sourceModel()->data(left, RDMSensorModels::Roles::DeviceManufacturerRole).toString();
auto rightDeviceManufacturer = sourceModel()->data(right, RDMSensorModels::Roles::DeviceManufacturerRole).toString();
auto same = QString::compare(leftDeviceManufacturer, rightDeviceManufacturer, Qt::CaseInsensitive) == 0;
return same;
}
Here are my QAbstractTableModel reimplemented functions
QVariant RDMSensorModels::headerData(int section, Qt::Orientation orientation, int role) const {
if (section < 1)
return QString("Device");
else
return QString("Sensor %1").arg(section);
}
int RDMSensorModels::rowCount(const QModelIndex& parent) const {
if (parent.isValid())
return 0;
return m_RDMDevices.count();
}
int RDMSensorModels::columnCount(const QModelIndex& parent) const {
if (parent.isValid())
return 0;
return m_ColumnCount;
}
QVariant RDMSensorModels::data(const QModelIndex& index, int role) const {
if (!index.isValid())
return {};
int deviceIndex = index.row();
switch (role) {
case SensorGraphReadingsRole: {
auto& readings = m_RDMDevices[deviceIndex]->Sensors()[index.column() - 1]->LastReadings();
auto maxElement = f_SensorMaxReading(index.row(), index.column() - 1);
auto minElement = f_SensorMinReading(index.row(), index.column() - 1);
QVariantList values;
for (int i = 0; i < readings.size(); i++) {
values.push_back(Utils::Math::map(readings[i], maxElement, minElement, 0, 1));
}
return values;
}
case SensorMinReadingRole: return f_SensorMinReading(deviceIndex, index.column() - 1);
case SensorMaxReadingRole: return f_SensorMaxReading(deviceIndex, index.column() - 1);
case DeviceUIDRole: return f_DeviceUIDString(deviceIndex);
case DeviceUniverseRole: return f_DeviceUniverseString(deviceIndex);
case DeviceLabelRole: return f_DeviceLabelString(deviceIndex);
case DeviceManufacturerRole: return f_DeviceManufacturerString(deviceIndex);
case DeviceModelRole: return f_DeviceModelString(deviceIndex);
case SensorRangeMaxValueRole: return f_SensorRangeMaxValueString(deviceIndex, index.column() - 1);
case SensorRangeMinValueRole: return f_SensorRangeMinValueString(deviceIndex, index.column() - 1);
case SensorCurrentValueRole: return f_SensorCurrentValueString(deviceIndex, index.column() - 1);
case SensorNameRole: return f_SensorNameString(deviceIndex, index.column() - 1);
case SensorCurrentValueNormalizedRole: return f_SensorCurrentValueNormalized(deviceIndex, index.column() - 1);
case SensorMinNormalValueNormalizedRole: return f_SensorMinNormalValueNormalized(deviceIndex, index.column() - 1);
case SensorMaxNormalValueNormalizedRole: return f_SensorMaxNormalValueNormalized(deviceIndex, index.column() - 1);
case SensorValidRole: {
auto sensorCount = f_DeviceSensorCount(deviceIndex);
return sensorCount && (index.column() <= sensorCount);
}
default: return {};
}
}
QHash<int, QByteArray> RDMSensorModels::roleNames() const { return s_RoleNames; }
Any help would be greatly appreciated!
Well it turns out, trying to replicate the issue on a smaller scale made my brain neurons fire enough, that i figured out the problem. My model column count can change and it does change, however, I had not written anything that notifies about column count changing beginRemoveColumns and endRemoveColumns and beginInsertColumns and endInsertColumns. I implemented those in my code like so
void RDMSensorModels::UpdateColumnCount() {
int sensorCount = 1;
for (auto device : m_RDMDevices) {
int deviceSensorCount = device->Sensors().size();
if (deviceSensorCount + 1 > sensorCount)
sensorCount = deviceSensorCount + 1; // +1 for device column
}
if (m_ColumnCount != sensorCount) {
if (m_ColumnCount < sensorCount) {
beginInsertColumns(QModelIndex(), m_ColumnCount, sensorCount - 1);
m_ColumnCount = sensorCount;
endInsertColumns();
} else {
beginRemoveColumns(QModelIndex(), sensorCount, m_ColumnCount - 1);
m_ColumnCount = sensorCount;
endRemoveColumns();
}
}
}
And the proxy model now works as expected. Hopefully this helps anyone else having issues with QSortFilterProxyModel.
It's interesting to note that the QAbstractItemModelTester did not catch this problem as I would have expected it to as my model changes column count depending on the largest sensor count for devices currently found.

Printing QTableView with vertical and horizontal headers

I have a program that gets data from SQL (using QSQLQuery) and puts the result into a QTableView.
For user ease of reading I needed to transpose the SQL output (swap rows and columns) but couldn't achieve this easily in SQL (even using PIVOT). Instead I used a proxy model in Qt which works great.
Problem is I need to be able to print the entire QTableView. I can print the contents of the table with the horizontal headers but for the life of me, cant work out how to print the vertical headers (technically the first column since each row has a label due to the transposition). Qt doesn't recognize the first 'column' as being a column of row-names, nor do I know how to get it to treat it as vertical headers for printing.
Proxy model code:
class Horizontal_proxy_model : public QAbstractProxyModel {
public:
Horizontal_proxy_model(QObject * parent = 0);
QModelIndex mapToSource(const QModelIndex &proxyIndex) const;
QModelIndex mapFromSource(const QModelIndex &sourceIndex) const;
QModelIndex index(int row, int column, const QModelIndex &parent =
QModelIndex()) const;
QModelIndex parent(const QModelIndex &child) const;
int rowCount(const QModelIndex &parent) const;
int columnCount(const QModelIndex &parent) const;
QVariant headerData(int section, Qt::Orientation orientation, int role)
const;
};
Horizontal_proxy_model::Horizontal_proxy_model(QObject *parent) :
QAbstractProxyModel(parent) {
}
QModelIndex Horizontal_proxy_model::mapToSource(const QModelIndex
&proxyIndex) const {
if (sourceModel()) {
return sourceModel()->index(proxyIndex.column(), proxyIndex.row());
} else {
return QModelIndex();
}
}
QModelIndex Horizontal_proxy_model::mapFromSource(const QModelIndex
&sourceIndex) const {
return index(sourceIndex.column(), sourceIndex.row());
}
QModelIndex Horizontal_proxy_model::index(int row, int column, const
QModelIndex &) const {
return createIndex(row, column, (void*) 0);
}
QModelIndex Horizontal_proxy_model::parent(const QModelIndex &) const {
return QModelIndex();
}
int Horizontal_proxy_model::rowCount(const QModelIndex &) const {
return sourceModel() ? sourceModel()->columnCount() : 0;
}
int Horizontal_proxy_model::columnCount(const QModelIndex &) const {
return sourceModel() ? sourceModel()->rowCount() : 0;
}
QVariant Horizontal_proxy_model::headerData(
int section, Qt::Orientation orientation, int role) const {
if (!sourceModel()) { return QVariant(); }
Qt::Orientation new_orientation = orientation == Qt::Horizontal ?
Qt::Vertical : Qt::Horizontal;
return sourceModel()->headerData(section, new_orientation, role);
}
Here are the basics of how I'm trying to print;
void Snapshot_finance::on_pushButton_print_clicked()
{
QString html;
html = "<html><body><table border=\"0\">";
for(int row = 0; row < ui->tableView->model()->rowCount(); row++) {
html += "<tr>";
for(int column = 0; column < ui->tableView->model()->columnCount();
column++) {
QString data = ui->tableView->model()->data(ui->tableView->model()->
index(row, column), Qt::DisplayRole).toString();
html += "<td>" + data + "</td>";
}
html += "</tr>";
}
html += "</table></body></html>";
QPrinter printer;
QPrintDialog *dialog = new QPrintDialog(&printer);
if(dialog->exec() == QDialog::Accepted) {
QTextDocument document;
document.setHtml(html);
document.print(&printer);
}
}
Really appreciate any help or advice!
If you want to print the headers you must add them as shown in the following code:
void Snapshot_finance::on_pushButton_print_clicked()
const QString format("<td>%1</td>");
QString html;
QAbstractItemModel *md = ui->tableView->model();
html = "<html><body><table border=\"0\">";
html += "<td></td>";
for(int column = 0; column < md->columnCount();
column++) {
QString data = md->headerData(column, Qt::Horizontal, Qt::DisplayRole).toString();
html += format.arg(data);
}
for(int row = 0; row < md->rowCount() ; row++) {
html += "<tr>";
QString data = md->headerData(row, Qt::Vertical, Qt::DisplayRole).toString();
html += format.arg(data);
for(int column = 0; column < md->columnCount();
column++) {
QString data = md->index(row, column).data(Qt::DisplayRole).toString();
html += format.arg(data);
}
html += "</tr>";
}
html += "</table></body></html>";
QPrinter printer;
QPrintDialog *dialog = new QPrintDialog(&printer);
if(dialog->exec() == QDialog::Accepted) {
QTextDocument document;
document.setHtml(html);
document.print(&printer);
}
}
TableView:
Part of PDF
The code that is implemented for the test can be found in the following link.

My Qt QAbstractProxyModel crashes with ChartView but not with TableView?

I have a custom QAbstractTableModel and a proxy model which flips the axes of the first model. With the table this works, I just switch the model. When I switch to my proxy model for the chart it crashes at the point where I assign the rows to the QHXYModelMapper. What have I screwed up?
This is the table model:
#include "tablemodel.h"
TableModel::TableModel(QObject *parent) :
QAbstractTableModel(parent)
{
}
int TableModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_data.count();
}
int TableModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
if(m_data.count() < 1)
{
return 0;
}
return m_data[0].count();
}
QVariant TableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role != Qt::DisplayRole)
return QVariant();
if (orientation == Qt::Horizontal) {
if (section % 2 == 0)
return "x";
else
return "y";
} else {
return QString("%1").arg(section + 1);
}
}
QVariant TableModel::data(const QModelIndex &index, int role) const
{
if (!role == Qt::DisplayRole)
{
return QVariant();
}
return m_data[index.row()].at(index.column());
}
bool TableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.isValid() && role == Qt::EditRole) {
m_data[index.row()].replace(index.column(), value.toDouble());
emit dataChanged(index, index);
return true;
}
return false;
}
void TableModel::appendRow(QVector<double> row)
{
emit layoutAboutToBeChanged();
emit beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_data.append(row);
emit endInsertRows();
emit layoutChanged();
}
void TableModel::clear()
{
for(int i = 0; i < m_data.count(); ++i)
{
m_data[i].clear();
}
m_data.clear();
}
This is the proxy implementation:
HorizontalProxyModel::HorizontalProxyModel(QObject *parent) : QAbstractProxyModel(parent)
{
}
QModelIndex HorizontalProxyModel::mapToSource(const QModelIndex &proxyIndex) const
{
if (sourceModel()) {
return sourceModel()->index(proxyIndex.column(), proxyIndex.row());
} else {
return QModelIndex();
}
}
QModelIndex HorizontalProxyModel::mapFromSource(const QModelIndex &sourceIndex) const
{
return index(sourceIndex.column(), sourceIndex.row());
}
QModelIndex HorizontalProxyModel::index(int row, int column, const QModelIndex &) const
{
return createIndex(row, column, (void*) 0);
}
QModelIndex HorizontalProxyModel::parent(const QModelIndex &) const
{
return QModelIndex();
}
int HorizontalProxyModel::rowCount(const QModelIndex &) const
{
return sourceModel() ? sourceModel()->columnCount() : 0;
}
int HorizontalProxyModel::columnCount(const QModelIndex &) const
{
return sourceModel() ? sourceModel()->rowCount() : 0;
}
QVariant HorizontalProxyModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (!sourceModel()) { return QVariant(); }
Qt::Orientation new_orientation = orientation == Qt::Horizontal ?
Qt::Vertical : Qt::Horizontal;
return sourceModel() ? sourceModel()->headerData(section, new_orientation, role) : 0;
}
QVariant HorizontalProxyModel::data(const QModelIndex &index) const
{
qDebug() << "h model data";
return sourceModel() ? sourceModel()->data(sourceModel()->index(index.column(), index.row())) : 0;
}
And this is where I assign the model:
void MainWindow::plot(QAbstractItemModel* m)
{
QChart* chart = new QChart;
qDebug() << m->rowCount() << " " << m->columnCount();
for(int row = 0; row < m->rowCount(); ++row)
{
QLineSeries *series = new QLineSeries;
QHXYModelMapper* mapper = new QHXYModelMapper;
QString name = "Row " + QString::number(row);
series->setName(name);
mapper->setModel(m);
mapper->setSeries(series);
mapper->setXRow(row); //crashes here if proxy model
mapper->setYRow(row);
chart->addSeries(series);
}
chart->createDefaultAxes();
QChart* oldChart = chartView->chart();
chartView->setChart(chart);
oldChart->deleteLater();
}
EDIT
Some more info...
Looking at the debugger, it seems that the index being created in the ProxyModel and passed to the original model is -1/-1 (invalid). Does line 9 in this debug output mean that the base class QAbstractProxyModel::data() is being called, instead of the one from my derived proxy model? If so, why?
1 __pthread_kill
0x7fff91eaff06
2 pthread_kill 0x7fff907204ec
3 abort 0x7fff876cd6df
4 qt_message_fatal(QtMsgType, QMessageLogContext const&, QString const&) 0x100ac3e79
5 QMessageLogger::fatal(const char *, ...) const 0x100ac5847
6 qt_assert_x(const char *, const char *, const char *, int) 0x100ac0682
7 QList<QVector<double>>::operator[](int) const qlist.h 541 0x10000fced
8 TableModel::data(QModelIndex const&, int) const tablemodel.cpp 46 0x10000f8f1
9 QAbstractProxyModel::data(QModelIndex const&, int) const 0x100c4c28b
10 QtCharts::QXYModelMapperPrivate::valueFromModel(QModelIndex) 0x10171dfb3
11 QtCharts::QXYModelMapperPrivate::initializeXYFromModel() 0x10171d92f
12 QtCharts::QHXYModelMapper::setYRow(int) 0x101720bf4
13 MainWindow::plot(QAbstractItemModel *)
UDPATE: FIX
So, the fix I have right now is to call QHXYModelMapper::setColumnCount() manually, like so:
mapper->setModel(m);
mapper->setSeries(series);
mapper->setColumnCount(m->columnCount());
mapper->setXRow(row);
mapper->setYRow(row);
The docs seem to imply that the default value will use the total number of columns in the model, if I don't explicitly set this:
http://doc.qt.io/qt-5/qhxymodelmapper.html#columnCount-prop
Talked it over on the Qt forums a bit, this appears to be a bug, where the default value for the HXYModelMapper columnCount property does not actually retrieve the proper columnCount from the model. The workaround is to call setColumnCount yourself and set it to the columnCount of your model.
mapper->setModel(m);
mapper->setSeries(series);
mapper->setColumnCount(m->columnCount());
mapper->setXRow(row);
mapper->setYRow(row);
Bug report: https://bugreports.qt.io/browse/QTBUG-57342

How to get first item as default item in combo box delegate?

I am using QAbstractTableModel and ComboBoxItemDelegate which inherits QStyledItemDelegate. I am able to populate my data in combo boxes in table view but by default nothing is displayed but after clicking correct data is visible in dropdown. How can i set first item as a default item and display it?
My Combox delegate:
class ComboBoxItemDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
ComboBoxItemDelegate(QObject* parent = 0);
~ComboBoxItemDelegate();
virtual QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const;
virtual void setEditorData(QWidget* editor, const QModelIndex& index) const;
virtual void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const;
virtual void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const;
};
Create editor function:
QWidget* ComboBoxItemDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
QComboBox* cb = new QComboBox(parent);
QStringList destList = SFilterEditorData::instance().getDestinationList();
cb->addItem(QString("All"));
for (int i = 0; i < destList.size(); i++)
{
cb->addItem(destList.at(i));
}
cb->setEditable(true);
return cb;
}
set Editor function:
void ComboBoxItemDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const
{
if (QComboBox* cb = qobject_cast<QComboBox*>(editor)) {
//QString currentText = index.data(Qt::EditRole).toString();
QString currentText = index.model()->data(index, Qt::DisplayRole).toString();
int cbIndex = cb->findText(currentText);
// if it is valid, adjust the combobox
if (cbIndex >= 0)
cb->setCurrentIndex(cbIndex);
}
else {
QStyledItemDelegate::setEditorData(editor, index);
}
}
Inserting rows in my model:
bool STableModel::insertRows(int position, int rows, const QModelIndex &parent)
{
int columns = columnCount();
beginInsertRows(parent, position, position + rows - 1);
for (int row = 0; row < rows; ++row) {
QStringList items;
for (int column = 0; column < columns; ++column)
{
items.append(""); // This might be the reason for this issue.
}
rowList.insert(position, items);
}
endInsertRows();
return true;
}
Setting data in model:
QVariant STableModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (role == Qt::DisplayRole)
return rowList[index.row()][index.column()];
else if (role == Qt::CheckStateRole && index.column() == 0)
{
int status = SFilterEditorData::instance().getStatus(index.row());
if (status)
return Qt::Checked;
else
return Qt::Unchecked;
}
else
return QVariant();
}
bool STableModel::setData(const QModelIndex &index,
const QVariant &value, int role)
{
if (!index.isValid() /*|| role != Qt::EditRole*/)
return false;
if (role == Qt::CheckStateRole)
{
if ((Qt::CheckState)value.toInt() == Qt::Checked)
{
SFilterEditorData::instance().setStatus(index.row(),1);
return true;
}
else
{
SFilterEditorData::instance().setStatus(index.row(), 0);
return true;
}
}
i have referred this link: http://www.qtcentre.org/threads/39194-setEditorData()-for-QComboBox-Delegate