使用QT 制作一个简单的截图标记的工具

该文章介绍了一个使用QT编写的工具,该工具允许用户在全屏无边框窗口中对图片进行标记,包括添加文本、线条、矩形等,并能进行截图保存。通过鼠标右键菜单控制功能,用户可以选择不同的标注类型和颜色线宽,以及进行区域截图。实现过程中涉及到了窗口透明性设置、鼠标事件处理和自定义菜单等功能。
摘要由CSDN通过智能技术生成


前言

使用QT制作了一个对图片进行标记,并可截图保存的小工具。


提示:以下是本篇文章正文内容,

一、对功能实现的简单介绍:

1,主窗口设置为:全屏,透明无边框,所有功能的入口都设置在鼠标右键菜单中

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    this->setWindowFlags(Qt::FramelessWindowHint);//设置窗口无边框
    this->setAttribute(Qt::WA_TranslucentBackground);//设置窗口背景透明,注意,需要配合paintEvent函数使用(透明度设为1)
    this->showFullScreen();//设置全屏显示
    //设置上下文菜单命令的响应策略为默认策略,此时点击菜单会自动响应QT已定义好的事件:只需重写contextMenuEvent事件
    this->setContextMenuPolicy(Qt::DefaultContextMenu);
}

2,鼠标右键菜单的实现:

