Read Access Violation when accessing a QBarDataItem in a loop in QLineEdit::editingFinished

35 views Asked by At

I am currently experimenting a bit with the Qt DataVisualization.

The goal is that I have a QLineEdit and when I exit it, I use the QLineEdit::editingFinished signal. And then I set the value that was entered to the selected Bar.

I get an error on line 46 when I start the second for loop when I try to access the QBarDataItem. I am getting a Read Access Violation there and I get the error on this line of the qarraydatapointer.h file from Qt:

bool needsDetach() const noexcept { return !d || d->needsDetach(); }

Here my code :

Main file:

#include <QApplication>
#include <QFile>
#include "Window.h"

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    
    // creates a window
    Window window;
    QFile file(app.applicationDirPath() + "/Test.txt");

    // Writes the x and y position of the selected Bar
    QObject::connect(window.getSeries(), &QBar3DSeries::selectedBarChanged, [&]() {
        if (!file.open(QIODevice::ReadWrite)) {
            QTextStream stream(&file);
            stream << window.getSeries()->selectedBar().x() << ", "
                   << window.getSeries()->selectedBar().y() << ", "
                   << window.getSeries()->dataProxy()->rowAt(window.getSeries()->selectedBar().x())->data()->value() << "\n";
        }
    });

    QFile file1(app.applicationDirPath() + "/Data.txt");
    if (file1.open(QIODevice::ReadOnly | QIODevice::Text)) {
        QTextStream in(&file1);
        QBarDataRow *data = new QBarDataRow;
        while (!in.atEnd()) {
            QString line = in.readLine();
            *data << line.toFloat();
        }
        window.getSeries()->dataProxy()->addRow(data);
    }  
    
    window.show();
    file.close();
    file1.close();
    return app.exec();
}

Window.h:

#ifndef WINDOW_H
#define WINDOW_H

#include <QMainWindow>
#include <QtDataVisualization>

#define WIDTH 1400
#define HEIGHT 800

struct Data {
    Data() {
        data1 = new QBarDataRow;
        data2 = new QBarDataRow;
        data3 = new QBarDataRow;
        data4 = new QBarDataRow;
        data5 = new QBarDataRow;
    }
    ~Data() {
        delete data1;
        delete data2;
        delete data3;
        delete data4;
        delete data5;
    }

public:
    QList<QBarDataRow *> data{ data1, data2, data3, data4, data5 };
    QBarDataRow *data1;
    QBarDataRow *data2;
    QBarDataRow *data3;
    QBarDataRow *data4;
    QBarDataRow *data5;
};

class Window : public QMainWindow {
    Q_OBJECT

public:
    Window(QWidget *parent = nullptr);
    ~Window();
    QBar3DSeries *getSeries();

protected:
    void resizeEvent(QResizeEvent *event) override;

private:
    Q3DBars *m_bars;
    QBar3DSeries *m_series;
    Data *m_data;
    QWidget *m_graph;
};

#endif // WINDOW_H

Window.cpp:

#include "Window.h"
#include <QLineEdit>

static void setData(Data *data, QBar3DSeries *series) {
    *data->data1 << 1.0f << 3.0f << 6.5f << 5.0f << 2.2f;
    *data->data2 << 2.0f << 1.5f << 5.0f << 2.0f << 2.4f;
    *data->data3 << 3.0f << 2.5f << 4.0f << 1.0f << 3.4f;
    *data->data4 << 2.9f << 2.5f << 2.3f << 3.1f << 1.7f;
    //*data->data5 << 1.6f << 1.1f << 1.8f << 4.8f << 3.4f;

    series->dataProxy()->addRow(data->data1);
    series->dataProxy()->addRow(data->data2);
    series->dataProxy()->addRow(data->data3);
    series->dataProxy()->addRow(data->data4);
    //series->dataProxy()->addRow(data->data5);
}

