截图软件01

写在前面:

因为一直使用的是系统自带的截图工具,感觉使用的总是不舒服,正好想到可以使用Qt制作一个适合自己使用的截图工具,学习和使用一举两得。
不过理想和现实总是有差距的,第一步总是比较缓慢的但是是值得的。
首先我将学习Qt的一个截图样例程序Screenshot Example。

代码:

#include <QtWidgets>

#include "screenshot.h"

//! [0]
Screenshot::Screenshot()
    :  screenshotLabel(new QLabel(this))
{
    screenshotLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    screenshotLabel->setAlignment(Qt::AlignCenter);

    const QRect screenGeometry = QApplication::desktop()->screenGeometry(this);
    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);

    typedef void (QSpinBox::*QSpinBoxIntSignal)(int);
    connect(delaySpinBox, static_cast<QSpinBoxIntSignal>(&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);
}
//! [0]

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

//! [2]
void Screenshot::newScreenshot()
{
    if (hideThisWindowCheckBox->isChecked())
        hide();
    newScreenshotButton->setDisabled(true);

    QTimer::singleShot(delaySpinBox->value() * 1000, this, &Screenshot::shootScreen);
}
//! [2]

//! [3]
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;
    foreach (const QByteArray &bf, QImageWriter::supportedMimeTypes())
        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)));
    }
}
//! [3]

//! [4]
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();
}
//! [4]

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


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

这个截图软件实现了三个功能:

  1. 可以延时截图
  2. 可以在截图中隐藏截图软件
  3. 可以保存截图

Screenshot Class Definition 截图类的定义:


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

override:

引入了虚函数描述符override,如果派生类在虚函数声明时使用了override描述符,那么该函数必须重载其基类中的同名函数,否则代码将无法通过编译。
下面这篇文章说的很清楚:https://www.cnblogs.com/xinxue/p/5471708.html

We reimplement the 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:
我们重新实现QWidget :: resizeEvent()函数,以确保在用户调整应用程序窗口小部件时,屏幕截图的预览可以正确缩放。 我们还需要几个专用插槽来方便选项:
The newScreenshot() slot prepares a new screenshot.
The saveScreenshot() slot saves the last screenshot.
The shootScreen() slot takes the screenshot.
The updateCheckBox() slot enables or disables the Hide This Window option.
newScreenshot()插槽准备一个新的屏幕截图。
saveScreenshot()槽保存上一个屏幕截图。
shootScreen()插槽获取屏幕截图。
updateCheckBox()插槽启用或禁用“隐藏此窗口”选项。
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.
我们还声明了一个私有函数updateScreenshotLabel(),每当一个新的屏幕截图或resize事件改变屏幕截图预览标签的大小时被调用。
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构造函数的实现:

首先是构造函数:
第一步是初始化显示截图预览的标签,并设置相应的属性(方法)。
对于布局的问题:
在构造函数里:

 QVBoxLayout *mainLayout = new QVBoxLayout(this);

这里任何布局的构造过程中,如果是显示的带参数版本的构造函数,这个参数就是当前布局作用的顶层布局相当于以下的代码:

QVBoxLayout *mainLayout = new QVBoxLayout;
。。。。
this->setLayout(mainLayout);

调用布局的无参构造函数,则构建的是一个没有被加载使用的布局 必须将其加到某个布局当中。
同时注意这些布局类型的析构函数,其内部的部件类都不会被析构,只有布局对象会被析构。
布局管理器类不是一个窗口部件他们派生自QLayout类进一步派生自QObject类,在一个运行程序当中,布局是不可见的。当将子布局对象加载到父布局对象当中时子布局的对象就会自动的重新定义自己的父对象,也就是说将主布局装到对话框当中去,那么对话框就会成为主布局的父对象,同时对话框也会成为所有主布局当中的对象的父对象。
所有一开始并不需要给主布局当中的对象添加父对象的说明。

QGroupBox

这里写图片描述
接下来创建一个组合框 包含所有的选项按钮等等:
组合框提供一个名字 内部是子对象 提供热键进行切换

QGroupBox *optionsGroupBox = new QGroupBox(tr("Options"), this);
//这里调用两个参数的构造函数 创建一个指定名称的组合框 父对象为当前的对象

QSpinBox

