Qt - QAbstractItemModel - czyli jak poinformować widok, że pojawiły się nowe dane

0

Jak w tytule - czyli jak poinformować widok o tym, że wczytano dane do modelu.

konstruktor mojego modelu wygląda tak:

MyModel2::MyModel2(QWidget *parent, QStringList _data, int _row, int _column) : Matrix(_data), myRow(_row), myColumn(_column)
{    
    (void) parent;

    QString strData;
    QStringList field;
    MyDataModel data;

    QRegularExpression regex("[\n|;]");

    for(int i=0; i<Matrix.size(); ++i){
        strData = Matrix.at(i);
        field = strData.split(regex, Qt::SkipEmptyParts);

        data.setLiczba1(field.at(0));
        data.setDzialanie(field.at(1));
        data.setLiczba2(field.at(2));
        data.setZnak(field.at(3));
        data.setWynik(field.at(4));

        myData.push_back(data);
    }
}

funkcja setData która ustawia dane w widoku wygląda tak

bool MyModel2::setData(const QModelIndex &index, const QVariant &value, int role)
{
    bool result = false;

//    beginResetModel();

    if(index.isValid() && role == Qt::EditRole){
        int row = index.row();
        int column = index.column();

        switch(column)
        {
            case 0:
                myData[row].setLiczba1(value.toString());
                qDebug()<<"kolumna ("<<column<<") ="<<myData.at(row).getLiczba1();
                break;

            case 1:
                myData[row].setDzialanie(value.toString());
                qDebug()<<"kolumna ("<<column<<") ="<<myData.at(row).getDzialanie();
                break;

            case 2:
                myData[row].setLiczba2(value.toString());
                qDebug()<<"kolumna ("<<column<<") ="<<myData.at(row).getLiczba2();
                break;

            case 3:
                myData[row].setZnak(value.toString());
                qDebug()<<"kolumna ("<<column<<") ="<<myData.at(row).getZnak();
                break;

            case 4:
                myData[row].setWynik(value.toString());
                qDebug()<<"kolumna ("<<column<<") ="<<myData.at(row).getWynik();
                break;

            default:
                break;
        }

        emit dataChanged(index, index, {Qt::EditRole, Qt::DisplayRole});
     }

//    endResetModel();

    return result;
}

SLOT który uruchamia obiekt QFileDialog znajduje się w klasie MainWindow i wygląda tak

void MainWindow::OpenFileDialog(void)
{
    QString filter = "All Files (*.*) ;; Text File (*.txt) ;; CSV File (*.csv)";
    QString getFileName = QFileDialog::getOpenFileName(this, QString("Open File"), QDir::homePath(), filter);
}

w MainWindow widok ustawiam w konstruktorze po prostu tak

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
    //jakiś tam kod

    absModel = new MyModel2(nullptr, dane, 2, 5); //<- to tak nie może być, konstruktor wywołuje się RAZ
//a skoro wywołuje się raz, to jak poinformować widok, że tam zostały załadowane dane ?

    myView = new QTableView();
    myView->setModel(absModel);

    QObject::connect(pbOpenFile, &QPushButton::clicked, this, &MainWindow::OpenFileDialog);
    QObject::connect(pbQuit, &QPushButton::clicked, this, &QMainWindow::close);

   //jakiś tam kod
}

Proszę zwrócić uwagę na konstruktor w MainWindow i obiekt absModel

Wiedząc jak to wygląda, jak mogę poinformować widok, że wczytałem dane z pliku i co należy zrobić, żeby się pokazały w widoku ?

Poprawiłem kilka błędów projektowych - dodałem klasę zajmującą się plikiem CSV.

Może za dużo informacji tu przedstawiłem ale może dzięki temu ktoś naprowadzi mnie na rozwiązanie mojego problemu.

emit dataChanged - coś nie chce działać. Próbowałem tworzyć obiekty modelu w slocie odpowiedzialnym za okno dialogowe QFileDialog ale to chyba jest bez sensu...

0

