ПБ
11 января 2019 г. 7:09

Qt, Qml, QAbstractTableModel

Всем привет, часто нахожу ответы на вашем форуме. Огромное спасибо. Пролистал кучу других форумов, просмотрел документацию. Ничего не получается.
Не могу заставить чекбокс в хэдере таблицы работать с чекбоксами в колонке таблицы. Мне нужно либо выделить все, либо снять все чекбоксы. Пробовал двумя способами. Не работает. Помогите, пожалуйста.

#ifndef FOODTABLEMODEL_H
#define FOODTABLEMODEL_H

#include <QAbstractTableModel>
#include <QModelIndex>
#include <QHash>
#include <QVariant>
#include <QByteArray>
#include <QList>

struct Food
{
    bool check;
    QString description;
    int price;
    QString code;
};

class FoodTableModel : public QAbstractTableModel
{
    Q_OBJECT
public:
    enum RoleNames
    {
        CHECK = 0,
        DESCRIPTION = 1,
        PRICE = 2,
        CODE = 3
    };
    FoodTableModel(QObject *parent = nullptr);

    int rowCount(const QModelIndex &parent) const;
    int columnCount(const QModelIndex &parent) const;
    QVariant data(const QModelIndex &index, int role) const;
    QHash<int, QByteArray> roleNames() const;
    bool setData(const QModelIndex &index, const QVariant &value, int role);
    Q_INVOKABLE void checkTable(bool value);

private:
    QList<Food> foods;
};

#endif // FOODTABLEMODEL_H
#include "foodtablemodel.h"
#include <QDebug>

FoodTableModel::FoodTableModel(QObject *parent) :
    QAbstractTableModel { parent }
{
    foods.append({ false, "Pizza", 12, "ASR1" });
    foods.append({ false, "Milk", 1, "BE-23" });
    foods.append({ false, "Coffe", 4, "ARTY8-9" });
    /*foods.append({ "Lizza", "12", "ASR1" });
    foods.append({ "SMilk", "34", "BE-23" });
    foods.append({ "YCoffe", "5", "QRTY8-9" });*/
}

int FoodTableModel::rowCount(const QModelIndex &parent) const
{
    return foods.size();
}

int FoodTableModel::columnCount(const QModelIndex &parent) const
{
    return roleNames().size();
}

QVariant FoodTableModel::data(const QModelIndex &index, int role) const
{
    QVariant variant;
    const int row = index.row();
    const int col = role;
    switch (col) {
    case CHECK: variant = foods.at(row).check; break;
    case DESCRIPTION: variant = foods.at(row).description; break;
    case PRICE: variant = foods.at(row).price; break;
    case CODE: variant = foods.at(row).code; break;
    }
    return variant;
}

QHash<int, QByteArray> FoodTableModel::roleNames() const
{
    QHash<int, QByteArray> roles;
    roles.insert(CHECK, "check");
    roles.insert(DESCRIPTION, "description");
    roles.insert(PRICE, "price");
    roles.insert(CODE, "code");
    return roles;
}

bool FoodTableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if(index.isValid() && role == CHECK) {
        foods[index.row()].check = value.toBool();
        emit dataChanged(index, index, { role });
        return true;
    }
    else return false;
}

void FoodTableModel::checkTable(bool value)
{
    for (int i = 0; i < foods.size(); i++) {
        qDebug() << i << value;
        foods[i].check = value;
    }
}

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "foodtablemodel.h"
#include "person.h"
#include "sortfilterproxymodel.h"

int main(int argc, char *argv[]) {
    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;

    FoodTableModel food;

    qmlRegisterType<SortFilterProxyModel>("SortFilterProxyModel", 0, 1, "SortFilterProxyModel");

    engine.rootContext()->setContextProperty("food", &food);

    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;

    return app.exec();
}

import QtQuick 2.12
import QtQuick.Controls 1.4 as C1
import QtQuick.Controls.Styles 1.4
import QtQuick.Controls 2.4
import SortFilterProxyModel 0.1
import "JavaScript/TableViewCheck.js" as TVC