旋转框部件类
QSpinBox 提供整型数或者离散数的调节 (e.g., month names); QDoubleSpinBox 提供连续的浮点值的选择。
这里写图片描述

delaySpinBox = new QSpinBox(optionsGroupBox);
delaySpinBox->setSuffix(tr(" s"));//设置显示的后缀 - 单位
delaySpinBox->setMaximum(60);//设置显示的后缀 - 单位

下面这一句话我一开始都没反应过来什么意思,对于typedef必须得总结下
http://blog.sina.com.cn/s/blog_7090a2d1010163ol.html

typedef void (QSpinBox::*QSpinBoxIntSignal)(int);

要理解typedef,只要记住一句话就差不多了,那就是:typedef在语句中所起的作用只不过是把语句原先定义变量的功能变成了定义类型的功能而已。
现在,回过来看上面的这个函数原型 typedef void (QSpinBox::*QSpinBoxIntSignal)(int);,盖住 typedef不看 ,再简单不过,QSpinBoxIntSignal就是一个函数指针,指向的函数接受一个整型参数并返回一个无类型指针 。加上typedef之后QSpinBoxIntSignal就是一种新的类型,就可以像int一样地去用它,不同的是它声明是一种函数指针,这种指针指向的函数接受一个整型参数并返回一个无类型指针 。
总结 就是先将typedef忽略看是个什么 那个这个定义的变量就是我们用于声明同种类型变量的新的自定义类型。

QSpinBox 类有这样的信号:

void  valueChanged(int i)
void  valueChanged(const QString &text)
/*
我们在使用这两个重载的同名信号的时候怎么去区分这两个信号是很重要的,因为如果只是使用函数名的方式去调用那么怎么知道我们链接的是哪个信号?就像之前写的一个简单的画图软件的例子,如果不适用虚函数的方法,基类的指针它调用的同名函数如何才能知道是调用的哪个具体类的对应函数?
当时才去的做法就是做指针的类型转换,将对应的基类指针强制类型转换成对应的派生类的指针,再去调用对应的方法。
这里的问题同样的道理,我们将要调用的信号(函数)取地址构成一个临时的函数指针无名变量,将这个函数指针类型转换为对应我们要调用的函数指针类型,这就是给编译器看的告诉编译器我们调用的信号是哪个信号,将具体的返回值参数信息以函数指针的形式告诉编译器让他知道要选择哪个信号。
如果不告诉编译器具体是以什么方式去调用,那么编译器在遇到多个选择的时候,编译器选择不做任何选择,也就是报错,让程序员选择!
*/

再次提起static_cast这样的类型转换的用法:
https://www.cnblogs.com/QG-whz/p/4509710.html

connect(delaySpinBox, static_cast<QSpinBoxIntSignal>(&QSpinBox::valueChanged),
    this, &Screenshot::updateCheckBox);

这里将旋转框的值的变化的信号插入到&Screenshot::updateCheckBox 这样一个成员函数当中。

QCheckBox

选择框box
这里写图片描述
构建一个隐藏界面的选择栏

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

对应就是下面这张图的布局方式
这里写图片描述
0 0 位置是一个延迟文字提示
0 1位置是前面的旋转框box
1 0 到 1 2 位置是前面的选择框box
整个这个网格布局被装载到前面定义的组合box。

将组合box添加到主布局当中

mainLayout->addWidget(optionsGroupBox);

设置按钮部分的水平布局buttonsLayout
设置几个按钮 并且将几个按钮的按下对的信号插入对应的槽

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()这个函数 也就是截图 什么作用后面再说
接下来设置延迟框的初始值为5
设置当前窗口对象的名称
初始大小为300*200

this->setLayout(mainLayout);
shootScreen();
delaySpinBox->setValue(5);

setWindowTitle(tr("Screenshot"));
resize(300, 200);

总结:

Screenshot()构造函数的作用:
这里写图片描述
完成上面这个界面的所有的部件的布局,
主要的布局形式如下:
这里写图片描述

结构还是非常的清晰的,未来在描述结构框图的时候应多采用这样的形式。
目前用的是一款在线的编辑软件:https://www.processon.com/diagraming/5a22a2abe4b0dce08034be54

目前只是从头到尾理了理构造函数干的事情,知道了怎么搭建出一个这样的桌面应用程序的框架。
还未涉及事件以及文件保存的事情接下来将具体研究剩下的部分。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值