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