Qt - własny model po QAbstractItemModel

1

Tworzę sobie swój własny model który dziedziczy po QAbstractItemModel i pojawił się u mnie "mały" problem...

mam 2 klasy

  1. MainWindow
  2. MyModel

.1. klasa - MainWindow
zawartość pliku nagłówkowego mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QTableView>

class mymodel;

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    mymodel *_myModel;
    QTableView *view;
};
#endif // MAINWINDOW_H

zawartość pliku żródłowego mainwindow.cpp

#include "mainwindow.h"
#include "mymodel.h"

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
    view = new QTableView();
}

MainWindow::~MainWindow()
{
}
  1. klasa - MyModel

zawartość pliku nagłówkowego mymodel.h

#ifndef MYMODEL_H
#define MYMODEL_H

#include <QAbstractItemModel>

class MyModel : public QAbstractItemModel
{
public:
    MyModel(QWidget *parent=nullptr);

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

#endif // MYMODEL_H

zawartość pliku źródłowego mymodel.cpp

#include "mymodel.h"

MyModel::MyModel(QWidget *parent)
{

}

int MyModel::columnCount(const QModelIndex &parent) const
{
    return 3;
}

int MyModel::rowCount(const QModelIndex &parent) const
{
    return 4;
}

i teraz mam pytanie, w jaki sposób mogę pokazać ilość wierszy i kolumn w mainwindow ? Bo chciałbym aby widok wyświetlił model

wyprzedzam już pytania typu "po co robisz swój model" skoro są inne "gotowe" - akurat chcę się nauczyć robić własne modele i zrozumieć jak to działa

0

Najpierw zacznij od opisania co będzie reprezentował ten model.
Drzewo? Np strukturę katalogów.
Listę?
Tabelę?
Inne cudo?

Zależnie od tego, należy dziedziczyć po potomkach QAbstractItemModel.

Następnie opisz jaki zestaw funkcjonalności ma mieć ten model danych.
Czy użytkownik ma prawo modyfikować dane?
Czy masz API do modyfikowania zdanych.
Czy ten model jest tylko proxy do innej struktury danych?
Itp itd.

1

dzięki za odpowiedź, nie wiedziałem, że muszę to doprecyzować, ponieważ sądziłem, że ten model jest tylko do tabel, więc tak

  1. Model ma pobierać dane z bazy danych
  2. Tabela ma być edytowalna - ale nie wszystkie kolumny

Zależnie od tego, należy dziedziczyć po potomkach QMainWindow.

Czy możesz rozwinąć myśl ? Bo nie wiem, co miałoby dziedziczyć po QMainWindow i w jakim celu ?

Czy użytkownik ma prawo modyfikować dane?

tak, ale nie wszystkie kolumny

Czy masz API do modyfikowania zdanych.

czy możesz przybliżyć myśl o jakie API Tobie chodzi ? Ponieważ nie rozumiem tego pytania

Czy ten model jest tylko proxy do innej struktury danych?

tego też nie rozumiem, ponieważ nie wiem o co chodzi z tym "proxy". Natomiast mogę powiedzieć, że struktura danych ma być tabelaryczna

0
zkubinski napisał(a):

dzięki za odpowiedź, nie wiedziałem, że muszę to doprecyzować, ponieważ sądziłem, że ten model jest tylko do tabel, więc tak

Model jest abstrakcją na jakieś dane, od ciebie zależy co reprezentuje. W tym wypadku:

zkubinski napisał(a):
  1. Model ma pobierać dane z bazy danych
  2. Tabela ma być edytowalna - ale nie wszystkie kolumny

Zależnie od tego, należy dziedziczyć po potomkach QMainWindow.

Czy możesz rozwinąć myśl ? Bo nie wiem, co miałoby dziedziczyć po QMainWindow i w jakim celu ?

Raczej nie QMainWindow, ale bardziej potrzebujesz QTableView aby zaprezentować dane. Ów QTableView umieść jako central widget instancji QMainWindow.

zkubinski napisał(a):

Czy użytkownik ma prawo modyfikować dane?

tak, ale nie wszystkie kolumny

Czy masz API do modyfikowania zdanych.

czy możesz przybliżyć myśl o jakie API Tobie chodzi ? Ponieważ nie rozumiem tego pytania

Czy ten model jest tylko proxy do innej struktury danych?

tego też nie rozumiem, ponieważ nie wiem o co chodzi z tym "proxy". Natomiast mogę powiedzieć, że struktura danych ma być tabelaryczna

Generalnie skoro potrzebujesz dobierać się do bazy danych to bardziej przyda ci się oprzeć nie na QAbstractItemModel, a QSqlTableModel.

1

Model jest abstrakcją na jakieś dane, od ciebie zależy co reprezentuje.

tak, wiem

Raczej nie QMainWindow, ale bardziej potrzebujesz QTableView aby zaprezentować dane. Ów QTableView umieść jako central widget instancji QMainWindow.

to też wiem, że muszę te dane wyświetlić w widoku QTableView, ale chciałem aby kolega @MarekR22 rozwinął myśl o co jemu chodzi, bo sądzę, że wie o tym iż muszę wyświetlić to w QTableView być może miał na myśli inną rzecz ?

Generalnie skoro potrzebujesz dobierać się do bazy danych to bardziej przyda ci się oprzeć nie na QAbstractItemModel, a QSqlTableModel.

próbowałem QSqlTableModel, a w zasadzie QSqlQueryModel i tak:

  1. QSqlTableModel ma problemy z zapytaniami setQuery i to działa tylko na jednej tabeli, więc nie jest to satysfakcjonujące rozwiązanie
  2. QSqlQueryModel jest tylko do wyświetlania danych pobranych za pomocą zapytań SQL, generalnie da się zrobić model edytowalny ale już poległem przy próbie dodawania i usuwania wierszy w tym modelu dlatego padł wybór na QAbstractItemModel ponieważ stworzenie własnego modelu daje większe możliwości niż modele które są "gotowe" ponieważ mają jakieś ograniczenia i jak jedno się zrobi, to na drugim łamię zęby

ogólnie rzecz biorąc mam już jakieś doświadczenia z modelami ale to początki

PS. Jak chcecie, to mogę umieścić cały swój kod oparty o klasę QSqlQueryModel mam tam zaimplementowany QItemDelegate, Qt::ItemFlags (Qt::ItemIsEditable), no i mam swój "model" ale połamałem zęby na dodawaniu i usuwaniu rekordów, bo dodają się i usuwają ale nie są edytowalne, gdyż QSqlQueryModel nie ma czegoś takiego jak insertRow i removeRow, co prawda dziedziczy to po QAbstractItemModel ale nie da się już edytować tych rekordów

0

A nie wystarczy ci QSqlTableModel? Jeśli nie to czym to się ma różnić?

0

nie wystarczy, bo chcę mieć możliwość wykonywania zapytań SQL, a gdy użyję tej klasy, to wszystkie dane musiałbym po prostu umieszczać w jednej tabeli

1
zkubinski napisał(a):

próbowałem QSqlTableModel, a w zasadzie QSqlQueryModel i tak:

  1. QSqlTableModel ma problemy z zapytaniami setQuery i to działa tylko na jednej tabeli, więc nie jest to satysfakcjonujące rozwiązanie
  2. QSqlQueryModel jest tylko do wyświetlania danych pobranych za pomocą zapytań SQL, generalnie da się zrobić model edytowalny ale już poległem przy próbie dodawania i usuwania wierszy w tym modelu dlatego padł wybór na QAbstractItemModel ponieważ stworzenie własnego modelu daje większe możliwości niż modele które są "gotowe" ponieważ mają jakieś ograniczenia i jak jedno się zrobi, to na drugim łamię zęby

ogólnie rzecz biorąc mam już jakieś doświadczenia z modelami ale to początki

PS. Jak chcecie, to mogę umieścić cały swój kod oparty o klasę QSqlQueryModel mam tam zaimplementowany QItemDelegate, Qt::ItemFlags (Qt::ItemIsEditable), no i mam swój "model" ale połamałem zęby na dodawaniu i usuwaniu rekordów, bo dodają się i usuwają ale nie są edytowalne, gdyż QSqlQueryModel nie ma czegoś takiego jak insertRow i removeRow, co prawda dziedziczy to po QAbstractItemModel ale nie da się już edytować tych rekordów

Hmmm, z opisu jaki efekt chcesz uzyskać wynika, że potrzebujesz tak de facto prezentacji tego, co zwróci zapytanie QSqlQuery. W takim wypadku nie ma co się bawić w dodawanie/usuwanie wierszy, tylko robisz reset modelu opartego o QSqlQuery za każdym razem jak ustawiasz nowe zapytanie. Edycję pola zrobisz wtedy poprzez pobieranie QSqlRecord i QSqlField.

Na razie, zamiast pokazywać kod, opisz jakieś przykładowe zapytania i ich wynik, oraz w jaki sposób ma wyglądać ich edycja.

1

trochę się nie rozumiemy, źle objaśniłem sprawę, a więc tak:

