QT5 教程 MainWindow 和 ImageViewer 使用B

ImageViewer B 的主窗口

本教程是上一节ImageViewer A 的主窗口的延续。

我们将从上一教程中构建的菜单中完成操作。换句话说,我们需要将每个 QAction 连接到适当的插槽。

将操作连接到插槽


建立联系

我们将使用 Qt Designer 将每个操作连接到适当的插槽。等效代码如下所示:

connect(openAct, SIGNAL(triggered()), this, SLOT(open()));
connect(printAct, SIGNAL(triggered()), this, SLOT(print()));
connect(exitAct, SIGNAL(triggered()), this, SLOT(close()));
connect(zoomInAct, SIGNAL(triggered()), this, SLOT(zoomIn()));
connect(zoomOutAct, SIGNAL(triggered()), this, SLOT(zoomOut()));
connect(normalSizeAct, SIGNAL(triggered()), this, SLOT(normalSize()));
connect(fitToWindowAct, SIGNAL(triggered()), this, SLOT(fitToWindow()));
connect(aboutAct, SIGNAL(triggered()), this, SLOT(about()));
connect(aboutQtAct, SIGNAL(triggered()), qApp, SLOT(aboutQt()));

让我们通过双击imageviewer.ui来打开Designer以将操作连接到插槽。

右键单击 Action Editor 中的 openAtc,然后选择“Go to slot…”

点击确定。

然后,它会为我们生成一个代码到imageviewer.cpp中:

void ImageViewer::on_openAct_triggered()
{
    
}

Designer 还将插槽声明写入头文件imageviewer.h

void on_openAct_triggered();

让我们将插槽的名称切换为open(),如前面等效代码中所示。这不是必需的,但我们只想在 Qt 示例中拥有相同的代码。

对所有触发的动作做同样的事情。

此外,我们需要将指向这些操作的指针放入imageviewer.h中:

QAction *openAct;
QAction *printAct;
QAction *exitAct;
QAction *zoomInAct;
QAction *zoomOutAct;
QAction *normalSizeAct;
QAction *fitToWindowAct;
QAction *aboutAct;
QAction *aboutQtAct;

指针赋值

即使我们在头文件中定义了指向 UI 的小部件或操作的指针,如上所示,我们还没有为它们分配任何对象。所以,我们现在需要在imageviewer.cpp中这样做:

...
openAct = ui->openAct;
printAct = ui->printAct;
exitAct = ui->exitAct;
zoomInAct = ui->zoomInAct;
zoomOutAct = ui->zoomOutAct;
normalSizeAct = ui->normalSizeAct;
fitToWindowAct = ui->fitToWindowAct;
aboutAct = ui->aboutAct;
aboutQtAct = ui->aboutQtAct;
...

这样,我们可以使用指针openAct->*代替ui->openAct->*

open()

至此,剩下的流程就和原来的Qt教程差不多了。

我们要实现on_actionOpen_triggered()插槽的功能。我们将名称更改为open()

void ImageViewer::open()
{
    qDebug() << "open()";
    QString fileName = QFileDialog::getOpenFileName(this,
                                     tr("Open File"), QDir::currentPath());
    if (!fileName.isEmpty()) {
         QImage image(fileName);
         if (image.isNull()) {
             QMessageBox::information(this, tr("Image Viewer"),
                                      tr("Cannot load %1.").arg(fileName));
             return;
         }
         imageLabel->setPixmap(QPixmap::fromImage(image));
         scaleFactor = 1.0;

         printAct->setEnabled(true);
         fitToWindowAct->setEnabled(true);
         updateActions();

         if (!fitToWindowAct->isChecked())
             imageLabel->adjustSize();
    }
}

第一步是询问用户要打开的文件的名称。Qt 带有QFileDialog,这是一个用户可以从中选择文件的对话框。

静态getOpenFileName()函数显示一个模式文件对话框。它返回所选文件的文件路径,如果用户取消对话框,则返回空字符串。如果文件无法打开,我们使用QMessageBox显示一个带有错误消息的对话框。如果用户按下取消,QFileDialog 返回一个空字符串。

除非文件名是空字符串,否则我们会通过构造一个尝试从文件加载图像的 QImage 来检查文件的格式是否为图像格式。如果构造函数返回一个空图像,我们使用 QMessageBox 来提醒用户。

QMessageBox类提供了一个带有短消息、图标和一些按钮的模式对话框。与 QFileDialog 一样,创建 QMessageBox 的最简单方法是使用它的静态便利函数。QMessageBox 提供了一系列沿两个轴排列的不同消息:严重性(问题、信息、警告和关键)和复杂性(必要响应按钮的数量)。在此特定示例中,带有 OK 按钮(默认)的信息消息就足够了,因为该消息是正常操作的一部分。

