The Screenshot example shows how to take a screenshot of the desktop.
With the application the users can take a screenshot of their desktop. They are provided with a couple of options:
In addition the application allows the users to save their screenshot if they want to.
class Screenshot : public QWidget { Q_OBJECT public: Screenshot(); protected: void resizeEvent(QResizeEvent *event) override; private slots: void newScreenshot(); void saveScreenshot(); void shootScreen(); void updateCheckBox(); private: void updateScreenshotLabel(); QPixmap originalPixmap; QLabel *screenshotLabel; QSpinBox *delaySpinBox; QCheckBox *hideThisWindowCheckBox; QPushButton *newScreenshotButton; };
Screenshot
类继承
QWidget
and is the application's main widget. It displays the application options and a preview of the screenshot.
重实现 QWidget::resizeEvent () function to make sure that the preview of the screenshot scales properly when the user resizes the application widget. We also need several private slots to facilitate the options:
newScreenshot()
slot prepares a new screenshot.
saveScreenshot()
slot saves the last screenshot.
shootScreen()
slot takes the screenshot.
updateCheckBox()
slot enables or disables the
Hide This Window
选项。
We also declare the private function
updateScreenshotLabel()
which is called whenever a new screenshot is taken or when a resize event changes the size of the screenshot preview label.
In addition we need to store the screenshot's original pixmap. The reason is that when we display the preview of the screenshot, we need to scale its pixmap, storing the original we make sure that no data are lost in that process.
Screenshot::Screenshot() : screenshotLabel(new QLabel(this)) { screenshotLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); screenshotLabel->setAlignment(Qt::AlignCenter); const QRect screenGeometry = screen()->geometry(); screenshotLabel->setMinimumSize(screenGeometry.width() / 8, screenGeometry.height() / 8); QVBoxLayout *mainLayout = new QVBoxLayout(this); mainLayout->addWidget(screenshotLabel); QGroupBox *optionsGroupBox = new QGroupBox(tr("Options"), this); delaySpinBox = new QSpinBox(optionsGroupBox); delaySpinBox->setSuffix(tr(" s")); delaySpinBox->setMaximum(60); connect(delaySpinBox, &QSpinBox::valueChanged, this, &Screenshot::updateCheckBox); hideThisWindowCheckBox = new QCheckBox(tr("Hide This Window"), optionsGroupBox); QGridLayout *optionsGroupBoxLayout = new QGridLayout(optionsGroupBox); optionsGroupBoxLayout->addWidget(new QLabel(tr("Screenshot Delay:"), this), 0, 0); optionsGroupBoxLayout->addWidget(delaySpinBox, 0, 1); optionsGroupBoxLayout->addWidget(hideThisWindowCheckBox, 1, 0, 1, 2); mainLayout->addWidget(optionsGroupBox); QHBoxLayout *buttonsLayout = new QHBoxLayout; newScreenshotButton = new QPushButton(tr("New Screenshot"), this); connect(newScreenshotButton, &QPushButton::clicked, this, &Screenshot::newScreenshot); buttonsLayout->addWidget(newScreenshotButton); QPushButton *saveScreenshotButton = new QPushButton(tr("Save Screenshot"), this); connect(saveScreenshotButton, &QPushButton::clicked, this, &Screenshot::saveScreenshot); buttonsLayout->addWidget(saveScreenshotButton); QPushButton *quitScreenshotButton = new QPushButton(tr("Quit"), this); quitScreenshotButton->setShortcut(Qt::CTRL | Qt::Key_Q); connect(quitScreenshotButton, &QPushButton::clicked, this, &QWidget::close); buttonsLayout->addWidget(quitScreenshotButton); buttonsLayout->addStretch(); mainLayout->addLayout(buttonsLayout); shootScreen(); delaySpinBox->setValue(5); setWindowTitle(tr("Screenshot")); resize(300, 200); }
In the constructor we first create the QLabel displaying the screenshot preview.
We set the
QLabel
's size policy to be
QSizePolicy::Expanding
both horizontally and vertically. This means that the
QLabel
's size hint is a sensible size, but the widget can be shrunk and still be useful. Also, the widget can make use of extra space, so it should get as much space as possible. Then we make sure the
QLabel
is aligned in the center of the
Screenshot
widget, and set its minimum size.
Next, we create a group box that will contain all of the options' widgets. Then we create a
QSpinBox
和
QLabel
为
Screenshot Delay
option, and connect the spinbox to the
updateCheckBox()
slot. Finally, we create a
QCheckBox
为
Hide This Window
option, add all the options' widgets to a
QGridLayout
installed on the group box.
We create the applications's buttons and the group box containing the application's options, and put it all into a main layout. Finally we take the initial screenshot, and set the initial delay and the window title, before we resize the widget to a suitable size depending on the screen geometry.
void Screenshot::resizeEvent(QResizeEvent * /* event */) { QSize scaledSize = originalPixmap.size(); scaledSize.scale(screenshotLabel->size(), Qt::KeepAspectRatio); if (scaledSize != screenshotLabel->pixmap(Qt::ReturnByValue).size()) updateScreenshotLabel(); }
resizeEvent()
function is reimplemented to receive the resize events dispatched to the widget. The purpose is to scale the preview screenshot pixmap without deformation of its content, and also make sure that the application can be resized smoothly.
To achieve the first goal, we scale the screenshot pixmap using Qt::KeepAspectRatio . We scale the pixmap to a rectangle as large as possible inside the current size of the screenshot preview label, preserving the aspect ratio. This means that if the user resizes the application window in only one direction, the preview screenshot keeps the same size.
To reach our second goal, we make sure that the preview screenshot only is repainted (using the private
updateScreenshotLabel()
function) when it actually changes its size.
void Screenshot::newScreenshot() { if (hideThisWindowCheckBox->isChecked()) hide(); newScreenshotButton->setDisabled(true); QTimer::singleShot(delaySpinBox->value() * 1000, this, &Screenshot::shootScreen); }
私有
newScreenshot()
slot is called when the user requests a new screenshot; but the slot only prepares a new screenshot.
First we see if the
Hide This Window
option is checked, if it is we hide the
Screenshot
widget. Then we disable the
New Screenshot
button, to make sure the user only can request one screenshot at a time.
We create a timer using the
QTimer
class which provides repetitive and single-shot timers. We set the timer to time out only once, using the static
QTimer::singleShot
() function. This function calls the private
shootScreen()
slot after the time interval specified by the
Screenshot Delay
option. It is
shootScreen()
that actually performs the screenshot.
void Screenshot::saveScreenshot() { const QString format = "png"; QString initialPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); if (initialPath.isEmpty()) initialPath = QDir::currentPath(); initialPath += tr("/untitled.") + format; QFileDialog fileDialog(this, tr("Save As"), initialPath); fileDialog.setAcceptMode(QFileDialog::AcceptSave); fileDialog.setFileMode(QFileDialog::AnyFile); fileDialog.setDirectory(initialPath); QStringList mimeTypes; const QList<QByteArray> baMimeTypes = QImageWriter::supportedMimeTypes(); for (const QByteArray &bf : baMimeTypes) mimeTypes.append(QLatin1String(bf)); fileDialog.setMimeTypeFilters(mimeTypes); fileDialog.selectMimeTypeFilter("image/" + format); fileDialog.setDefaultSuffix(format); if (fileDialog.exec() != QDialog::Accepted) return; const QString fileName = fileDialog.selectedFiles().first(); if (!originalPixmap.save(fileName)) { QMessageBox::warning(this, tr("Save Error"), tr("The image could not be saved to \"%1\".") .arg(QDir::toNativeSeparators(fileName))); } }
saveScreenshot()
slot is called when the user push the
Save
button, and it presents a file dialog using the
QFileDialog
类。
QFileDialog enables a user to traverse the file system in order to select one or many files or a directory. The easiest way to create a QFileDialog is to use the convenience static functions. Here, we instantiate the dialog on the stack in order to be able to set up the supported mime types of QImageWriter , allowing the user to save in a variety of formats.
We define the default file format to be png, and we make the file dialog's initial path the location of pictures as obtained from QStandardPaths , defaulting to the path the application is run from.
We run the dialog by invoking QDialog::exec () and return if the user canceled the dialog. If the dialog has been accepted, we obtain a file name by calling QFileDialog::selectedFiles (). The file does not have to exist. If the file name is valid, we use the QPixmap::save () function to save the screenshot's original pixmap in that file.
void Screenshot::shootScreen() { QScreen *screen = QGuiApplication::primaryScreen(); if (const QWindow *window = windowHandle()) screen = window->screen(); if (!screen) return; if (delaySpinBox->value() != 0) QApplication::beep(); originalPixmap = screen->grabWindow(0); updateScreenshotLabel(); newScreenshotButton->setDisabled(false); if (hideThisWindowCheckBox->isChecked()) show(); }
shootScreen()
slot is called to take the screenshot.
First, we find the instance of QScreen the window is located by retrieving the QWindow 及其 QScreen , defaulting to the primary screen. If no screen can be found, we return. Although this is unlikely to happen, applications should check for null pointers since there might be situations in which no screen is connected.
If the user has chosen to delay the screenshot, we make the application beep when the screenshot is taken using the static QApplication::beep () 函数。
We then take the screenshot using the QScreen::grabWindow () function. The function grabs the contents of the window passed as an argument, makes a pixmap out of it and returns that pixmap. The window id can be obtained with QWidget::winId () 或 QWindow::winId (). Here, however, we just pass 0 as the window id, indicating that we want to grab the entire screen.
We update the screenshot preview label using the private
updateScreenshotLabel()
function. Then we enable the
New Screenshot
button, and finally we make the
Screenshot
widget visible if it was hidden during the screenshot.
void Screenshot::updateCheckBox() { if (delaySpinBox->value() == 0) { hideThisWindowCheckBox->setDisabled(true); hideThisWindowCheckBox->setChecked(false); } else { hideThisWindowCheckBox->setDisabled(false); } }
Hide This Window option is enabled or disabled depending on the delay of the screenshot. If there is no delay, the application window cannot be hidden and the option's checkbox is disabled.
updateCheckBox()
slot is called whenever the user changes the delay using the
Screenshot Delay
选项。
void Screenshot::updateScreenshotLabel() { screenshotLabel->setPixmap(originalPixmap.scaled(screenshotLabel->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); }
私有
updateScreenshotLabel()
function is called whenever the screenshot changes, or when a resize event changes the size of the screenshot preview label. It updates the screenshot preview's label using the
QLabel::setPixmap
() 和
QPixmap::scaled
() 函数。
QPixmap::scaled () returns a copy of the given pixmap scaled to a rectangle of the given size according to the given Qt::AspectRatioMode and Qt::TransformationMode .
We scale the original pixmap to fit the current screenshot label's size, preserving the aspect ratio and giving the resulting pixmap smoothed edges.