C1.TableView {
    id: tableView
    clip: true
    sortIndicatorVisible: true
    currentRow: rowCount ? 0 : -1
    model: SortFilterProxyModel {
        source: food.rowCount() > 0 ? food : null

        sortOrder: tableView.sortIndicatorOrder
        sortCaseSensitivity: Qt.CaseInsensitive
        sortRole: food.rowCount() && tableView.getColumn(tableView.sortIndicatorColumn).role !== "check" > 0 ?
                      tableView.getColumn(tableView.sortIndicatorColumn).role : ""
    }
    C1.TableViewColumn {
        width: 30
        role: "check"
        resizable: false
        delegate: C1.CheckBox {
            id: checkBox
            anchors.left: parent.left
            anchors.leftMargin: parent.width / 3
            checked: model.check
            onVisibleChanged: if (visible) checked = model.check
            onClicked: model.check = checked
        }
    }
    C1.TableViewColumn {
        role: "description"
        title: "Description"
    }
    C1.TableViewColumn {
        role: "price"
        title: "Price"
    }
    C1.TableViewColumn {
        role: "code"
        title: "Code"
    }
    style: TableViewStyle {
                headerDelegate: Rectangle {
                    height: textItem.implicitHeight
                    width: textItem.implicitWidth
                    Text {
                        id: textItem
                        anchors.fill: parent
                        anchors.leftMargin: 6
                        verticalAlignment: Text.AlignVCenter
                        horizontalAlignment: styleData.textAlignment
                        text: styleData.value
                    }
                    C1.CheckBox {
                        anchors.left: parent.left
                        anchors.leftMargin: parent.width / 3
                        property bool isPressed: styleData.pressed
                        visible: styleData.column === 0
                        onIsPressedChanged: {
                            if(isPressed) {
                                checked  = !checked
                                for(var i = 0; i < rowCount; i++) {
                                    food.setData(foods.index(i, 0), checked, "check")
                                }
                                //food.checkTable(checked)
                            }
                        }
                    }
                }
            }
}

Виртуальный хостинг со скидкой 10 процентов
Виртуальный хостинг со скидкой 10 процентов
EVILEG предлагает надёжный хостинг со скидкой 10% на виртуальный хостинг и 5% на VPS
10

Добрый день. Интересная задача. Мне тоже нужно будет что-то подобное сделать. Так что с удовольствием поковыряюсь. Но большая просьба - оформите исходники, пожалуйста. В текущем виде ими невозможно воспользоваться.

0
ПБ

Александр, я все исправил, но случайно ответил ваш ответ как решение. Теперь вы просто обязаны мне помочь.

0

:)))) это было очень ошибочное решение, настолько поверить в меня. Но я постараюсь.

0

Не увидел в исходниках описание этой модели - SortFilterProxyModel.
Из QML кода обращение идёт к ней.

0
ПБ

Эта модель отвечает за сортировку колонок, поэтому я не скидывал файлы реализации.

0

Несколько упросил ваш пример и сделал его рабочим - проверяйте:

main.c

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>

#include "checkmodel.h"

int main(int argc, char *argv[])
{
  QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

  QGuiApplication app(argc, argv);

  QQmlApplicationEngine engine;

  CheckModel someModel;
  engine.rootContext()->setContextProperty("someModel", &someModel);

  engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
  if (engine.rootObjects().isEmpty())
    return -1;

  return app.exec();
}

checkmodel.h

#ifndef CHECKMODEL_H
#define CHECKMODEL_H

#include <QAbstractTableModel>

struct SomeStruct
{
    bool check;
    QString description;
    int value;
};

class CheckModel : public QAbstractTableModel
{
Q_OBJECT