如果支持格式,我们通过设置标签的像素图在imageLabel中显示图像。然后我们启用Print and Fit to Window 菜单项并更新其余的视图菜单项。默认情况下启用打开退出条目。

如果 Fit to Window 选项关闭,QScrollArea::widgetResizable属性为 false,我们有责任(不是 QScrollArea)根据其内容为 QLabel 提供合理的大小。我们调用{QWidget::adjustSize()}{adjustSize()}来实现这个,本质上是一样的

imageLabel->resize(imageLabel->pixmap()->size());

Zoom

void ImageViewer::zoomIn()
{
    scaleImage(1.25);
}

void ImageViewer::zoomOut()
{
    scaleImage(0.8);
}

void ImageViewer::normalSize()
{
    imageLabel->adjustSize();
    scaleFactor = 1.0;
}

我们使用私有scaleImage()函数实现缩放槽。我们将比例因子分别设置为 1.25 和 0.8。这些因子值确保放大操作和缩小操作将相互抵消(因为 1.25 * 0.8 == 1),这样就可以使用缩放功能恢复正常的图像大小。

缩放时,我们使用 QLabel 的能力来缩放其内容。这种缩放不会改变内容的实际大小提示。由于adjustSize()函数使用了这些尺寸提示,我们唯一需要做的就是恢复当前显示图像的正常尺寸是调用adjustSize()并将比例因子重置为 1.0。

void ImageViewer::fitToWindow()
{
    bool fitToWindow = fitToWindowAct->isChecked();
    scrollArea->setWidgetResizable(fitToWindow);
    if (!fitToWindow) {
        normalSize();
    }
    updateActions();
}

每次用户切换 Fit to Window 选项时,都会调用 fitToWindow ()槽。如果调用 slot 来打开选项,我们告诉滚动区域使用QScrollArea::setWidgetResizable()函数调整其子部件的大小。然后我们使用私有updateActions()函数禁用 Zoom In、Zoom Out 和 Normal Size 菜单项。

如果QScrollArea::widgetResizable属性设置为 false(默认值),则滚动区域遵循其子窗口​​小部件的大小。如果此属性设置为 true,则滚动区域将自动调整小部件的大小,以避免滚动条可以避免,或者利用额外的空间。但是滚动区域将遵循其子小部件的最小尺寸提示,独立于小部件的可调整大小属性。所以在这个例子中,我们在构造函数中将imageLabel的尺寸策略设置为忽略,以避免当滚动区域变得小于标签的最小尺寸提示时出现滚动条。

如果调用槽来关闭选项,则{QScrollArea::setWidgetResizable}属性设置为 false。我们还通过将标签的大小调整到其内容,将图像像素图恢复到正常大小。最后我们更新视图菜单条目。

更新操作

void ImageViewer::updateActions()
{
    zoomInAct->setEnabled(!fitToWindowAct->isChecked());
    zoomOutAct->setEnabled(!fitToWindowAct->isChecked());
    normalSizeAct->setEnabled(!fitToWindowAct->isChecked());
}

私有updateActions()函数根据是否打开或关闭适合窗口选项启用或禁用放大、缩小和正常大小菜单条目。

void ImageViewer::scaleImage(double factor)
{
    Q_ASSERT(imageLabel->pixmap());
    scaleFactor *= factor;
    imageLabel->resize(scaleFactor * imageLabel->pixmap()->size());

    adjustScrollBar(scrollArea->horizontalScrollBar(), factor);
    adjustScrollBar(scrollArea->verticalScrollBar(), factor);

    zoomInAct->setEnabled(scaleFactor < 3.0);
    zoomOutAct->setEnabled(scaleFactor > 0.333);
}

scaleImage()中,我们使用 factor 参数来计算显示图像的新缩放因子,并调整 imageLabel 的大小。由于我们在构造函数中将 scaledContents属性设置为 true,因此对QWidget::resize()的调用将缩放标签中显示的图像。我们还调整滚动条以保留图像的焦点。

最后,如果比例因子小于 33.3% 或大于 300%,我们禁用相应的菜单项,以防止图像像素图变得太大,在窗口系统中消耗过多的资源。

void ImageViewer::adjustScrollBar(QScrollBar *scrollBar, double factor)
{
    scrollBar->setValue(int(factor * scrollBar->value()
                            + ((factor - 1) * scrollBar->pageStep()/2)));
}

每当我们放大或缩小时,我们都需要相应地调整滚动条。简单地打电话会很诱人

scrollBar->setValue(int(factor * scrollBar->value()));

但这会使左上角成为焦点,而不是中心。因此我们需要考虑滚动条句柄的大小(页面步长)。

运行代码

源代码

ImageViewer.pro

