Data re-driven testing in Qt - qt

Can I get QTest to use a single data-driving set more than once?
We using Qt's QTest framework for the low-level tests in a project, and using data-driven style where it makes sense.
I've come to a point where I want to do several distinct sets of tests on a single set of test data. The obvious design is
void testcalss::teststuff_data()
{
QTest::addColumn<int>("number");
QTest::addColumn<double>("size");
QTest::newRow("first") << 1 << 3.14;
QTest::newRow("second") << 42 << 2.718;
}
void testclass::teststuff()
{
QFETCH(int, number);
QFETCH(double, value);
// Test one thing about this stuff
QCOMPARE( f(number), value ); // <=== If it fails here ...
// test some other thing
QCOMPARE( g(number), h(value)); // <=== ... it doesn't even try this
}
More or less right out of the documentation, right?
But a small annoyance is the way it shortcuts subsequent tests if an early one fails (notes in the comments). Sometimes this is a desired behavior and sometimes not, but just now I want it to try those second test even if the first one fails.
A possible alternate implementation scheme is
void testcalss::teststuff_data()
{
QTest::addColumn<int>("number");
QTest::addColumn<double>("size");
QTest::newRow("first") << 1 << 3.14;
QTest::newRow("second") << 42 << 2.718;
}
void testclass::teststuff()
{
QFETCH(int, number);
QFETCH(double, value);
QCOMPARE( f(number), value );
}
void testclass::teststuff2()
{
QFETCH(int, number); // <=== No data!
QFETCH(double, value);
QCOMPARE( g(number), h(value));
}
but, of course, the input from teststuff_data() has already been consumed by the time teststuff2 starts.

This trick is to re-invoke the previous code in a context that Qt recognizes as setting up some data. A new _data method that just calls the old one does not require any code duplication.
void testcalss::teststuff_data()
{
QTest::addColumn<int>("number");
QTest::addColumn<double>("size");
QTest::newRow("first") << 1 << 3.14;
QTest::newRow("second") << 42 << 2.718;
}
void testclass::teststuff()
{
QFETCH(int, number);
QFETCH(double, value);
QCOMPARE( f(number), value );
}
void testclass::teststuff2_data() // <=== define a _data method that ...
{
teststuff_data(); // <=== ... re-runs the previous set-up
}
void testclass::teststuff2()
{
QFETCH(int, number);
QFETCH(double, value);
QCOMPARE( g(number), h(value));
}
As usual the test framework is sensitive to the order of method declaration so the header should look like
class testclass: public QObject
{
Q_OBJECT
// c'tor and what not ...
private slots:
void teststuff_data();
void teststuff;
void teststuff2_data();
void teststuff2;
}

Related

QOpenGLWidget's context is null

I am trying to add a QOpenGLWidget to my QGraphicsScene, but the application crashes when I call initializeOpenGLFunctions(). I am pretty sure that the context of OpenGLView is null and that is why it is crashing (provides no logs) for two reasons:
When I print it, it outputs 0x0
When I try to enable QOpenGLDebugLogger it outputs that there is no current context.
I thought that QOpenGLWidget would have an OpenGLContext out of the box. Any idea why the context is not getting set? Am I missing something in my initialization?
QSurfaceFormat format;
format.setDepthBufferSize(24);
format.setStencilBufferSize(8);
format.setVersion(3, 2);
format.setProfile(QSurfaceFormat::CoreProfile);
format.setOption(QSurfaceFormat::DebugContext);
QSurfaceFormat::setDefaultFormat(format);
OpenGLView view = new OpenGLView();
header
class OpenGLView : public QOpenGLWidget, protected QOpenGLFunctions
{
}
#include "OpenGLView.h"
OpenGLView::OpenGLView(QWidget *parent) : QOpenGLWidget(parent) {
initializeGL();
}
void OpenGLView::initializeGL() {
initializeOpenGLFunctions(); // crashes
// ...
}
void OpenGLView::paintGL() {
// ...
}
void OpenGLView::resizeGL(int w, int h) {
// ...
}
It is because you called initializeGL() in the constructor. By that time, the context has not been initialized. The context is first initialized when the widget is shown. Details taken from Qt's source code below:
void QOpenGLWidgetPrivate::initialize()
{
Q_Q(QOpenGLWidget);
if (initialized)
return;
...
QScopedPointer<QOpenGLContext> ctx(new QOpenGLContext);
ctx->setFormat(requestedFormat);
if (shareContext) {
ctx->setShareContext(shareContext);
ctx->setScreen(shareContext->screen());
}
if (Q_UNLIKELY(!ctx->create())) {
qWarning("QOpenGLWidget: Failed to create context");
return;
}
...
context = ctx.take();
initialized = true;
q->initializeGL();
}
bool QOpenGLWidget::event(QEvent *e)
{
Q_D(QOpenGLWidget);
switch (e->type()) {
...
case QEvent::Show: // reparenting may not lead to a resize so reinitalize on Show too
if (d->initialized && window()->windowHandle()
&& d->context->shareContext() != QWidgetPrivate::get(window())->shareContext())
{
// Special case: did grabFramebuffer() for a hidden widget that then became visible.
// Recreate all resources since the context now needs to share with the TLW's.
if (!QCoreApplication::testAttribute(Qt::AA_ShareOpenGLContexts))
d->reset();
}
if (!d->initialized && !size().isEmpty() && window()->windowHandle()) {
d->initialize();
if (d->initialized)
d->recreateFbo();
}
break;
...
}
return QWidget::event(e);
}