  enum Roles
      {
          CHECK, DESCRIPTION, VALUE
      };

public:
  CheckModel(QObject* parent = nullptr);
  int rowCount(const QModelIndex &parent = QModelIndex()) const {
    Q_UNUSED(parent); return list.count();
  }
  int columnCount(const QModelIndex &parent = QModelIndex()) const {
    Q_UNUSED(parent); return 3;
  }
  QHash<int, QByteArray> roleNames() const;
  QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
  Q_INVOKABLE void setAllChecked(bool value);
  Q_INVOKABLE void setRowChecked(int row, bool value);

private:
  void fillModel();

  QVector<SomeStruct> list;
};

#endif // CHECKMODEL_H

checkmodel.cpp

#include "checkmodel.h"

#include <QtDebug>

CheckModel::CheckModel(QObject *parent) : QAbstractTableModel (parent)
{
  fillModel();
}

QHash<int, QByteArray> CheckModel::roleNames() const
{
  QHash<int, QByteArray> roles;
  roles.insert(Qt::UserRole+CHECK, "check");
  roles.insert(Qt::UserRole+DESCRIPTION, "description");
  roles.insert(Qt::UserRole+VALUE, "value");
  return roles;
}

QVariant CheckModel::data(const QModelIndex &index, int role) const
{
  if (!index.isValid())
    return QVariant();

  SomeStruct s = list.at(index.row());
  switch (role) {
    case Qt::UserRole+CHECK: return s.check;
    case Qt::UserRole+DESCRIPTION: return s.description;
    case Qt::UserRole+VALUE: return s.value;
    }

  return QVariant();
}

void CheckModel::setAllChecked(bool value)
{
  beginResetModel();
  qDebug() << 1;
  for (SomeStruct &s : list) {
      s.check = value;
      qDebug() << value;
    }
  endResetModel();
}

void CheckModel::setRowChecked(int row, bool value)
{
  if (row < 0 || row >= list.count())
    return;
  SomeStruct &s = list[row];
  s.check = value;
  emit dataChanged(index(row,0), index(row, columnCount()-1));
}

void CheckModel::fillModel()
{
  SomeStruct one = {true, "Some definition", 100};
  SomeStruct two = {false, "Some definition again", 200};
  SomeStruct three = {true, "Definition", 1000};
  list << one << two << three;
}

main.qml

import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 1.4

Window {
    visible: true
    width: 640
    height: 480

    TableView {
        anchors.fill: parent

        model: someModel

        TableViewColumn {
            role: "check"
            title: "Check"
            delegate: CheckBox {
                checked: styleData.value
                MouseArea {
                    anchors.fill: parent
                    onClicked: {
                        someModel.setRowChecked(styleData.row, !styleData.value)
                    }
                }
            }
        }
        TableViewColumn {
            role: "description"
            title: "Description"
        }
        TableViewColumn {
            role: "value"
            title: "Value"
        }

        headerDelegate: Item {
            height: textItem.implicitHeight

            Text {
                id: textItem
                verticalAlignment: Text.AlignVCenter
                horizontalAlignment: styleData.textAlignment
                text: styleData.value
            }
            CheckBox {
                anchors.left: parent.left
                anchors.leftMargin: parent.width / 3
                property bool isPressed: styleData.pressed
                visible: styleData.column === 0
                onIsPressedChanged: {
                    if (isPressed) {
                        checked  = !checked
                        someModel.setAllChecked(checked)
                    }
                }
            }
        }
    }
}

Также прикладываю исходники для удобства.
QmlAllColumnCheckBox.zip QmlAllColumnCheckBox.zip

На идеальный код мой вариант не претендует. Если будут замечания у вас или других более опытных коллег - буду рад услышать.
Но в целом поставленную вами задачу он решает.

Будут вопросы - задавайте.

0

Маленькие заметки:
- QML != JS. Старайтесь избегать использования кода javascript.
- struct заставляет выдумывать массу не нужных костылей (enum, например, или ручное прописывание ролей) - замените его на класс, туда же можно кучу логики вынести.
- в своем примере вы добавили в качестве модели прокси-модель - это хорошо, но я не зря просил её реализацию, т.к. в дальнейшем вы пытались обращаться к функциям модели. В таком случае вам нужно было либо сделать подобные функции в прокси, либо обращаться к модели прокси-модели.

