Qt - QAbstractItemModel

0

Próbuję zrozumieć jak działają modele w Qt i chcę zrobić własny model - wiem, że jest QTableWidget do płaskiego modelu danych i w sobie wszystko ma... ale są sytuacje gdzie wbudowany gotowiec nie spełnia wszystkich oczekiwań (nie przytoczę jakich, bo nie mam na tyle doświadczenia, żeby się tutaj wypowiedzieć). Więc próbuję zrobić swój model. Wziąłem na warsztat klasę QAbstractItemModel zrobiłem własną podklasę i w związku z tym mam kilka pytań.

te dwie funkcje są oczywiste...

int MyModel2::columnCount(const QModelIndex &/*parent*/) const
{
    return 5;
}
int	MyModel2::rowCount(const QModelIndex &/*parent*/) const
{
    return 2;
}

natomiast chciałbym się dowiedzieć następujących rzeczy o funkcjach:

  1. czy ciało funkcji index jest wystarczające ? Bo z tego co rozumiem z dokumentacji, to w tej funkcji należy ustawić wskaźnik QModelIndex na index w modelu aby można było w ogóle ustawiać dane i je odczytywać. Czy to co zrobiłem jest prawidłowe ? Czy to już wszystko, jeżeli chodzi o ciało tej funkcji ? Co ze zmienną parent dla płaskiego modelu danych ?
QModelIndex MyModel2::index(int row, int column, const QModelIndex &/*parent*/) const
{
    QModelIndex idx;

    idx=createIndex(row,column);

    return idx;
}
  1. funkcja parent - co może być "rodzicem" dla płaskiego modelu danych ? W dokumentacji doczytałem się, że do płaskiego modelu danych można wykorzystać QAbstractItemModel::checkIndex ale jak to użyć ? i co nam to daje ? Nieprawidłowy index ?
QModelIndex MyModel2::parent(const QModelIndex &/*index*/) const
{
    return QModelIndex();
}
  1. funkcja setData (ustawia dane w modelu) czy to co zrobiłem poniżej jest poprawne ? Jak załadować dane do modelu za pomocą tej funkcji ? np macierz z pliku (pomijam obsługę plików bo to umiem ale bardziej chodzi mi o to, jak tej funkcji "pokazać" co ma załadować i gdzie)
bool MyModel2::setData(const QModelIndex &index, const QVariant &value, int role)
{
    int w=-1, k=-1;

    w=index.row();
    k=index.column();

    if(role==Qt::EditRole){
        if(!checkIndex(index)){
//            qDebug()<<"index"<<false;
            return false;
        }
        else if(w!=-1 && k!=-1){
            qDebug()<<w<<" "<<k;
            value.toString();
        }
//        qDebug()<<"if"<<true;
        return true;
    }

    return false;
}
  1. funkcja data (ustawia dane w widoku) - niby coś się ustawia ale jak ustawić dane w konkretnym wierszu i konkretnej kolumnie ? Np user kliknie wiersz 100 kolumna 1029 i jak to obsłużyć ? Wiem, że za pomocą pętli ale jak ? Bo bez sensu jest pisać
if(index.row()==0 && index.column()==0)
return data;
QVariant MyModel2::data(const QModelIndex &index, int role) const
{
    int w=-1, k=-1;

    w=index.row();
    k=index.column();

    QVariant data="test";
    QVariant data2="test 2";

    if(/*role==Qt::CheckStateRole || */role==Qt::DisplayRole || role==Qt::TextAlignmentRole){
        if(w!=-1 && k!=-1){
            return data;
        }
    }
    return QVariant();
}

no dobra, ale jak nada się treść tych funkcji, to jak ich używać ? Czy one inteligentnie same ustawiają dane ? czy trzeba je po prostu wywołać z parametrami ?

jest jeszcze jedna zagadka

screenshot-20200419160232.png

ten kwadracik do zaznaczenia, wiem, że jest rola

role==Qt::CheckStateRole

ale to działa programowo. Jak tego skubańca można obsłużyć aby za pomocą myszki się zaznaczał i zwracał swój stan ?

Może dziwne mam pytania ale nie za bardzo to rozumiem i chciałbym prosić o wyjaśnienie.

0

ehh jak byś z pół roku temu spytał to bym ci udostępnił moją własną implementację z dokumentacją oxygen, tyle że... czyściłem dysk a teraz cierpię na amnezję ;). Ale pamiętam że o modelach doczytywałem w 2 książkach o qt(płatne) i z netu pamiętam na pewno:
https://www.informit.com/articles/article.aspx?p=1405547&seqNum=3
https://doc.qt.io/qt-5/qtwidgets-itemviews-simpletreemodel-example.html

