QTableView, clearSpans, setSpan and resizeColumnsToContents: How to do it and weird crash
-
Hi all :-)
I'm implementing a model/view interface for a table. The table contains row and column spans.
As far as I got it, there's currently no way to pass spans directly from the model to the view. So I played around how to do it. I though that reimplementing
dataChanged
would be the right place to do so.My first attempt was:
void DrawView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles) { QTableView::dataChanged(topLeft, bottomRight, roles); clearSpans(); for (int row = 0; row < m_model->rowCount(); row += 2) { setSpan(row, 0, 2, 1); if (m_model->isTableEmpty(row)) { setSpan(row, 1, 2, 2); } } resizeColumnsToContents(); }
This worked:
but as soon as there are not only row spans, but also column ones, the resizing doesn't work as expected anymore:
So I put the
setSpan
and resizing stuff into aQTimer::singleShot
:void DrawView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles) { QTableView::dataChanged(topLeft, bottomRight, roles); QTimer::singleShot(0, [this] { clearSpans(); for (int row = 0; row < m_model->rowCount(); row += 2) { setSpan(row, 0, 2, 1); if (m_model->isTableEmpty(row)) { setSpan(row, 1, 2, 2); } } resizeColumnsToContents(); }); }
This made it work also for column spans:
But: (and now, it gets weird ;-) in some cases, the program now crashes when being closed. It's a quite complicated code base, and it's very hard to track this down … however, the program contains network functionality. And as soon as it becomes a server or a client (a tcp server is started or it connects to one), the program crashes when closing:
#0 0x00007ffff7d1f310 in ?? () from /usr/lib64/libQt5Widgets.so.5 #1 0x00007ffff7d1f3d1 in QTableView::clearSpans() () from /usr/lib64/libQt5Widgets.so.5 #2 0x00005555556d8b2f in operator() (__closure=0x55555684afd0) at /home/tobias/tmp/git/muckturnier/src/DrawOverviewPage/DrawView.cpp:37 #3 0x00005555556d8e55 in QtPrivate::FunctorCall<QtPrivate::IndexesList<>, QtPrivate::List<>, void, DrawView::dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)::<lambda()> >::call(struct {...} &, void **) (f=..., arg=0x5555563a7fd8) at /usr/include/qt5/QtCore/qobjectdefs_impl.h:146 #4 0x00005555556d8e27 in QtPrivate::Functor<DrawView::dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)::<lambda()>, 0>::call<QtPrivate::List<>, void>(struct {...} &, void *, void **) (f=..., arg=0x5555563a7fd8) at /usr/include/qt5/QtCore/qobjectdefs_impl.h:256 #5 0x00005555556d8df6 in QtPrivate::QFunctorSlotObject<DrawView::dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)::<lambda()>, 0, QtPrivate::List<>, void>::impl(int, QtPrivate::QSlotObjectBase *, QObject *, void **, bool *) (which=1, this_=0x55555684afc0, r=0x55555596d0f0, a=0x5555563a7fd8, ret=0x0) at /usr/include/qt5/QtCore/qobjectdefs_impl.h:443 #6 0x00007ffff6e4efc4 in QObject::event(QEvent*) () from /usr/lib64/libQt5Core.so.5 #7 0x00007ffff7a441de in QApplicationPrivate::notify_helper(QObject*, QEvent*) () from /usr/lib64/libQt5Widgets.so.5 #8 0x00007ffff6e23ef8 in QCoreApplication::notifyInternal2(QObject*, QEvent*) () from /usr/lib64/libQt5Core.so.5 #9 0x00007ffff6e27463 in QCoreApplicationPrivate::sendPostedEvents(QObject*, int, QThreadData*) () from /usr/lib64/libQt5Core.so.5 #10 0x00007ffff6e76023 in ?? () from /usr/lib64/libQt5Core.so.5 #11 0x00007ffff58a0d22 in ?? () from /usr/lib64/libglib-2.0.so.0 #12 0x00007ffff58a3ed7 in ?? () from /usr/lib64/libglib-2.0.so.0 #13 0x00007ffff58a44fc in g_main_context_iteration () from /usr/lib64/libglib-2.0.so.0 #14 0x00007ffff6e75b26 in QEventDispatcherGlib::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) () from /usr/lib64/libQt5Core.so.5 #15 0x00007ffff6e2291b in QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) () from /usr/lib64/libQt5Core.so.5 #16 0x00007ffff6e2ac1d in QCoreApplication::exec() () from /usr/lib64/libQt5Core.so.5 #17 0x00005555555d7995 in main (argc=1, argv=0x7fffffffd6a8) at /home/tobias/tmp/git/muckturnier/src/main.cpp:168
The interesing thing is that this table view doesn't even have anything to do with the network stuff … also I can't figure out what exactly goes wrong reading the above backtrace … no crash when no server runs.
However, I suppose it's simply the wrong way to do it.
After some trying, I ended up just putting the resizing into a timer call and clearing the spans before I update the view, like that:
void DrawView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles) { clearSpans(); QTableView::dataChanged(topLeft, bottomRight, roles); for (int row = 0; row < m_model->rowCount(); row += 2) { setSpan(row, 0, 2, 1); if (m_model->isTableEmpty(row)) { setSpan(row, 1, 2, 2); } } QTimer::singleShot(0, this, &QTableView::resizeColumnsToContents); }
This doesn't cause a crash when closign anymore, and it seems to work.
However, I don't really get it. Can somebody explain to me what's happening here, and tell me if this is the right way to set spans and resize the columns?
Thanks for all hellp and expertise :-)
Cheers, Tobias
-
Hi all :-)
I'm implementing a model/view interface for a table. The table contains row and column spans.
As far as I got it, there's currently no way to pass spans directly from the model to the view. So I played around how to do it. I though that reimplementing
dataChanged
would be the right place to do so.My first attempt was:
void DrawView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles) { QTableView::dataChanged(topLeft, bottomRight, roles); clearSpans(); for (int row = 0; row < m_model->rowCount(); row += 2) { setSpan(row, 0, 2, 1); if (m_model->isTableEmpty(row)) { setSpan(row, 1, 2, 2); } } resizeColumnsToContents(); }
This worked:
but as soon as there are not only row spans, but also column ones, the resizing doesn't work as expected anymore:
So I put the
setSpan
and resizing stuff into aQTimer::singleShot
:void DrawView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles) { QTableView::dataChanged(topLeft, bottomRight, roles); QTimer::singleShot(0, [this] { clearSpans(); for (int row = 0; row < m_model->rowCount(); row += 2) { setSpan(row, 0, 2, 1); if (m_model->isTableEmpty(row)) { setSpan(row, 1, 2, 2); } } resizeColumnsToContents(); }); }
This made it work also for column spans:
But: (and now, it gets weird ;-) in some cases, the program now crashes when being closed. It's a quite complicated code base, and it's very hard to track this down … however, the program contains network functionality. And as soon as it becomes a server or a client (a tcp server is started or it connects to one), the program crashes when closing:
#0 0x00007ffff7d1f310 in ?? () from /usr/lib64/libQt5Widgets.so.5 #1 0x00007ffff7d1f3d1 in QTableView::clearSpans() () from /usr/lib64/libQt5Widgets.so.5 #2 0x00005555556d8b2f in operator() (__closure=0x55555684afd0) at /home/tobias/tmp/git/muckturnier/src/DrawOverviewPage/DrawView.cpp:37 #3 0x00005555556d8e55 in QtPrivate::FunctorCall<QtPrivate::IndexesList<>, QtPrivate::List<>, void, DrawView::dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)::<lambda()> >::call(struct {...} &, void **) (f=..., arg=0x5555563a7fd8) at /usr/include/qt5/QtCore/qobjectdefs_impl.h:146 #4 0x00005555556d8e27 in QtPrivate::Functor<DrawView::dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)::<lambda()>, 0>::call<QtPrivate::List<>, void>(struct {...} &, void *, void **) (f=..., arg=0x5555563a7fd8) at /usr/include/qt5/QtCore/qobjectdefs_impl.h:256 #5 0x00005555556d8df6 in QtPrivate::QFunctorSlotObject<DrawView::dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)::<lambda()>, 0, QtPrivate::List<>, void>::impl(int, QtPrivate::QSlotObjectBase *, QObject *, void **, bool *) (which=1, this_=0x55555684afc0, r=0x55555596d0f0, a=0x5555563a7fd8, ret=0x0) at /usr/include/qt5/QtCore/qobjectdefs_impl.h:443 #6 0x00007ffff6e4efc4 in QObject::event(QEvent*) () from /usr/lib64/libQt5Core.so.5 #7 0x00007ffff7a441de in QApplicationPrivate::notify_helper(QObject*, QEvent*) () from /usr/lib64/libQt5Widgets.so.5 #8 0x00007ffff6e23ef8 in QCoreApplication::notifyInternal2(QObject*, QEvent*) () from /usr/lib64/libQt5Core.so.5 #9 0x00007ffff6e27463 in QCoreApplicationPrivate::sendPostedEvents(QObject*, int, QThreadData*) () from /usr/lib64/libQt5Core.so.5 #10 0x00007ffff6e76023 in ?? () from /usr/lib64/libQt5Core.so.5 #11 0x00007ffff58a0d22 in ?? () from /usr/lib64/libglib-2.0.so.0 #12 0x00007ffff58a3ed7 in ?? () from /usr/lib64/libglib-2.0.so.0 #13 0x00007ffff58a44fc in g_main_context_iteration () from /usr/lib64/libglib-2.0.so.0 #14 0x00007ffff6e75b26 in QEventDispatcherGlib::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) () from /usr/lib64/libQt5Core.so.5 #15 0x00007ffff6e2291b in QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) () from /usr/lib64/libQt5Core.so.5 #16 0x00007ffff6e2ac1d in QCoreApplication::exec() () from /usr/lib64/libQt5Core.so.5 #17 0x00005555555d7995 in main (argc=1, argv=0x7fffffffd6a8) at /home/tobias/tmp/git/muckturnier/src/main.cpp:168
The interesing thing is that this table view doesn't even have anything to do with the network stuff … also I can't figure out what exactly goes wrong reading the above backtrace … no crash when no server runs.
However, I suppose it's simply the wrong way to do it.
After some trying, I ended up just putting the resizing into a timer call and clearing the spans before I update the view, like that:
void DrawView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles) { clearSpans(); QTableView::dataChanged(topLeft, bottomRight, roles); for (int row = 0; row < m_model->rowCount(); row += 2) { setSpan(row, 0, 2, 1); if (m_model->isTableEmpty(row)) { setSpan(row, 1, 2, 2); } } QTimer::singleShot(0, this, &QTableView::resizeColumnsToContents); }
This doesn't cause a crash when closign anymore, and it seems to work.
However, I don't really get it. Can somebody explain to me what's happening here, and tell me if this is the right way to set spans and resize the columns?
Thanks for all hellp and expertise :-)
Cheers, Tobias
@l3u_ said in QTableView, clearSpans, setSpan and resizeColumnsToContents: How to do it and weird crash:
QTimer::singleShot(0, [this]
You should pass a context to this so the lambda/connect gets deleted when the view gets deleted. Never use a connect without a context. Best define QT_NO_CONTEXTLESS_CONNECT to disable them completely.
-
@l3u_ said in QTableView, clearSpans, setSpan and resizeColumnsToContents: How to do it and weird crash:
QTimer::singleShot(0, [this]
You should pass a context to this so the lambda/connect gets deleted when the view gets deleted. Never use a connect without a context. Best define QT_NO_CONTEXTLESS_CONNECT to disable them completely.
@Christian-Ehrlicher Okay, so it's the wrong approach – as I supposed ;-)
I never heard about QT_NO_CONTEXTLESS_CONNECT until now … could you give me a hint about how to pass a context? Or where I can read about this? Thanks in advance!
Edit: Is this this one? https://doc.qt.io/qt-5/qobject.html#connect-5
So that I would not write
QTimer::singleShot(0, [this] { doSomething(); });
but instead
QTimer::singleShot(0, this, [this] { doSomething(); });
so that the current object is defined as the receiver/context?
-
@l3u_ said in QTableView, clearSpans, setSpan and resizeColumnsToContents: How to do it and weird crash:
QTimer::singleShot(0, [this]
You should pass a context to this so the lambda/connect gets deleted when the view gets deleted. Never use a connect without a context. Best define QT_NO_CONTEXTLESS_CONNECT to disable them completely.
@Christian-Ehrlicher
After adding-DQT_NO_CONTEXTLESS_CONNECT
to myCMakeLists.txt
'sadd_definitions()
, I can still compileQTimer::singleShot(0, [this]
? Using Qt 5.15.11? -
@Christian-Ehrlicher
After adding-DQT_NO_CONTEXTLESS_CONNECT
to myCMakeLists.txt
'sadd_definitions()
, I can still compileQTimer::singleShot(0, [this]
? Using Qt 5.15.11?@l3u_ said in QTableView, clearSpans, setSpan and resizeColumnsToContents: How to do it and weird crash:
Using Qt 5.15.11
This was added later. You should have mentioned this in your problem description.
-
@l3u_ said in QTableView, clearSpans, setSpan and resizeColumnsToContents: How to do it and weird crash:
Using Qt 5.15.11
This was added later. You should have mentioned this in your problem description.
@Christian-Ehrlicher Okay, so it seems
QT_NO_CONTEXTLESS_CONNECT
is a Qt 6 thing. However, if I build my stuff using 6.6.1, I still can doQTimer::singleShot(0, [this] { doSomething(); });
Seems like the macro only affects the
connect
template itself, but not aQTimer::singleShot
call? -
Yeah, okay, I think I got it now.
Simply never connect to a lambda as such, but always provide an object that will receive the signal. Otherwise, the catched
this
may already have been deleted, thus causing a segfault.However, my intial question still remains: Is the approach posted above the correct way to update spans and resize columns of a
QTableView
?