【问题标题】:Qml TableView: crash while scrollingQml TableView:滚动时崩溃
【发布时间】:2016-04-08 10:20:54
【问题描述】:

我的目标是实现一种具有高数据更新率的模拟器。
该应用程序由以下部分组成:

  • 数据模型:它存储数据并被 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;
}

ma​​in.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();
}

ma​​in.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

            }
        }
    }
}

【问题讨论】:

    标签: c++ windows qt qml tableview


    【解决方案1】:

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

    问题的关键点在这里:

    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 Signal/Slot 机制来做到这一点。
    因此:
    在 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 信号将由适当的线程发出。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-08
      • 2019-12-22
      • 2011-07-10
      • 2011-12-26
      • 1970-01-01
      相关资源
      最近更新 更多