Window::Window(QWidget *parent) : QMainWindow(parent) {
    // Initialising variables
    m_bars = new Q3DBars;
    m_series = new QBar3DSeries;
    m_data = new Data;

    setWindowTitle("Physik");
    resize(WIDTH, HEIGHT);

    m_bars->rowAxis()->setRange(0, 4);
    m_bars->columnAxis()->setRange(0, 4);
    m_bars->scene()->activeCamera()->setCameraPosition(30, 30);

    // set values which can then be seen
    setData(m_data, m_series);
    m_bars->addSeries(m_series);

    // Creates a QWidget, which is a child of the QMainWindow (this)
    m_graph = QWidget::createWindowContainer(m_bars, this);
    m_graph->setGeometry(0, 0, WIDTH * 0.7, HEIGHT);
    //--------------------------------------------------------------
    QLineEdit *lineEdit = new QLineEdit(this);
    lineEdit->setGeometry(WIDTH * 0.75, HEIGHT * 0.1, WIDTH * 0.85, HEIGHT * 0.15);
    connect(lineEdit, &QLineEdit::editingFinished, [&]() {
        QPoint position = m_series->selectedBar();
        for (size_t i{}; i < m_data->data.size(); ++i) {
            if (i == position.x()) {
                size_t j{};
                for (auto &value : *m_data->data[i]) {
                    if (j == position.y()) {
                        value.setValue(lineEdit->text().toFloat());
                    }
                    ++j;
                }
                break;
            }
        }
    });
}

Window::~Window() {}

QBar3DSeries *Window::getSeries() {
    return m_series;
}

void Window::resizeEvent(QResizeEvent *event) {
    QMainWindow::resizeEvent(event);
    if (m_graph) {
        m_graph->resize(event->size().width() * 0.7, event->size().height());
    }
}

I tried to write my own QBarDataItem and QBarDataRow who inherit public both classes and tried to rewrite the functions, but it didn't work, and I really tried a lot of different stuff. But nothing worked, so I hope you can help me.

I know it's better to make this problem not too specific so that many people can help. In the end, I am only trying that I have a selected Bar and when I get out of the QLineEdit the value is then being set to the selected Bar.enter code here

1

There are 1 answers

5
Remy Lebeau On

When your m_data object is constructed, its constructor implicitly initializes the data member first, which is receiving a copy of the data1..data5 members before they have been initialized. Those members are initialized after the data list is populated, and the pointers stored in the data list are never being updated later. So the data list is left holding invalid pointers.

So, inside of your editingFinished lambda, when your loop retrieves a stored pointer from m_data->data[i] and tries to dereference that pointer, you experience Undefined Behavior, which in this case is resulting in a runtime error.

Try this instead for your Data struct:

struct Data {
    Data() {
        data1 = new QBarDataRow;
        data2 = new QBarDataRow;
        data3 = new QBarDataRow;
        data4 = new QBarDataRow;
        data5 = new QBarDataRow;

        //initialize the list here...
        data = QList<QBarDataRow *>{ data1, data2, data3, data4, data5 };
    }

    ~Data() {
        delete data1;
        delete data2;
        delete data3;
        delete data4;
        delete data5;
    }

public:
    //don't initialize the list here...
    //QList<QBarDataRow *> data{ data1, data2, data3, data4, data5 };
    QList<QBarDataRow *> data;
    QBarDataRow data1;
    QBarDataRow data2;
    QBarDataRow data3;
    QBarDataRow data4;
    QBarDataRow data5;
};

Also, there's no real need to new/delete the data1..data5 members manually at all, eg:

struct Data {
    Data() {
        data = QList<QBarDataRow *>{ &data1, &data2, &data3, &data4, &data5 };
    }

    // or:
    // Data() : data{ &data1, &data2, &data3, &data4, &data5 } {}

public:
    QList<QBarDataRow *> data;
    QBarDataRow data1;
    QBarDataRow data2;
    QBarDataRow data3;
    QBarDataRow data4;
    QBarDataRow data5;
};

Or:

struct Data {
    QList<QBarDataRow *> data{ &data1, &data2, &data3, &data4, &data5 };
    QBarDataRow data1;
    QBarDataRow data2;
    QBarDataRow data3;
    QBarDataRow data4;
    QBarDataRow data5;
};

That being said, you also don't need the secondary loops at all, since you already know exactly which item to update:

connect(lineEdit, &QLineEdit::editingFinished, [&]() {
    QPoint position = m_series->selectedBar();
    int x = position.x(), y = position.y();
    if (x >= 0 && x < m_data->data.size()) {
        QBarDataRow *row = m_data->data[x];
        if (y >= 0 && y < row->size()) {
            (*row)[y].setValue(lineEdit->text().toFloat());
        }
    }
});