pthread is not starting for class instance

NOTE: C++98
Hi, I'm a little new to c++ and I am writing a databaes program and am attempting to start a timer using the boost::asio package using pthread. The aim of the timer is to start after sql queries have been placed inside a buffer, of which will run an execute function if nothing has been received for a period of time. I have managed to get it to compile, but it doesn't look like the pthread instance is starting.
I have called the pthread inside my getInstance method, and the boost::asio alarm has been set up accordingly. What I will show below is that by calling io_run() directly starts the timer falls into a loop within the alarm.
database.h
void *run_io(void *arg);
class Database
{
private:
static Database *dbInstance; //= NULL;
public:
boost::asio::io_service io_service;
boost::posix_time::millisec interval;
boost::asio::deadline_timer timer;
pthread_t timerThread;
public:
static Database &getInstance()
{
if (!dbInstance)
{
dbInstance = new Database();
// pthread_create(&dbInstance->timerThread,NULL,run_io,&dbInstance->io_service);
std::cout << " INSTANCE CREATED " << std::endl;
pthread_create(&dbInstance->timerThread, NULL, run_io, (void *)&dbInstance->io_service);
// pthread_join(&dbInstance->timerThread, NULL);
}
return *dbInstance;
}
};
database.cpp
Database *Database::dbInstance = NULL;
Database::Database()
: interval(2000), timer(io_service, interval) {}
Database::~Database()
{
sqlite3_close(db);
}
void Database::setAlarm(const boost::system::error_code& /* e */)
{
std::cout << "[TEST] WE ARE IN SET ALARM " << std::endl;
DB_WRITE_TIME = 500;
boost::posix_time::milliseconds interval(DB_WRITE_TIME);
// Reschedule the timer for 1 second in the future:
timer.expires_at(timer.expires_at() + interval);
// Posts the timer event
timer.async_wait(boost::bind(&Database::setAlarm, this, _1));
}
int Database::buffer()
{
// DO BUFFER STUFF
timer.async_wait(boost::bind(&Database::setAlarm, this, _1));
// io_service.run() <-- uncommenting this results in the loop
return rc ;
}
void *run_io(void *arg)
{
boost::asio::io_service *io_service = (boost::asio::io_service *)arg;
io_service->run();
}
So I don't feel like the pthread is even starting. I tried putting a print statement in there to see if it came out, and nothing appeared in my terminal.
---- EDIT ----
I have made changes as per Sehe's advice, however it still does not look like I am able to call the alarm handler (setAlarm()). I had to slightly modify it to be compatible with the whole program, but essentially it is this (I gave the interval time a value of 5000 to give it enough time for the tests):
database.h
class Database
{
private:
static boost::shared_ptr<Database> dbInstance;
private:
typedef boost::asio::io_service io_service;
io_service io;
boost::scoped_ptr<io_service::work> work;
boost::posix_time::millisec interval;
boost::asio::deadline_timer timer;
boost::thread timerThread;
void run_io()
{
std::cout << "ENTER IO THREAD" << std::endl;
io.run();
std::cout << "LEAVE IO THREAD" << std::endl;
}
public:
static Database &getInstance()
{
if (!dbInstance)
{
std::cout << " INSTANCE CREATED " << std::endl;
dbInstance.reset(new Database());
dbInstance->timerThread = boost::thread(boost::bind(&Database::run_io,dbInstance));
}
return *dbInstance;
}
Database(); // <-- default constructor (doesn't take any args)
~Database();
database.cpp
boost::shared_ptr<Database> Database::dbInstance;
static const int DB_WRITE_TIME = 5000;
Database::Database()
: work(new io_service::work(io)), interval(5000), timer(io, interval)
{
// std::cout << " CONSTRUCTED " << std::endl;
}
Database::~Database()
{
// std::cout << " DESTROYED " << std::endl;
// sqlite3_close(db);
}
void Database::setAlarm(const boost::system::error_code& ec)
{
std::cout << "[TEST] WE ARE IN SET ALARM - ec message = " << ec.message() << std::endl;
executeSqlInBuffer(); // once timer expire, call the execute function
if(!ec)
{
boost::posix_time::milliseconds interval(DB_WRITE_TIME);
timer.expires_from_now(interval);
timer.async_wait(boost::bind(&Database::setAlarm, this, _1));
}
}
void Database::teardown()
{
// std::cout << " INSTANCE SHUTTING DOWN " << std::endl;
timer.cancel(); // stop timer loop
work.reset(); // allows io.run() to exit
if(timerThread.joinable())
{
std::cout << " JOINED " << std::endl;
timerThread.join(); // releasing bound of shared_ptr
}
else std::cout << " NOT JOINED " << std::endl;
dbInstance.reset(); // releasing instance
}
int Database::buffer()
{
// do buffering
if(buffer.size() == max_size)
{
executeSqlInBuffer();
}
std::cout << timer.expires_from_now(interval) << std::endl;
// std::cout << " ~ BEFORE TIMER ~ " << std::endl;
timer.async_wait(boost::bind(&Database::setAlarm, this, _1));
return 1;
}
main.cpp
int main()
{
pthread_t thread1; // a few pthreads in main that handle other areas of the program.
pthread_create(&thread1,NULL,thread1Arg,NULL);
pthread_t dbThread; // my pthread for the database
pthread_create(&dbThread,NULL,dbThreadArg,NULL);
Database& database = Database::getInstance();
database.teardown();
pthread_join(thread1,NULL);
pthread_join(dbThread,NULL);
return 0;
}
You can also see here that it enters and leaves the IO thread, and creates an instance, plus the debug output for timer.expires_from_now(interval):
INSTANCE CREATED
JOINED
ENTER IO THREAD
LEAVE IO THREAD
...
...
0 ---> first cycle
1 ---> second cycle
...
1 ---> nth cycle
I'm very ccnfused why anyone who uses Boost or C++11 (or both...) would ever use raw pthread threads (see e.g. C++ boost asynchronous timer to run in parallel with program for a good juxtaposition).
The real problem is likely that you have io_service running out of work (see e.g. https://www.boost.org/doc/libs/1_57_0/doc/html/boost_asio/reference/io_service__work.html).
If you have no pending async operations the thread just exits.
Another problem is accuracy issues with
timer.expires_at(timer.expires_at() + interval);
It's possible that some handlers take so much time that by the time you schedule your next alarm, the deadline has already expired. It's probably better to use
timer.expires_from_now(interval);
Note this also matches the comment better. The comment suffers from comment already because it says "1 second" but it is actually some defined constant DB_WRITE_TIME
or separate your timer from the other handlers in some other way to guarantee accurate scheduling.
Finally, you had UB due to the absense of any shutdown. The static instance never gets destroyed, but what's worth the non-detached thread never is joined, creating undefined behaviour at shutdown.
This problem is actually almost identical to the one recently discussed here, where I also explains the way work guards work in more detail: asio::io_service is ending immediately with work
Here's a c++11 rewrite with the necessary fix:
Since I now noticed you're that person stuck in c++03 land for some weird reason, a Boost Thread version:
C++03 DEMO/Boost Thread
Live On Coliru
#include <boost/asio.hpp>
#include <boost/make_shared.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/thread.hpp>
#include <iostream>
static const int DB_WRITE_TIME = 500;
class Database
{
private:
static boost::shared_ptr<Database> dbInstance;
Database()
: work(new io_service::work(io)),
interval(750),
timer(io, interval)
{
std::cout << "INSTANCE CREATED" << std::endl;
}
void on_timer_completed(const boost::system::error_code& ec) {
std::cout << "[on_timer_completed] " << ec.message() << std::endl;
if (!ec) {
boost::posix_time::milliseconds interval(DB_WRITE_TIME);
// Reschedule the timer
timer.expires_from_now(interval);
timer.async_wait(boost::bind(&Database::on_timer_completed, this, _1));
}
}
int buffer()
{
// DO BUFFER STUFF
timer.expires_from_now(interval);
timer.async_wait(boost::bind(&Database::on_timer_completed, this, _1));
// io_service.run() <-- uncommenting this results in the loop
return 1; // rc ;
}
public:
void do_stuff() {
buffer(); // whatever it does
}
void teardown() {
std::cout << "INSTANCE SHUTTING DOWN\n";
timer.cancel(); // stop timer loop
work.reset(); // allows io.run() to exit
if (timerThread.joinable()) {
timerThread.join(); // releasing the bound shared_ptr
}
dbInstance.reset(); // releasing the instance
}
~Database() {
//sqlite3_close(db);
std::cout << "INSTANCE DESTROYED\n";
}
private:
typedef boost::asio::io_service io_service;
io_service io;
boost::scoped_ptr<io_service::work> work;
boost::posix_time::millisec interval;
boost::asio::deadline_timer timer;
boost::thread timerThread;
void run_io() {
std::cout << "ENTER IO THREAD" << std::endl;
io.run();
std::cout << "LEAVE IO THREAD" << std::endl;
}
public:
static Database &getInstance()
{
if (!dbInstance)
{
dbInstance.reset(new Database());
dbInstance->timerThread =
boost::thread(boost::bind(&Database::run_io, dbInstance));
}
return *dbInstance;
}
};
boost::shared_ptr<Database> Database::dbInstance;
int main() {
Database& db = Database::getInstance();
boost::this_thread::sleep_for(boost::chrono::seconds(1));
db.do_stuff();
boost::this_thread::sleep_for(boost::chrono::seconds(3));
// ....
db.teardown();
}
Prints
INSTANCE CREATED
ENTER IO THREAD
[on_timer_completed] Success
[on_timer_completed] Success
[on_timer_completed] Success
[on_timer_completed] Success
[on_timer_completed] Success
INSTANCE SHUTTING DOWN
[on_timer_completed] Operation canceled
LEAVE IO THREAD
INSTANCE DESTROYED

Custom listModel does not notify the view

I have my custom list model where I put data which should be displayed on the QML view. But for some kind of reason the view in QML sometimes is updated normally, sometimes with previous data and sometimes updating is not performed.
Here is the function where I fill the model - this function is called from some other thread.
void MyScreen::fillListModel()
{
const QString SEPARATOR = " ";
myListModel->resetModel();
for (int i = 0; i < MAX_ROWS; ++i)
{
QString key = QString::fromUtf16(MyData::getParameterKey(i).c_str());
QString val = QString::fromUtf16(MyData::getParameterVal(i).c_str());
myListModel->addItem(key + SEPARATOR + val);
}
}
Implementation of model reset:
void BrowsingModelBase::resetModel()
{
beginResetModel();
m_items.clear();
endResetModel();
}
Implementation of addItem():
void BrowsingModelBase::addItem(const BrowsingItemModelBase &item)
{
int count = m_items.size();
beginInsertRows(QModelIndex(), count, count);
m_items.append(item);
endInsertRows();
}
Finally my QML file:
MyScreen {
Column {
id: myFlowList
y: 110
x: 220
ListView {
height:1000
spacing: 35;
model: myListModelRoot.myListModel
delegate: Text {
text: text1
}
}
}
}
The strange thing is that after loop with line
myListModel->addItem(key + SEPARATOR + val);
when I print logs with data from myListModel it is filled with proper data, but the view is usually updated with previous data. Is it possible that data change signal is stuck somewhere? Any idea what is the solution?
Assuming that you call your model's methods from another thread, with the model being fundamentally not thread-safe, you have two options:
Make certain methods of your model thread-safe, or
Explicitly invoke the methods in a thread-safe fashion.
But first, you'd gain a bit of performance by adding all the items at once, as a unit. That way, the model will emit only one signal for all of the rows, instead of one signal per row. The views will appreciate it very much.
class BrowsingModelBase {
...
};
Q_DECLARE_METATYPE(QList<BrowsingItemModelBase>)
void BrowsingModelBase::addItems(const QList<BrowsingItemModelBase> & items)
{
beginInsertRows(QModelIndex(), m_items.size(), m_items.size() + items.size() - 1);
m_items.append(items);
endInsertRows();
}
You should probably also have a method called clear instead of resetModel, since to reset a model has a much more general meaning: "change it so much that it's not worth emitting individual change signals". To reset a model does not mean "clear it"! Thus:
void BrowsingModelBase::clear()
{
beginResetModel();
m_items.clear();
endResetModel();
}
Finally, following the 2nd approach of safely invoking the model's methods, fillListModel becomes as follows. See this answer for discussion of postTo.
template <typename F>
void postTo(QObject * obj, F && fun) {
if (obj->thread() != QThread::currentThread()) {
QObject signalSource;
QObject::connect(&signalSource, &QObject::destroyed, obj, std::forward<F>(fun));
} else
fun();
}
void MyScreen::fillListModel()
{
auto separator = QStringLiteral(" ");
QList<BrowserItemModelBase> items;
for (int i = 0; i < MAX_ROWS; ++i) {
auto key = QString::fromUtf16(MyData::getParameterKey(i).c_str());
auto val = QString::fromUtf16(MyData::getParameterVal(i).c_str());
items << BrowserItemModelBase(key + separator + val);
}
postTo(myListModel, [this, items]{
myListModel->clear();
myListModel->addItems(items);
});
}
Alternatively, following the first approach, you can make the clear and addItems methods thread-safe:
/// This method is thread-safe.
void BrowsingModelBase::addItems(const QList<BrowsingItemModelBase> & items)
{
postTo(this, [this, items]{
beginInsertRows(QModelIndex(), m_items.size(), m_items.size() + items.size() - 1);
m_items.append(items);
endInsertRows();
});
}
/// This method is thread-safe.
void BrowsingModelBase::clear()
{
postTo(this, [this]{
beginResetModel();
m_items.clear();
endResetModel();
});
}
You then need no changes to fillListModel, except to make it use addItems:
void MyScreen::fillListModel()
{
auto separator = QStringLiteral(" ");
myListModel->clear();
QList<BrowserItemModelBase> items;
for (int i = 0; i < MAX_ROWS; ++i) {
auto key = QString::fromUtf16(MyData::getParameterKey(i).c_str());
auto val = QString::fromUtf16(MyData::getParameterVal(i).c_str());
items << BrowserItemModelBase(key + separator + val);
}
myListModel->addItems(items);
}
The problem is most likely the fact that you are calling the fillListModel() method not from the main GUI thread. You can update the model from other threads but the beginResetModel();, endResetModel();, beginInsertRows(QModelIndex(), count, count);... methods have to be called in the main GUI thread.
One way to call these method in GUI thread (maybe not the most efficient) is to :
Create signals for each method you want to call:
signals:
//these signals are emitted from worker thread
void requestBeginResetModel();
void requestEndResetModel();
Create slots that will actually call the methods:
private slots:
//these slots execute the model reset operations in main thread
void callBeginResetModel();
void callEndResetModel();
Connect the signals and the slots:
//connect the appropriate signals
connect(this, SIGNAL(requestBeginResetModel()),
this, SLOT(callBeginResetModel()));
connect(this, SIGNAL(requestEndResetModel()),
this, SLOT(callEndResetModel()));
Your reset model will then be:
void BrowsingModelBase::resetModel()
{
emit requestBeginResetModel();
m_items.clear();
emit requestEndResetModel();
}
Finally the slots are implemented as:
void ObjectModel::callBeginResetModel()
{
beginResetModel();
}
void ObjectModel::callEndResetModel()
{
endResetModel();
}
Note that you will have to do the same for row insert methods as well. Or alternatively you could fill you model in the resetModel() method in between emitted signals.

How can I avoid infinite loop when modifying textboxes (QLineEdit) that change related item info?

I have several fields in a widget, that each can affect the behavior of an item, and changing some of them will change others.
I read somewhere that the editingFinished() signal of a line edit is triggered only by user actions - and not by code changes... Is that true ?
connect(m_lineEdit1, SIGNAL(editingFinished()), this, SLOT(m_lineEdit1Changed()));
connect(m_lineEdit2, SIGNAL(editingFinished()), this, SLOT(m_lineEdit2Changed()));
connect(this, SIGNAL(someSignal()), this, SLOT(updateData()));
void m_lineEdit1Changed()
{
changedata1();
emit someSignal();
}
void m_lineEdit2Changed()
{
changedata2();
emit someSignal();
}
void updateData()
{
m_lineEdit1.setText(fromdata);
m_lineEdit2.setText(fromdata);
}
If I change m_lineEdit1, and update the entire widget (which changes, through code, m_lineEdit2), I hit a breakpoint in m_lineEdit2Changed()
This leads to an infinite loop of updates...
What can I do to get around it ?
Blocking signals is a bit of a sledgehammer of an approach. You can use a sentinel class to explicitly prevent recursion:
#define SENTINEL_STRINGIFY(x) #x
#define SENTINEL_TOSTRING(x) SENTINEL_STRINGIFY(x)
#define SENTINEL_AT __FILE__ ":" SENTINEL_TOSTRING(__LINE__)
class Sentinel {
Q_DISABLE_COPY(Sentinel);
static QMutex m_mutex;
static QSet<QString> m_sentinels;
QString const m_sentinel;
bool const m_ok;
static bool checkAndSet(const QString & sentinel) {
QMutexLocker lock(&m_mutex);
if (m_sentinels.contains(sentinel)) return false;
m_sentinels.insert(sentinel);
return true;
}
public:
explicit Sentinel(const char * sentinel) :
m_sentinel(sentinel), m_ok(checkAndSet(m_sentinel)) {}
~Sentinel() {
if (!m_ok) return;
QMutexLocker lock(&m_mutex);
m_sentinels.remove(m_sentinel);
}
bool operator()() const { return m_ok; }
};
QMutex Sentinel::m_mutex;
QSet<QString> Sentinel::m_sentinels;
...
void Foo::m_lineEdit1Changed()
{
Sentinel s(SENTINEL_AT);
if (!s) return; // exit if this method is on the call stack
...
changedata1();
emit someSignal();
}
This is thread-safe and can be used from any thread.
A technique to avoid this problem is to use the QObject::blockSignals() function.
In your example you would do:
void updateData()
{
m_lineEdit1.blockSignals(true);
m_lineEdit1.setText(fromdata);
m_lineEdit1.setText(fromdata);
m_lineEdit1.blockSignals(false);
}
The blockSignals() call prevents the object sending any signals while you are changing the data in the line edit.

How to update data in QAbstractTableModel with different row count

I am developing an application that updates the data in QTableView from apache server once per second. The server sends data as XML table. Number of columns is constant, but the number of rows changes each time. The data in the rows may also vary.
To convert the XML into the data, I created a class TxTableData, which is used in TxTableModel (child of QAbstractTableModel). Also TxTableModel uses QTimer to update data from the server.
The problem is that if the number of lines decreases - QTableview did not react to it. When the number of rows increases - it's all right.
I need remove all rows from QTableView and fill it with new data, but QTableView does not do this. Can you
class TxTableModel : public QAbstractTableModel
{
Q_OBJECT
public:
TxTableModel(QObject *parent = 0);
void refreshData();
void parseXml(const QByteArray &xml);
public slots:
void httpDone(bool error);
void timerDone();
protected:
HttpConnect http;
TxTableData m_Data;
QTimer * timer;
};
TxTableModel::TxTableModel(QObject *parent) :
QAbstractTableModel(parent)
{
timer = new QTimer(this);
connect(&http, SIGNAL(done(bool)), this, SLOT(httpDone(bool)));
connect(timer, SIGNAL(timeout()), this, SLOT(timerDone()));
timer->start(1000);
}
void TxTableModel::refreshData()
{
TxRequest request;
request.setObject("order");
request.setMethod("getlist");
request.addParam("begin_time", 60*60*4);
request.addParam("end_time", 60*4);
http.queryAsync(request);
}
void TxTableModel::parseXml(const QByteArray &xml)
{
//qDebug() << xml;
int count = m_Data.getRowCount();
QXmlInputSource inputSource;
QXmlSimpleReader reader;
TxSaxTableHandler handler(&m_Data, false);
inputSource.setData(xml);
reader.setContentHandler(&handler);
reader.setErrorHandler(&handler);
beginResetModel();
reader.parse(inputSource);
endResetModel();
}
void TxTableModel::httpDone(bool error)
{
if (error) {
qDebug() << http.errorString();
} else {
parseXml(http.readAll());
}
}
void TxTableModel::timerDone()
{
refreshData();
}
It looks like you're not providing the full source of TxTableModel model, as it's missing implementation of rowCount, columnCount, data, setData, etc methods.
As for the problem, my guess would be:
As it was already suggested you can try cleaning the model before reloading it by calling removeRows(0, rowCount());
in your removeRows implementation, you should call beginRemoveRows before updating the rows collection and endRemoveRows after you're done. This should notify views about the model content change.
There is an example on how to implement the QAbstractTableModel here: Address Book Example
hope this helps, regards

Resources