Как обращаться к QStandardItemModel в потоке (QThread)?

225
12 августа 2018, 19:40

Мне надо обрабатывать отображение миниатюр фотографий в отдельном потоке, иначе код очень долго обрабатывается. У меня есть в основном классе class MainWindow модель и переменная:

mainwindow.h

public:
    static QStandardItemModel * model = new QStandardItemModel;
    static QStandardItem *item_one;
    static int rowTableDisk = 0;

Я создаю поток для добавления миниатюр так:

threadsminiature.h

    #ifndef THREADSMINIATURE_H
    #define THREADSMINIATURE_H
    #include <QThread>
    #include <QImageReader>
    #include <mainwindow.h>
    class ThreadsMiniature : public QThread
    {
    public:
        explicit ThreadsMiniature(QString threadName, QString Path);
        void run();
        QString _Path;
    private:
        QString name;   // Имя потока
    };
    #endif // THREADSMINIATURE_H

threadsminiature.cpp

#include "threadsminiature.h"
ThreadsMiniature::ThreadsMiniature(QString threadName, QString Path) :
    name(threadName),
    _Path(Path)
{
}
void ThreadsMiniature::run()
{
    int icoWidth    = 200;
    int icoHeight    = 200;
    QImageReader imageReader(_Path);
    QSize size;
    int image_width;
    int image_height;
    if (imageReader.supportsOption(QImageIOHandler::Size))
    {
        size = imageReader.size();
        image_width = size.width();
        image_height = size.height();
    }
    double ratio = (double)image_width / (double)image_height;
    if (ratio >= 1)
    {
        image_width = icoWidth;
        image_height = image_width / ratio;
    } else {
        image_height = icoHeight;
        image_width = image_height * ratio;
    }
    imageReader.setScaledSize(QSize(image_width, image_height));
    //QImage image = imageReader.read();
    MainWindow::item_one = new QStandardItem();
    MainWindow::item_one->setData(QVariant(QPixmap::fromImage(imageReader.read())), Qt::DecorationRole);
    MainWindow::model->setItem(MainWindow::rowTableDisk, 2, MainWindow::item_one);
}

В основном коде заполняю QTableView так :

mainwindow.cpp