1
zkubinski napisał(a):

te dwie funkcje są oczywiste...

int MyModel2::columnCount(const QModelIndex &/*parent*/) const
{
    return 5;
}
int	MyModel2::rowCount(const QModelIndex &/*parent*/) const
{
    return 2;
}

A są źle :P.
Powinieneś sprawdzać wartość parent. Jeśli to ma być tabela to sprawdzasz cz isValid. Jeśli nie zwracasz zero.

Parę razy zamieściłem tu przykłady użycia QAbstractTableModel:

0

@MarekR22 A są źle :P.
Powinieneś sprawdzać wartość parent. Jeśli to ma być tabela to sprawdzasz cz isValid. Jeśli nie zwracasz zero.

ale jak to sprawdzić dla płaskiego modelu danych ? podasz przykład z tym isValid ?

poza tym w płaskim modelu danych nie ma czegoś takiego jak parent więc nie da się tego sprawdzić, no chyba, że dla płaskiego modelu danych parent będzie adres obiektu w którym wszystkie dane siedzą

np

QVariant *ptrMatrix;
QVariant Matrix(1,2,3,4,5;1,2,3,4,5);
ptrMatrix=&Matrix;

return *ptrMatrix;
0
int MyModel2::columnCount(const QModelIndex &parent) const
{
    if (parent.isValid()) return 0;
    return 5;
}

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

@zkubinski: Nie masz wrażenia, że kręcisz się w kółko?

zkubinski napisał(a):

te dwie funkcje są oczywiste...

int MyModel2::columnCount(const QModelIndex &/*parent*/) const
{
    return 5;
}
int	MyModel2::rowCount(const QModelIndex &/*parent*/) const
{
    return 2;
}

Gdybyś przeczytał dokumentację to byś wiedział, że są zaimplementowane źle.

  1. czy ciało funkcji index jest wystarczające ?

Wystarczające do czego?

  1. funkcja parent - co może być "rodzicem" dla płaskiego modelu danych ?

Z dokumentacji:
An invalid model index can be constructed with the QModelIndex constructor. Invalid indexes are often used as parent indexes when referring to top-level items in a model.

  1. funkcja setData (ustawia dane w modelu) czy to co zrobiłem poniżej jest poprawne ? Jak załadować dane do modelu za pomocą tej funkcji ?

To zależy jakie masz dane i jak je przechowujesz. Nie ma uniwersalnej odpowiedzi.

  1. funkcja data (ustawia dane w widoku) - niby coś się ustawia ale jak ustawić dane w konkretnym wierszu i konkretnej kolumnie ? Np user kliknie wiersz 100 kolumna 1029 i jak to obsłużyć ? Wiem, że za pomocą pętli ale jak ? Bo bez sensu jest pisać

Funkcja data niczego nie ustawia tylko pobiera dane spod danego indexu.

jest jeszcze jedna zagadka

screenshot-20200419160232.png

ten kwadracik do zaznaczenia, wiem, że jest rola

role==Qt::CheckStateRole

ale to działa programowo. Jak tego skubańca można obsłużyć aby za pomocą myszki się zaznaczał i zwracał swój stan ?

Od tego są klasy widoku czy delegatów. Model na pewno nie powinien się zastanawiać czy coś "pochodzi" od myszki czy nie.

Może dziwne mam pytania ale nie za bardzo to rozumiem i chciałbym prosić o wyjaśnienie.

Cały czas odnoszę wrażenie, że mieszasz model z widokiem i nie potrafisz tego rozdzielić w głowie.

0

Nie chcę się z tobą sprzeczać, bo mam wrażenie, że jesteś jedyną osobą na tym forum, która najwięcej mnie NAPROWADZIŁA jeżeli chodzi o Qt. Ale nie mogę pominąć kilku kwestii. Odniosę się do jednego z przykładów, bo na wszystko musiałbym napisać niezły elaborat.

jest jeszcze jedna zagadka

screenshot-20200419160232.png

ten kwadracik do zaznaczenia, wiem, że jest rola

role==Qt::CheckStateRole

ale to działa programowo. Jak tego skubańca można obsłużyć aby za pomocą myszki się zaznaczał i zwracał swój stan ?

@tajny_agent Od tego są klasy widoku czy delegatów. Model na pewno nie powinien się zastanawiać czy coś "pochodzi" od myszki czy nie.

@tajny_agent na forum również dostałeś konkretne odpowiedzi