setData powinno zwracać true jeśli jakiekolwiek dane uległy zmianie.
Skąd się bierze dane w konstruktorze MainWindow?

0

@tajny_agent:

Skąd się bierze dane w konstruktorze MainWindow?

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
    QStringList dane;
    dane << ("1;x;7.5;=;7\n") << ("2;x;7;=;14\n");

    absModel = new MyModel2(nullptr, dane, 2, 5);
}

wygląda to jak wyżej. Chwilowo jest to po to, żeby program mi się nie wysypywał...

Ogólnie to działa ale dla danych ustawionych na sztywno w programie, więc funkcja setData zwraca true, tylko teraz chcę zrobić nie dla danych na sztywno osadzonych w kodzie ale dla każdego pliku który się wczyta

0

Jak zwraca true jak zwracasz false? Zresztą, zwracałem na to uwagę w poprzednim wątku.

0

Tak mniej więcej:

bool MyModel2::loadFromFile(QString const& filePath)
{
  // ... wczytywanie z pliku
  if (!success) {
    return false;
  }
  beginResetModel();
  data = readedData;
  endResetModel();
  return true;
}

void MainWindow::onOpenFileBtnClicked()
{
  auto filePath = QFileDialog::getOpenFileName(/*...*/);
  if (!filePath.isEmpty()) {
    if (!model->loadFromFile(filePath)) {
      // MsgBox: bład wczytywania czy coś tam
    }
  }
}

MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent)
{
  model = new MyModel2(this);
  // ... 
}
1
MarekR22 napisał(a):
zkubinski napisał(a):

rozumiem, że nikt nie wie o co chodzi, nikt nie rozumie i wątek do zamknięcia ?

Ja bym radził zacząć od opisania funkcjonalności jaką ma realizować twój program (z punktu widzenia użytkownika).

Nie możesz uzyskać odpowiedzi, bo nadal nie wiadomo co ten kod ma robić.
Jak to będzie wiadomo, to będzie można problem podzielić na mniejsze i wskazać jak każdy fragment powinien zostać zrealizowany.

Jak masz konto na github/gilab/bitbucket to daj link do repo z twoim projektem, wtedy w weekend popatrzę (pewnie mnie kq uprzedzi :)).
Jest dużo łatwiej jak coś można skompilować uruchomić i przetestować.

0

@MarekR22:

Nie możesz uzyskać odpowiedzi, bo nadal nie wiadomo co ten kod ma robić.
Jak to będzie wiadomo, to będzie można problem podzielić na mniejsze i wskazać jak każdy fragment powinien zostać zrealizowany.

Program ma mieć możliwość otwierania plików za pomocą przycisku Otwórz plik CSV i potem ma być wczytany do modelu i wyświetlony w widoku
Inaczej tego wyjaśnić nie umiem.

screenshot-2020120730551.png

Jak masz konto na github/gilab/bitbucket to daj link do repo z twoim projektem, wtedy w weekend popatrzę

kod źródłowy dostępny jest na GitLab-ie

