Qt - QSqlQueryModel - jak zresetować model po wpisaniu danych ?

0

Na poniższym zrzucie mam na pierwszym planie okno QDialog a w tle jest okno QMainWindow

pytanie jest takie

jak po zmianie danych w oknie pokazanym na pierwszym tle zaktualizować model aby dane równocześnie pojawiły się w oknie na drugim planie ? Dodam, że oba okna korzystają z tego samego modelu

screenshot-20220621153325.png

kod modelu

#include "sqlquerymodelborrowingusers.h"
#include <QDebug>

SqlQueryModelBorrowingUsers::SqlQueryModelBorrowingUsers(QObject *parent, QSqlDatabase *dbConnect) : QSqlQueryModel{parent}
{
    db = dbConnect;

    QString userData = "SELECT pIDuserWyp, pImie, pNazwisko FROM 'tUzytkownicyWypozyczajacy'";

    this->setQuery(userData, *dbConnect);

    this->query().exec();
}

int SqlQueryModelBorrowingUsers::rowCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent);
    int row=0;

    QString rows = "SELECT count(*) FROM 'tUzytkownicyWypozyczajacy'";

    QSqlQuery queryRow(*db);
    queryRow.exec(rows);

    QSqlRecord recRow;
    queryRow.first();
    recRow = queryRow.record();
    row = recRow.value(0).toInt();

    return row;
}

int SqlQueryModelBorrowingUsers::columnCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent);
    int column=0;

    QString columns = "SELECT count(*) FROM PRAGMA_table_info('tUzytkownicyWypozyczajacy')";

    QSqlQuery queryColumn(*db);
    queryColumn.exec(columns);

    QSqlRecord recColumn;
    queryColumn.first();
    recColumn = queryColumn.record();
    column = recColumn.value(0).toInt();

    return column;
}

QVariant SqlQueryModelBorrowingUsers::headerData(int section, Qt::Orientation orientation, int role) const
{
    if(role==Qt::DisplayRole && orientation==Qt::Horizontal){
        if(section==1){
            QVariant header1;
            header1="Imię";

            return header1;
        }
        if(section==2){
            QVariant header2;
            header2="Nazwisko";

            return header2;
        }
    }

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

    return QVariant();
}

QVariant SqlQueryModelBorrowingUsers::data(const QModelIndex &index, int role) const
{
    if(this->query().first() && this->query().isValid()){
        if(this->query().isActive()){
            if(this->query().isSelect()){
                QVariant myData = QSqlQueryModel::data(index, role);

                if(role==Qt::FontRole && index.column()==1){
                    QFont font;
                    font.setWeight(QFont::ExtraBold);

                    myData = font.toString();
                }

                if(role==Qt::TextAlignmentRole && (index.column()==1 || index.column()==2)){
                    return Qt::AlignCenter;
                }

                if(role==Qt::BackgroundRole && index.row() % 2 == 0){
                    QColor color;
                    color.setRgb(176,161,106,69);

                    myData = color;
                }

                if(role==Qt::BackgroundRole && index.row() % 2 == 1){
                    QColor color;
                    color.setRgb(112,102,67,44);

                    myData = color;
                }

                return myData;
            }
        }
    }
    else if(this->query().isValid()==false){
        return QVariant();
    }

    return QVariant();
}

bool SqlQueryModelBorrowingUsers::setData(const QModelIndex &index, const QVariant &value, int role)
{
    QModelIndex pID = QSqlQueryModel::index(index.row(),0,QModelIndex());
    int _pID = data(pID,Qt::EditRole | Qt::DisplayRole).toInt();

    bool isSet=false;

    if(!index.isValid()){
        return false;
    }
    else if(index.column()<1){
        return false;
    }
    else{
        if(role==Qt::EditRole || role==Qt::DisplayRole){
            if(index.column()==1){
                isSet = setName(_pID, value.toString());

                this->refresh();
                return isSet;
            }
            if(index.column()==2){
                isSet = setSurname(_pID, value.toString());

                this->refresh();
                return isSet;
            }
        }
    }

    return isSet;
}