Uważasz, że twoja odpowiedź jest wyczerpująca/konkretna ? NIE, ona jest tak ogólnikowa, że nie wiadomo od czego wyjść i co z tym zrobić ? Od początku przelecieć QItemDelegate ? Ja zadałem pytanie Jak tego skubańca można obsłużyć aby za pomocą myszki się zaznaczał i zwracał swój stan ?, a ty mi odpowiadasz, że

Delegaty Koniec twojej odpowiedzi,

Ja wiem, że są delegaty i częściowo umiem ich używać ale gdybym miał w 100% pełną wiedzę na ich temat, to olałbym to forum, bo o co miałbym pytać jeżeli miałbym pełną wiedzę na ich temat ?

Oczekiwałem jakiegoś przykładu z kodem, no ale nic nie dostałem, sądziłem, że jak nie ma odpowiedzi, to nikt nie wie - no ale jeżeli wiesz, to czemu nie rzucisz tego przykładu ? Rozumiem, ze mam prowadzić własne dochodzenie które znacznie wydłuży mi czas nauki ? Ale przecież nie o to chodzi na forum prawda ? No chyba, że się mylę. Ja uważam, że dzielenie się wiedzą, nie na tym polega.

Jeżeli uważasz, że twoja wypoiedź jest precyzyjna, to nie wiem po co prowadzicie to forum ? Uważam, że jak wiesz, to dajesz konkretny przykład razem z kodem źródłowym - pewnie pomyślisz, że może jestem "roszczeniowy" czy jak ? Nie, nie jestem roszczeniowy, uważam, że z szacunku do innych poszukujących wiedzy powinno się im odpowiadać we właściwy sposób najbardziej precyzyjnie jak to możliwe, aby można było zaspokoić potrzebę wiedzy - jeżeli tego nie rozumiecie, to zamknijcie to forum, bo jest ono zbędne. Ciągle wałkuje się tutaj podstawy podstaw i zaglądają tutaj ludzie którzy chcą rozwiązać jakieś śmieszne zadania ze szkoły. To w końcu jest to forum programistyczne ? Czy parodia forum programistycznego ?

Już na Stack Overflow szybciej dowiem się konkretów niż tutaj, bo tam ludzie żonglują najróżniejszymi ilościami przykładów, a tutaj prowadzi się wieczne debaty z których nic nie wynika no i druga sprawa, w końcu tutaj jest polskojęzyczne forum, bo sądziłem, że po polsku łatwiej się dogadać, po trzecie, żeby wielu miało dostęp do porządnej wiedzy w rodzimym języku w razie czego ale jak zwykle w tym polskim grajdołku musi być jak jest... bo "angol trza znać" ale ja znam polski, a jeżeli brak literatury po polsku, to się wypełnia taką lukę, no chyba, że mamy być wyrobnikami...

Co do dokumentacji Qt - jest ona w miarę dobra (nie zachwyca polotem, no ale jest jak jest) ale uważam, że jest napisana w taki sposób aby można było zrozumieć z niej jak najmniej wkładając ogromny wysiłek w stosunku do rezultatów ? Osobiście uważam, że naprawdę dobra dokumentacja, powinna wyglądać mniej więcej tak

  1. Opis klasy
  2. Lista klas które z daną klasą współpracują - np QbstractItemModel współpracuje z QModelIndex - z tym, że sama klasa QModelIndex jest bezużyteczna i trzeba jej użyć w jednej z klas które z nią współpracują np QAbstractItemModel lub QAbstractListModel
  3. Opis metod i dobrze by było opisać te metody razem z przykładem użycia
  4. Zaraz pod opisem klasy powinno być kilka różnych przykładów (od najprostszych do zaawansowanych) jak z niej w pełni korzystać

Pewnie dużo wymagam, no ale nie ma ideałów

Obecnie jest tak, że tych przykładów szukasz dosłownie wszędzie i goni się po necie nie wiadomo za czym... mam wrażenie, że dokumentacja była pisana bez pomysłu, bo w sumie skąd programiści mieliby mieć doświadczenie w pisaniu dokumentacji ? Oni znają swój kod i wiedzą jak tej biblioteki używać. To tak, jakbym ja napisał swoją bibliotekę, znam ją ale dał do niej dokumentację tak ogólnikową, że trzeba się domyślać o co chodzi w użyciu tego wszystkiego. Jak do końca rozgryzę temat własnego modelu (a niewiele mi zostało) to napiszę takiego posta, że większość rzeczy stanie się jasne i to będzie mogło posłużyć za przykład do zrozumienia tego co jest w dokumentacji.

