截取屏幕

截图示例演示了如何截取桌面屏幕。

使用该应用程序,用户可以截取他们的桌面屏幕。他们可以提供一些选项

  • 延迟截图,给他们时间重新排列桌面。
  • 在截取屏幕时隐藏应用程序的窗口。

此外,应用程序允许用户保存他们的截图,如果他们想的话。

截图类定义

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 并且是应用程序的主小部件。它显示应用程序选项和截图预览。

我们重写了 QWidget::resizeEvent() 函数以确保用户调整应用程序小部件大小时,截图预览可以正确缩放。我们还需要几个私有槽来帮助选项

  • newScreenshot() 槽准备一个新的截图。
  • saveScreenshot() 槽保存最后一个截图。
  • shootScreen() 槽进行截图。
  • updateCheckBox() 槽启用或禁用 隐藏此窗口 选项。

我们还声明了私有的 updateScreenshotLabel() 函数,该函数在每次截取新的截图或当调整大小事件更改截图预览标签的大小时调用。

此外,我们需要存储截图的原始图元。原因是,当显示截图的预览时,我们需要缩放其图元,存储原始图元,我们可以确保在此过程中没有数据丢失。

截图类实现

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

在构造函数中,我们首先创建了一个显示截图预览的 QLabel

我们将 QLabel 的大小策略设置为 QSizePolicy::Expanding,水平和垂直方向均扩展。这意味着 QLabel 的大小提示是一个合理的尺寸,但是小部件可以缩小,仍然有用。此外,小部件可以利用额外的空间,因此应该尽可能多地获取空间。然后我们确保 QLabel 对齐在 Screenshot 小部件的中心,并设置其最小大小。

接下来,我们创建一个包含所有选项小部件的组框。然后我们为“屏幕截图延迟”选项创建一个QSpinBox和一个QLabel,并将该spinbox连接到updateCheckBox()槽。最后,我们为“隐藏此窗口”选项创建一个QCheckBox,并将所有选项小部件添加到组框上安装的QGridLayout

我们创建了应用程序的按钮和包含应用程序选项的组框,将所有内容放入主布局中。最后,我们进行初始屏幕截图,设置初始延迟和窗口标题,然后在根据屏幕几何形状调整到适当大小的之前。

void Screenshot::resizeEvent(QResizeEvent * /* event */)
{
    QSize scaledSize = originalPixmap.size();
    scaledSize.scale(screenshotLabel->size(), Qt::KeepAspectRatio);
    if (scaledSize != screenshotLabel->pixmap().size())
        updateScreenshotLabel();
}

重新实现了resizeEvent()函数以接收发送到小部件的尺寸更改事件。目的是不失真地缩放预览屏幕截图位图,并确保应用程序可以平滑地调整大小。

为实现第一个目标,我们使用Qt::KeepAspectRatio缩放屏幕截图位图。我们将其缩放为一个尽可能大的矩形,保持长宽比。这意味着如果用户只在一个方向上调整应用程序窗口的大小,预览屏幕截图将保持相同的大小。

为达成第二个目标,我们确保只有在预览屏幕截图实际改变大小时才会重绘(使用私有的updateScreenshotLabel()函数)。

void Screenshot::newScreenshot()
{
    if (hideThisWindowCheckBox->isChecked())
        hide();
    newScreenshotButton->setDisabled(true);

    QTimer::singleShot(delaySpinBox->value() * 1000, this, &Screenshot::shootScreen);
}

当用户请求新的屏幕截图时,会调用私有的newScreenshot()槽;但槽只准备新的屏幕截图。

首先,我们查看“隐藏此窗口”选项是否选中,如果选中,则隐藏Screenshot小部件。然后我们禁用“新屏幕截图”按钮,以确保用户只能同时请求一个屏幕截图。

我们使用QTimer类创建计时器,该类提供重复和单次计时器。我们使用静态的QTimer::singleShot()函数设置计时器,以单次超时。此函数会在“屏幕截图延迟”选项指定的间隔后调用私有的shootScreen()槽。正是shootScreen()执行屏幕截图。

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()槽,并使用QFileDialog类显示一个文件对话框。

QFileDialog允许用户遍历文件系统以选择一个或多个文件或目录。创建QFileDialog的简单方法是使用便利的静态函数。在此例中,我们为了能够设置QImageWriter的支持的MIME类型而将对话框实例化为栈中的对象,允许用户以各种格式保存。

我们将默认文件格式定义为png,并将文件对话框的初始路径设为通过QStandardPaths获得的图片位置,如果找不到,则默认为应用程序运行的路径。

我们通过调用QDialog::exec()来运行对话框。如果用户取消了对话框,则返回。如果对话框已被接受,则通过调用QFileDialog::selectedFiles()来获取文件名。文件不必存在。如果文件名有效,我们使用QPixmap::save()函数将屏幕截图的原始位图保存到该文件。

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()槽进行屏幕截图。

首先,我们通过检索QWindow及其QScreen来找到QScreen的实例,默认为主屏幕。如果找不到任何屏幕,我们将返回。虽然这种情况不太可能发生,但应用应检查空指针,因为可能存在没有连接到屏幕的情况。

如果用户选择延迟截图,当使用静态QApplication::beep()函数进行截图时,我们会让应用响铃。

然后我们使用QScreen::grabWindow()函数来截图。该函数获取作为参数传入的窗口的内容,将其转换为位图,并返回该位图。窗口ID可以通过QWidget::winId()或QWindow::winId()获得。这里,我们只是传递0作为窗口ID,表示我们要抓取整个屏幕。

我们使用私有函数updateScreenshotLabel()更新截图预览标签。然后我们启用新截图按钮,如果截图期间已隐藏,最后我们将Screenshot小部件设置为可见。

void Screenshot::updateCheckBox()
{
    if (delaySpinBox->value() == 0) {
        hideThisWindowCheckBox->setDisabled(true);
        hideThisWindowCheckBox->setChecked(false);
    } else {
        hideThisWindowCheckBox->setDisabled(false);
    }
}

根据截图延迟设置,启用或禁用隐藏此窗口选项。如果没有延迟,应用窗口无法隐藏,选项复选框将被禁用。

当用户使用截图延迟选项更改延迟时,调用updateCheckBox()槽。

void Screenshot::updateScreenshotLabel()
{
    screenshotLabel->setPixmap(originalPixmap.scaled(screenshotLabel->size(),
                                                     Qt::KeepAspectRatio,
                                                     Qt::SmoothTransformation));
}

私有函数updateScreenshotLabel()在截图更改时或当调整大小事件更改截图预览标签大小时被调用。它使用QLabel::setPixmap()和QPixmap::scaled()函数更新截图预览的标签。

QPixmap::scaled()函数返回一个按给定Qt::AspectRatioModeQt::TransformationMode缩小到给定大小的给定位图的副本。

我们将原始位图按当前截图标签的大小缩放,保持宽高比,并给予结果位图光滑的边缘。

示例项目 @ code.qt.io

© 2024 Qt公司有限公司。此处包含的文档贡献是各自所有者的版权。此处提供的文档是根据自由软件基金会发布的GNU自由文档许可版本1.3的条款许可的。Qt和相应的标志是芬兰及/或其他国家的Qt公司的商标。所有其他商标均为其各自所有者的财产。