Я использую Qt 5.15.0 на Manjaro.
У меня есть таблица, которую я создал с помощью TableView в qml.
Я могу удалить строку, щелкнув правой кнопкой мыши поле Id следующим образом:

Удаление и т. д. работает, но я получаю эти ошибки в qml:

Метод удаления строк выглядит следующим образом в main.qml:
function deleteRowFromDatabase(row) {
console.info("before" + model.countOfRows())
if (!model.removeEntry(row)) {
console.info(qsTr("remove row %1 failed").arg(row))
}
model = QuestionsProxyModel
console.info("after" + model.countOfRows())
}
Ошибка указывает на строку идентификатора делегата в main.qml
DelegateChoice {
column: 0
delegate: QuestionIdDelegate {
id: questionIdDelegate
width: tableView.columnWidthProvider(column)
text: model.id /// this is undefined
row: model.row
Component.onCompleted: {
questionIdDelegate.markForDelete.connect(
tableView.deleteRowFromDatabase)
}
}
}
Удаление строк реализовано из C++ в классе, производном от QIdentityProxyModel в questionsproxmodel.h:
bool QuestionsProxyModel::removeEntry(int row)
{
return removeRows(row, 1);
}
Эта модель использует класс QuestionSqlTableModel, производный от QSqlTableModel, в качестве исходной модели.
Удаление строк реализовано следующим образом в questionssqltablemodel.qml:
bool QuestionSqlTableModel::removeRows(int row, int count,
const QModelIndex &parent)
{
auto result = QSqlTableModel::removeRows(row, count, parent);
if (result) {
select(); // row is not deleted from sql database until select is called
}
return result;
}
Насколько я понимаю, countOfRows модели обновляется только после вызова select(), поэтому я предполагаю, что между QSqlTableModel::removeRows и select TableView считывает еще раз несуществующую строку и вызывает эти ошибки в QML. Как это можно предотвратить?
Полный исходный код, чтобы попробовать:
основной.cpp:
#include <QDebug>
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickStyle>
#include <QFile>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlError>
#include "questionsproxymodel.h"
#include "questionsqltablemodel.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QUrl dbUrl{"file:///home/sandro/Desktop/test.db"};
auto exists = QFile::exists(dbUrl.toLocalFile());
auto db = QSqlDatabase::addDatabase("QSQLITE", "DBConnection");
db.setDatabaseName(dbUrl.toLocalFile());
db.open();
if (!exists) {
const QString questionTableName = "questions";
QSqlQuery query{db};
query.exec("CREATE TABLE " + questionTableName +
" ("
"id INTEGER PRIMARY KEY AUTOINCREMENT)");
}
QScopedPointer<QuestionSqlTableModel> questionSqlTableModel(
new QuestionSqlTableModel(nullptr, db));
QScopedPointer<QuestionsProxyModel> questionsProxyModel{
new QuestionsProxyModel};
questionsProxyModel->setSourceModel(questionSqlTableModel.get());
if (!exists) {
for (int i = 0; i < 10; ++i) {
questionsProxyModel->addNewEntry();
}
}
QQmlApplicationEngine engine;
qmlRegisterSingletonInstance<QuestionsProxyModel>(
"QuestionsProxyModels", 1, 0, "QuestionsProxyModel",
questionsProxyModel.get());
const QUrl url(QStringLiteral("qrc:/qml/main.qml"));
engine.load(url);
return app.exec();
}
вопросыsqltablemodel.h
#include <QSqlTableModel>
class QuestionSqlTableModel : public QSqlTableModel {
Q_OBJECT
public:
explicit QuestionSqlTableModel(QObject *parent = nullptr,
const QSqlDatabase &db = QSqlDatabase());
bool removeRows(int row, int count, const QModelIndex &parent) override;
};
вопросыsqltablemodel.cpp
#include "questionsqltablemodel.h"
#include <QBuffer>
#include <QDebug>
#include <QPixmap>
#include <QSqlError>
#include <QSqlField>
#include <QSqlRecord>
#include <QSqlRelationalDelegate>
QuestionSqlTableModel::QuestionSqlTableModel(QObject *parent,
const QSqlDatabase &db)
: QSqlTableModel{parent, db}
{
setTable("questions");
setSort(0, Qt::AscendingOrder);
if (!select()) {
qDebug() << "QuestionSqlTableModel: Select table questions failed";
}
setEditStrategy(EditStrategy::OnFieldChange);
}
bool QuestionSqlTableModel::removeRows(int row, int count,
const QModelIndex &parent)
{
auto result = QSqlTableModel::removeRows(row, count, parent);
if (result) {
select(); // row is not deleted from sql database until select is called
}
return result;
}
вопросыproxymodel.h:
#include <QIdentityProxyModel>
#include <QObject>
class QuestionsProxyModel : public QIdentityProxyModel {
Q_OBJECT
enum questionRoles {
idRole = Qt::UserRole + 1,
};
public:
QuestionsProxyModel(QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
Q_INVOKABLE QVariant data(const QModelIndex &index,
int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant &value,
int role = Qt::EditRole) override;
bool addNewEntry();
Q_INVOKABLE bool removeEntry(int row);
Q_INVOKABLE int countOfRows() const;
private:
QModelIndex mapIndex(const QModelIndex &source, int role) const;
};
вопросыproxymodel.h:
#include "questionsproxymodel.h"
#include <QBuffer>
#include <QDebug>
#include <QPixmap>
#include <QByteArray>
#include <QSqlError>
#include <QSqlTableModel>
QuestionsProxyModel::QuestionsProxyModel(QObject *parent)
: QIdentityProxyModel(parent)
{
}
QHash<int, QByteArray> QuestionsProxyModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[idRole] = "id";
return roles;
}
QVariant QuestionsProxyModel::data(const QModelIndex &index, int role) const
{
QModelIndex newIndex = mapIndex(index, role);
if (role == idRole) {
return QIdentityProxyModel::data(newIndex, Qt::DisplayRole);
}
return QIdentityProxyModel::data(newIndex, role);
}
bool QuestionsProxyModel::setData(const QModelIndex &index,
const QVariant &value, int role)
{
QModelIndex newIndex = mapIndex(index, role);
if (role == idRole) {
return QIdentityProxyModel::setData(newIndex, value, Qt::EditRole);
}
return QIdentityProxyModel::setData(newIndex, value, role);
}
bool QuestionsProxyModel::addNewEntry()
{
auto newRow = rowCount();
if (!insertRows(newRow, 1)) {
return false;
}
if (!setData(createIndex(newRow, 0), newRow + 1)) {
removeRows(newRow, 1);
return false;
}
auto sqlModel = qobject_cast<QSqlTableModel *>(sourceModel());
return sqlModel->submit();
}
bool QuestionsProxyModel::removeEntry(int row)
{
return removeRows(row, 1);
}
int QuestionsProxyModel::countOfRows() const
{
return rowCount();
}
QModelIndex QuestionsProxyModel::mapIndex(const QModelIndex &source,
int role) const
{
switch (role) {
case idRole:
return createIndex(source.row(), 0);
}
return source;
}
main.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Window 2.15
import Qt.labs.qmlmodels 1.0
import QtQuick.Controls.Material 2.15
import QuestionsProxyModels 1.0
ApplicationWindow {
id: root
visible: true
width: 1460
height: 800
TableView {
id: tableView
width: parent.width
anchors.fill: parent
boundsBehavior: Flickable.StopAtBounds
reuseItems: true
clip: true
property var columnWidths: [60]
columnWidthProvider: function (column) {
return columnWidths[column]
}
model: QuestionsProxyModel
delegate: DelegateChooser {
id: chooser
DelegateChoice {
column: 0
delegate: QuestionIdDelegate {
id: questionIdDelegate
width: tableView.columnWidthProvider(column)
text: model.id
row: model.row
Component.onCompleted: {
questionIdDelegate.markForDelete.connect(
tableView.deleteRowFromDatabase)
}
}
}
}
ScrollBar.vertical: ScrollBar {}
function deleteRowFromDatabase(row) {
console.info("before" + model.countOfRows())
if (!model.removeEntry(row)) {
console.info(qsTr("remove row %1 failed").arg(row))
}
model = QuestionsProxyModel
console.info("after" + model.countOfRows())
}
}
}
QuestionIdDelegate.qml:
import QtQuick 2.15
import QtQuick.Controls 2.15
TextField {
property int row
signal markForDelete(int row)
id: root
implicitHeight: 100
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
readOnly: true
background: Frame {}
MouseArea {
id: mouseArea
anchors.fill: parent
acceptedButtons: Qt.RightButton
onClicked: {
eraseContextMenu.popup(root, 0, mouseArea.mouseY + 10)
}
}
Menu {
id: eraseContextMenu
y: root.y
MenuItem {
text: qsTr("Delete entry")
onTriggered: {
eraseDialog.open()
eraseContextMenu.close()
}
}
MenuItem {
text: qsTr("Cancel")
onTriggered: {
eraseContextMenu.close()
}
}
}
Dialog {
id: eraseDialog
title: qsTr("Delete database entry")
modal: true
focus: true
contentItem: Label {
id: label
text: qsTr("Do you really want to erase the entry with id %1?").arg(
root.text)
}
onAccepted: {
markForDelete(root.row)
}
standardButtons: Dialog.Ok | Dialog.Cancel
}
}
.про:
QT += quick
QT += quickcontrols2
QT += sql
CONFIG += c++17
DEFINES += QT_DEPRECATED_WARNINGS
HEADERS += \
questionsproxymodel.h \
questionsqltablemodel.h
SOURCES += \
main.cpp \
questionsproxymodel.cpp \
questionsqltablemodel.cpp
RESOURCES += qml.qrc
# Additional import path used to resolve QML modules just for Qt Quick Designer
QML_DESIGNER_IMPORT_PATH =
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
редактировать:
Ответ устраняет указанную выше ошибку. Однако я обнаружил еще одну проблему с кодом.
Если я удалю строку 1, а затем строку 2, мой вывод будет выглядеть так:
Эта ошибка цикла привязки указывает Dialog на QuestionsIDDelegate:
Dialog {
id: eraseDialog
title: qsTr("Delete database entry")
modal: true
focus: true
contentItem: Label {
id: label
text: qsTr("Do you really want to erase the entry with id %1?").arg(
root.text)
}
onAccepted: {
markForDelete(root.row)
}
standardButtons: Dialog.Ok | Dialog.Cancel
}
Вы используете имя роли id. Я не знаю точно, вызывает ли это проблему в вашем приложении, но, по крайней мере, я считаю это небезопасным, поскольку это встроенное свойство QML. Мне было бы любопытно узнать, получите ли вы лучшие результаты, просто изменив имя?
это хороший момент. Думаю, я переименую этот столбец. Однако в моем реальном приложении у меня есть больше столбцов, которые не имеют имени id с той же ошибкой назначения.