#-------------------------------------------------
#
# Project created by QtCreator 2013-09-30T12:23:59
#
#-------------------------------------------------

QT       += core gui
QT       += printsupport

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = ImageViewer
TEMPLATE = app


SOURCES += main.cpp\
        imageviewer.cpp

HEADERS  += imageviewer.h

FORMS    += imageviewer.ui

main.cpp

#include "imageviewer.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    ImageViewer w;
    w.show();

    return a.exec();
}

imageviewer.ui

<ui version="4.0">
<class>ImageViewer</class>
<widget class="QMainWindow" name="ImageViewer">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>393</width>
<height>325</height>
</rect>
</property>
<property name="windowTitle">
<string>ImageViewer</string>
</property>
<widget class="QWidget" name="centralWidget">
<layout class="QGridLayout" name="gridLayout"/>
</widget>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>393</width>
<height>21</height>
</rect>
</property>
<widget class="QMenu" name="fileMenu">
<property name="title">
<string>&File</string>
</property>
<addaction name="openAct"/>
<addaction name="printAct"/>
<addaction name="separator"/>
<addaction name="exitAct"/>
</widget>
<widget class="QMenu" name="viewMenu">
<property name="title">
<string>&View</string>
</property>
<addaction name="zoomInAct"/>
<addaction name="zoomOutAct"/>
<addaction name="normalSizeAct"/>
<addaction name="separator"/>
<addaction name="fitToWindowAct"/>
</widget>
<widget class="QMenu" name="helpMenu">
<property name="title">
<string>&Help</string>
</property>
<addaction name="aboutAct"/>
<addaction name="aboutQtAct"/>
</widget>
<addaction name="fileMenu"/>
<addaction name="viewMenu"/>
<addaction name="helpMenu"/>
</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"/>
<action name="openAct">
<property name="text">
<string>&Open...</string>
</property>
<property name="shortcut">
<string>Ctrl+O</string>
</property>
</action>
<action name="printAct">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>&Print...</string>
</property>
<property name="shortcut">
<string>Ctrl+P</string>
</property>
</action>
<action name="exitAct">
<property name="text">
<string>E&xit</string>
</property>
<property name="toolTip">
<string>Exit</string>
</property>
<property name="shortcut">
<string>Ctrl+Q</string>
</property>
</action>
<action name="zoomInAct">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Zoom &In (25%)</string>
</property>
<property name="shortcut">
<string>Ctrl+=</string>
</property>
</action>
<action name="zoomOutAct">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Zoom &Out (25%)</string>
</property>
<property name="shortcut">
<string>Ctrl+-</string>
</property>
</action>
<action name="normalSizeAct">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>&Normal Size</string>
</property>
<property name="shortcut">
<string>Ctrl+S</string>
</property>
</action>
<action name="fitToWindowAct">
<property name="checkable">
<bool>true</bool>
</property>
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>&Fit to Window</string>
</property>
<property name="shortcut">
<string>Ctrl+F</string>
</property>
</action>
<action name="aboutAct">
<property name="text">
<string>&About</string>
</property>
</action>
<action name="aboutQtAct">
<property name="text">
<string>About &Qt</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources/>
<connections/>
</ui>

imageviewer.h

#ifndef IMAGEVIEWER_H
#define IMAGEVIEWER_H

#include <QMainWindow>
#ifndef QT_NO_PRINTER
#include <QPrinter>
#endif

class QAction;
class QLabel;
class QMenu;
class QScrollArea;
class QScrollBar;


namespace Ui {
class ImageViewer;
}

class ImageViewer : public QMainWindow
{
    Q_OBJECT

public:
    explicit ImageViewer(QWidget *parent = 0);
    ~ImageViewer();

private slots:
    void open();
    void print();
    void zoomIn();
    void zoomOut();
    void normalSize();
    void fitToWindow();
    void about();

private:

    Ui::ImageViewer *ui;
    QLabel *imageLabel;
    QScrollArea *scrollArea;
    QAction *openAct;
    QAction *printAct;
    QAction *exitAct;
    QAction *zoomInAct;
    QAction *zoomOutAct;
    QAction *normalSizeAct;
    QAction *fitToWindowAct;
    QAction *aboutAct;
    QAction *aboutQtAct;

    double scaleFactor;

    void updateActions();
    void scaleImage(double factor);
    void adjustScrollBar(QScrollBar *scrollBar, double factor);

#ifndef QT_NO_PRINTER
    QPrinter printer;
#endif
};

#endif // IMAGEVIEWER_H

imageviewer.cpp

#include "imageviewer.h"
#include "ui_imageviewer.h"

#include <QtWidgets>
#include <QFileDialog>
#include <QMessageBox>

#ifndef QT_NO_PRINTER
#include <QPrintDialog>
#endif

