Qt中的图像浏览器

时间:2014-07-08 03:14:52

标签: c++ qt

下面的代码在左侧窗格中显示缩略图。单击缩略图时,右侧窗格中将显示完整大小的图像。

image-browser

我的印象是,尽管这段代码相当简短,但它并不是在Qt中执行此任务的最自然的方法。我重新发明轮子了吗?是否有更适合此任务的Model-View类?

// main.cpp
#include "PixmapPair.h"
#include <QLabel>
#include <QWidget>
#include <QApplication>
#include <QSplitter>
#include <QGridLayout>
int main(int argc, char* argv[])
{
    QApplication app(argc, argv);
    QSplitter* page = new QSplitter;

    QGridLayout* gridLayout = new QGridLayout;
    QWidget* leftPane = new QWidget(page);
    leftPane->setLayout(gridLayout);
    QLabel* rightPane = new QLabel(page);
    PixmapPair pair1(":/images/ocean.jpg",  gridLayout, rightPane);
    PixmapPair pair2(":/images/forest.jpg", gridLayout, rightPane);

    page->setWindowTitle("Images");
    page->show();
    return app.exec();
}

// PixmapPair.h
#include <QPixmap>
#include <QIcon>
#include <QLabel>
#include <QPushButton>
#include <QGridLayout>
class PixmapPair : public QObject
{
    Q_OBJECT
public:
    PixmapPair(QString file, QGridLayout * gridLayout, QLabel* rp)
        : rightPane(rp), largePixmap(file)
    {
        smallPixmap = largePixmap.scaled(QSize(100,100), Qt::KeepAspectRatio, Qt::SmoothTransformation);
        QPushButton* pushButton = new QPushButton;
        pushButton->setIcon(QIcon(smallPixmap));
        pushButton->setFlat(true);
        pushButton->setIconSize(QSize(100,100));
        QObject::connect(pushButton, SIGNAL(clicked()), SLOT(displayInRightPane()));
        gridLayout->addWidget(pushButton);
    }
public slots:
    void displayInRightPane()
    {
        rightPane->setPixmap(largePixmap);
    }
private:
    QLabel* rightPane;
    QPixmap largePixmap;
    QPixmap smallPixmap;
};

1 个答案:

答案 0 :(得分:3)

SplitView的左侧部分基本上是一个列出所有可用图片的列表。 Qt提供了一种使用模型/视图模式处理它的方法。

显示列表的类是QListView,它将根据函数setModel()给出的模型自动完成工作。 这个函数需要一个QAbstractItemModel,因为这个类是一个纯粹的抽象类,我们需要创建一个从它派生的自定义类。
继承它需要大量的粘合代码,但幸好Qt已经提供了一个类,当我们想要表示一个列表时它会处理大部分内容,它被称为QAbstractListModel

所以我创建了一个像这样的ImageListModel:

///////////////////////  
// imagelistmodel.h ///  
#ifndef IMAGELISTMODEL_H  
#define IMAGELISTMODEL_H  

#include <QAbstractListModel>
#include <QPixmap>

struct PixmapPair
{
        QString _file;
        QPixmap _small;
        QPixmap _large;
};

class ImageListModel : public QAbstractListModel
{
        Q_OBJECT
    public:
        // QAbstractItemModel retrieves various information (like text, color, ...)
        // from the same index using roles. We can define custom ones, however to 
        // avoid a clash with predefined roles, ours must start at Qt::UserRole. 
        // All numbers below this one are reserved for Qt internals.
        enum Roles
        {
            LargePixmapRole = Qt::UserRole + 1
        };

        explicit ImageListModel(std::initializer_list<QString> files, QObject *parent = 0);
        virtual ~ImageListModel();

        // QAbstractItemModel interface ===========================
    public:
        int rowCount(const QModelIndex &parent) const;
        QVariant data(const QModelIndex &index, int role) const;
        // ========================================================

    private:
        QList<PixmapPair*> _data;
};

#endif // IMAGELISTMODEL_H

///////////////////////
// imagelistmodel.cpp /
#include "imagelistmodel.h"