void Widget::contextMenuEvent(QContextMenuEvent *event)
{
    Q_UNUSED(event);
    QMenu *MainMenu=new QMenu(this);//----创建主菜单
   // QAction *pShot=new QAction("截屏",MainMenu);
    QAction *pSave=new QAction("保存",MainMenu);
    connect(pSave,&QAction::triggered,this,&Widget::onSave);
    QAction *pRemark=new QAction("标注",MainMenu);

    QAction *pCancel=new QAction("取消",MainMenu);
    connect(pCancel,&QAction::triggered,this,&Widget::onCancel);
    QAction *pQuit=new QAction("退出",MainMenu);
    connect(pQuit,&QAction::triggered,this,&Widget::close);

    QList <QAction *> actionList;
    actionList<<pSave<<pRemark<<pCancel<<pQuit;
    MainMenu->addActions(actionList);   //---添加子项到主菜单


    QMenu *menuChild=new QMenu();       //---创建子菜单
    QAction *pText=new QAction("文字",menuChild);//---创建子菜单的菜单子项
    connect(pText,&QAction::triggered,this,[this](){
        curType=Text;

    });
    QAction *pLine=new QAction("直线",menuChild);
    connect(pLine,&QAction::triggered,this,[this](){
        curType=Line;
    });
    QAction *pRect=new QAction("矩形",menuChild);
    connect(pRect,&QAction::triggered,this,[this](){
        curType=Rect;
    });
    QAction *pArrow=new QAction("箭头",menuChild);
    connect(pArrow,&QAction::triggered,this,[this](){
        curType=Arrow;
    });
    QAction *pEllipse=new QAction("椭圆",menuChild);
    connect(pEllipse,&QAction::triggered,this,[this](){
        curType=Ellipse;
    });
    QAction *pPixels=new QAction("手划线",menuChild);
    connect(pPixels,&QAction::triggered,this,[this](){
        curType=Pixels;
    });
    QAction *pColor=new QAction("颜色",menuChild);
    connect(pColor,&QAction::triggered,this,[this](){
        QColor color=QColorDialog::getColor(Qt::red,this,"选择颜色");
        if(color.isValid())
        curColor=color;
        update();
    });
    QAction *pWidth=new QAction("线宽",menuChild);
    connect(pWidth,&QAction::triggered,this,[this](){
        int width=QInputDialog::getInt(this,"线宽选择对话框","线宽:");
        if(width>0&&width<20)
        curWidth=width;
        curFontSize=width*2+30;
        update();
    });
    QList <QAction *> actionListOfChild;
    actionListOfChild<<pText<<pLine<<pRect<<pArrow<<pEllipse<<pPixels<<pColor<<pWidth;
    menuChild->addActions(actionListOfChild);//添加子项到子菜单

    pRemark->setMenu(menuChild);//设置子菜单归属pRemark
    MainMenu->addMenu(menuChild);//主菜单中添加子菜单
    MainMenu->exec(QCursor::pos());//移动到当前光标下显示
    

 3,标注主要有:添加文本,线段,方框,椭圆等简单形状,并可设置颜色等参数,都是极简单的QT入门函数用,这里就略过,只有在绘制箭头时用过直线的水平夹角来旋转绘图坐标,这个旋转角度的获取在鼠标释放时计算,但在后面使用时方向是反的,不注意的话箭头画出来是错乱的代码如下:

3-1,在释放鼠标时计算箭头的角度:

 QLineF line(startPoint,endPoint);//---两点连线,
            curangle=line.angle();
            curItem->item_angle=curangle;

3-2,重绘窗口时画出箭头末端的两根小短线:

              p.save();
             p.translate(vector_items.at(i)->endPoint);//---先把原来的坐标保存,后移动坐标原点到endPoint
             p.rotate(-vector_items.at(i)->item_angle);//---旋转坐标,让直线和X轴重合
             p.drawLine(-15,-10,0,0);
             p.drawLine(-15,10,0,0);
             p.restore();

 4,截图的实现代码:使用了QT 的消息循环,在右键菜单保存时启动消息循环,程序暂停在此处,等待鼠标给出区域数据后,退出消息循环,实现截图的区域范围数据

else if(btn==QMessageBox::No){
        isBoundary=true;
        loop.exec();
        pix=QApplication::primaryScreen()->grabWindow().copy(QRect(startPoint,endPoint));
    }
    QString filename=QFileDialog::getSaveFileName(this,"保存文件","D:/","*.png *.jpg");
    pix.save(filename);

if(isBoundary){    //----区域截屏保存时等待鼠标拖拽出需截屏的区域时的标记
        isBoundary=false;//----先把标记复位,以供下次使用
        endPoint=event->pos();
        vector_pixels.clear();//-----本次产生的数据不能做为图型的生成数据使用所以要清空
        loop.quit();        //  -----完成数据准备后把调用事件循环等待的地方退出循环
        return;

二  完整代码:

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QEventLoop>
class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
    enum Item_Type{None,Text,Line,Rect,Ellipse,Arrow,Pixels};
    struct  item        //保存已绘制完成的形状的数据
    {
       QPoint startPoint,endPoint;
       Item_Type item_type;
       QColor item_color;
       int item_width;
       QString item_string="";
       int item_fontsize=30;
       qreal item_angle=0;
       QVector <QPoint> item_vectorOfpoints;
    };
    void onSave();
    void onCancel();

private:                //-----保存当前状态和绘制形状的数据
    item *curItem;
    QVector <item*> vector_items;//保存所有已画完的图形的数据
    QVector <QPoint> vector_pixels;
    Item_Type curType;
    QPoint startPoint;
    QPoint endPoint;
    QColor curColor=Qt::red;
    int curWidth=3;
    int curFontSize=30;
    QString curString;
    qreal curangle=0;

    QEventLoop loop;
    bool isBoundary=false;
private:
    void paint(QPainter &p);    //---鼠标移动过程中产生的形状
    void paintItems(QPainter &p);//---重绘时把以前的图形复制到当前窗口
    // QWidget interface
protected:
    void mousePressEvent(QMouseEvent *event) override;
    void mouseReleaseEvent(QMouseEvent *event) override;
    void mouseMoveEvent(QMouseEvent *event) override;
    void paintEvent(QPaintEvent *event) override;
    void contextMenuEvent(QContextMenuEvent *event) override;
};
#endif // WIDGET_H

#include "widget.h"
#include <QMenu>
#include <QAction>
#include <QPainter>
#include <QMouseEvent>
#include <QFileDialog>
#include <QInputDialog>
#include <QColorDialog>
#include <QMessageBox>
#include <QApplication>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    this->setWindowFlags(Qt::FramelessWindowHint);//设置窗口无边框
    this->setAttribute(Qt::WA_TranslucentBackground);//设置窗口背景透明,注意,需要配合paintEvent函数使用(透明度设为1)
    this->showFullScreen();//设置全屏显示
    //设置上下文菜单命令的响应策略为默认策略,此时点击菜单会自动响应QT已定义好的事件:只需重写contextMenuEvent事件
    this->setContextMenuPolicy(Qt::DefaultContextMenu);
}

Widget::~Widget()
{
}

void Widget::onSave()
{
    QMessageBox::Button btn=QMessageBox::question(this,"保存文件范围?",
        "全屏保存--->点击YES\n 保存选区--->点击按钮NO,然后用鼠标托选区域",QMessageBox::Yes|QMessageBox::No);
    QPixmap pix;
    if(btn==QMessageBox::Yes){
        pix=QApplication::primaryScreen()->grabWindow();
    }
    else if(btn==QMessageBox::No){
        isBoundary=true;
        loop.exec();
        pix=QApplication::primaryScreen()->grabWindow().copy(QRect(startPoint,endPoint));
    }
    QString filename=QFileDialog::getSaveFileName(this,"保存文件","D:/","*.png *.jpg");
    pix.save(filename);
}

void Widget::onCancel()
{
    if(vector_items.isEmpty()){ return;}
    vector_items.removeLast();
    update();
}

