Qml TableView:滚动时崩溃

时间:2016-04-08 10:20:55

标签: c++ windows qt qml tableview

我的目标是实现一种具有高速率数据更新的模拟器 该申请由以下部分组成:

  • 数据模型:它存储数据,并由TableView用作模型
  • 模拟器:每250毫秒更新整个数据模型的线程
  • Qml视图:包含用于显示数据的TableView

一旦我启动应用程序,我就可以看到TableView中的数据发生了变化,但是如果我尝试使用鼠标滚轮滚动TableView(继续滚动一段时间),则会出现严重的崩溃。
我得到的唯一日志如下:

  

在QList :: at中的ASSERT失败:"索引超出范围",文件C:\ work \ build \ qt5_workdir \ w \ s \ qtbase \ include / QtCore /../../ src /corelib/tools/qlist.h,第510行

更有趣的是,我只在Windows环境中遇到此崩溃,而在CentOs机器中我没有收到任何错误。

我在这里尝试提取项目的主要部分并尽可能多地概括它。
查找下面的代码,或者如果您愿意,可以从this link下载完整项目

mydata.h

#ifndef MYDATA_H
#define MYDATA_H

#include <QObject>

class MyData : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int first READ first WRITE setFirst NOTIFY firstChanged)
    Q_PROPERTY(int second READ second WRITE setSecond NOTIFY secondChanged)
    Q_PROPERTY(int third READ third WRITE setThird NOTIFY thirdChanged)

public:

    explicit MyData(int first, int second, int third, QObject* parent=0);

    int first()const {return m_first;}
    int second()const {return m_second;}
    int third()const {return m_third;}

    void setFirst(int v){m_first=v;}
    void setSecond(int v){m_second=v;}
    void setThird(int v){m_third=v;}

signals:
    void firstChanged();
    void secondChanged();
    void thirdChanged();

private:
    int m_first;
    int m_second;
    int m_third;
};

#endif // MYDATA_H

mydata.cpp

#include "mydata.h"

MyData::MyData(int first, int second, int third, QObject* parent) : QObject(parent)
{
    m_first=first;
    m_second=second;
    m_third=third;
}

datamodel.h

#ifndef DATAMODEL_H
#define DATAMODEL_H

#include <QAbstractListModel>
#include <QMutex>
#include "mydata.h"


class DataModel: public QAbstractListModel
{
    Q_OBJECT
public:

    enum DataModelRoles {
        FirstRole = Qt::UserRole + 1,
        SecondRole,
        ThirdRole
    };

    //*****************************************/
    //Singleton implementation:
    static DataModel& getInstance()
            {
                static DataModel    instance; // Guaranteed to be destroyed.
                                      // Instantiated on first use.
                return instance;
            }

    DataModel(DataModel const&) = delete;
    void operator=(DataModel const&)  = delete;
    //*****************************************/

    QList<MyData*>& getData(){return m_data;}

    void addData(MyData* track);

    int rowCount(const QModelIndex & parent = QModelIndex()) const;

    QVariant data(const QModelIndex & index, int role = FirstRole) const;

protected:
    QHash<int, QByteArray> roleNames() const;

private:

    QMutex m_mutex;
    QList<MyData*> m_data;

    DataModel(QObject* parent=0);


};

#endif // DATAMODEL_H

datamodel.cpp

#include "DataModel.h"
#include "QDebug"

DataModel::DataModel(QObject* parent): QAbstractListModel(parent)
{

}

void DataModel::addData(MyData *track)
{
    beginInsertRows(QModelIndex(),rowCount(),rowCount());
    m_data<<track;
    endInsertRows();
}

int DataModel::rowCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent)
    return m_data.size();
}

QVariant DataModel::data(const QModelIndex &index, int role) const
{
    MyData* data=m_data[index.row()];
    switch (role) {
    case FirstRole:
        return data->first();

    case SecondRole:
        return data->second();

    case ThirdRole:
        return data->third();
    default:
        return QVariant();
    }
}



QHash<int, QByteArray> DataModel::roleNames() const
{
    QHash<int, QByteArray> roles;
    roles[FirstRole] = "First";
    roles[SecondRole] = "Second";
    roles[ThirdRole] = "Third";
    return roles;
}

simulator.h

#ifndef SIMULATOR_H
#define SIMULATOR_H

#include <QThread>

class Simulator: public QThread
{
    Q_OBJECT
public:
    Simulator(QObject* parent=0);
    void run() Q_DECL_OVERRIDE;

private:
    void createNewData();
    void updateExistingData();

    int randInt(int from, int to);
};

#endif // SIMULATOR_H

simulator.cpp

#include "simulator.h"
#include <math.h>
#include <mydata.h>
#include <datamodel.h>
Simulator::Simulator(QObject* parent) : QThread(parent)
{
    createNewData();
}