void MainWindow::TableListFiles(QStringList imagePathListCopy, QString DeviceFolder) {
    if (!DeviceFolder.isEmpty()) {
        //Заголовки столбцов
        QStringList horizontalHeader;
        horizontalHeader.append("Отметь, что перенести");
        horizontalHeader.append("Кликабельные ссылки на изображения");
        horizontalHeader.append("Папка в которую будет скопирован файл");
        model->setHorizontalHeaderLabels(horizontalHeader);
        for(int i=0; i<imagePathListCopy.count(); i++) {

            if(rowTableDisk == 0) {
                item_one = new QStandardItem("Выбрать/Убрать всё");
                model->setItem(rowTableDisk, 1, item_one);
                rowTableDisk++;
                //item_one = new QStandardItem("<a href=\""+imagePathListCopy.at(i)+"\">"+QFileInfo(imagePathListCopy.at(i)).fileName()+"</a>");
                item_one = new QStandardItem(imagePathListCopy.at(i));
                model->setItem(rowTableDisk, 1, item_one);
                QImage image(imagePathListCopy.at(i));
                item_one = new QStandardItem();
                item_one->setData(QVariant(QPixmap::fromImage(image)), Qt::DecorationRole);
                model->setItem(rowTableDisk, 2, item_one);
                item_one = new QStandardItem(DeviceFolder);
                model->setItem(rowTableDisk, 3, item_one);
            } else {
                //item_one = new QStandardItem("<a href=\""+imagePathListCopy.at(i)+"\">"+QFileInfo(imagePathListCopy.at(i)).fileName()+"</a>");
                item_one = new QStandardItem(imagePathListCopy.at(i));
                model->setItem(rowTableDisk, 1, item_one);

                ThreadsMiniature *threadB = new ThreadsMiniature(QString("thread_%1").arg(i),imagePathListCopy.at(i));
                threadB->start();
                //QImage image(imagePathListCopy.at(i));
                item_one = new QStandardItem(DeviceFolder);
                model->setItem(rowTableDisk, 3, item_one);
            }
            rowTableDisk++;
        }
    } else {
        //Заголовки столбцов
        QStringList horizontalHeader;
        horizontalHeader.append("Отметь, что перенести");
        horizontalHeader.append("Кликабельные ссылки на изображения");
        model->setHorizontalHeaderLabels(horizontalHeader);
        int rowTableDisk = 0;
        for(int i=0;i<imagePathListCopy.count();i++) {
            if(rowTableDisk == 0) {
                item_one = new QStandardItem("Выбрать/Убрать всё");
                model->setItem(rowTableDisk, 1, item_one);
                rowTableDisk++;
                //item_one = new QStandardItem("<a href=\""+imagePathListCopy.at(i)+"\">"+QFileInfo(imagePathListCopy.at(i)).fileName()+"</a>");
                item_one = new QStandardItem(imagePathListCopy.at(i));
                model->setItem(rowTableDisk, 1, item_one);
            } else {
                //item_one = new QStandardItem("<a href=\""+imagePathListCopy.at(i)+"\">"+QFileInfo(imagePathListCopy.at(i)).fileName()+"</a>");
                item_one = new QStandardItem(imagePathListCopy.at(i));
                model->setItem(rowTableDisk, 1, item_one);
            }
            rowTableDisk++;
        }
    }
    CheckBoxDelegate *CheckBoxD = new CheckBoxDelegate(this);
    HrefDelegate *HrefD = new HrefDelegate(this);
    TableDisk->setModel(model);
    TableDisk->setItemDelegateForColumn(0, CheckBoxD);
    TableDisk->setItemDelegateForColumn(1, HrefD);
    TableDisk->setColumnWidth(0,150);
    TableDisk->setColumnWidth(1,600);
    TableDisk->setColumnWidth(2,300);
}

Но ошибки :

Как использовать в потоке static QStandardItemModel * model = new QStandardItemModel; и static int rowTableDisk = 0; ?

Answer 1

Не поместилось в комментарий, оставлю как ответ:

C потоками лучше так не работать.

Есть как минимум QThreadPool, а также Qt Concurrent - я правда в нём не разбираюсь, цитата из документации - The QtConcurrent namespace provides high-level APIs that make it possible to write multi-threaded programs without using low-level threading primitives such as mutexes, read-write locks, wait conditions, or semaphores.

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

Оригинальные статьи:

youre-doing-it-wrong QThread

how to realy use qthreads

* UPD. *

Прочитал исходники.

У вас получается что в цикле будет создано несколько потоков, и каждый из них будет обращаться к одному и тому же статическому элементу (item_one) ?

Без синхронизации ?

* UPD 2. Пример. *

Я сделал предположение о том что Вам требуется, и сделал пример.

Пример грузит из некоторой папки все картинки форматов jpg/png в таблицу с двумя столбцами - имя картинки и preview. Код получения preview скопировал из вашего метода run в потоке

Класс ImageLoader - наследник QObject(чтобы умел слать сигнал) и QRunnable (чтобы можно было добавлять в QThreadPool).

Я не уверен что так правильно, потому-что метода moveToThread QThreadPool скорей всего не вызывает, так как на вход ожидает обычный QRunnable интерфейс, но всё же это лучше чем наследоваться от QThread.