void Widget::mousePressEvent(QMouseEvent *event)
{
    if(event->button()==Qt::LeftButton && curType!=None){
        startPoint=event->pos();

        if(curType==Pixels) vector_pixels<<startPoint;

    }
}

void Widget::mouseReleaseEvent(QMouseEvent *event)
{    if(isBoundary){    //----区域截屏保存时等待鼠标拖拽出需截屏的区域时的标记
        isBoundary=false;//----先把标记复位,以供下次使用
        endPoint=event->pos();
        vector_pixels.clear();//-----本次产生的数据不能做为图型的生成数据使用所以要清空
        loop.quit();        //  -----完成数据准备后把调用事件循环等待的地方退出循环
        return;
    }
    if(event->button()==Qt::LeftButton && curType!=None){
        endPoint=event->pos();
            qreal dist=(startPoint.x()-endPoint.x())*(startPoint.x()-endPoint.x())
                    +(startPoint.y()-endPoint.y())*(startPoint.y()-endPoint.y());
            if(dist<400&&curType!=Text){
                endPoint=startPoint;return;//处理按下同一点时出现的形状抛弃不要
            }
            //----通用数据赋值
        curItem=new item;//---先创建一个形状对象用以保存当前需要绘制的形状的数据
        curItem->item_type=curType;
        curItem->startPoint=startPoint;
        curItem->endPoint=endPoint;
        curItem->item_color=curColor;
        curItem->item_width=curWidth;

        if(curType==Arrow){                 //---箭头形状需要计算直线的水平夹角,用以旋转绘图系统坐标,方便绘制箭头的两根短线
            QLineF line(startPoint,endPoint);//---两点连线,
            curangle=line.angle();
            curItem->item_angle=curangle;
        }
        if(curType==Pixels){            //----鼠标手动画线时,需要一个容器把鼠标划过的所有点都放进去
            vector_pixels<<endPoint;
            curItem->item_vectorOfpoints=vector_pixels;//---同时也要更新当前形状的参数
        }
        if(curType==Text){//-----后面有个鼠标按下和抬起时的距离的判断,距离太短时抛弃掉数据,
            bool ok=false;//默认值
            QString str=QInputDialog::getText(this,"输入文字","标注文字:",
                                              QLineEdit::Normal,"",&ok);//""此处为在编辑框里固定显示的字符串
            if(ok && !str.isEmpty())  curString=str;
            curItem->item_fontsize=curFontSize;
            curItem->item_string=curString;
        }
        vector_items.append(curItem);

        endPoint=startPoint;//放置上次遗留的信息被自动利用来绘制形状//此处如果不处理会出现最后一次画的形状清除不掉的现象
        vector_pixels.clear();//清空上次鼠标按下以来的的点集合
        update();//----------需要及时调用重绘,否则显示和数据不同步
    }
}

void Widget::mouseMoveEvent(QMouseEvent *event)
{

    if(event->buttons()&Qt::LeftButton && curType!=None){
        endPoint=event->pos();
        if(curType==Pixels)vector_pixels<<endPoint;
        update();
    }
}

void Widget::paint(QPainter &p)     //---绘制当前形状
{
    if(isBoundary) {
        p.drawRect(QRect(startPoint,endPoint));
        return;
    }
    QPen pen;
    pen.setColor(curColor); pen.setWidth(curWidth);
    p.setPen(pen);
    switch (curType) {
    case Text:{
        QFont font;font.setPointSize(curFontSize);
        p.setFont(font);
        p.drawText(startPoint,curString);
        curString.clear();//画完之后就清空,以防鼠标左键按下后再次绘制
    }break;
    case Ellipse:{
        p.drawEllipse(QRect(startPoint,endPoint));
    }break;
    case Rect:{
        p.drawRect(QRect(startPoint,endPoint));
    }break;
    case Line:
    {
        p.drawLine(startPoint,endPoint);
    }break;
    case Arrow:{
       p.drawLine(startPoint,endPoint);     //----在鼠标拖动过程中不必画终点的两根短线,鼠标释放后再画
    }break;
    case Pixels:{
         p.drawPoints(vector_pixels);       //----同时画多个点的函数

    }break;
    default:
        break;
    }
}
void Widget::paintItems(QPainter &p)    //----把以前画的形状再重新复原到窗口上
{
    QPen pen;
    for(int i=0;i<vector_items.size();++i){
        pen.setColor(vector_items.at(i)->item_color);
        pen.setWidth(vector_items.at(i)->item_width);
        p.setPen(pen);
        switch (vector_items.at(i)->item_type) {
        case Ellipse:{
            p.drawEllipse(QRect(vector_items.at(i)->startPoint,vector_items.at(i)->endPoint));
        }break;
        case Rect:{
            p.drawRect(QRect(vector_items.at(i)->startPoint,vector_items.at(i)->endPoint));
        }break;
        case Line:{
            p.drawLine(vector_items.at(i)->startPoint,vector_items.at(i)->endPoint);
        }break;
        case Arrow:{                                    //---Arrow 形状中保存的是直线的水平角度,并不是两根小短线的坐标
            p.drawLine(vector_items.at(i)->startPoint,vector_items.at(i)->endPoint);
             p.save();
             p.translate(vector_items.at(i)->endPoint);//---先把原来的坐标保存,后移动坐标原点到endPoint
             p.rotate(-vector_items.at(i)->item_angle);//---旋转坐标,让直线和X轴重合
             p.drawLine(-15,-10,0,0);
             p.drawLine(-15,10,0,0);
             p.restore();
        }break;
        case Pixels:{
            p.drawPoints(vector_items.at(i)->item_vectorOfpoints);
        }break;
        case Text:{
            QFont font;font.setPointSize(vector_items.at(i)->item_fontsize);
            p.setFont(font);
            p.drawText(vector_items.at(i)->startPoint,vector_items.at(i)->item_string);
        }break;
        default:
            break;
        }
    }
}
void Widget::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event);
    QPainter painter(this);
    painter.setPen(curColor);
    painter.fillRect(this->rect(),QColor(0,0,0,1));//在完全透明的窗口上填充一个透明度为1的矩形,否则鼠标完全穿透到底层,无法绘制
    paint(painter);
    paintItems(painter);
    //endPoint=startPoint;//放置上次遗留的信息被自动利用来绘制形状//此处如果不处理会出现最后一次画的形状清除不掉的现象
}