0
ПБ

Александр, огромное спасибо. Я надеюсь, что у вас опыта побольше, чем у меня, и те нюансы, которые вы внесли, они обоснованы. Если у меня возникнут проблемы с объединением прокси модели и модели, которую вы реализовали, можно я буду у вас спрашивать здесь (или лучше задать новый вопрос)?

0

Обращайтесь, чем смогу - помогу.

0
ПБ

Александр, как я и ожидал, проблема появилась, причем очень странная. Когда кликая по хедеру табдицы, чтобы отсортировать колонку таблицы, то выделяются все чекбоксы. Прикладываю исходники модели сортровки (Это код не мой, я его нашел на просторах интернета). Стоит объеденить модели?

#ifndef SORTFILTERPROXYMODEL_H
#define SORTFILTERPROXYMODEL_H

#include <QtCore/qsortfilterproxymodel.h>
#include <QtQml/qjsvalue.h>

class SortFilterProxyModel : public QSortFilterProxyModel
{
    Q_OBJECT
    Q_PROPERTY(int count READ count NOTIFY countChanged)
    Q_PROPERTY(QObject *source READ source WRITE setSource)

    Q_PROPERTY(QByteArray sortRole READ sortRole WRITE setSortRole)
    Q_PROPERTY(Qt::SortOrder sortOrder READ sortOrder WRITE setSortOrder)

public:
    explicit SortFilterProxyModel(QObject *parent = 0);

    QObject *source() const;
    void setSource(QObject *source);

    QByteArray sortRole() const;
    void setSortRole(const QByteArray &role);

    void setSortOrder(Qt::SortOrder order);

    int count() const;
    Q_INVOKABLE QJSValue get(int index) const;

signals:
    void countChanged();

protected:
    int roleKey(const QByteArray &role) const;
    QHash<int, QByteArray> roleNames() const;
};

#endif // SORTFILTERPROXYMODEL_H

#include "sortfilterproxymodel.h"
#include <QtDebug>
#include <QtQml>

SortFilterProxyModel::SortFilterProxyModel(QObject *parent) : QSortFilterProxyModel(parent)
{
    connect(this, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SIGNAL(countChanged()));
    connect(this, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SIGNAL(countChanged()));
}

int SortFilterProxyModel::count() const
{
    return rowCount();
}

QObject *SortFilterProxyModel::source() const
{
    return sourceModel();
}

void SortFilterProxyModel::setSource(QObject *source)
{
    setSourceModel(qobject_cast<QAbstractItemModel *>(source));
}

QByteArray SortFilterProxyModel::sortRole() const
{
    return roleNames().value(QSortFilterProxyModel::sortRole());
}

void SortFilterProxyModel::setSortRole(const QByteArray &role)
{
    QSortFilterProxyModel::setSortRole(roleKey(role));
}

void SortFilterProxyModel::setSortOrder(Qt::SortOrder order)
{
    QSortFilterProxyModel::sort(0, order);
}

QJSValue SortFilterProxyModel::get(int idx) const
{
    QJSEngine *engine = qmlEngine(this);
    QJSValue value = engine->newObject();
    if (idx >= 0 && idx < count()) {
        QHash<int, QByteArray> roles = roleNames();
        QHashIterator<int, QByteArray> it(roles);
        while (it.hasNext()) {
            it.next();
            value.setProperty(QString::fromUtf8(it.value()), data(index(idx, 0), it.key()).toString());
        }
    }
    return value;
}

int SortFilterProxyModel::roleKey(const QByteArray &role) const
{
    QHash<int, QByteArray> roles = roleNames();
    QHashIterator<int, QByteArray> it(roles);
    while (it.hasNext()) {
        it.next();
        if (it.value() == role)
            return it.key();
    }
    return -1;
}

