通过该应用程序,用户可以对桌面进行截图。用户可以选择
延迟截屏,让用户有时间重新整理桌面。
在截屏时隐藏应用程序窗口。
此外,应用程序还允许用户保存截图。
截图类定义
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() 插槽启用或禁用Hide This Window 选项。
我们还声明了私有函数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 widget 的中心对齐,并设置其最小尺寸。
接下来,我们创建一个包含所有选项部件的组框。然后为Screenshot Delay 选项创建QSpinBox 和QLabel ,并将自旋框连接到updateCheckBox() 插槽。最后,我们为Hide This Window 选项创建一个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() 插槽会被调用,但该插槽只准备新的屏幕截图。
首先,我们要查看Hide This Window 选项是否被选中,如果被选中,我们就会隐藏Screenshot 部件。然后禁用New Screenshot 按钮,确保用户一次只能请求一张截图。
我们使用QTimer 类创建一个计时器,该类提供重复和单次截图计时器。我们使用静态QTimer::singleShot() 函数将计时器设置为只超时一次。该函数在Screenshot Delay 选项指定的时间间隔后调用私有的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
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)));
}
}
当用户按下Save 按钮时,会调用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() 函数更新屏幕截图预览标签。然后,我们启用New Screenshot 按钮,最后,如果Screenshot 部件在截图过程中被隐藏,我们将使其可见。
void Screenshot::updateCheckBox()
{
if (delaySpinBox->value() == 0) {
hideThisWindowCheckBox->setDisabled(true);
hideThisWindowCheckBox->setChecked(false);
} else {
hideThisWindowCheckBox->setDisabled(false);
}
}
Hide This Window 选项的启用或禁用取决于截图的延迟时间。如果没有延迟,应用程序窗口就无法隐藏,该选项的复选框也会被禁用。
每当用户使用Screenshot Delay 选项更改延迟时,就会调用updateCheckBox() 槽。
void Screenshot::updateScreenshotLabel()
{
screenshotLabel->setPixmap(originalPixmap.scaled(screenshotLabel->size(),
Qt::KeepAspectRatio,
Qt::SmoothTransformation));
}
每当截图发生变化,或调整大小事件改变截图预览标签的大小时,就会调用updateScreenshotLabel() 私有函数。它使用QLabel::setPixmap() 和QPixmap::scaled() 函数更新截图预览的标签。
QPixmap::scaled() 返回给定像素图的副本,并根据给定的Qt::AspectRatioMode 和Qt::TransformationMode 将其缩放为给定大小的矩形。
我们缩放原始像素图以适应当前截图标签的大小,同时保留长宽比,并使生成的像素图边缘平滑。
示例项目 @ code.qt.io