Arrow
ArrowСәуір 1, 2017, 3:39 Т.Қ.

Несколько изображений на форме с возможностью прокрутки

QImage, QLabel, QScrollArea

Нужен совет по реализации определенной идеи:

Имеется: Изображения QImage, количество которых всегда разное и известно становится в определенный момент в процессе работы программы. Оно может изменяться в большую или меньшую сторону (может достигать и 100 шт).

Не обходимо: Реализовать возможность просмотра этих изображений один за одним с прокруткой (как страницы в программе для просмотра PDF файлов).

Думаю реализовать так:

На QMainWindow установить QScrollArea и в нее запихивать динамически QLabel (с QImage внутри).

Ширина QLabel (и QImage в нем) устанавливается по ширине QScrollArea, а высота по QImage масштабируется пропорционально в зависимости от ширины.

Загружать думаю не все сразу а по мере прокрутки, а высоту полосы прокрутки устанавливать из расчета - количество изображений умножить на высоту. Таким образом даю возможность перелистнуть куда угодно и показать определенное изображение (в зависимости от положения полосы прокрутки).

Вопрос в следующем: правильно ли я предполагаю все это дело реализовать или есть другой вариант (более простой или более правильный).

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Ол саған ұнайды ма? Әлеуметтік желілерде бөлісіңіз!

21
Evgenii Legotckoi
  • Сәуір 2, 2017, 2:56 Т.Ж.

В QLabel устанавливается не QImage, а QPixmap. Так что тут скорее будет удобнее оперировать QPixmap. Впрочем с небольшими дополнительными преобразованиями можно будет работать и с QImage.

Что касается QScrollArea и масштабирования QLabel, то вы правы в общем направлении, вот только ресайзить QLabel а вместе с ним и изображение не очень удобно, QScrollArea из-за специфики своей работы будет позволять оставаться QLabel тех размеров, которые имеет изображение, то есть ресайз QLabel не будет иметь эффекта. Поэтому стоит ресайзить саму картинку. Но нужно будет хранить оригинальную картинку, а в QLabel каждый раз устанавливать картинку с изменёнными размерами. Иначе качество картинки быстро ухудшится в ноль.

Что касается динамической подгрузки, то это уже второй этап разработки данного user case. Для начала Вам стоит реализовать отображение всех загружаемых картинок, с их отображением. А потом уже разобраться с подгрузкой при достижении крайних картинок в QScrollArea.

Для того, чтобы QLabel располагались вертикально или горизонтально, стоит применить QVBoxLayout или QHBoxLayout.

Вот пример с реализацией первой части вашей задумки:

widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

#include <QGridLayout>
#include <QScrollArea>
#include <QPushButton>
#include <QVBoxLayout>
#include <QLabel>
#include <QImage>
#include <QPixmap>

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = 0);
    ~Widget();

private slots:
    void addImage();

protected:
    virtual void resizeEvent(QResizeEvent *event) override;

private:
    QGridLayout* m_gridLayout;
    QGridLayout* m_gridLayout_2;
    QScrollArea* m_scrollArea;
    QPushButton* m_pushButton;
    QVBoxLayout* m_verticalLayout;
    QWidget*     m_scrollAreaWidgetContents;
    QVector<QPair<QLabel*, QPixmap> > m_imagesVector;
};

#endif // WIDGET_H

widget.cpp

#include "widget.h"

#include <QFileDialog>
#include <QDir>
#include <QStandardPaths>
#include <QScrollBar>

#include <QDebug>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    resize(640, 480);

    m_gridLayout = new QGridLayout(this);
    m_scrollArea = new QScrollArea(this);
    m_scrollArea->setWidgetResizable(true);
    m_scrollAreaWidgetContents = new QWidget();
    m_gridLayout_2 = new QGridLayout(m_scrollAreaWidgetContents);
    m_verticalLayout = new QVBoxLayout();
    m_gridLayout_2->addLayout(m_verticalLayout, 0, 0, 1, 1);
    m_scrollArea->setWidget(m_scrollAreaWidgetContents);
    m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    m_gridLayout->addWidget(m_scrollArea, 0, 0, 1, 1);

    m_pushButton = new QPushButton("Add Image", this);
    connect(m_pushButton, &QPushButton::clicked, this, &Widget::addImage);
    m_gridLayout->addWidget(m_pushButton, 1, 0);
}