Musiałem się rozpisać ale twoja wypowiedź o "precyzyjności" po prostu mnie wkurzyła. Nie chcę palić za sobą mostów bo nie w tym rzecz ale rzecz w tym, że jak mamy sobie pomagać, to róbmy to jak trzeba, a nie na odwal się. Jeszcze pewnie w ramach "oburzenia" albo mnie zbanujecie albo olejecie moje posty (taka polska tradycja) :D

1
zkubinski napisał(a):

Nie chcę się z tobą sprzeczać

W sprzeczaniu się nie ma nic złego. Ba, przeważnie jest ciekawe i każda strona coś może wyciągnąć dla siebie ;)

(...)

Nie za bardzo chce mi się dyskutować o tym jak powinno wyglądać forum etc. Jak każde ma swoją specyfikę, ale IMHO jest top1 jeśli chodzi o rodzimy internet.
Piszesz o "łatwości" uzyskania odpowiedzi np. na SO? No to dam Ci przykład jeszcze łatwiejszego uzyskania odpowiedzi.
Pierwszy wynik z Google'a dla "qtableview checkbox" => Checkboxes in QTableView with my custom model

Co do dokumentacji Qt - jest ona w miarę dobra (nie zachwyca polotem, no ale jest jak jest) ale uważam, że jest napisana w taki sposób aby można było zrozumieć z niej jak najmniej wkładając ogromny wysiłek w stosunku do rezultatów ? Osobiście uważam, że naprawdę dobra dokumentacja, powinna wyglądać mniej więcej tak

IMHO dokumentacja Qt jest world-class i chciałbym żeby wszystkie biblioteki/frameworki taką miały.

Wracając do checkboxów.

Qt::ItemFlags FooModel::flags(QModelIndex const&) const override
{
  auto result = QAbstractItemModel::flags(index);
  if (index.isValid())
    result |= Qt::ItemIsUserCheckable;
  return result;
}

Taka implementacja spowoduje, że klasa widoku (QTableView, QListView, etc.) automatycznie wyświetli checkbox w danej komórce. A jaki wpływ ten checkbox będzie miał na dane to już zależy od Ciebie - w sensie jak to obsłużysz w data/setData. Bo model może reprezentować praktycznie wszystko, od zwykłej struktury poprzez tablicę aż po drzewo i nikt nie jest w stanie odpowiedzieć "jak to zrobić dobrze" nie znając kontekstu.

3

Twoim podstawowym błędem jest to, że na warsztat wybrałeś QAbstractItemModel.
Trzeba było popatrzeć na moje przykłady i zacząć od dziedziczenia po QAbstractTableModel, bo najwyraźniej potrzebujesz tabelę.
To naprawdę dużo upraszcza.

Może zamiast dyskutować o pierdołach, zacznij od początku.
Opisz co chcesz uzyskać z punktu widzenia użytkownika, a jakie masz dane do zaprezentowania.
Twoja klasa dziedzicząca po QAbstractTableModel pełni rolę adaptora: ma dopasować jakiś zestaw twoich danych, do wymagań QTableView.

Jak opiszesz co konkretnie chcesz uzyskać to pomogę ci to napisać jak należy. Naprawianie cudzego kodu (początkującego) często jest trudniejsze, niż napisanie od początku (szczególnie jeśli nie wiadomo co ma to robić).

0

@tajny_agent Sam nie wiesz do czego Ci to potrzebne, więc nie dziw się, że inni tym bardziej nie wiedzą ;) BTW Qt::DisplayRole - The key data to be rendered in the form of text. Nie wiem w jaki sposób powiązałeś to z wyświetlaniem checkboxa.

a więc tak - jest deklaracja funkcji która wygląda sobie tak

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

definicja (podstawy podstaw) tej funkcji wyglądają tak:

QVariant MyModel2::data(const QModelIndex &index, int role) const
{
           switch(role){
            case Qt::CheckStateRole:
                switch(index.column()){
                case 0:      
                    break;

                case 1:
                    data = Matrix2(index.row());
                    break;

                case N:
                    break:

                default:
                    break;
                }
                break;

            default:
                break;
        }

        return data; //zwraca typ QVariant()
}

Powyższa rola case Qt::CheckStateRole: da nam taki checkbox jak na screenie (ten zaznaczony na niebiesko kwadracik, a obok niego można coś wpisać)

screenshot-20200423151153.png

Aby wyłączyć ten kwadracik, to trzeba zmienić rolę case Qt::CheckStateRole: na rolę case Qt::DisplayRole: co w konsekwencji da nam całe pole tekstowe bez żadnych checkboxów

screenshot-20200423151451.png

i teraz pytanie - jak obsłużyć ten checbox (zakładając, że za parę dni wpadnę na pomysł jego użycia) - po prostu ciekawski jestem, a im więcej wiem, to więcej umiem i jestem w stanie pisać lepsze programy.