ImageListModel::ImageListModel(std::initializer_list<QString> files, QObject *parent)
    : QAbstractListModel(parent)
{
    auto iter = files.begin();

    while (iter != files.end())
    {
        QPixmap large(*iter);
        PixmapPair *pair = new PixmapPair();
        pair->_file = *iter;
        pair->_large = large;
        pair->_small = large.scaled(100, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation);

        _data.append(pair);
        ++iter;
    }
}

ImageListModel::~ImageListModel()
{
    qDeleteAll(_data);
}


int ImageListModel::rowCount(const QModelIndex &parent) const
{
    // This function should return the number of rows contained in the parent  
    // parameter, the parent parameter is used for trees in order to retrieve the  
    // number of rows contained in each node. Since we are doing a list each element  
    // doesn't have child nodes so we return 0  
    // By convention an invalid parent means the topmost level of a tree. In our case  
    // we return the number of elements contained in our data store.
    if (parent.isValid())
        return 0;
    else
        return _data.count();
}

QVariant ImageListModel::data(const QModelIndex &index, int role) const
{
    if (index.isValid())
    {
        switch (role)
        {
            case Qt::DecorationRole:
            {
                // DecorationRole = Icon show for a list
                return _data.value(index.row())->_small;
            }
            case Qt::DisplayRole:
            {
                // DisplayRole = Displayed text
                return _data.value(index.row())->_file;
            }
            case LargePixmapRole:
            {
                // This is a custom role, it will help us getting the pixmap more
                // easily later.
                return _data.value(index.row())->_large;
            }
        }
    }

    // returning a default constructed QVariant, will let Views knows we have nothing 
    // to do and we let the default behavior of the view do work for us.
    return QVariant();
}
///////////////////////

我们的清单现已准备好,我们差不多完成了。

// main.cpp ///////////
#include <QApplication>

#include <QSplitter>
#include <QLabel>
#include <QListView>

#include "imagelistmodel.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QSplitter page;
    QListView *imageList = new QListView(&page);
    imageList->setModel(new ImageListModel({ "ocean.jpg", "forest.jpg" }, imageList));
    // We tell the list view to show our icon, this mode will call the data function
    // of our model with the role : DecorationRole.
    imageList->setViewMode(QListView::IconMode);
    // We want our list to show data vertically
    imageList->setFlow(QListView::TopToBottom);
    // We allow only one selection at a time in the list
    imageList->setSelectionMode(QListView::SingleSelection);
    QLabel *imagePresenter = new QLabel(&page);

    // We connect to the signal emitted when the selection is changed
    // to update the image presenter.
    QObject::connect(imageList->selectionModel(), &QItemSelectionModel::selectionChanged, [imageList, imagePresenter] {
        QModelIndex selectedIndex = imageList->selectionModel()->selectedIndexes().first();
        // We use our custom role here to retrieve the large image using the selected
        // index.
        imagePresenter->setPixmap(selectedIndex.data(ImageListModel::LargePixmapRole).value<QPixmap>());
    });

    page.setWindowTitle("Images");
    page.show();
    return a.exec();
}

该解决方案的优点是:
- 我们可以通过将自定义ListModel包装到QSortFilterProxyModel中来轻松添加过滤 - 无需创建和管理大量按钮 - 模型永远不需要知道谁在屏幕上显示它 - 如有必要,QListView将自动滚动 - 使用自定义角色可以让我们轻松检索大图像。如果我们在另一列中添加了大图像,它会在使用QTableView的模型时显示,当我们想要从所选索引中检索它时,我们必须创建一个指向右列的新索引。 (不是很难但需要更多代码,如果我们将模型包装在ProxyModel中,则容易出错)

Lambda解释

对于C ++中的lambda,完整语法为:

[CAPTURES]\(PARAMETERS\)->RESULT {FUNCTION}.  
  • 在括号之间,我们捕获变量以便能够在FUNCTION中使用它们,而不必将它们作为参数传递。
  • 括号内的PARAMETERS与任何其他函数具有相同的含义,如果省略,则lambda不带任何参数。
  • RESULT是FUNCTION的返回类型,可以省略。
  • 使用正文执行

在这个例子中,我决定忽略信号给出的参数,所以省略了括号。我使用捕获的控件来检索用户选择并更新显示的图片。