Qt::ItemFlags SqlQueryModelBorrowingUsers::flags(const QModelIndex &index) const
{
    Qt::ItemFlags flag = QSqlQueryModel::flags(index); //export domyślnie ustawionych flag przez klasę nadrzędną

    if(!index.isValid()){
        return Qt::NoItemFlags;
    }
    else if(index.isValid()){
        if(index.column()==1 || index.column()==2){
            flag |= Qt::ItemIsEditable; //dopisanie nowej flagi do wcześniej wyeksportowanych flag

            return flag;
        }
    }

    return flag;
}

bool SqlQueryModelBorrowingUsers::setName(int pID, const QString &pName)
{
    QSqlQuery queryUpdateName;
    QVariant _pID;

    _pID = pID;

    QString strUpdateName = "UPDATE 'tUzytkownicyWypozyczajacy' SET pImie = ? WHERE pIDuserWyp = ?";

    queryUpdateName.prepare(strUpdateName);
    queryUpdateName.addBindValue(pName);
    queryUpdateName.addBindValue(_pID.toString());

    return queryUpdateName.exec();
}

bool SqlQueryModelBorrowingUsers::setSurname(int pID, const QString &pSurname)
{
    QSqlQuery queryUpdateSurname;
    QVariant _pID;

    _pID = pID;

    QString strUpdateSurname = "UPDATE 'tUzytkownicyWypozyczajacy' SET pNazwisko = ? WHERE pIDuserWyp = ?";

    queryUpdateSurname.prepare(strUpdateSurname);
    queryUpdateSurname.addBindValue(pSurname);
    queryUpdateSurname.addBindValue(_pID.toString());

    return queryUpdateSurname.exec();
}

void SqlQueryModelBorrowingUsers::myAddRow()
{
    QSqlQuery lastID, addRecord;
    QString strGetID = "SELECT pIDuserWyp FROM 'tUzytkownicyWypozyczajacy'";
    lastID.prepare(strGetID);

    int id=0;
    QVector<int> tabID;

    if(lastID.exec()){
        if(lastID.isActive()){
            if(lastID.isSelect()){
                while (lastID.next()){
                    id=lastID.value(0).toInt();
                    tabID.push_back(id);
                }
            }
        }
    }

    QVariant getID, setID, saveID;
    getID = max(tabID);

    if(lastID.last()>=1){
        setID=getID.toInt()+1;
        saveID=setID.toString();

        QString strAddRecord = "INSERT INTO 'tUzytkownicyWypozyczajacy' (pIDuserWyp, pImie, pNazwisko) VALUES (?,'','')";

        addRecord.prepare(strAddRecord);
        addRecord.addBindValue(saveID.toString(),QSql::In);
        addRecord.exec();
    }
    else if(lastID.last()==0){
        QString strAddRecord = "INSERT INTO 'tUzytkownicyWypozyczajacy' (pIDuserWyp, pImie, pNazwisko) VALUES (1,'','')";

        addRecord.prepare(strAddRecord);
        addRecord.exec();
    }

    refresh();
}

void SqlQueryModelBorrowingUsers::myRemoveRow()
{
    QModelIndex getIDfromTable;
    QVariant getIDtoRemove;

    QSqlQuery currentID, removeRow;
    QString strGetCurrentID = "SELECT pIDuserWyp FROM 'tUzytkownicyWypozyczajacy'";
    currentID.prepare(strGetCurrentID);

    int i=0;

    if(currentID.exec()){
        if(currentID.isActive()){
            if(currentID.isSelect()){
                while (currentID.next()){
                    i=currentID.at();
                    if(i==this->row){
                        getIDfromTable=index(i,0,QModelIndex());
                        getIDtoRemove=getIDfromTable.data();
                    }
                }
            }
        }
    }

    QString strRemoveRow = "DELETE FROM 'tUzytkownicyWypozyczajacy' WHERE pIDuserWyp = (?)";
    removeRow.prepare(strRemoveRow);
    removeRow.addBindValue(getIDtoRemove.toString(),QSql::In);
    removeRow.exec();

    refresh();
}