Один из способов предотвратить это — проверить, что свойство не является неопределенным:
text: model.id === undefined ? "" : model.id
Большое спасибо, это действительно решает ошибку. Можете ли вы также взглянуть на ошибку цикла привязки. Что также отображается с кодом? Также означает ли поведение, что QML TableView и модель на мгновение не синхронизированы?
Я не собираюсь исправлять код, но попытаюсь объяснить, почему у вас возникают проблемы. Я чувствую, почему вещи помогут большему количеству людей.
Это изображение может быть не на 100% точным, но именно так нужно визуализировать вещи в своем уме. Когда вы представляете QML, у вас есть три различных направления, в которых живет объект: C++, QML Engine и JavaScript engine. Каждый из этих переулков без тени сомнения верит, что они контролируют жизнь и смерть объекта.
Когда вы просто передаете целые числа, которые передаются по значению, это не проблема. Когда вы передаете QString, из-за устава Qt на копирование при записи, это незначительная проблема. Когда вы передаете реальные объекты, что еще хуже, сложные контейнеры с реальными объектами, вы должны полностью это понимать. Ответ, который «решил» вашу проблему, на самом деле только замаскировал ее. Вы обнаружите, что существует много кода QML и JavaScript, который существует только для маскировки приложения, не соблюдающего правила.
Если бы это приложение использовало реальную базу данных, была бы четвертая полоса. Да, SQLite предоставляет интерфейс SQL и позволяет многое, но реальная база данных имеет внешний механизм, обеспечивающий общий доступ и контролирующий жизнь курсоров. Файлы SQLite, как правило, предназначены для одного пользователя. Да, несколько потоков в вашем приложении могут получить к нему доступ, но пока ваше приложение работает, вы не можете открыть окно терминала и использовать инструменты командной строки для проверки базы данных. Думайте об этом больше как о действительно хорошей индексированной файловой системе без совместного использования.
Итак, вы создаете объект на C++, а затем предоставляете его QML. Движок QML теперь безоговорочно верит, что он управляет жизнью и смертью этого объекта, несмотря на то, что у него нет собственной копии.
QML действительно слаб. На самом деле он мало что может сделать, поэтому ему приходится передавать любой важный объект JavaScript. Движок JavaScript теперь верит без тени сомнения, что теперь он контролирует жизнь и смерть этого объекта.
Вам также необходимо представить эти дорожки как независимые потоки. Скорее всего, внутри каждого будет много потоков, но, как правило, любой сигнал или связь между ними будет проходить в цикле событий цели как событие в очереди. Это означает, что он будет обработан только тогда, когда он, наконец, окажется в верхней части очереди для этого цикла событий.
Вот, кстати, почему вы никогда не можете использовать интеллектуальные указатели при использовании QML/JavaScript. Особенно те, которые подсчитывают ссылки и удаляют объект, когда ссылок больше нет. Линия C++ не может узнать, что QML или JavaScript все еще используют объект.
Ответ, говорящий вам проверить неопределенное свойство, маскирует проблему, заключающуюся в том, что ваш код едет по всем полосам в нетрезвом виде. В конце концов, на более быстром (а иногда и более медленном) процессоре сборка мусора для одной из полос запустится в самый неподходящий момент, и вас встретит дамп стека. (Код вождения в нетрезвом виде ударит по неуступчивому дереву.)
Правильное решение №1: Никогда не используйте QML или JavaScript. Просто используйте C++ и виджеты. Оставайтесь полностью в полосе C++. Если это путь, открытый для вас, это хороший путь. Существует очень много производственного кода, делающего именно это. Вы можете получить копию этой книги (или просто скачать исходный код со страницы) и разобраться в ее создании.
Правильное решение №2: На самом деле никогда ничего не делайте в QML или JavaScript. Это все вместе другое решение, чем № 1. Вы можете использовать QML только для пользовательского интерфейса, оставив всю логику на C++. Ваш код не работает, потому что вы действительно пытаетесь что-то сделать. Я не создавал и не пробовал ваш код. я просто увидел
function deleteRowFromDatabase(row)
которого вообще не должно быть. С++ содержит вашу модель. Вы выдаете сигнал из своих дорожек QML/JavaScript, когда действие пользователя требует удаления. Этот сигнал становится событием в очереди на полосе C++. При обработке строка будет удалена, а модель обновлена. Если вы правильно отобразили свою модель в QML, она выдаст некоторую форму сигнала «модель изменена», и пользовательский интерфейс будет соответствующим образом обновлен. Одним из основных моментов MVC (Model-View-Controler) является связь с/от модели. Когда данные изменяются, он уведомляет представление (я).
Правильное решение №3: Никогда не используйте С++. Пусть ваш C++ будет "Hello World!" оболочка, которая просто запускает ваш QML. Никогда не создавайте и не делитесь объектом между C++ и двумя другими дорожками. Делайте все внутри JavaScript и QML.
Ошибки цикла привязки, как правило, происходят, когда код в нетрезвом виде проезжает по всем трем дорожкам. Они также случаются, когда код не рассматривает каждую дорожку как минимум как один отдельный поток с собственным циклом обработки событий. Линия C++ еще не завершила (возможно, даже не начала) операцию удаления, но ваш код уже пытается использовать результат.
Я не знаю почему, но некоторые люди редактируют эти вещи и удаляют правильные ответы. Полная информация была слишком длинной для SO и содержится здесь,
с исходником даже.
Я знаю о решении 1. В решении 2 вы предлагаете подключить сигнал из QML к модели на C++. Значит, он вызывается в нужное время?
Нет. Вы не можете контролировать время между дорожками. Это то, чего не понимают люди, тянущиеся к QML и JavaScript. Единственный правильный способ его использования — не использовать его вообще. Эта функция исчезает: function deleteRowFromDatabase(row) { console.info("before" + model.countOfRows()) if (!model.removeEntry(row)) { console.info(qsTr("удалить строку %1 не удалось") .arg(row)) } model = QuestionsProxyModel console.info("after" + model.countOfRows()) } Вы отправляете сигнал C++ и все.
Вы пытаетесь на самом деле «использовать» QML и JavaScript, но это просто невозможно. Вы пытаетесь контролировать порядок выполнения на другой полосе с собственным потоком и циклом обработки событий. Этого нельзя делать. Хуже того, код пытается повторно использовать модель сразу после этого. Вы просто отправляете сигнал на C++. Когда модель была обновлена, она должна автоматически сигнализировать об изменении других данных полосы движения. Все это должно продолжаться счастливо, даже если вы никогда не касаетесь модели в QML/JavaScript. В худшем случае у вас есть полоса C++, которая отправляет сигнал, когда это делается, чтобы принудительно обновить. Не должен быть нужен.
Хорошо, я думаю, я понимаю. Я должен выдать сигнал из QML вместо прямого вызова стирания из javascript, который вызывает слот на сайте C++. Таким образом, все это вызывается в нужное время. Спасибо за объяснение.
Я попробовал это, даже отправив сигнал на сайт С++ и удалив с С++, все равно возникают обе ошибки, и мне все еще нужно исправление из другого ответа. Я был бы очень признателен за решение здесь для опубликованного кода.
Вам нужно опубликовать код, где вы пытались сделать все на C++. Если вы получаете эту ошибку даже тогда, это потому, что вы: 1) все еще пытаетесь переназначить модель 2) делаете что-то еще неправильно
Вы не предоставили весь код. То, что вы предоставили, даже не скомпилируется. Отсутствуют операторы включения файлов. Какую версию Qt вы используете? Я спрашиваю, потому что, согласно этому сообщению: medium.com/@yuvaram.aligeti/… qmlRegisterSingletonInstance не является частью раннего Qt 5.x. Это определенно не является частью моей установки 5.12 на Ubuntu 2.04 LTS. Если вы хотите, чтобы люди физически отлаживали ваш код, вам необходимо предоставить полный ТОЧНЫЙ код, включая файл .PRO. Вы также должны быть конкретными в своих версиях. Если это 6.x, вы сами по себе.
Я добавил недостающий файл pro. Я не вижу, чтобы включаемые файлы отсутствовали. Я скопировал полный код, кроме включения защиты в файлы .h.
Questionsproxymodel.cpp (второй .h в его имени) даже не имеет оператора #include для questionsproxymodel.h. Несколько других отсутствуют такие вещи. Вы никогда не указывали, какую именно версию Qt вы используете. Код, который вы разместили здесь, НЕ БУДЕТ СОСТАВЛЯТЬСЯ в Ubuntu 20.04 LTS с версией Qt из репозиториев. qmlRegisterSingletonInstance не существует в этой версии. В этом нет абсолютно никаких причин использовать прокси-модель. Я закончил тратить время на это. Вы пытаетесь «исправить код», когда проблема заключается в плохой архитектуре.
вы правы, я пропустил, чтобы включить их. Без понятия, почему они пропали. Я использую Qt5.15.0 на Manjaro, и qmlRegisterSingletonInstance существует с тех пор, как он был представлен в Qt 5.14.
Я сократил его до минимального примера. он все еще немного громоздкий из-за фона sql