前言
使用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();
}