void SqlQueryModelBorrowingUsers::refresh()
{
    this->query().clear();

    this->setQuery("SELECT pIDuserWyp, pImie, pNazwisko FROM 'tUzytkownicyWypozyczajacy'");

    this->query().exec();
}

SqlQueryModelBorrowingUsers::~SqlQueryModelBorrowingUsers()
{
    this->query().clear();
}

1

Okno Dialogowe jest modalne, czyli nie zamkniesz Main przed nim, więc nic nie stoi na przeszkodzie aby przekazać uchwyt lub wskaźnik na funkcje.
W przeciwnym przypadku pozostaje jedynie slot+event

2

QSqlQueryModel jest read-only, na dodatek nie masz żadnego cache'u, więc zdaje się, że jedyną opcją jest całkowity reset modelu po jakiejkolwiek zmianie(IMHO dataChanged, beginInsertRowsitp. tutaj nie dadzą rezultatu).
*Mogę się mylić, bo dawno z qt nie korzystałem

PS. Liczba kolumn raczej nie będzie się zmieniać, więc można ją ustalić na sztywno lub zainicjować raz, np. w konstruktorze.
Nie wiem też po co te kombinacje z ID w myAddRow. Tym powinna się chyba baza zająć (AUTOINCREMENT?)
No i czy te wszystkie first()/isValid()/isActive()/isSelect() są wszędzie konieczne to też mam wątpliwości.

Zamiast ustalać kolor tła w modelu zrzuciłbym to raczej na widok (QAbstractItemView::setAlternatingRowColors).
Generalnie model tego typu nie powinien się mieszać w sposób wyświetlania danych.

0

@tajny_agent:

Liczba kolumn raczej nie będzie się zmieniać, więc można ją ustalić na sztywno lub zainicjować raz, np. w konstruktorze.

zgadzam się ale ja się uczę robić własny model i chcę zrozumieć co i skąd się bierze i jak do tego dojść aby to zadziałało jak trzeba. Łatwe to nie było ale coś mi się udało zrobić

Nie wiem też po co te kombinacje z ID w myAddRow

racja ale myAddRow dodaje nowy wpis do bazy i myRemoveRow usuwa wpis z bazy, bo niby są funkcje dodawania i usuwania rekordów ale póki co, to nie mam pojęcia jak ich użyć, a dla mnie baza bez możliwości dodawania i usuwania rekordów nie jest funkcjonalna

Tym powinna się chyba baza zająć (AUTOINCREMENT?)

jest AUTOINCREMENT, bo inaczej nie rozróżniłbym rekordu bez podania ID, tylko ta kolumna jest ukryta

No i czy te wszystkie first()/isValid()/isActive()/isSelect() są wszędzie konieczne to też mam wątpliwości.

teoretycznie nie ale w dokumentacji pisze, że niby każda z tych funkcji jakby popycha następną jeżeli chodzi o kolejność sprawdzeń (czyli najpierw weryfikowane jest isValid, ale żeby isValid zwrócił true to musi być na coś ustawiony jedną z funkcji np next lub first, bo inaczej zawsze isValid zwróci false nawet jeżeli zapytanie działa poprawnie, następnie sprawdzane jest isActive, a na końcu isSelect) i akurat wpisałem to, bo chcę sobie uzmysłowić do czego to może się przydać i jak to wykorzystać, bo zastanawiam się czy tego nie użyć do wypisania komunikatów w stylu "błąd połączenia z bazą" albo "nie udało się wykonać zapytania" - w zależności na którym poziomie się wysypie

Zamiast ustalać kolor tła w modelu zrzuciłbym to raczej na widok (QAbstractItemView::setAlternatingRowColors).
Generalnie model tego typu nie powinien się mieszać w sposób wyświetlania danych.

masz rację, bo ogólnie wiele funkcji wziąłem z klas nadrzędnych ale to tylko po to aby się naumieć, wyćwiczyć i zrozumieć.