Widget::~Widget()
{

}

void Widget::addImage()
{
    QStringList fileNameList = QFileDialog::getOpenFileNames(this,
                                                             "Open Files",
                                                             QStandardPaths::displayName(QStandardPaths::HomeLocation),
                                                             tr("Images (*.png *.jpg)"));

    int imageWidth = m_scrollArea->width() - 30;

    for (QString fileName : fileNameList)
    {
        QLabel* imageLabel = new QLabel(this);
        QPixmap pix(fileName);
        m_imagesVector.append(qMakePair(imageLabel, pix));
        imageLabel->setPixmap(pix.scaledToWidth(imageWidth));
        m_verticalLayout->addWidget(imageLabel);
    }
}

void Widget::resizeEvent(QResizeEvent *event)
{
    Q_UNUSED(event)
    int imageWidth = m_scrollArea->width() - 30;

    for (QPair<QLabel*, QPixmap> pair : m_imagesVector)
    {
        pair.first->setPixmap(pair.second.scaledToWidth(imageWidth));
    }
}

Здесь не использовался графический дизайнер, чтобы смогли посмотреть как реализована область прокрутки. Во вложении весь проект.

Что касается динамической подгрузки, то подумайте сами:

  1. Нужна ли она Вам?
  2. Если нужна, то опробуйте реализовать ей самостоятельно. Думаю, что здесь лишь стоит поработать на контейнером, который содержит все изображения.

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

    Arrow
    • Сәуір 2, 2017, 4:52 Т.Ж.

    Похоже, что динамическая подгрузка нужна т.к. при загрузке 20 изображений программа не отвечает в течении 5 секунд. Если изображений окажется 100 - это будет 500 секунд (более 8 минут). Загрузка изображений идет из папки указанной пользователем через класс обертку, который загружает и настраивает изображение.

    Вот код отображения изображений:

    ImgDoc                             *imgDoc;
    QVector<QLabel*>    vector;
    
    --------------------------------------------------------------
    
    void MainWindow::ImgLoad()
    {
       // Загрузка
    
        // Удаление предыдущих изображений
        ImgDelete();
    
        for (int i = 0; i <= 20; i++) {
            vector.append(new QLabel());
            vector.last()->setAlignment(Qt::AlignHCenter);
            vector.last()->setPixmap(QPixmap::fromImage(
                                     imgDoc->img(i)->renderImage(physicalDpiX(),
                                                                 physicalDpiY())));
    
            ui->vLayout->addWidget(vector.last());
        }
    }
    
    void MainWindow::ImgDelete()
    {
        // Удаление
    
        vector.clear();
    
        QLayoutItem *item;
        while( (item = ui->vLayout->itemAt(0)) ) {
            ui->vLayout->removeItem(item);
            ui->vLayout->removeWidget(item->widget());
            delete item->widget();
            delete item;
            ui->vLayout->update();
        }
    }

    Думаю изначально подгружать на отображение 10 изображений, а остальные в фоновом режиме в отдельном процессе используя QThread.

    А зачем в своем коде вы использовали такую конструкцию QVector > - можно подробнее, что это дает (я плохо знаком с классом QPair).

      Evgenii Legotckoi
      • Сәуір 2, 2017, 5:28 Т.Ж.

      Что касается конструкции QVector, то это первое, что пришло на ум, что можно было накидать по быстрому.

      У вас за изображения отвечает специальный класс, который хранит в себе эти изображения и видимо впоследствии он будет отталкиваться от индекса изображения в списке изображений, чтобы подгружать картинки с 50-ой по 70-ю, например.

      А мне было лень так сильно запариваться сегодня , чтобы накидать пример, показывающий первую часть вашей задумки. Поэтому я взял вектор, который содержит QPair. QPair в свою очередь содержит целевой Label и оригинальное изображение. Что в моём случае, что в вашем приходится рендерить картинку из оригинального изображения. Просто я при этом не пересоздаю сами QLabel. Я мог бы вообще без QVector обойтись, если бы не проблема с масштабированием изображения при изменении размеров окна приложения.

      Возможно, лучшим решением было бы использовать QMap, но QVector с QPair как-то быстрее на ум пришли.

      Подгрузка через QThread будет однозначно правильным решением.

        Arrow
        • Сәуір 2, 2017, 5:49 Т.Ж.

        Большое спасибо за консультацию :)

          Arrow
          • Сәуір 2, 2017, 12:25 Т.Қ.

          Пытался реализовать динамическую подгрузку изображений и попытался сделать это так:

          Заголовочный файл класса:

          class ImageLoader : public QObject
          {
              Q_OBJECT
          public:
              explicit ImageLoader();
              virtual ~ImageLoader();
          
          private:
              ImgDoc            *doc;
              QVector<QLabel*>    vector;
              int x, y;
          
          signals:
              void finished();
              void returnValue(QVector  val);
          
          public slots:
              void process();
          };
          

          Реализация:

          ImageLoader::ImageLoader(const QString &str, const int &X, const int &Y) : QObject()
          {
              doc = ImgDoc::load(str);
              x = X;
              y = Y;
          }
          
          ImageLoader::~ImageLoader()
          {
          
          }
          
          void ImageLoader::process()
          {
              for (int i = 0; i <= doc->numPages(); i++) {
                  vector.append(new QLabel());
                  vector.last()->setAlignment(Qt::AlignHCenter);
                  vector.last()->setPixmap(QPixmap::fromImage(
                                           doc->img(i)->renderImage(x, y)));
              }
          
             /* Вернуть данные в основной поток */
             returnValue(vector);
          
              emit finished();
          }
          

          Вызываю так:

              QThread* thread = new QThread;
              ImageLoader *imgLoader = new ImageLoader(str, physicalDpiX(), physicalDpiY(),
                                                       *ui->vLayout);
          
              connect(thread, &QThread::started, imgLoader, &ImageLoader::process);
          
              connect(imgLoader, &ImageLoader::finished, thread, &QThread::terminate);
          
              connect(imgLoader, &ImageLoader::returnValue, this, &MainWindow::getValue);
          
              imgLoader->moveToThread(thread);
          
              thread->start();
          
          -------------------------------------------
          
          void MainWindow::getValue(QVector val)
          {
              /* Вывести данные */
             for (int i = 0; i <= val.length(); i++) {
                  ui->vLayout->addWidget(val.at(i));
              }
          }
            Arrow
            • Сәуір 2, 2017, 12:33 Т.Қ.

            Только ничего не выводится в результате, хотя по отладчику видно, что рабетает. Я что-то сильно запутался и не могу понять "где я натупил ?" или может даже сказать "где не натупил ?" :).

            Хотел сделать чтобы сразу загружалось 5 изображений, а затем во втором потоке шла обработка остальных и они подгрузились позже. Это для того. чтобы уйти от подгрузки при достижении крайних положений в QScrollArea.

            Возможно даже правильнее будет реализовать подгрузку оставшихся изображений по партиям (10 шт. в партии).

            Только изображения никак не выводятся.

              Arrow
              • Сәуір 2, 2017, 12:36 Т.Қ.

              Функции getValue и returnValue выглядят так. В первый раз съело часть.

              void getValue ( QVector < QLabel * > val );
              
              void returnValue ( QVector < QLabel * >  val);
                Evgenii Legotckoi
                • Сәуір 2, 2017, 12:50 Т.Қ.

                Не уверен, что правильно будет передавать вектор с указателями как объект. Он ведь в данном случае копируется, а копируется ли он правильно, это уже вопрос. Лучше передавать как ссылку.

                void getValue ( QVector < QLabel * > &val );
                void returnValue ( QVector < QLabel * > &val);

                Возможно, что при передаче данных из потока в поток, эти данные портятся, такое может быть. Раскидайте qDebug() повсюду и посмотрите, где данные пропадают.

                Особенно интересно, заходит ли вообще вот в этот цикл:

                for (int i = 0; i <= val.length(); i++) {
                    ui->vLayout->addWidget(val.at(i));
                }
                  Arrow
                  • Сәуір 2, 2017, 1:17 Т.Қ.

                  Раньше не писало ничего, а теперь при достижении конца цикла (выход из цикла)вылетает сообщение:

                  Приложение остановлено, так как оно получило сигнал от операционной системы.

                  Сигнал: SIGSEGV

                  Назначение: Segmentation fault

                  qDebug() выводит все данные без проблем.

                  Вылетает здесь:

                  void ImageLoader::process()
                  {
                      qDebug() << "pages: " << doc->numPages();
                      for (int i = 0; i <= doc->numPages(); i++) {
                          vector.append(new QLabel());
                          vector.last()->setAlignment(Qt::AlignHCenter);
                          vector.last()->setPixmap(QPixmap::fromImage(
                                                   doc->page(i)->renderToImage(x, y)));
                  
                         /* Вылетает здесь после прохождения всего цикла */        
                          qDebug() << "i: " << i << "vector: " << vector.at(i);
                      }
                  
                      qDebug() << "vector: " << vector;
                  
                      emit returnValue(vector);
                  
                      emit finished();
                  }
                    Evgenii Legotckoi
                    • Сәуір 2, 2017, 2:10 Т.Қ.

                    При первом же проходе вылетает?

                    Сдаётся мне, что как-то иначе нужно перепроектировать динамическую подгрузку. QVector хранится в стеке, а не в куче, скорее всего из-за этого и возникают проблемы.

                    imgLoader хранит все изображения и добавляет их в вектор. Но это в целом бесполезная работа. Как вариант, передавать в ImgLoader указатель на layout, который содержит изображение, и уже там всё подчищать и добавлять.

                      Arrow
                      • Сәуір 2, 2017, 2:19 Т.Қ.

                      Это я уже попробовал (передача указателя на layout) - ничего не выходит, изображения не добавляются и нет никаких ни ошибок, ни вылетов или даже намеков в чем проблема.

                      Пробовал как вариант сработать без класса и запихнуть одну функцию в поток (QtConcurrent::run) - ничего.
                        Arrow
                        • Сәуір 2, 2017, 2:23 Т.Қ.

                        Если проблема в хранении QVector, то чем его можно заменить? Не обработывать же в функции класса ImgLoader одно изображение и отсылать его вQMainWindow на загрузку в layout?

                          Evgenii Legotckoi
                          • Сәуір 2, 2017, 2:28 Т.Қ.

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

                            Arrow
                            • Сәуір 3, 2017, 9:58 Т.Ж.

                            Проверил на Windows - вылетает на этом коде в классе работающем в отдельном потоке:

                            void ImageLoader::process()
                            {
                                for (int i = 5; i < doc->numPages(); i++) {
                                    vec.append(new QLabel()); // Вылетает здесь
                                    vec.last()->setAlignment(Qt::AlignHCenter);
                                    vec.last()->setPixmap(QPixmap::fromImage(
                                                             doc->page(i)->renderToImage(x, y)));
                                }
                            
                                emit returnValue(vec);
                            
                                emit finished();
                            }

                            Если не запускать второй поток программа работает стабильно - все загружает и обрабатывает правильно, только долго ждать приходится.

                              Evgenii Legotckoi
                              • Сәуір 3, 2017, 12:30 Т.Қ.
                              • Жауап шешім ретінде белгіленді.

                              В общем ситуация такая получается. UI виджеты нельзя создавать в потоке отличном от main потока приложения. Поэтому не работает как положено. Можно сделать иначе. Создавать в отдельном потоке QPixmap и выплёвывать его в сторону главного окна приложения. В слоте главного окна приложения создавать уже QLabel и добавлять его в verticalLayout. Тогда никаких задержек не происходит при открытии большого числа изображений.

                              Вот пример

                              imgloader.h

                              #ifndef IMGLOADER_H
                              #define IMGLOADER_H
                              
                              #include <QObject>
                              #include <QPixmap>
                              
                              class ImgLoader : public QObject
                              {
                                  Q_OBJECT
                              public:
                                  explicit ImgLoader(QObject *parent = 0);
                              
                                  void process();
                                  void setFileNameList(QStringList fileNameList);
                                  void setWidth(int width);
                              
                              signals:
                                  void finished();
                                  void sendPixmap(QPixmap pix);
                              
                              public slots:
                              
                              private:
                                  QStringList m_fileNameList;
                                  int m_width;
                              };
                              
                              #endif // IMGLOADER_H
                              

                              imgloader.cpp

                              #include "imgloader.h"
                              
                              
                              ImgLoader::ImgLoader(QObject *parent) :
                                  QObject(parent)
                              {
                              
                              }
                              
                              void ImgLoader::process()
                              {
                                  for (QString fileName : m_fileNameList)
                                  {
                                      QPixmap pix(fileName);
                                      emit sendPixmap(pix.scaledToWidth(m_width));
                                  }
                              
                                  emit finished();
                              }
                              
                              void ImgLoader::setFileNameList(QStringList fileNameList)
                              {
                                  m_fileNameList = fileNameList;
                              }
                              
                              void ImgLoader::setWidth(int width)
                              {
                                  m_width = width;
                              }
                              

                              Часть widget.h

                              private slots:
                                  void addImages();
                                  void addImageLabel(QPixmap pixmap);

                              Часть widget.cpp

                              void Widget::addImages()
                              {
                                  QStringList fileNameList = QFileDialog::getOpenFileNames(this,
                                                                                           "Open Files",
                                                                                           QStandardPaths::locate(QStandardPaths::HomeLocation, QString()),
                                                                                           tr("Images (*.png *.jpg)"));
                              
                                  int imageWidth = m_scrollArea->width() - 30;
                              
                                  ImgLoader *imgLoader = new ImgLoader;
                                  connect(imgLoader, &ImgLoader::finished, &m_thread, &QThread::terminate);
                                  connect(&m_thread, &QThread::started, imgLoader, &ImgLoader::process);
                                  connect(&m_thread, &QThread::finished, imgLoader, &ImgLoader::deleteLater);
                                  connect(imgLoader, &ImgLoader::sendPixmap, this, &Widget::addImageLabel);
                                  imgLoader->setFileNameList(fileNameList);
                                  imgLoader->setWidth(imageWidth);
                                  imgLoader->moveToThread(&m_thread);
                                  m_thread.start();
                              }
                              
                              void Widget::addImageLabel(QPixmap pixmap)
                              {
                                  QLabel* imageLabel = new QLabel(this);
                                  imageLabel->setPixmap(pixmap);
                                  m_verticalLayout->addWidget(imageLabel);
                              }
                                Arrow
                                • Сәуір 4, 2017, 6:29 Т.Ж.

                                Спасибо! О том, что "UI виджеты нельзя создавать в потоке отличном от main потока приложения" не знал.

                                  Arrow
                                  • Сәуір 4, 2017, 6:35 Т.Ж.

                                  И последний вопрос по теме:

                                  Я так понимаю вариант передачи параметров в класс через соответствующие методы setFileNameList и setWidth предпочтительнее, чем через конструктор класса.

                                    Evgenii Legotckoi
                                    • Сәуір 4, 2017, 6:58 Т.Ж.

                                    Да можно и через конструктор класса. Просто я написал через методы. Главное передать эти параметры до того, как поместите объект в другой поток.

                                    Ещё с точки зрения потокобезопасности и сохранности данных там нужно мьютексами перекрывать слоты, кажется. Но не буду врать. Самому нужно почитать про это подробнее.

                                      Arrow
                                      • Сәуір 4, 2017, 7:07 Т.Ж.

                                      А где почитать можно?

                                        Evgenii Legotckoi
                                        • Сәуір 4, 2017, 7:15 Т.Ж.

                                        Официальная документация по Qt. Пока только её могу посоветовать.

                                          Arrow
                                          • Сәуір 4, 2017, 8:34 Т.Ж.

                                          Ок :)

                                            Пікірлер

                                            Тек рұқсаты бар пайдаланушылар ғана пікір қалдыра алады.
                                            Кіріңіз немесе Тіркеліңіз
                                            Г

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

                                            • Нәтиже:66ұпай,
                                            • Бағалау ұпайлары-1
                                            t

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

                                            • Нәтиже:33ұпай,
                                            • Бағалау ұпайлары-10
                                            t

                                            Qt - Тест 001. Сигналы и слоты

                                            • Нәтиже:52ұпай,
                                            • Бағалау ұпайлары-4
                                            Соңғы пікірлер
                                            G
                                            GoattRockҚыр. 3, 2024, 1:50 Т.Қ.
                                            Linux жүйесінде файлдарды қалай көшіруге болады Задумывались когда-нибудь о том, как мы привыкли доверять свои вещи службам грузоперевозок? Сейчас такие услуги стали неотъемлемой частью нашей жизни, особенно когда речь идет о переездах между …
                                            d
                                            dblas5Шілде 5, 2024, 11:02 Т.Ж.
                                            QML - Сабақ 016. SQLite деректер қоры және онымен QML Qt-та жұмыс істеу Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
                                            k
                                            kmssrАқп. 8, 2024, 6:43 Т.Қ.
                                            Qt Linux - Сабақ 001. Linux астында Autorun Qt қолданбасы как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
                                            АК
                                            Анатолий КононенкоАқп. 5, 2024, 1:50 Т.Ж.
                                            Qt WinAPI - Сабақ 007. Qt ішінде ICMP Ping арқылы жұмыс істеу Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
                                            Енді форумда талқылаңыз
                                            Evgenii Legotckoi
                                            Evgenii LegotckoiМаусым 24, 2024, 3:11 Т.Қ.
                                            добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
                                            F
                                            FynjyШілде 22, 2024, 4:15 Т.Ж.
                                            при создании qml проекта Kits есть но недоступны для выбора Поставил Qt Creator 11.0.2. Qt 6.4.3 При создании проекта Qml не могу выбрать Kits, они все недоступны, хотя настроены и при создании обычного Qt Widget приложения их можно выбрать. В чем может …
                                            BlinCT
                                            BlinCTМаусым 25, 2024, 1 Т.Ж.
                                            Нарисовать кривую в qml Всем привет. Имеется Лист листов с тосками, точки получаны интерполяцией Лагранжа. Вопрос, как этими точками нарисовать кривую? ChartView отпадает сразу, в qt6.7 появился новый элемент…
                                            BlinCT
                                            BlinCTМамыр 5, 2024, 5:46 Т.Ж.
                                            Написать свой GraphsView Всем привет. В Qt есть давольно старый обьект дял работы с графиками ChartsView и есть в 6.7 новый но очень сырой и со слабым функционалом GraphsView. По этой причине я хочу написать х…
                                            Evgenii Legotckoi
                                            Evgenii LegotckoiМамыр 2, 2024, 2:07 Т.Қ.
                                            Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Добрый день. По моему мнению - да, но то, что будет касаться вызовов к функционалу Андроида, может создать огромные трудности.

                                            Бізді әлеуметтік желілерде бақылаңыз