QHash<int, QByteArray> SortFilterProxyModel::roleNames() const
{
    if (QAbstractItemModel *source = sourceModel())
        return source->roleNames();
    return QHash<int, QByteArray>();
}



Это измененное место инициализации модели в файле main.qml

model: SortFilterProxyModel {
        source: someModel
        sortOrder: tableView.sortIndicatorOrder
        sortCaseSensitivity: Qt.CaseInsensitive
        sortRole: someModel.rowCount() > 0 && tableView.getColumn(tableView.sortIndicatorColumn).role !== "check" ?
                      tableView.getColumn(tableView.sortIndicatorColumn).role : ""
    }

Дописал вопрос, и понял. Выделение должно происходить, когда мы кликаем по чекбоксу в хедере, а оно происходит при нажатии в любом месте хедера.
Вот решение.

onIsPressedChanged: {
                if(isPressed && styleData.column === 0) {
                    checked  = !checked
                    food.setAllChecked(checked)
                    }
                }
            }
1

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
G
24 июля 2019 г. 4:20
G0tzef

C++ - Тест 005. Структуры и Классы

  • Результат:66баллов,
  • Очки рейтинга-1
VK
24 июля 2019 г. 3:49
Viktoriia Komarova

C++ - Тест 001. Первая программа и типы данных

  • Результат:40баллов,
  • Очки рейтинга-8
G
24 июля 2019 г. 3:25
G0tzef

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:80баллов,
  • Очки рейтинга4
Последние комментарии
23 июля 2019 г. 12:14
IscanderChe

Вот ссылка: https://github.com/iscander-che/TestReportViewer .
23 июля 2019 г. 5:42
Евгений Легоцкой

Хорошо, хотя конечно это С, а не С++ )))) Но если вдруг будут проблемы, то решение через класс со статическими переменными вы видели ))
23 июля 2019 г. 5:33
IscanderChe

"Не потребует каждый раз объявлять extern в других файлах". И так не требует. У меня в тестовом классе эти переменные используются без дополнительного объявления. Так же объявил их в cpp-файле о…
23 июля 2019 г. 5:28
IscanderChe

В репозиторий могу сегодня вечером выложить. "Или ее надо компилить самому под дистриб?" Тут я не совсем понимаю, что вы имеете ввиду. Я выложу в репозиторий исходный код утилиты, и всё.
23 июля 2019 г. 4:32
Евгений Легоцкой

Есть комментарий по вашему коду. Лучше бы вместо глобальных переменных в стиле Си, то есть с использоавнием extern, написали бы статические переменные в рамках класса. IMHO - это будет выглядеть…
Сейчас обсуждают на форуме
24 июля 2019 г. 4:57
Михаиллл

Это не помогает. Ниже мой код Rectangle{ //Flickable { //contentX: 100 id: rectangleForListView y: parent.height * 0.15 height: parent.height * 0.…
24 июля 2019 г. 3:17
Евгений Легоцкой

Ну вот теперь я несколько в ступоре. Запустите из консоли проект и посмотрите тогда, на что ругается. Также, следовало бы посмотреть в настройках самой виртуальной машины, что по поддержке OpenG…
23 июля 2019 г. 8:20
Михаиллл

Так работает QFile f1(dbAdress); f1.setPermissions(QFileDevice::WriteOther);
23 июля 2019 г. 7:11
Pavel K.

Советую использовать нечто такое или такое
22 июля 2019 г. 10:50
Pavel K.

Благодарю.Буду пробовать.
Ищу работу?
5,000.00 руб. - 15,000.00 руб.
Дизайнер
Moskovskiy, Moscow, Russia
25,000.00 руб. - 30,000.00 руб.
Разработчик Qt/C++
Barnaul, Altai Krai, Russia

Для зарегистрированных пользователей на сайте присутствует минимальное количество рекламы

EVILEG
О нас
Услуги
Присоединяйтесь к нам
© EVILEG 2015-2019
Рекомендует хостинг TIMEWEB