  1. program łączy się z bazą i przy pomocy zapytania pobiera dane z bazy
    (w bazie jest tak, że mam jedną tabelę główną w której zawarte są klucze obce i tutaj chciałbym korzystać z QItemDelegate aby wyświetlać listę rozwijaną QComboBox, wiem, że można to lepiej osiągnąć za pomocą QSqlRelationalDelegate ale chcę to sobie poćwiczyć i się nauczyć)
  2. pobrane dane z bazy ładują się do modelu
  3. model wyświetla dane w widoku QTableView
  4. model ten musi być edytowalny aby użytkownik mógł sobie zmienić dowolny rekord wedle uznania
  5. musi mieć możliwość dodawania i usuwania wybranych rekordów

Co chcę osiągnąć ?

  1. Umieć tworzyć zapytania SQL i ich wynik zwracać do modelu
  2. otrzymany wynik zapytania model musi umieć zwrócić na przykład wybrane dane widokowi
    (chciałbym też umieć ukrywać niektóre kolumny np z ID bo user nie musi ich oglądać, a niektóre kolumny chciałbym mieć zablokowane do edycji gdyż jest pewna potrzeba aby user w tych kolumnach nie grzebał)
  3. dodawać, usuwać i edytować wybrane rekordy
1

pozwolę sobie zrobić mały update pierwszego postu. Poradziłem sobie z problemem wyświetlania rekordów w widoku. Wszystko się rozeszło o właściwe zrozumienie jak odwołać się do wskaźnika metody wirtualnej, jest to pokazane w pliku mainwindow.cpp w miejscu absModel = new MyModel();

no więc kod jest taki:

  1. Klasa MainWindow

plik mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QWidget>
#include <QGridLayout>
#include <QTabWidget>
#include <QVBoxLayout>
#include <QHBoxLayout>

#include <QAbstractItemModel>
#include <QTableView>

class MyModel;

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    QWidget *mainWigdet, *widgetTab1, *widgetTab2;
    QGridLayout *mainLayout;
    QTabWidget *mainTabWidget;
    QVBoxLayout *layout1;
    QHBoxLayout *BttLayout1;

    QAbstractItemModel *absModel;
    QTableView *view;
};
#endif // MAINWINDOW_H

plik mainwindow.cpp

#include "mainwindow.h"
#include "mymodel.h"

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
    //![1]
        mainWigdet = new QWidget(this);
    //![/1]

    //![2]
        mainLayout = new QGridLayout(mainWigdet);
    //![/2]

    //![3]
        mainTabWidget = new QTabWidget(mainWigdet);
    //![/3]

    //![4]
        widgetTab1 = new QWidget(mainTabWidget);
        widgetTab2 = new QWidget(mainTabWidget);
    //![/4]

        mainTabWidget->addTab(widgetTab1, "Widok");
        layout1 = new QVBoxLayout();

    absModel = new MyModel();
    //absModel->rowCount();
    //absModel->columnCount();

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

    mainWigdet->setLayout(mainLayout);
    mainLayout->addWidget(mainTabWidget,0,0);

    widgetTab1->setLayout(layout1);
    layout1->addWidget(view);

    this->setCentralWidget(mainWigdet);
}

MainWindow::~MainWindow()
{
}
  1. Klasa MyModel

plik mymodel.h

#ifndef MYMODEL_H
#define MYMODEL_H

#include <QAbstractItemModel>

class MyModel : public QAbstractItemModel
{
public:
    MyModel(QWidget *parent=nullptr);

    static int _column, _row;
    QModelIndex _parent;

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

    bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;

    QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
    QModelIndex parent(const QModelIndex &index) const override;
};

#endif // MYMODEL_H

plik mymodel.cpp

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

int MyModel::_row=5;
int MyModel::_column=3;

MyModel::MyModel(QWidget *parent)
{
}

int MyModel::columnCount(const QModelIndex &parent) const
{
    qDebug()<<"funkcja columnCount"<<_column;
    return _column;
}

int MyModel::rowCount(const QModelIndex &parent) const
{
    qDebug()<<"funkcja rowCount"<<_row;
    return _row;
}

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

QModelIndex MyModel::index(int row, int column, const QModelIndex &parent) const
{
    qDebug()<< "wykonala sie funkcja index";
    qDebug()<< "parent"<<_parent.parent();
    return _parent;
}

QModelIndex MyModel::parent(const QModelIndex &index) const
{
}

bool MyModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
}