#ifndef IMAGELOADER_H
#define IMAGELOADER_H
#include <QObject>
#include <QRunnable>
#include <QFileInfo>
#include <QImage>
class ImageLoader: public QObject, public QRunnable
{
    Q_OBJECT
public:
    explicit ImageLoader(const QFileInfo& img, QObject* parent = nullptr);
    virtual ~ImageLoader(){
        m_img = nullptr;    //не удаляем - удалить обязан тот кто будет обрабатывать сигнал imgReady
    }
    virtual void run() override;
signals:
    void imgReady(const QFileInfo& file, QImage* img);
private:
    QFileInfo m_imgFile;
    QImage*   m_img;
};
#endif // IMAGELOADER_H

#include "imageloader.h"
#include <QImageReader>
#include <QDebug>
ImageLoader::ImageLoader(const QFileInfo &img, QObject *parent):
    QObject(parent),
    m_imgFile{img},
    m_img{nullptr}
{
    setAutoDelete(true);
}
void ImageLoader::run()
{
    if (!m_imgFile.exists()){
        qDebug() << __FUNCTION__ << "file" << m_imgFile.absoluteFilePath() << "do not exists";
        return;
    }
    int icoWidth    = 200;
    int icoHeight    = 200;
    QImageReader imageReader(m_imgFile.absoluteFilePath());
    QSize size;
    int image_width;
    int image_height;
    if (imageReader.supportsOption(QImageIOHandler::Size))
    {
        size = imageReader.size();
        image_width = size.width();
        image_height = size.height();
    }else{
        qCritical() << "QImageReader don support option QImageIOHandler::Size";
        return;
    }
    double ratio = (double)image_width / (double)image_height;
    if (ratio >= 1)
    {
        image_width = icoWidth;
        image_height = image_width / ratio;
    } else {
        image_height = icoHeight;
        image_width = image_height * ratio;
    }
    imageReader.setScaledSize(QSize(image_width, image_height));
    m_img = new QImage();
    if (imageReader.read(m_img)){
        emit imgReady(m_imgFile, m_img);
    }else{
        qWarning() << "can't read image" << m_imgFile.fileName() << imageReader.errorString();
        delete m_img;
    }
}

MainWindow

#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QDir>
#include <QStandardItemModel>
#include <QMutex>
#include <QFileInfo>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();
private slots:
    void on_m_selectDirBtn_clicked();
    void onImgReady(const QFileInfo& file, QImage* img);
private:
    Ui::MainWindow *ui;
    QDir           m_imgDir;
    QStandardItemModel* m_imgModel;
};
#endif // MAINWINDOW_H

MainWindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QFileDialog>
#include <QDebug>
#include <QThreadPool>
#include <QMutexLocker>
#include "imageloader.h"
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow),
    m_imgDir{"", "*.jpg; *.png", QDir::Name | QDir::IgnoreCase, QDir::Files},
    m_imgModel{}
{
    ui->setupUi(this);
    m_imgModel = new QStandardItemModel(0, 2, this);
    ui->m_imageTableView->setModel(m_imgModel);
}
MainWindow::~MainWindow()
{
    delete ui;
}
void MainWindow::on_m_selectDirBtn_clicked()
{
    QString dir = QFileDialog::getExistingDirectory(this);
    qDebug() << dir;
    if (dir.isEmpty()){
        qWarning() << "Пользователь отменил выбор директории";
    }else{
        m_imgDir.setPath(dir);
        qDebug() << m_imgDir.entryList();
        m_imgModel->clear();
        for (const auto& img: m_imgDir.entryList()){
            ImageLoader* imgLoader = new ImageLoader(QFileInfo(m_imgDir.filePath(img)));
            connect(imgLoader, SIGNAL(imgReady(QFileInfo,QImage*)),
                    this, SLOT(onImgReady(QFileInfo,QImage*)),
                    Qt::QueuedConnection);
            QThreadPool::globalInstance()->start(imgLoader);
        }
    }
}
void MainWindow::onImgReady(const QFileInfo &file, QImage *img)
{
    if (img){
        QStandardItem* imgItem = new QStandardItem();
        imgItem->setData(QVariant(QPixmap::fromImage(*img)), Qt::DecorationRole);
        m_imgModel->appendRow({new QStandardItem(file.fileName()),
                               imgItem});
        ui->m_imageTableView->resizeRowToContents(m_imgModel->rowCount()-1);
        ui->m_imageTableView->resizeColumnsToContents();
        qInfo() << "img loaded" << "threads count:" << QThreadPool::globalInstance()->activeThreadCount();
    }
    if (img){
        delete img;
    }
}