1
  1. powinieneś pisać kod rozumiejąc co robisz i co chcesz osiągnać. Nadziałem się na różne dziwne kwiatki.
  2. powinieneś słuchać co ci ludzi piszą (rady z tego i innych pytań nie zastosowałeś
  3. Nadal nie wiadomo, co ten kod ma robić

Model poprawiony dający możliwość łatwego modyfikowania danych.
Wczytywanie danych napisz w innej funkcji a potem wynik przekaż do MyModel2::setData i gotowe.

#ifndef MYDATAMODEL_H
#define MYDATAMODEL_H

#include <QString>

class MyDataModel
{
public:
    MyDataModel();
    MyDataModel(double _liczba1, QString _dzialanie, double _liczba2, QString _znak, double _wynik);

    void setLiczba1(double _liczba1);
    void setDzialanie(QString _dzialanie);
    void setLiczba2(double _liczba2);
    void setZnak(QString _znak);
    void setWynik(double _wynik);

    double getLiczba1() const;
    QString getDzialanie() const;
    double getLiczba2() const;
    QString getZnak() const;
    double getWynik() const;

private:
    double liczba1;
    QString dzialanie;
    double liczba2;
    QString znak;
    double wynik;
};

#endif // MYDATAMODEL_H
#include "mydatamodel.h"

MyDataModel::MyDataModel():liczba1(1),dzialanie("Dz"),liczba2(2),znak("Zn"),wynik(0.5)
{

}

MyDataModel::MyDataModel(double _liczba1, QString _dzialanie, double _liczba2, QString _znak, double _wynik)
    : liczba1{_liczba1}
    , dzialanie{_dzialanie}
    , liczba2{_liczba2}
    , znak{_znak}
    , wynik{_wynik}
{
}

void MyDataModel::setLiczba1(double _liczba1)
{
    liczba1=_liczba1;
}

void MyDataModel::setDzialanie(QString _dzialanie)
{
    dzialanie=_dzialanie;
}

void MyDataModel::setLiczba2(double _liczba2)
{
    liczba2=_liczba2;
}

void MyDataModel::setZnak(QString _znak)
{
    znak=_znak;
}

void MyDataModel::setWynik(double _wynik)
{
    wynik=_wynik;
}

double MyDataModel::getLiczba1() const
{
    return liczba1;
}

QString MyDataModel::getDzialanie() const
{
    return dzialanie;
}

double MyDataModel::getLiczba2() const
{
    return liczba2;
}

QString MyDataModel::getZnak() const
{
    return znak;
}

double MyDataModel::getWynik() const
{
    return wynik;
}
#ifndef MYMODEL2_H
#define MYMODEL2_H

#include <QAbstractTableModel>
#include <QVector>
#include "mydatamodel.h"

class Dzialania;

class MyModel2 : public QAbstractTableModel
{
    Q_OBJECT

public:
    explicit MyModel2(QObject *parent = nullptr);

    enum{liczba1=0, dzialanie=1, liczba2=2, znak=3, wynik=4};

    Qt::ItemFlags flags(const QModelIndex &index) const override;

    //"setData" - ustawia dane w modelu
    bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;

    //"data" - ustawia dane w widoku
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;

    QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;

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

    void append(const MyDataModel& data);
    void insertAt(int row, const MyDataModel& data);
    void clear();
    void setData(QVector<MyDataModel> data);

private:
    void defaultData();
private:
    QVector<MyDataModel> myData;

private slots:
};

#endif // MYMODEL2_H
#include "mymodel2.h"
#include <QDebug>
#include <QVariant>

MyModel2::MyModel2(QObject *parent)
    : QAbstractTableModel(parent)
{
    defaultData();
}

Qt::ItemFlags MyModel2::flags(const QModelIndex &index) const
{
    Qt::ItemFlags flags = QAbstractItemModel::flags(index);
    flags = flags | Qt::ItemIsEditable;
    return flags;
}

int MyModel2::columnCount(const QModelIndex &parent) const
{
    if(parent.isValid()){
        return 0;
    }
    return wynik + 1;
}

int MyModel2::rowCount(const QModelIndex &parent) const
{
    if(parent.isValid()){
        return 0;
    }
    return myData.count();
}

void MyModel2::append(const MyDataModel &data)
{
    beginInsertRows({}, myData.count(), myData.count());
    myData.append(data);
    endInsertRows();
}

void MyModel2::insertAt(int row, const MyDataModel &data)
{
    row = qBound(0, row, myData.count());
    beginInsertRows({}, row, row);
    myData.insert(row, data);
    endInsertRows();
}

void MyModel2::clear()
{
    beginResetModel();
    myData.clear();
    endResetModel();
}

void MyModel2::setData(QVector<MyDataModel> data)
{
    beginResetModel();
    myData = std::move(data);
    endResetModel();
}

void MyModel2::defaultData()
{
    myData.append({1, "+", 2, "=", 3});
    myData.append({1, "x", 2, "=", 2});
}

bool MyModel2::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if(index.isValid() && role == Qt::EditRole){
        int row = index.row();
        int column = index.column();

        switch(column)
        {
            case 0:
                myData[row].setLiczba1(value.toDouble());
                qDebug()<<"kolumna ("<<column<<") ="<<myData.at(row).getLiczba1();
                break;

            case 1:
                myData[row].setDzialanie(value.toString());
                qDebug()<<"kolumna ("<<column<<") ="<<myData.at(row).getDzialanie();
                break;

            case 2:
                myData[row].setLiczba2(value.toDouble());
                qDebug()<<"kolumna ("<<column<<") ="<<myData.at(row).getLiczba2();
                break;

            case 3:
                myData[row].setZnak(value.toString());
                qDebug()<<"kolumna ("<<column<<") ="<<myData.at(row).getZnak();
                break;

            case 4:
                myData[row].setWynik(value.toDouble());
                qDebug()<<"kolumna ("<<column<<") ="<<myData.at(row).getWynik();
                break;

        }

        emit dataChanged(index, index, {Qt::EditRole, Qt::DisplayRole});
        return true;
     }

    return false;
}

