Я использую Qt 5.1.1 и QtCreator 2.8.1 на Mac с OS-X 10.8.5. У меня есть QAbstractListModel, который управляет объектами ImageData. Я могу загружать изображения и отображать их в QML с помощью GridView после регистрации ImageProvider в main.cpp.
Затем я выбираю отдельные изображения в представлении, например, несколько выбранных изображений показаны ниже с оранжевой рамкой:
и затем функция модели C ++: deleteSelected () выдает ожидаемый результат:
Однако, когда я пытаюсь изменить размер окна, скажем, захватывая один из углов, я получаю сбой. Трассировка стека говорит: Тип исключения: EXC_CRASH (SIGABRT), и я получаю ошибку Qt:
ASSERT failure in QList<T>::at: "index out of range", … QtCore/qlist.h, line 452
The program has unexpectedly finished.
Так что, возможно, я неправильно удалил элементы модели или не смог сообщить модели об изменениях, но я думал, что начало и конец RemoveRows испустили правильные сигналы для обработки синхронизации? Без сомнения, я что-то упускаю по этому поводу.
Я также вызвал метод begin и end ResetModel, который предотвращает сбой приложения после изменения размера, но в этом случае любые другие виды, присоединенные к модели, возвращаются к отображению всех исходных элементов.
Я искал решение этой проблемы, пробовал много экспериментов с кодом и изучал размещенный код Вот, Вот, Вот, а также несколько других мест.
Не получается заставить это работать должным образом, какие-либо предложения? Спасибо!
...
// Other Classes:
#include "datamodelcontroller.h"#include "imageprovider.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QQmlApplicationEngine engine;
// Initialize and register model:
DataModelController model;
QQmlContext *context = engine.rootContext();
context->setContextProperty("DataModelFromContext", &model);
// Register image provider for each "role" to the model:
ImageProvider *imageProvider = new ImageProvider(&model);
engine.addImageProvider(QLatin1String("provider"), imageProvider);
// Get the main.qml path from a relative path:
PathResolver pathObject("qml/DebugProject/main.qml");
QString qmlPath = pathObject.pathResult;
// Create Component:
QQmlComponent *component = new QQmlComponent(&engine);
component->loadUrl(QUrl(qmlPath));
// Display Window:
QObject *topLevel = component->create();
QQuickWindow *window = qobject_cast<QQuickWindow*>(topLevel);
QSurfaceFormat surfaceFormat = window->requestedFormat();
window->setFormat(surfaceFormat);
window->show();
return app.exec();
}
class DataModelController : public QAbstractListModel
{
Q_OBJECT
public:
explicit DataModelController(QObject *parent = 0);
enum DataRoles {
FileNameRole = Qt::UserRole + 1,
ImageRole
};
// QAbstractListModel:
void addData(ImageData*& imageObj);
int rowCount(const QModelIndex & parent = QModelIndex()) const;
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
QHash<int, QByteArray> roleNames() const;
// Get the Model Data:
QList<ImageData*> getModelData();
signals:
void imageSelectedStateChange(bool selectedImageStateValue);
public slots:
void testLoadData();
void removeData(int index);
void setSelected(int index);
bool isSelected(int index);
void toggleSelected(int index);
void deleteSelected();
int count();
private:
// Model Data here:
QList<ImageData*> _modelData;
};
DataModelController::DataModelController(QObject *parent) : QAbstractListModel(parent)
{
}
void DataModelController::addData(ImageData*& imageObj) {
beginInsertRows(QModelIndex(), rowCount(), rowCount());
_modelData << imageObj; // for QList<>
endInsertRows();
}
int DataModelController::rowCount(const QModelIndex &) const {
return _modelData.size();
}
// Slot:
int DataModelController::count() {
return this->rowCount();
}
QVariant DataModelController::data(const QModelIndex & index, int role) const
{
if(!index.isValid())
return QVariant();
ImageData* imgObj = _modelData[index.row()];
if (role == FileNameRole) {
string imgFileName = imgObj->getFileName();
QString fileName(imgFileName.c_str());
return fileName;
}
if (role == ImageRole) {
QString url = QString::number(index.row());
return url;
}
return QVariant();
}
QHash<int, QByteArray> DataModelController::roleNames() const {
QHash<int, QByteArray> roles;
roles[FileNameRole] = "filename";
roles[ImageRole] = "thumbnail";
return roles;
}
QList<ImageData*> DataModelController::getModelData() {
return _modelData;
}
void DataModelController::testLoadData()
{
int width = 256, height = 256;
for (int i = 0; i < 5; i++) {
ostringstream digit;
digit<<i;
string imgPath("qml/DebugProject/TempImages/"+digit.str()+".jpg");
PathResolver path(imgPath.c_str());
QImage img(path.pathResult);
// Initialize an Image Object:
string fileName("file"+digit.str());
ImageData *image = new ImageData(fileName);
image->setData(NULL);
image->nX = width;
image->nY = height;
image->setThumbnailQImage(img, width, height);
image->setSelected(false);
this->addData(image);
}
}
void DataModelController::removeData(int index) {
cout << "deleting index: " << index << endl;
this->beginRemoveRows(QModelIndex(), index, index);
_modelData.removeAt(index);
// delete _modelData.takeAt(index); // tried this
this->endRemoveRows();
//this->beginResetModel();
//this->endResetModel();
}
void DataModelController::setSelected(int index) {
ImageData *imgObj = this->getModelData().at(index);
if (!imgObj->getState()) {
imgObj->setSelected(true);
emit imageSelectedStateChange(imgObj->getState());
}
}
bool DataModelController::isSelected(int index) {
ImageData *imgObj = this->getModelData().at(index);
return imgObj->getState();
}
void DataModelController::toggleSelected(int index) {
ImageData *imgObj = this->getModelData().at(index);
imgObj->setSelected(!imgObj->getState());
emit imageSelectedStateChange(imgObj->getState());
}
void DataModelController::deleteSelected() {
for (int i = this->rowCount()-1; i >= 0; i--) {
if (this->isSelected(i)) {
cout << i << ": state: " << this->isSelected(i) << endl;
this->removeData(i);
cout << this->rowCount();
}
}
}
class ImageData
{
public:
ImageData();
ImageData(string filename);
long nX, nY; // image width, height
void setFileName(string filename);
string getFileName() const;
void setSelected(bool state);
bool getState() const;
void setThumbnail(const int width, const int height);
void setThumbnailQImage(QImage &imgStart, int width, int height);
QImage getThumbnail() const;
void setData(float* data);
float* getData() const;private:
string _fileName;
QImage _thumbnail;
float* _data;
bool _isSelected;
void normalizeAndScaleData(float*& dataVector);
void getMaxMinValues(float& datamax, float& datamin, float*& data, const int numPixels, bool verbose);
unsigned char* getByteArrayFromFloatArray(int bytesPerRow, float*& dataVector);
};
import QtQuick 2.1
import QtQuick.Controls 1.0
import QtQuick.Window 2.1
import QtQuick.Controls.Styles 1.0
import QtQuick.Layouts 1.0
ApplicationWindow {
id: mainAppWindow
width: 1024*1.5*0.5
height: 256
color: "gray"property real imgScale: 0.5
Window {
id: gridViewMenu
width: 160
height: 128
opacity: 0.8
color: "black"visible: true
x: 1024;
Column {
id: colButtons
x: 10;
y: 10;
spacing: 25
CustomButton {
id: deleteSelectedButton; text: "Delete Selected"onClicked: {
DataModelFromContext.deleteSelected();
}
}
CustomButton {
id: testDataButton; text: "Load Test Images"visible: true
onClicked: DataModelFromContext.testLoadData()
}
}
}
Rectangle {
id: mainRect
width: mainAppWindow.width*0.95
height: mainAppWindow.height*0.8
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
color: "transparent"
// GRIDVIEW is here:
Rectangle {
id: gridContainer
anchors.centerIn: parent
width: parent.width
height: parent.height*0.9
color: "transparent"
GridView {
property int itemWidth: mainRect.width*imgScale;
id: gridView
interactive: true
anchors.centerIn: parent
width: parent.width;
height: parent.height;
cellWidth: itemWidth/3
cellHeight: itemWidth/3
focus: true
model: DataModelFromContext
delegate: gridDelegate
Behavior on opacity {
NumberAnimation { duration: 500; easing.type: Easing.InOutQuad }
}
Keys.onPressed: {
if (event.key == Qt.Key_D) {
DataModelFromContext.deleteSelected()
}
}
} // end of gridView
// GRIDVIEW Delegate:
Component {
id: gridDelegate
Rectangle {
id: gridImageWrapper
width: gridView.cellWidth
height: gridView.cellHeight
color: "black"Rectangle {
id: imageBorder
anchors.fill: parent
color: "transparent"border.color: "green"border.width: 1
z: 1
}
MouseArea {
id: selectImage
anchors.fill: parent
onClicked: {
// toggleSelected triggers the C++ signal: imageSelectedStateChange
DataModelFromContext.toggleSelected(index);
console.log(index + "; " + DataModelFromContext.count() )
}
}
Rectangle {
id: selectedImageBorder
anchors.fill: parent
color: "transparent"border.color: "orange"border.width: 2
opacity: 0
z: 2
Connections {
target: DataModelFromContext
onImageSelectedStateChange: {
selectedImageBorder.opacity = DataModelFromContext.isSelected(index);
}
}
}
Image {
property int itemWidth: mainRect.width*imgScale
id: frontIcon
anchors.centerIn: parent
source: "image://provider/" + thumbnail
smooth: true
visible: true
sourceSize.width: itemWidth/3;
sourceSize.height: itemWidth/3;
}
} // end of Grid Delegate Rectangle
} // end of Grid Delegate
} // end of gridContainer Rectangle} // End: mainRect
} // End Main Application Window
#include "imagedata.h"
ImageData::ImageData()
{
}
ImageData::ImageData(string filename)
{
_fileName = filename;
}
void ImageData::setFileName(string filename) {
_fileName = filename;
}
string ImageData::getFileName() const {
return _fileName;
}
void ImageData::setSelected(bool state) {
_isSelected = state;
}
bool ImageData::getState() const {
return _isSelected;
}
QImage ImageData::getThumbnail() const {
return _thumbnail;
}
void ImageData::setThumbnailQImage(QImage &imgStart, int width, int height) {
QImage img;
//QImage *imgStart = new QImage(pix, nX, nY, bytesPerRow, QImage::Format_Indexed8);
img = QPixmap::fromImage(imgStart).scaled(width, height, Qt::KeepAspectRatio).toImage();
_thumbnail = img;
}
void ImageData::setData(float* data) {
_data = data;
}
float* ImageData::getData() const {
return _data;
}
// Function: getMaxMinValues
void ImageData::getMaxMinValues(float& datamax, float& datamin, float*& data,
const int numPixels, bool verbose) {
datamin = 1.0E30;
datamax = -1.0E30;
for (int pix = 0; pix < numPixels-1; pix++) {
if (data[pix] < datamin) {datamin = data[pix];}
if (data[pix] > datamax) {datamax = data[pix];}
}
if (verbose) {
std::cout << "Min and Max pixel values = " << datamin << "; " << datamax << std::endl;
}
}// Function: normalizeAndScaleData
void ImageData::normalizeAndScaleData(float*& dataVector)
{
// ---- Find Max and Min Values:
float datamin, datamax;
this->getMaxMinValues(datamax, datamin, dataVector, nX*nY, false);
// Get average and standard deviation:
float avg = 0, sig = 0;
for (int px = 0; px < nX*nY; px++) {
avg += dataVector[px];
}
avg /= nX*nY;
for (int px = 0; px<nX*nY; px++) {
sig += powf(dataVector[px] - avg, 0.5);
//sig += powf(dataVector[px] - avg, 2.0);
}
sig = pow(sig/(nX*nY), 0.5);
int deviations = 5;
if (datamin < avg-deviations*sig) {datamin = avg-deviations*sig;}
if (datamax > avg+deviations*sig) {datamax = avg+deviations*sig;}
// ---- ScaleImage Data Here (linear scaling):
for (int px = 0; px<nX*nY; px++) {
dataVector[px] = (dataVector[px]-datamin)/(datamax - datamin);
}
}
unsigned char * ImageData::getByteArrayFromFloatArray(int bytesPerRow, float*& dataVector)
{
unsigned char *pix = new unsigned char[nX*nY];
for (int row = 0; row < nY; row++) {
for (int col = 0; col < nX; col++)
pix[row*bytesPerRow + col] = (unsigned char) 255*dataVector[row*nX + col];
}
return pix;
}
// Function: setThumbnail
void ImageData::setThumbnail(const int width, const int height) {
QImage img;
float *dataVector = this->getData();
if (dataVector == NULL) {
dataVector = new float[width*height];
} else {
normalizeAndScaleData(dataVector);
}
// Map to Byte Array (nX = cols of pixels in a row, nY = rows):
int bytesPerPixel = 1; // = sizeof(unsigned char)
int pixelsPerRow = nX;
int bytesPerRow = bytesPerPixel*pixelsPerRow;
unsigned char *pix = getByteArrayFromFloatArray(bytesPerRow, dataVector);
// Calculate the Thumbnail Image:
QImage *imgStart = new QImage(pix, nX, nY, bytesPerRow, QImage::Format_Indexed8);
img = QPixmap::fromImage(*imgStart).scaled(width, height, Qt::KeepAspectRatio).toImage();
_thumbnail = img;
}
РЕДАКТИРОВАТЬ: после комментария userr1728854 ниже о проверке диапазона я отредактировал первую часть DataModelController :: data (), чтобы проверить, была ли это проблема.
Мой код теперь выглядит следующим образом (на него легче ссылаться ниже, чем на изменение исходного кода, плюс я не хотел менять контекст моего вопроса, изменяя то, что я опубликовал):
QVariant DataModelController::data(const QModelIndex & index, int role) const
{
cout << "model index = " << index.row() << endl; // add this to help troubleshoot
if (!index.isValid() || index.row() > this->rowCount() || !this->modelContainsRow(index.row()) ) {
return QVariant();
}
Таким образом, даже если это не самый надежный способ добавить проверку диапазона в метод data (), строка:
соиЬ << «модельный индекс =» << index.row () << епсИ;
должен хотя бы напечатать:index.row ()«, когда я изменяю размер окна, а это не так. Таким образом, изменение размера окна не появляется для доступа к методу data (), и программа по-прежнему аварийно завершает работу.
Бывает, что некоторые классы Qt хранят индексы уже удаленных элементов и могут вызывать такие методы, как QAbstractItemModel::data
передавая эти индексы в качестве параметров. В вашем коде отсутствует проверка диапазона для значения строки индекса в этом методе, поэтому вы получаете ошибку «index out of range».
Это также хорошая идея, чтобы поместить проверки диапазона в остальной части кода, который имеет дело с DataModelController::_modelData
,
Это объяснение для документирования фактического «исправления», которое я реализовал для своего кода. Обсуждение, приведенное выше, на самом деле не касается этих деталей, поэтому я пишу это здесь в надежде, что это может принести пользу другим в будущем.
Во-первых, я по праву наградил награду userr1728854
, Хотя ответ не дал всех подробностей о том, как это исправить, ответ, безусловно, был правильным в определении реальной причины сбоя моего кода, и за это я благодарен.
Я также благодарю Marek R
за его комментарий об установке точки останова в функции assert_x, которая помогла мне отследить сбой поставщика изображений, код которого я собирался опубликовать выше, но, должно быть, забыл (не важно, что поставщик изображений не был источником ошибка, просто симптом).
Происхождение моей проблемы было, когда я удалил объекты (скажем, строки 2,3
) из моей модели. Как показано в примере ниже, модель сохранила ссылки на набор номеров индексов, которые больше не существуют (4, 5
):
Поэтому в следующий раз, когда я изменил размер своего окна QML, модель предоставила ссылки на индексы, которые больше не существуют, на поставщика изображений, что дало ошибку. Т.е. вместо того, чтобы передавать новые относительные показатели 0, 1, 2, 3
(для остальных элементов) мой код передавал старые абсолютные индексы: 0, 1, 4, 5
,
Это происходит с помощью ролевого имени «thumbnail» в коде QML для компонента изображения:
Image {
.
.
source: "image://provider/" + thumbnail
.
.
}
Исправление было простым двухэтапным процессом:
ИСПРАВЛЕНИЕ (1): Замените «миниатюру» rolename на index
переменная модели QML, значения которой автоматически обновляются, чтобы соответствовать единичному элементу модели при удалении или добавлении строк. Итак, наконец, я использовал этот код:
Image {
.
.
source: "image://provider/" + index
.
.
}
ИСПРАВЛЕНИЕ (2): Мне нужно было добавить проверку диапазона в поставщике изображений, чтобы предотвратить передачу значений индекса после удаления (первоначально удаленный индекс = -1) в мою модель. Проверка диапазона, которую я использовал, проста (есть, вероятно, лучшие, но пока это работает нормально):
bool ImageProvider::isValidIndex(DataModelController* model, int index) {
int size = model->count();
if (index >= size || index < 0)
return false;
return true;
}
Это немного скучно, но я надеюсь, что это кому-нибудь поможет.