На форме одна кнопка для выбора директории и TableView для отображения модели:

mainwindow.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>489</width>
    <height>256</height>
   </rect>
  </property>
  <property name="maximumSize">
   <size>
    <width>640</width>
    <height>480</height>
   </size>
  </property>
  <property name="windowTitle">
   <string>Потоки</string>
  </property>
  <widget class="QWidget" name="centralWidget">
   <layout class="QVBoxLayout" name="verticalLayout_2">
    <item>
     <widget class="QTableView" name="m_imageTableView"/>
    </item>
    <item>
     <widget class="QPushButton" name="m_selectDirBtn">
      <property name="text">
       <string>Выбрать директорию</string>
      </property>
     </widget>
    </item>
   </layout>
  </widget>
  <widget class="QMenuBar" name="menuBar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>489</width>
     <height>21</height>
    </rect>
   </property>
  </widget>
  <widget class="QToolBar" name="mainToolBar">
   <attribute name="toolBarArea">
    <enum>TopToolBarArea</enum>
   </attribute>
   <attribute name="toolBarBreak">
    <bool>false</bool>
   </attribute>
  </widget>
  <widget class="QStatusBar" name="statusBar"/>
 </widget>
 <layoutdefault spacing="6" margin="11"/>
 <resources/>
 <connections/>
</ui>

Как выглядит:

Вывод в консоль:

"C:/Users/User/Pictures"
("2017-07-17_01.13.04.png", "galio.jpg", "galio.under.lol.png", "galio_and_text.png", "galio_w320.png", "lol.jpg", "lol.png", "LoL_Bug.1.png", "LoL_Bug.2.png", "LoL_Bug.3.png", "LoL_Bug.png", "photo_2018-03-08_23-37-28.jpg", "photo_2018-03-08_23-37-45.jpg", "photo_2018-03-08_23-37-51.jpg", "photo_2018-03-08_23-37-54.jpg", "photo_2018-03-08_23-37-57.jpg", "photo_2018-03-08_23-38-00.jpg", "photo_2018-03-08_23-38-02.jpg", "photo_2018-03-08_23-38-05.jpg", "photo_2018-03-08_23-38-08.jpg", "twitch.png", "Безымянный.png", "Снимок.PNG")
img loaded threads count: 4
img loaded threads count: 4
img loaded threads count: 4
img loaded threads count: 4
img loaded threads count: 4
img loaded threads count: 4
img loaded threads count: 4
img loaded threads count: 2
img loaded threads count: 2
img loaded threads count: 1
img loaded threads count: 1
img loaded threads count: 1
img loaded threads count: 1
img loaded threads count: 1
img loaded threads count: 1
img loaded threads count: 1
img loaded threads count: 1
img loaded threads count: 1
img loaded threads count: 1
img loaded threads count: 1
img loaded threads count: 1
img loaded threads count: 1
img loaded threads count: 0

Как видим, было задействовано до 4 потоков.

Чуть не забыл - в main.cpp пришлось зарегистрировать QFileInfo, чтобы слать его в сигнале в качестве параметра:

#include "mainwindow.h"
#include <QApplication>
#include <QFileInfo>
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    qRegisterMetaType<QFileInfo>("QFileInfo");
    w.show();
    return a.exec();
}
Answer 2

Возможно не корректный код, но рабочий и быстрый :

threadsminiature.h