QVariant MyModel2::data(const QModelIndex &index, int role) const
{

    if(!index.isValid()){
        return {};
    }

    auto row = index.row();
    QVariant result;
    switch(role) {
    case Qt::DisplayRole:
    case Qt::EditRole:  {
            switch (index.column()) {
                case 0:
                    result = myData.at(row).getLiczba1();
                break;

                case 1:
                    result = myData.at(row).getDzialanie();
                break;

                case 2:
                    result = myData.at(row).getLiczba2();
                break;

                case 3:
                    result = myData.at(row).getZnak();
                break;

                case 4:
                    result = myData.at(row).getWynik();
                break;
            }
        }
    }

    return result;
}

QVariant MyModel2::headerData(int section, Qt::Orientation orientation, int role) const
{
    if(role==Qt::DisplayRole && orientation==Qt::Horizontal){
        switch (section){
        case 0:
            return QString("liczba");

        case 1:
            return QString("działanie");

        case 2:
            return QString("liczba");

        case 3:
            return QString("równa się");

        case 4:
            return QString("wynik");
        }
    }

    if(role==Qt::DisplayRole && orientation==Qt::Vertical) {
        return QString("Lp %1").arg(section+1);
    }

    return QVariant();
}
0

@MarekR22:

Nadziałem się na różne dziwne kwiatki.

jakie kwiatki ?