No i widzę stworzone przez siebie kolumny i wiersze oraz zastanawiam się dalej co trzeba zrobić. W sumie się domyślam, że trzeba te metody jakoś ogólnie oprogramować co mają robić, potem dodać jakąś klasę z zapytaniami QSqlQuery i przekierować te zapytanie do funkcji MyModel::setData i zobaczyć efekty ale jeszcze muszę wiedzieć ile ma wierszy i kolumn tabela w bazie itp...

Jak macie jakieś uwagi (nawet te krytyczne), pomysły, propozycje to podzielcie się.

0

Chcesz koniecznie zrobić sposobem trudniejszym, cóż, kimże my z Markiem jesteśmy aby ci bronić? Proszę bardzo, reimplementuj QAbstractItemModel - w sumie, to nawet wskazane jest na pewnym etapie nauki Qt aby jakiś item model od zera napisać.
Jednak tutaj zacznij od implementacji metody index(), jak będziesz ją miał wtedy reszta będzie duużo prostsza.

0

no właśnie z tym indexem mam problem, pomożecie ? Bo jak nie zrobię indexu, to w sumie nie będę wiedział co jakich danych w której konkretnej komórce się odwołać, bo w sumie te funkcje wymagają indexu, no ale nie tylko indexu

2

Żeby było jasne
QSqlQueryModel dostarcza model "read only" z jednego powodu. Autorzy nie wiedzę jakie sql query QSqlQueryModel dostanie (może to być SELECT z wieloma join), ergo nie są w stanie stwierdzić jak obsłużyć modyfikację.
W przypadku QSqlTableModel masz tabelę i nie znając jej szczegółów można ją dość łatwo modyfikować UPDATE table_name SET field1 = ? WHERE Clause id=?.

Teraz ty znając swoje swoją bazę danych i query, które chcesz użyć do pobierania danych. Możesz więc dziedziczyć po QSqlQueryModel i zaimplementować modyfikację danych przez użytkownika.
Najprościej będzie w setData robić odpowiedni UPDATE i po sukcesie odświeżać cały model.
Optymalizacje typu: zmiana wartości w komórce, usuwanie dodawanie wierszy, bez pełnego refresh modelu są bardzo trudne w tym przypadku.

IMO masz dziedziczyć po QSqlQueryModel i nadpisać metody:

  • flags - żeby poinformować: co ma być edytowalne, a co nie
  • setData - żeby obsłużyć zapis nowych danych najpierw beginResetModel(), potem UPDATE ... na bazie danych, a na koniec endResetModel().

Wydaje mis się, że to powinno wystarczyć.

1

Generalnie po to używa się nieco bardziej zaawansowanych, bazowych klas modeli aby właśnie nie potrzebować się pałować z implementacją:

QModelIndex QAbstractItemModel::index(int row, int column, const QModelIndex &parent = QModelIndex()) const

W tejże metodzie, przy pomocy createIndex() tworzysz QModelIndex dla podanych danych wejściowych. Prawdziwy dynks tkwi w zmapowaniu danych z QSqlQuery na 2-wymiarową strukturę jakiej używa QAbstractitemModel - a tutaj to już sam musisz sobie wymyślić jak odwzorować zapytanie dajmy na to "select count from table SomeTable where <some condition="condition">" na wiersz, kolumnę oraz rodzica.
Co jest oczywistym, to to, iż po każdej zmianie zapytania dotychczas potworzone QModelIndexy tracą cały sens, stąd wspomniałem o konieczności resetowania całego modelu w takim przypadku.

Suma sumarum, będziesz musiał na piechotę zrobić to, co już jest w QSqlQueryModelu.

0

w sumie dla mnie jakiejś filozofii nie ma aby zliczyć ile jest wierszy i kolumn w bazie, bo to jest to samo co macierz (algorytm macierzy umiem zrobić) ale bardziej mnie zastanawia to, że jak sobie pozliczam ile mam tych wierszy i kolumn to jak to "pokazać" QModelIndex-owi i przekazać tej funkcji ? Druga sprawa, to jak stworzyć tego parenta (root index) ? Bo tam też jest taka funkcja

Domyślam się następujących rzeczy:

  1. Zliczyć w macierzy (bazie) ile jest wierszy i przekazać do tej funkcji - tylko skąd tego parenta wziąć ?
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
  1. Zliczyć ile jest kolumn i przekazać to tej funkcji - analogicznie to co wyżej skąd tego parenta wziąć ?
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
  1. Po zliczeniu wierszy i kolumn poniższa funkcja index pobierze sobie z automatu te dane, ponieważ rowCount i columnCount zwraca ile mają wierszy i kolumn - tylko znowu mam problem z tym parentem
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
  1. Tego parenta chyba robi się tu i ta funkcja go zwraca i powyższe funkcje sobie to pobierają, jak to w klasie