void Widget::contextMenuEvent(QContextMenuEvent *event)
{
    Q_UNUSED(event);
    QMenu *MainMenu=new QMenu(this);//----创建主菜单
   // QAction *pShot=new QAction("截屏",MainMenu);
    QAction *pSave=new QAction("保存",MainMenu);
    connect(pSave,&QAction::triggered,this,&Widget::onSave);
    QAction *pRemark=new QAction("标注",MainMenu);

    QAction *pCancel=new QAction("取消",MainMenu);
    connect(pCancel,&QAction::triggered,this,&Widget::onCancel);
    QAction *pQuit=new QAction("退出",MainMenu);
    connect(pQuit,&QAction::triggered,this,&Widget::close);

    QList <QAction *> actionList;
    actionList<<pSave<<pRemark<<pCancel<<pQuit;
    MainMenu->addActions(actionList);   //---添加子项到主菜单


    QMenu *menuChild=new QMenu();       //---创建子菜单
    QAction *pText=new QAction("文字",menuChild);//---创建子菜单的菜单子项
    connect(pText,&QAction::triggered,this,[this](){
        curType=Text;

    });
    QAction *pLine=new QAction("直线",menuChild);
    connect(pLine,&QAction::triggered,this,[this](){
        curType=Line;
    });
    QAction *pRect=new QAction("矩形",menuChild);
    connect(pRect,&QAction::triggered,this,[this](){
        curType=Rect;
    });
    QAction *pArrow=new QAction("箭头",menuChild);
    connect(pArrow,&QAction::triggered,this,[this](){
        curType=Arrow;
    });
    QAction *pEllipse=new QAction("椭圆",menuChild);
    connect(pEllipse,&QAction::triggered,this,[this](){
        curType=Ellipse;
    });
    QAction *pPixels=new QAction("手划线",menuChild);
    connect(pPixels,&QAction::triggered,this,[this](){
        curType=Pixels;
    });
    QAction *pColor=new QAction("颜色",menuChild);
    connect(pColor,&QAction::triggered,this,[this](){
        QColor color=QColorDialog::getColor(Qt::red,this,"选择颜色");
        if(color.isValid())
        curColor=color;
        update();
    });
    QAction *pWidth=new QAction("线宽",menuChild);
    connect(pWidth,&QAction::triggered,this,[this](){
        int width=QInputDialog::getInt(this,"线宽选择对话框","线宽:");
        if(width>0&&width<20)
        curWidth=width;
        curFontSize=width*2+30;
        update();
    });
    QList <QAction *> actionListOfChild;
    actionListOfChild<<pText<<pLine<<pRect<<pArrow<<pEllipse<<pPixels<<pColor<<pWidth;
    menuChild->addActions(actionListOfChild);//添加子项到子菜单

    pRemark->setMenu(menuChild);//设置子菜单归属pRemark
    MainMenu->addMenu(menuChild);//主菜单中添加子菜单
    MainMenu->exec(QCursor::pos());//移动到当前光标下显示
    //MainMenu->move(QCursor::pos());
    //MainMenu->show();
}

总结
 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值