1
zkubinski napisał(a):

Liczba kolumn raczej nie będzie się zmieniać, więc można ją ustalić na sztywno lub zainicjować raz, np. w konstruktorze.

zgadzam się ale ja się uczę robić własny model i chcę zrozumieć co i skąd się bierze i jak do tego dojść aby to zadziałało jak trzeba. Łatwe to nie było ale coś mi się udało zrobić

Wciąż będziesz wiedział co się skąd bierze z tą różnicą, że zapytania do bazy będą szły tylko wtedy kiedy rzeczywiście są potrzebne a nie przy każdej możliwej okazji. Podobnie jest rowCount.

Nie wiem też po co te kombinacje z ID w myAddRow

racja ale myAddRow dodaje nowy wpis do bazy i myRemoveRow usuwa wpis z bazy, bo niby są funkcje dodawania i usuwania rekordów ale póki co, to nie mam pojęcia jak ich użyć, a dla mnie baza bez możliwości dodawania i usuwania rekordów nie jest funkcjonalna

Tym powinna się chyba baza zająć (AUTOINCREMENT?)

jest AUTOINCREMENT, bo inaczej nie rozróżniłbym rekordu bez podania ID, tylko ta kolumna jest ukryta

Skoro id jest w bazie jako autoincrement to po prostu nie powinieneś tego ręcznie wpisywać. Baza sama wypełni to pole odpowiednią wartością. Po to to właśnie jest. Możesz sobie później to id wyciągnąć z bazy poprzez select czy lastrowid jeśli Ci potrzebne.

No i czy te wszystkie first()/isValid()/isActive()/isSelect() są wszędzie konieczne to też mam wątpliwości.

teoretycznie nie ale w dokumentacji pisze, że niby każda z tych funkcji jakby popycha następną jeżeli chodzi o kolejność sprawdzeń (czyli najpierw weryfikowane jest isValid, ale żeby isValid zwrócił true to musi być na coś ustawiony jedną z funkcji np next lub first, bo inaczej zawsze isValid zwróci false nawet jeżeli zapytanie działa poprawnie, następnie sprawdzane jest isActive, a na końcu isSelect) i akurat wpisałem to, bo chcę sobie uzmysłowić do czego to może się przydać i jak to wykorzystać, bo zastanawiam się czy tego nie użyć do wypisania komunikatów w stylu "błąd połączenia z bazą" albo "nie udało się wykonać zapytania" - w zależności na którym poziomie się wysypie

No ok tylko, że w dokumentacji np. metody first() jest napisane że, Retrieves the first record in the result, if available, and positions the query on the retrieved record. Note that the result must be in the active state and isSelect() must return true before calling this function or it will do nothing and return false.
Czyli teoretycznie najpierw powinno się wołać isSelect() a dopiero później first() żeby to miało sens. U Ciebie jest odwrotnie, isSelect wywołujesz na samym końcu.

Z kolei w opisie isActive() jest notka, że aktywne zapytanie może kolidować z transakcjami w pewnych bazach. Nie używasz transakcji, więc nie widzę potrzeby sprawdzania tego.

To jest moim zdaniem niepotrzebne:

`if(this->query().first() && this->query().isValid()){
        if(this->query().isActive()){
            if(this->query().isSelect()){

Dlaczego?
first() jeśli może to ustawia query na pierwszy rekord i informuje czy się udało.
isValid() zwraca true jeśli query jest ustawione na poprawny rekord.

Czyli jeśli first() == true to automatycznie isValid() == true.
Jeśli first() == false to isValid() nawet nie zostanie sprawdzone (bo short-circuit evaluation).
Transakcji nie masz, więc isActive()wydaje się zbędne, podobnie jak isSelect() wywoływane na końcu.
Koniec końców dane i tak pobierasz metodą klasy nadrzędnej, więc jak coś jest faktycznie nie halo to dostaniesz pusty QVariant.

0

@tajny_agent:

Skoro id jest w bazie jako autoincrement to po prostu nie powinieneś tego ręcznie wpisywać. Baza sama wypełni to pole odpowiednią wartością. Po to to właśnie jest. Możesz sobie później to id wyciągnąć z bazy poprzez select czy lastrowid jeśli Ci potrzebne.

dzięki, poprawiłem funkcję wstawiania rekordów na

void SqlQueryModelBorrowingUsers::myAddRow()
{
    QSqlQuery lastID, addRecord;
    QString strGetID = "SELECT pIDuserWyp FROM 'tUzytkownicyWypozyczajacy'";
    lastID.prepare(strGetID);

    if(lastID.exec()){
        if(lastID.last() && lastID.isValid()){
            QString strAddRecord = "INSERT INTO 'tUzytkownicyWypozyczajacy' (pImie, pNazwisko) VALUES ('','')";

            addRecord.prepare(strAddRecord);
            addRecord.exec();
        }
        else if(!lastID.isValid()){
            QString strAddRecord = "INSERT INTO 'tUzytkownicyWypozyczajacy' (pImie, pNazwisko) VALUES ('','')";

            addRecord.prepare(strAddRecord);
            addRecord.exec();
        }
    }

    refresh();
}

Jeżeli widzisz gdzieś błąd logiczny daj znać. Zastanawia mnie też czy da radę się pozbyć tego else aby nie dublować zapytania. Bo w tym warunku chodzi tylko o dodanie rekordu jak nic nie ma w bazie.

co do reszty o której pisałeś, to masz rację. W moim przypadku przyda się tylko funkcja exec(), isValid() i next()

0

Naucz się wreszcie algebrze boolowskiej:

        if(lastID.last() && lastID.isValid()) rob_cos();
        else if(!lastID.isValid()) rob_cos();

tożsame z:

        if(lastID.last() || !lastID.isValid()) rob_cos();

Zacznij myśleć logicznie, wg ciebie jak tablica pusta to nie wolno nic dodać?
Nie udało się znaleźć rekordu - to nic nie wstawiamy?!?!

Zacznij czytać dokumentację!
Bo wystarczy tyle:

void SqlQueryModelBorrowingUsers::myAddRow(const QString &Imie="", const QString &Nazwisko="")
{
    QSqlQuery q;
    q.prepare("INSERT INTO 'tUzytkownicyWypozyczajacy' (pImie, pNazwisko) VALUES (':imie',':nazwisko')");
    q.bindValue(":imie",Imie);
    q.bindValue(":nazwisko",Nazwisko);
    q.exec();
    setQuery(query()); // to powinno odświeżyć model
}
0

@_13th_Dragon:

setQuery(query()); // to powinno odświeżyć model

możliwe, że źle sprecyzowałem pytanie ale - mam dwa różne widoki typu QTableView i jeden model QSqlQueryModel i oba widoki korzystają z tego jednego modelu. Możliwe, że model musi poinformować oba widoki, że coś się zmieniło i widok musi się odświeżyć, tylko jak to zrobić ?

0

@_13th_Dragon:

Pokaż jak przekazujesz model do okna

#include "borrowinguserswindow.h"
#include <QDebug>

BorrowingUsersWindow::BorrowingUsersWindow(QWidget *parent) : QDialog(parent)
{
    this->resize(400,300);
    this->setWindowTitle(QString(tr("Dodawanie użytkownika wypożyczającego")));

    mainLayout = new QVBoxLayout(this);

    lblNameSurnameHLayout = new QHBoxLayout();

    lblName = new QLabel(this);
    lblName->setText(QString(tr("Imię")));

    lblSurname = new QLabel(this);
    lblSurname->setText(QString(tr("Nazwisko")));

    leEdtNameSurnameHLayout = new QHBoxLayout();

    leEdtName = new QLineEdit(this);
    leEdtSurname = new QLineEdit(this);

    tblViewUserData = new QTableView(this);

    bttHLayout = new QHBoxLayout();

    bttAdd = new QPushButton(this);
    bttAdd->setText(QString(tr("Dodaj")));
    bttAdd->setIcon(QIcon(":icons/add.png"));

    bttQuit = new QPushButton(this);
    bttQuit->setText(QString(tr("Zamknij")));
    bttQuit->setIcon(QIcon(":icons/exit.svg"));

    bttRem = new QPushButton(this);
    bttRem->setText(QString(tr("Usuń")));
    bttRem->setIcon(QIcon(":icons/remove.svg"));

    lblNameSurnameHLayout->addWidget(lblName);
    lblNameSurnameHLayout->addWidget(lblSurname);

    leEdtNameSurnameHLayout->addWidget(leEdtName);
    leEdtNameSurnameHLayout->addWidget(leEdtSurname);

    bttHLayout->addWidget(bttAdd);
    bttHLayout->addWidget(bttRem);
    bttHLayout->addWidget(bttQuit);

    mainLayout->addLayout(lblNameSurnameHLayout);
    mainLayout->addLayout(leEdtNameSurnameHLayout);
    mainLayout->addWidget(tblViewUserData);
    mainLayout->addLayout(bttHLayout);

    QObject::connect(bttQuit, &QPushButton::clicked, this, &QDialog::close);
}

void BorrowingUsersWindow::setDatabaseConnectionAddress(QSqlDatabase *connectionDB)
{
    this->_connectionDB = connectionDB;
    modelSqlBorrowingUsers = new SqlQueryModelBorrowingUsers(this, _connectionDB);
    tblViewUserData->setModel(modelSqlBorrowingUsers);
    tblViewUserData->setColumnHidden(0,true);

    QObject::connect(tblViewUserData, &QTableView::clicked, modelSqlBorrowingUsers, &SqlQueryModelBorrowingUsers::getRowFromTable);
    QObject::connect(bttAdd, &QPushButton::clicked, modelSqlBorrowingUsers, &SqlQueryModelBorrowingUsers::myAddRow);
    QObject::connect(bttRem, &QPushButton::clicked, modelSqlBorrowingUsers, &SqlQueryModelBorrowingUsers::myRemoveRow);

    QObject::connect(leEdtName, &QLineEdit::textEdited, modelSqlBorrowingUsers, &SqlQueryModelBorrowingUsers::setName);
    QObject::connect(leEdtSurname, &QLineEdit::textEdited, modelSqlBorrowingUsers, &SqlQueryModelBorrowingUsers::setSurname);
}

QSqlDatabase *BorrowingUsersWindow::databaseConnectionAddress()
{
    return this->_connectionDB;
}

BorrowingUsersWindow::~BorrowingUsersWindow()
{

}

odpowiada za to funkcja void BorrowingUsersWindow::setDatabaseConnectionAddress(QSqlDatabase *connectionDB)

1

Przecież masz inny obiekt modelu!
Przekaż model z głównego okienka do tego poprzez referencję, zresztą mówiłem już o tym tu:
https://4programmers.net/Forum/C_i_C++/361767-qt_qsqlquerymodel_jak_zresetowac_model_po_wpisaniu_danych?p=1851951#id1851951
ale nie czytasz dalej niż pierwsze parę słów.

Proste tłumaczenie slotów i sygnałów lekko przerobione z dokumentacji: https://doc.qt.io/qt-6/signalsandslots.html

#include <QObject>

class Counter : public QObject
{
    Q_OBJECT
    
    private:
    int m_value;
  	public:
    Counter():m_value(0) {}
    int value()const { return m_value; }

    public slots: 
    void setValue(int value);
    signals: 
    void valueChanged(int newValue);
};

void Counter::setValue(int value)
{
    if (value != m_value) {
        m_value = value;
        emit valueChanged(value);
    }
}

void foo()
{
	Counter a, b;
    QObject::connect
	(
		&a,&Counter::valueChanged,
        &b,&Counter::setValue
	);

    a.setValue(12);     // a.value() == 12, b.value() == 12
    b.setValue(48);     // a.value() == 12, b.value() == 48
    a.setValue(10);     // a.value() == 10, b.value() == 10
}

Zacznij dokumentację czytać!

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