QModelIndex parent(const QModelIndex &index) const override;

Z pozostałymi funkcjami zobaczymy czy sobie sam poradzę...

Czy mogę prosić @MasterBLB o dalsze wyjaśnienie ?

0

Inaczej, nim się weźmiesz za model musisz@zkubinski zastanowić się, jakie QSqlQuery chcesz obsługiwać, i dać kilka przykładowych z całego spektrum przewidywanych. Wyjaśnienie dokładnej budowy bazy danych na którym to ma operować także może być potrzebne.

0

zastanowić się, jakie QSqlQuery chcesz obsługiwać

czy masz na myśli to, czy moje zapytania będą zawierały "INNER JOIN" lub inne podobne skomplikowane zapytania ? Czy po prostu to będą zwykłe "SELECT" z jednej tabeli ?

dać kilka przykładowych z całego spektrum przewidywanych.

masz na myśli, że mam dać klika przykładowych zapytań ?

0

Tak i tak na oba pytania.

0

@zkubinski:
parent ma znaczenie tylko w przypadku modeli hierarchicznych, jak np. podział administracyjny państwa gdzie np. Polska jest 'rodzicem' dla województw, województwa są 'rodzicami' dla powiatów itd.
Dla list czy tabel parent nie jest istotny, dlatego w takich przypadkach powinno się dziedziczyć po QAbstractListModel czy QAbstractTableModel, żeby nie wynajdować koła na nowo.

0

Kilka dni myślałem nad przykładem, bo chciałbym zrozumieć te indexy, parenty i resztę no i chyba wymyśliłem:
Poniżej baza ma następującą strukturę, nie jest to jakiś konkretny mój problem ale starałem się pomyśleć przykład tak aby posłużył mi jako wzór do moich przyszłych rozwiązań.

Główna tabela to tWypozyczone

screenshot-20191111175734.png

Natomiast przykładowe zapytanie to:

SELECT 
`myDB`.`tWypozyczone`.`pIdWypozyczone`,
`myDB`.`tUzytkownicy`.`pImie`,
`myDB`.`tUzytkownicy`.`pNazwisko`,
`myDB`.`tUzytkownicy`.`pLogin`
FROM
`myDB`.`tWypozyczone`
INNER JOIN
`myDB`.`tUzytkownicy`
ON
`myDB`.`tUzytkownicy`.`pIdUzytkownicy` = `myDB`.`tWypozyczone`.`pIdUzytkownicy`
ORDER BY
`myDB`.`tWypozyczone`.`pIdWypozyczone`
ASC

Wiem, że w tym przypadku mógłbym skorzystać z klasy QSqlRelationalTableModel ale chciałbym wiedzieć jak tworzyć własne modele. Zależy mi na tym aby zrozumieć jak tworzyć index na dane we własnym modelu.

Mogę też dać przykład "zapytania w zapytaniu" ale to już byłby zbyt zaawansowany poziom.

0

To tak - wykonanie QSqlQuery w rezultacie daje X wierszy z bazy, a każdy wiersz ma Y kolumn. Zatem pierwsza sprawa jaką będziesz potrzebował w implementacji funkcji index() to przeniesienie wyników do jakiejś pomocniczej tabeli, zamiast ciągle się pałować z query.next().
Mając tą tabelę bardzo prosto jest już podać dla createIndex(row, column) wymagane parametry - wiersz i kolumnę będziesz miał, a parent nie jest potrzebny, zatem można przesłać null.
Ponadto od razu będziesz też w stanie zaimplementować rowCount() i columnCount()
Jednakże twój model musi mieć dodatkowo metodę setQuery która będzie ową pomocniczą tablicę ustawiała, a po tym jak skończy robiła reset() modelu.

0
MasterBLB napisał(a):

Mając tą tabelę bardzo prosto jest już podać dla createIndex(row, column, parent) wymagane parametry - wiersz i kolumnę będziesz miał, a parent nie jest potrzebny, zatem można przesłać null.

Gwoli ścisłości to trzeci parametr createIndex nie dotyczy rodzica, ale pozwala na 'podpięcie' do indexu czegoś w stylu user-data.
Później można to wyciągnąć za pomocą QModelIndex::internalId()/QModelIndex::internalPointer().

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