ImageViewer::ImageViewer(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::ImageViewer)
{
    ui->setupUi(this);

    openAct = ui->openAct;
    printAct = ui->printAct;
    exitAct = ui->exitAct;
    zoomInAct = ui->zoomInAct;
    zoomOutAct = ui->zoomOutAct;
    normalSizeAct = ui->normalSizeAct;
    fitToWindowAct = ui->fitToWindowAct;
    aboutAct = ui->aboutAct;
    aboutQtAct = ui->aboutQtAct;

    imageLabel = new QLabel;
    imageLabel->setBackgroundRole(QPalette::Base);
    imageLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
    imageLabel->setScaledContents(true);

    scrollArea = new QScrollArea;
    scrollArea->setBackgroundRole(QPalette::Dark);
    scrollArea->setWidget(imageLabel);
    setCentralWidget(scrollArea);

    setWindowTitle(tr("Image Viewer"));
    resize(500, 400);

    connect(openAct,SIGNAL(triggered()),this,SLOT(open()));
    connect(printAct, SIGNAL(triggered()), this, SLOT(print()));
    connect(exitAct, SIGNAL(triggered()), this, SLOT(close()));
    connect(zoomInAct, SIGNAL(triggered()), this, SLOT(zoomIn()));
    connect(zoomOutAct, SIGNAL(triggered()), this, SLOT(zoomOut()));
    connect(normalSizeAct, SIGNAL(triggered()), this, SLOT(normalSize()));
    connect(fitToWindowAct, SIGNAL(triggered()), this, SLOT(fitToWindow()));
    connect(aboutAct, SIGNAL(triggered()), this, SLOT(about()));
}

ImageViewer::~ImageViewer()
{
    delete ui;
}

void ImageViewer::updateActions()
{
    zoomInAct->setEnabled(!fitToWindowAct->isChecked());
    zoomOutAct->setEnabled(!fitToWindowAct->isChecked());
    normalSizeAct->setEnabled(!fitToWindowAct->isChecked());
}

void ImageViewer::scaleImage(double factor)
{
    Q_ASSERT(imageLabel->pixmap());
    scaleFactor *= factor;
    imageLabel->resize(scaleFactor * imageLabel->pixmap()->size());

    adjustScrollBar(scrollArea->horizontalScrollBar(), factor);
    adjustScrollBar(scrollArea->verticalScrollBar(), factor);

    zoomInAct->setEnabled(scaleFactor < 3.0);
    zoomOutAct->setEnabled(scaleFactor > 0.333);
}

void ImageViewer::adjustScrollBar(QScrollBar *scrollBar, double factor)
{
    scrollBar->setValue(int(factor * scrollBar->value()
                            + ((factor - 1) * scrollBar->pageStep()/2)));
}

void ImageViewer::open()
{
    QString fileName = QFileDialog::getOpenFileName(this,
                                     tr("Open File"), QDir::currentPath());
    if (!fileName.isEmpty()) {
         QImage image(fileName);
         if (image.isNull()) {
             QMessageBox::information(this, tr("Image Viewer"),
                                      tr("Cannot load %1.").arg(fileName));
             return;
         }
         imageLabel->setPixmap(QPixmap::fromImage(image));
         scaleFactor = 1.0;

         printAct->setEnabled(true);
         fitToWindowAct->setEnabled(true);
         updateActions();

         if (!fitToWindowAct->isChecked())
             imageLabel->adjustSize();
    }
}

void ImageViewer::print()
{
    Q_ASSERT(imageLabel->pixmap());
#ifndef QT_NO_PRINTER
    QPrintDialog dialog(&printer, this);
    if (dialog.exec()) {
        QPainter painter(&printer);
        QRect rect = painter.viewport();
        QSize size = imageLabel->pixmap()->size();
        size.scale(rect.size(), Qt::KeepAspectRatio);
        painter.setViewport(rect.x(), rect.y(), size.width(), size.height());
        painter.setWindow(imageLabel->pixmap()->rect());
        painter.drawPixmap(0, 0, *imageLabel->pixmap());
    }
#endif
}

void ImageViewer::zoomIn()
{
    scaleImage(1.25);
}

void ImageViewer::zoomOut()
{
    scaleImage(0.8);
}

void ImageViewer::normalSize()
{
    imageLabel->adjustSize();
    scaleFactor = 1.0;
}

void ImageViewer::fitToWindow()
{
    bool fitToWindow = fitToWindowAct->isChecked();
    scrollArea->setWidgetResizable(fitToWindow);
    if (!fitToWindow) {
        normalSize();
    }
    updateActions();
}
void ImageViewer::about()
{
    QMessageBox::about(this, tr("About Image Viewer"),
            tr("<b>Image Viewer</b> example."));
}
分类: C++标签: