Разве неправильно изменять данные QChart из обработчика QValueAxis :: rangeChanged? - PullRequest
0 голосов
/ 14 мая 2019

Я пытаюсь обновить набор данных в QChart при масштабировании (чтобы избежать рисования большого количества точек данных на пиксель). Для этого я подключаюсь к сигналу QValueAxis::rangeChanged, а в обработчике удаляю серию и заменяю ее новой.

Но вскоре после того, как обработчик возвращается, у меня возникает ошибка.

Вот сокращенный тестовый пример:

#include <vector>
#include <QtCharts>
#include <QApplication>

class ChartView : public QWidget
{
public:
    ChartView(QWidget* parent=nullptr)
        : QWidget(parent)
        , chartView_(new QChartView)
        , axisX_(new QValueAxis)
    {
        const auto layout=new QVBoxLayout(this);
        layout->addWidget(chartView_);

        axisX_->setMinorTickCount(-1);
        connect(axisX_, &QValueAxis::rangeChanged, this, &ChartView::updateChart);
        chartView_->chart()->addAxis(axisX_, Qt::AlignBottom);

        chartView_->setRubberBand(QChartView::RectangleRubberBand);

        updateChart();
    }

private:
    void updateChart()
    {
        qDebug() << "Entering updateChart()";

        chartView_->chart()->removeAllSeries();

        const auto series=new QLineSeries;
        for (int i=0;i<10;++i)
            series->append(i, 1);

        chartView_->chart()->addSeries(series);

        {
            QSignalBlocker block(axisX_); // prevent recursive call of updateChart()
            series->attachAxis(axisX_);
        }

        qDebug() << "Leaving updateChart()";
    }

private:
    QtCharts::QChartView* chartView_;
    QtCharts::QValueAxis* axisX_=nullptr;
};

int main(int argc, char** argv)
{
    QApplication app(argc, argv);

    ChartView view;
    view.resize(800,600);
    view.show();

    return app.exec();
}

Когда я запускаю приложение и просто выбираю резинку, оно сразу вылетает. Я попытался скомпилировать Qt с помощью Address Sanitizer, и вот что он мне дает (полный вывод здесь ):

    #0 0x7f9328931211 in QtCharts::AbstractDomain::blockRangeSignals(bool) domain/abstractdomain.cpp:149
    #1 0x7f932882299a in QtCharts::ChartDataSet::zoomInDomain(QRectF const&) /home/ruslan/packages/qt-everywhere-src-5.12.3/qtcharts/src/charts/chartdataset.cpp:451
    #2 0x7f932885bd0d in QtCharts::QChartPrivate::zoomIn(QRectF const&) /home/ruslan/packages/qt-everywhere-src-5.12.3/qtcharts/src/charts/qchart.cpp:925
    #3 0x7f93288575dd in QtCharts::QChart::zoomIn(QRectF const&) /home/ruslan/packages/qt-everywhere-src-5.12.3/qtcharts/src/charts/qchart.cpp:412
    #4 0x7f9328860fe9 in QtCharts::QChartView::mouseReleaseEvent(QMouseEvent*) /home/ruslan/packages/qt-everywhere-src-5.12.3/qtcharts/src/charts/qchartview.cpp:233

<snip>

0x60b00001b238 is located 40 bytes inside of 112-byte region [0x60b00001b210,0x60b00001b280)
freed by thread T0 here:
    #0 0x7f9328cad9d8 in operator delete(void*, unsigned long) (/usr/lib/gcc/x86_64-linux-gnu/7/libasan.so+0xe19d8)
    #1 0x7f9328935bfc in QtCharts::XYDomain::~XYDomain() domain/xydomain.cpp:43
    #2 0x7f93288678a8 in QScopedPointerDeleter<QtCharts::AbstractDomain>::cleanup(QtCharts::AbstractDomain*) (/home/ruslan/opt/qt5-sanitize/lib/libQt5Charts.so.5+0x13c8a8)
    #3 0x7f9328867726 in QScopedPointer<QtCharts::AbstractDomain, QScopedPointerDeleter<QtCharts::AbstractDomain> >::reset(QtCharts::AbstractDomain*) /home/ruslan/packages/qt-everywhere-src-5.12.3/qtbase/include/QtCore/../../src/corelib/tools/qscopedpointer.h:159
    #4 0x7f9328865dc2 in QtCharts::QAbstractSeriesPrivate::setDomain(QtCharts::AbstractDomain*) /home/ruslan/packages/qt-everywhere-src-5.12.3/qtcharts/src/charts/qabstractseries.cpp:438
    #5 0x7f932881d5ba in QtCharts::ChartDataSet::removeSeries(QtCharts::QAbstractSeries*) /home/ruslan/packages/qt-everywhere-src-5.12.3/qtcharts/src/charts/chartdataset.cpp:171
    #6 0x7f9328856b5d in QtCharts::QChart::removeSeries(QtCharts::QAbstractSeries*) /home/ruslan/packages/qt-everywhere-src-5.12.3/qtcharts/src/charts/qchart.cpp:293
    #7 0x7f9328856e19 in QtCharts::QChart::removeAllSeries() /home/ruslan/packages/qt-everywhere-src-5.12.3/qtcharts/src/charts/qchart.cpp:304
    #8 0x55f99f5904ae in ChartView::updateChart() /home/ruslan/tmp/qtcharts-crash-test/main.cpp:30

<snip>

previously allocated by thread T0 here:
    #0 0x7f9328cac458 in operator new(unsigned long) (/usr/lib/gcc/x86_64-linux-gnu/7/libasan.so+0xe0458)
    #1 0x7f932881c706 in QtCharts::ChartDataSet::addSeries(QtCharts::QAbstractSeries*) /home/ruslan/packages/qt-everywhere-src-5.12.3/qtcharts/src/charts/chartdataset.cpp:106
    #2 0x7f9328856adf in QtCharts::QChart::addSeries(QtCharts::QAbstractSeries*) /home/ruslan/packages/qt-everywhere-src-5.12.3/qtcharts/src/charts/qchart.cpp:281
    #3 0x55f99f59051a in ChartView::updateChart() /home/ruslan/tmp/qtcharts-crash-test/main.cpp:36

Когда я использую Qt::QueuedConnection для подключения к сигналу QValueAxis::rangeChanged, проблема исчезает. По-видимому, в момент генерации rangeChanged состояние QChart и связанных с ним объектов еще не согласовано, а после возврата в цикл обработки событий оно становится согласованным.

Мой вопрос здесь: действительно ли неправильно изменять данные QChart из обработчика сигнала QValueAxis::rangeChanged? Или это просто ошибка Qt, о которой следует сообщать? Кажется, я не могу найти упоминания об этом в документации, но на самом деле я не знаю, как это можно сформулировать.

Я использую Qt 5.12.3 и g ++ 7.4.0.

...