Wystarczy mi, jak pokażecie jak zaznaczenie tego checkboxa zwraca jego stan "zaznaczony" - "odznaczony" lub "true" - "false"

1

Czytałeś dokumentację?
enum Qt::ItemDataRole

| Qt::CheckStateRole | 10 | This role is used to obtain the checked state of an item. (Qt::CheckState) |

Gdzie w tym przykładzie zwracasz coś typu: Qt::CheckState?

Jeszcze raz:

Opisz co chcesz uzyskać z punktu widzenia użytkownika, a jakie masz dane do zaprezentowania.

0
zkubinski napisał(a):

Wystarczy mi, jak pokażecie jak zaznaczenie tego checkboxa zwraca jego stan "zaznaczony" - "odznaczony" lub "true" - "false"

bool FooModel::setData(QModelIndex const& index, QVariant const& value, int role) override
{
  /* ... */
  if (role == Qt::CheckStateRole) {
    auto state = value.toInt();
    switch (state) {
      case 0: /* checkbox został odznaczony */
      case 1: /* checkbox jest cześciowo zaznaczony */
      case 2: /* checkbox jest zaznaczony */
    }
  }
}
0

Dobra, dla przykładu chciałbym wyświetlić macierz 2x5 np:

55;44;78;34;23,4
1;3;4;88;9

Potem móc coś tam zmienić i zapisać

1

To mi działa

#ifndef TABLEWITHCHECKBOX_H
#define TABLEWITHCHECKBOX_H

#include <QObject>
#include <QAbstractTableModel>

class TableWithCheckbox : public QAbstractTableModel
{
    Q_OBJECT
public:
    ~TableWithCheckbox();
    explicit TableWithCheckbox(QObject *paret);

    void setValues(const QVector<QString>& items);

public: /* QAbstractTableModel */
    int rowCount(const QModelIndex &parent) const override;
    int columnCount(const QModelIndex &parent) const override;
    QVariant data(const QModelIndex &index,  int role) const override;
    Qt::ItemFlags flags(const QModelIndex &index) const override;
    bool setData(const QModelIndex &index, const QVariant &value, int role) override;

private:
    struct Item {
        bool checked;
        QString name;
    };
    QVector<Item> items;
};

#endif // TABLEWITHCHECKBOX_H


#include "tablewithcheckbox.h"

TableWithCheckbox::~TableWithCheckbox()
{}

TableWithCheckbox::TableWithCheckbox(QObject *paret)
    : QAbstractTableModel(paret)
{

}

void TableWithCheckbox::setValues(const QVector<QString> &newItems)
{
    beginResetModel();
    items.clear();
    items.reserve(newItems.size());
    for (const auto& s : newItems) {
        items.append({false, s});
    }
    endResetModel();
}

int TableWithCheckbox::rowCount(const QModelIndex &parent) const
{
    if (parent.isValid()) return 0;
    return items.size();
}

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

QVariant TableWithCheckbox::data(const QModelIndex &index, int role) const
{
    if ((role == Qt::DisplayRole || role == Qt::EditRole) && index.column() == 0) {
        return items[index.row()].name;
    }
    if (role == Qt::CheckStateRole && index.column() == 0) {
        return items[index.row()].checked ? Qt::Checked : Qt::Unchecked;
    }
    return {};
}

Qt::ItemFlags TableWithCheckbox::flags(const QModelIndex &index) const
{
    return QAbstractTableModel::flags(index) | Qt::ItemIsEditable | Qt::ItemIsUserCheckable;
}

bool TableWithCheckbox::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (role == Qt::EditRole && index.column() == 0) {
        items[index.row()].name = value.toString();
        emit dataChanged(index, index, {Qt::DisplayRole});
        return true;
    }
    if (role == Qt::CheckStateRole && index.column() == 0) {
        items[index.row()].checked = value.value<Qt::CheckState>() == Qt::Checked;
        emit dataChanged(index, index, {Qt::CheckStateRole});
        return true;
    }
    return false;
}

demo.gif

1
zkubinski napisał(a):

Dobra, dla przykładu chciałbym wyświetlić macierz 2x5 np:

55;44;78;34;23,4
1;3;4;88;9

Potem móc coś tam zmienić i zapisać

Przeczytaj to i zastanów się, czy my możemy zrozumieć o co chodzi?
Jak do tego mają się te checkboxy? NIJAK!

Gdyby chodziło po prostu o wyświetlenie danych to jeden z moich przykładów to pokrywa (z drobnymi udziwnieniami).

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