powinieneś słuchać co ci ludzi piszą (rady z tego i innych pytań nie zastosowałeś

radę @tajny_agent próbowałem zastosować ale nie wychodziło i udostępniłem działający kod

Wczytywanie danych napisz w innej funkcji a potem wynik przekaż do MyModel2::setData i gotowe.

mówisz o moim kodzie który dałem czy o tym który dałeś teraz na forum ?

Nadal nie wiadomo, co ten kod ma robić

no ale przecież napisałem, że program ma wczytywać plik CSV, przekazać dane z pliku do modelu i wyświetlić zawartość pliku w widoku... serio nie wiem czego oczekujecie co mam opisać ?

0

no właśnie ty robisz na QAbstractTableModel a ja chciałem się nauczyć implementować model na QAbstractItemModel bo tam jest wszystko do zrobienia od zera... Cała zabawa polega na tym, żeby to zrozumieć

1
zkubinski napisał(a):

@MarekR22:

Nadziałem się na różne dziwne kwiatki.

jakie kwiatki ?

Kwiatek z pętlą for:

QVariant MyModel2::headerData(int section, Qt::Orientation orientation, int role) const
{
    if(role==Qt::DisplayRole && orientation==Qt::Horizontal){
        switch (section){
        case 0:
            return QString("liczba");

        case 1:
            return QString("działanie");

        case 2:
            return QString("liczba");

        case 3:
            return QString("równa się");

        case 4:
            return QString("wynik");
        }
    }

    if(role==Qt::DisplayRole && orientation==Qt::Vertical){

        for(int i=0; i<=section; ++i){
            return QString("Lp %1").arg(section+1);
        }

//        switch (section){
//        case 0:
//            return QString("Lp %1").arg(section+1);

//        case 1:
//            return QString("Lp %1").arg(section+1);

//        case 2:
//            return QString("Lp %1").arg(section+1);

//        case 3:
//            return QString("Lp %1").arg(section+1);

//        case 4:
//            return QString("Lp %1").arg(section+1);
//        }
    }

    return QVariant();
}

Kwiatek, z dziwną i zbędną konwersją:

QVariant MyModel2::data(const QModelIndex &index, int role) const
{
    QVariant result = QVariant();

    unsigned int row = static_cast<unsigned int>(index.row());
    unsigned int col = static_cast<unsigned int>(index.column());

Pole Matrix, które nic nie robi i jest używane tylko w konstruktorze.

parent w konstruktorze, nie dość, że jest typu QWidget * to na dodatek jest ignorowane.

Ogólnie wrażenie jest takie, że nie masz opanowanych podstaw programowania w C++ i w Qt, a skaczesz od razu na głębszą wodę jaką jest QAbstractItemModel.

powinieneś słuchać co ci ludzi piszą (rady z tego i innych pytań nie zastosowałeś

radę @tajny_agent próbowałem zastosować ale nie wychodziło i udostępniłem działający kod

Chodziło mi np o zwracanie true w setData. Pewnie nie zadziałało, bo nie obsłużyłeś poprawnie (wcale) EditRole

Wczytywanie danych napisz w innej funkcji a potem wynik przekaż do MyModel2::setData i gotowe.

mówisz o moim kodzie który dałem czy o tym który dałeś teraz na forum ?

Widzisz w swoim kodzie, MyModel2::setData?
Weź skopiuj kod do siebie, zobacz różnicę jaką wyświetli ci git difftool -d, przeanalizuj na spokojnie nowy kod i moją krytykę, a dopiero potem zadawaj pytania.
Jako, że zareagowałeś po 16 minutach to jakoś wątpię, żebyś przetrawił nowe dane i zaproponowane zmiany.

Nadal nie wiadomo, co ten kod ma robić

no ale przecież napisałem, że program ma wczytywać plik CSV, przekazać dane z pliku do modelu i wyświetlić zawartość pliku w widoku... serio nie wiem czego oczekujecie co mam opisać ?

Tak ale jak widzę, że dane zawierają jakiś wzór, który może być niepoprawny/bezsensowny i sama aplikacja nie ma, żadnej wartości dla użytkownika, to wpadam w konsternację. To wczytywanie CSV w tej chwili jest jak wiosło na pustyni, niezbyt sensowne i przydatne.

A i jeszcze jedno, moja stopka jest na serio. Kara to ignorowanie wątków nadawcy.

0

@MarekR22:
zacznę trochę od końca ale wątpię by ciebie to przekonało ale mimo to napiszę, będziesz miał tego świadomość

A i jeszcze jedno, moja stopka jest na serio. Kara to ignorowanie wątków nadawcy.

ja w życiu nauczyłem się, żeby starać się nie oceniać innych, ponieważ pozory często mylą, zwodzą i nigdy nie wiesz jakie powody stoją za czynami, nie wiesz też jaką historię ma ten człowiek... mówię to bardzo poważnie, nie piszę tego na moje "widzi mi się" (oczywiście nie usprawiedliwia to tego, że jeżeli ktoś czyni zło, nawet tzw "mniejsze zło" jest złem i w mojej ocenie nie ma na to usprawiedliwienia - człowiek ma wolę poznania dobra od zła ale zawsze wybiera to gorsze, bo szczerze mówiąc jest to najprostsze i najłatwiejsze w wykonaniu)

a teraz do rzeczy - ustosunkuję się do twoich zarzutów

Kwiatek z pętlą for

nie nazwałbym tego kwiatkiem (mogłeś zapytać co tam kombinuję :P) ponieważ to są próby zrobienia elastycznego modelu - mam na myśli, to, że tą pętlą próbowałem dostosować model do dowolnej ilości wierszy i kolumn - w tej chwili dzięki tej pętli nie jest ona ograniczona do konkretnej ilości wierszy ale jak odkomentujesz warunek switch to model będzie ograniczony do 5 wierszy (a tego nie chcę) i pytanie jest takie - jak to zrobić poprawnie ? - takim przykładem jest QTableWidget tam wczytujesz dowolne ilości wierszy i kolumn

Kwiatek, z dziwną i zbędną konwersją:

faktycznie :D nie zauważyłem tego, ponieważ ciągle skupiałem się na wczytywaniu pliku do modelu, a gdzieś uciekła mi poprawność

Pole Matrix, które nic nie robi i jest używane tylko w konstruktorze.
parent w konstruktorze, nie dość, że jest typu QWidget * to na dodatek jest ignorowane.

pole Matrix było wcześniej ustawione na sztywno w konstruktorze i miało zainicjalizowane dane na sztywno, próbowałem wcześniej tego użyć w ten sposób, że jak wczytam dane z pliku, to te dane powędrują właśnie do tego konterera - ale dooopa coś mi nie idzie jak trzeba

i jakiego typu ma być wskaźnik parent ? Ja wszędzie w samouczkach widzę typ QWidget

co do wskaźnika parent to problem jest taki, że pod systemem windows po kliknięciu przycisku zamknij program się nie zamyka (owszem okno znika ale proces wisi, nie mogę tego zdebugować, nawet znalazłem na necie wątki dotyczące QFileDialog::getOpenFileName że faktycznie jest problem z wiszącym procesem po zniknięciu okna dialogowego Open File przynajmniej tak rozumiem co tam ludzie piszą

Chodziło mi np o zwracanie true w setData. Pewnie nie zadziałało, bo nie obsłużyłeś poprawnie (wcale) EditRole

I nie wiem jak to zrobić, dlatego dobrze by było gdyby ktoś podpowiedział - najlepiej podając krótki przykład kodu

Jako, że zareagowałeś po 16 minutach to jakoś wątpię, żebyś przetrawił nowe dane i zaproponowane zmiany.

w tak szybkim czasie nie jestem w stanie tego zrobić - ja potrzebuję trochę czasu...

Tak ale jak widzę, że dane zawierają jakiś wzór, który może być niepoprawny/bezsensowny i sama aplikacja nie ma, żadnej wartości dla użytkownika, to wpadam w konsternację. To wczytywanie CSV w tej chwili jest jak wiosło na pustyni, niezbyt sensowne i przydatne.

ogólnie nie dziw się, że znajdujesz takie kwiatki, bo to nie jest program do użytku, tylko traktuj to jako kod do nauki, bo chcę się nauczyć robić własny model i nauczyć się z niego korzystać - gdybym zrobił to poprawnie, to znaczyłoby to, że znam Qt ale jak robię coś źle to znaczy, że nie znam Qt na tyle aby robić to dobrze - jak uda mi się to osiągnąć, to będę miał na przyszłość wzór jak to robić dobrze.

Ogólnie nie jestem jeszcze na tym etapie aby pisać programy "dla publiki" - na razie to nauka biblioteki Qt i dopiero jak będę umiał robić dużo rzeczy to będę mógł powiedzieć, że jestem w stanie pisać programy do użytku "publicznego"

Mam nadzieję, że rozumiesz ?

0
zkubinski napisał(a):

co do wskaźnika parent to problem jest taki, że pod systemem windows po kliknięciu przycisku zamknij program się nie zamyka (owszem okno znika ale proces wisi, nie mogę tego zdebugować, nawet znalazłem na necie wątki dotyczące QFileDialog::getOpenFileName że faktycznie jest problem z wiszącym procesem po zniknięciu okna dialogowego Open File przynajmniej tak rozumiem co tam ludzie piszą

Z tego co widzę dalsze/bogatsze wyjaśnienia nie mają sensu.

1 użytkowników online, w tym zalogowanych: 0, gości: 1