void Simulator::run()
{
    long updateRate=250;
    while(true)
    {
        updateExistingData();
        msleep(updateRate);
    }
}


void Simulator::createNewData()
{
    int numOfData=10000;
    for(int i=0;i<numOfData;i++)
    {
        int first=i;
        int second=randInt(0,1000);
        int third=randInt(0,1000);
        MyData* data=new MyData(first,second,third);
        DataModel::getInstance().addData(data);
    }

}

void Simulator::updateExistingData()
{
    QList<MyData*> list=DataModel::getInstance().getData();
    for(int i=0;i<list.size();i++)
    {
        MyData* curr=list.at(i);
        curr->setSecond(curr->second()+1);
        curr->setThird(curr->third()+2);
        QModelIndex index=DataModel::getInstance().index(i,0, QModelIndex());
        emit DataModel::getInstance().dataChanged(index,index);    
    }
}

int Simulator::randInt(int from, int to)
{
    // Random number between from and to
    return qrand() % ((to + 1) - from) + from;
}

的main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "simulator.h"
#include "datamodel.h"
#include <QQmlContext>

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    DataModel& model=DataModel::getInstance();

    Simulator* s=new Simulator();
    s->start();

    QQmlApplicationEngine engine;

    QQmlContext *ctxt = engine.rootContext();
    ctxt->setContextProperty("myModel", &model);

    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    return app.exec();
}

main.qml

import QtQuick 2.5
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.1
import QtQuick.Controls.Styles 1.4

Window {
    visible: true
    width: 800
    height: 600

    TableView {
        id: tableView
        width: parent.width
        height: parent.height
        frameVisible: true

        model: myModel
        sortIndicatorVisible: true
        property string fontName: "Arial"

        TableViewColumn {
            id: firstColumn
            title: "First"
            role: "First"
            movable: false
            resizable: false
            width: tableView.viewport.width/3
            delegate: Text{
                font.family: tableView.fontName
                text: styleData.value
                horizontalAlignment: TextInput.AlignHCenter
                verticalAlignment: TextInput.AlignVCenter

            }
        }
        TableViewColumn {
            id: secondColumn
            title: "Second"
            role: "Second"
            movable: false
            resizable: false
            width: tableView.viewport.width/3
            delegate: Text{
                font.family: tableView.fontName
                text: styleData.value
                horizontalAlignment: TextInput.AlignHCenter
                verticalAlignment: TextInput.AlignVCenter

            }
        }
        TableViewColumn {
            id: thirdColumn
            title: "Third"
            role: "Third"
            movable: false
            resizable: false
            width: tableView.viewport.width/3
            delegate: Text{
                font.family: tableView.fontName
                text: styleData.value
                horizontalAlignment: TextInput.AlignHCenter
                verticalAlignment: TextInput.AlignVCenter

            }
        }
    }
}

1 个答案:

答案 0 :(得分:0)

我很高兴与您分享我自己答案的解决方案,希望它可以帮助那些犯同样错误的人。

问题的关键在于:

void Simulator::updateExistingData()
{
    QList<MyData*> list=DataModel::getInstance().getData();
    for(int i=0;i<list.size();i++)
    {
        MyData* curr=list.at(i);
        curr->setSecond(curr->second()+1);
        curr->setThird(curr->third()+2);
        QModelIndex index=DataModel::getInstance().index(i,0, QModelIndex());
        emit DataModel::getInstance().dataChanged(index,index);    //ERROR!
    }
}

实际上我在一个不是Gui线程的线程中的DataModel上发出一个信号:这将陷入gui线程(访问数据以填充TableView)和更新程序之间的并发访问问题线程(访问数据以更新值)。

解决方案是在gui线程上发出dataChanged信号,我们可以通过使用Qt信号/插槽机制来实现。
因此:
datamodel.h

public slots:
    void updateGui(int rowIndex);

datamodel.cpp

void DataModel::updateGui(int rowIndex)
{
    QModelIndex qIndex=index(rowIndex,0, QModelIndex());
    dataChanged(qIndex,qIndex);
}

simulator.h

signals:
    void dataUpdated(int row);

simulator.cpp

Simulator::Simulator(QObject* parent) : QThread(parent)
{
    createNewData();
    connect(this,SIGNAL(dataUpdated(int)), &DataModel::getInstance(), SLOT(updateGui(int)));
}

...

void Simulator::updateExistingData()
{
    QList<MyData*> list=DataModel::getInstance().getData();
    for(int i=0;i<list.size();i++)
    {
        MyData* curr=list.at(i);
        curr->setSecond(curr->second()+1);
        curr->setThird(curr->third()+2);
        QModelIndex index=DataModel::getInstance().index(i,0, QModelIndex());
        emit dataUpdated(i);
    }
}

使用Signal / Slot方法,我们确信请求将由创建该类的线程(Gui线程)在接收器类中处理,因此正确的线程将发出以下dataChanged信号。