6

OS-X 10.8.5 を搭載した Mac で Qt 5.1.1 と QtCreator 2.8.1 を実行しています。ImageData オブジェクトを管理する QAbstractListModel があります。main.cpp に ImageProvider を登録した後、GridView を使用して QML で画像を読み込んで問題なく表示できます。

次に、ビュー内の個々の画像を選択します。たとえば、選択した複数の画像がオレンジ色の境界線で下に表示されます。

ここに画像の説明を入力

次に、C++ モデル関数 deleteSelected() は、期待される結果を生成します。

ここに画像の説明を入力

ただし、ウィンドウのサイズを変更しようとすると、コーナーの 1 つをつかむなどしてクラッシュします。スタック トレースには次のように表示されます: Exception Type: EXC_CRASH (SIGABRT) で、Qt エラーが発生します:

ASSERT failure in QList<T>::at: "index out of range", … QtCore/qlist.h, line 452
The program has unexpectedly finished.

おそらく、モデル アイテムを不適切に削除したか、モデルに変更を通知できなかったのでしょう。間違いなく、これについて他に何かが欠けています。

サイズ変更後にアプリケーションがクラッシュするのを防ぐために、ResetModel の begin と end も呼び出しましたが、その場合、モデルに関連付けられた他のビューはすべて元のアイテムの表示に戻ります。

私はこれに対する解決策を探し、多くのコード実験を試み、ここ、ここ、ここ、および他のいくつかの場所に投稿されたコード研究まし

これを適切に機能させることができないようです。何か提案はありますか? ありがとう!

以下は関連するコードです。


main.cpp:

...

// 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();
}

DataModelController.h:

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.cpp

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();
        }
    }
}

ImageData.h:

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);
};

main.qml:

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

イメージデータ.cpp:

#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() メソッドに追加する最も確実な方法ではない場合でも、次の行:

cout << "model index = " << index.row() << endl;

ウィンドウのサイズを変更すると、少なくとも「index.row()」を出力する必要がありますが、そうではありません。そのため、ウィンドウのサイズを変更しても data() メソッドにアクセスするようには見えず、プログラムは引き続きクラッシュします。

4

2 に答える 2

7

これは、コードに実装した実際の「修正」を文書化するための説明です。上記の議論は実際にはそれらの詳細には入っていないので、将来他の人に利益をもたらすことを期待してここに書いています.

まず、賞金を正当に に与えましたuserr1728854。答えはそれを修正する方法のすべての詳細を示していませんでしたが、私のコードがクラッシュした本当の理由を特定する上で答えは確かに正しかったので、私は感謝しています.

assert_x 関数にブレークポイントを設定することについての彼のコメントにも感謝Marek Rします。これは、画像プロバイダーへのクラッシュを追跡するのに役立ちました。そのコードは上に投稿するつもりでしたが、忘れていたに違いありません (画像プロバイダーがバグの原因、単なる症状)。

私の問題の原因は2,3、モデルからオブジェクト (行など) を削除したときでした。以下の例に示すように、モデルは、存在しない一連のインデックス番号への参照を保持していました ( 4, 5)。

行を削除した後のモデル インデックス

次に、QML ウィンドウのサイズを変更すると、モデルはもはや存在しないインデックス参照を画像プロバイダーに提供し、セグメンテーション違反を引き起こしました。0, 1, 2, 3つまり、(残りの項目に対して)新しい相対インデックスを渡すのではなく、私のコードは古い絶対インデックス: を渡していました0, 1, 4, 5

これは、画像コンポーネントの QML コードの「サムネイル」ロール名を介して行われます。

Image {
    .
    .
    source: "image://provider/" + thumbnail
    .
    .
}

修正は簡単な 2 ステップのプロセスでした。

修正 (1): 「サムネイル」ロール名をindexQML モデルの変数に置き換えます。この変数の値は、行が削除または追加されたときにモデル項目に 1 対 1 で一致するように自動的に更新されます。最後に、このコードを使用しました:

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;
}

これは少し長文ですが、誰かの役に立てば幸いです。

于 2013-11-20T03:42:03.640 に答える
1

一部の Qt クラスは、既に削除されたアイテムのインデックスを保存し、QAbstractItemModel::dataそれらのインデックスをパラメーターとして渡すなどのメソッドを呼び出すことができます。コードには、そのメソッドのインデックスの行値の範囲チェックがないため、「インデックスが範囲外です」というエラーが発生します。を扱う残りのコードに範囲チェックを入れるのも良い考えDataModelController::_modelDataです。

于 2013-11-10T01:07:53.293 に答える