#ifndef THREADSMINIATURE_H
#define THREADSMINIATURE_H
#include <QObject>
#include <QThread>
#include <QImageReader>
#include <QVector>
class ThreadsMiniature : public QThread
{
    Q_OBJECT
public:
    explicit ThreadsMiniature(QObject *parent=0, QString Path="", int rowTableDisk = 0);
    void run();
    QString _Path;
    int _rowTableDisk;
    //QList<QImage> MiniatureList;
signals:
    //QImage drowMiniature(QImage image) {return image;}
    void drowMiniature(QImage);
private:
    QString name;   // Имя потока
};
#endif // THREADSMINIATURE_H

Более двух значений в функцию MainWindow::Miniature, можно передать с помощью

QImage image = imageReader.read();
image.setText("row", QString("%1").arg(_rowTableDisk));

Так как QVector< QImage > не имеет метода SetObjectName:

threadsminiature.cpp

#include "threadsminiature.h"
#include <QtCore>
ThreadsMiniature::ThreadsMiniature(QObject *parent, QString Path, int rowTableDisk) :
    QThread(parent),
    _Path(Path),
    _rowTableDisk(rowTableDisk)
{
}
void ThreadsMiniature::run()
{
    int icoWidth    = 200;
    int icoHeight    = 200;
    QImageReader imageReader(_Path);
    QSize size;
    int image_width;
    int image_height;
    if (imageReader.supportsOption(QImageIOHandler::Size))
    {
        size = imageReader.size();
        image_width = size.width();
        image_height = size.height();
    }
    double ratio = (double)image_width / (double)image_height;
    if (ratio >= 1)
    {
        image_width = icoWidth;
        image_height = image_width / ratio;
    } else {
        image_height = icoHeight;
        image_width = image_height * ratio;
    }
    imageReader.setScaledSize(QSize(image_width, image_height));
    QImage image = imageReader.read();
    image.setText("row", QString("%1").arg(_rowTableDisk));
    emit drowMiniature(image);
}

Обойти обращение к модели(QStandardItemModel), в потоке можно вынеся в отделенную функцию MainWindow::Miniature обращение к этой модели.

mainwindow.cpp

.....
     //item_one = new QStandardItem("<a href=\""+imagePathListCopy.at(i)+"\">"+QFileInfo(imagePathListCopy.at(i)).fileName()+"</a>");
     item_one = new QStandardItem(imagePathListCopy.at(i));
                model->setItem(rowTableDisk, 1, item_one);
     ThreadsMiniature *threadB = new ThreadsMiniature(this,imagePathListCopy.at(i),rowTableDisk);
     connect(threadB, SIGNAL(drowMiniature(QImage)),this,SLOT(Miniature(QImage)));
      threadB->start();
      item_one = new QStandardItem(DeviceFolder);
                model->setItem(rowTableDisk, 3, item_one);
....
void MainWindow::Miniature (QImage image) {
        QString rowString = image.text("row");
        qDebug()<<rowString;
        int rowTable = rowString.toInt();
        item_one = new QStandardItem();
        item_one->setData(QVariant(QPixmap::fromImage(image)), Qt::DecorationRole);//DecorationRole
        model->setItem(rowTable, 2, item_one);
        //rowMiniature++;
}
READ ALSO
Выполнение/завершение по датам

Выполнение/завершение по датам

Использую cron и quartzНужно реализовать следующие: Администратор назначает на конкретную дату некое задание, которое обновляет базу данных

193
Java + Python на сервере

Java + Python на сервере

У меня есть серверное приложение на JavaВозникла необходимость запускать пользовательские скрипты, для этого я решил использовать Python, так...

242
Java spring hibernate lazy загрузка вне сессии

Java spring hibernate lazy загрузка вне сессии

Я использую Spring Data JPA (Hibernate)

179
Работа с директориями в java

Работа с директориями в java

Всем приветСтолкнулся с такой проблемой: пытаюсь программно работать с каталогом, но сталкиваюсь с тем, что студия на проверке существования...

214