【Qt 实现一个画板,基于QWidget,可以绘制直线和矩形】

news2024/11/15 7:19:52

【Qt 实现一个画板,基于QWidget,可以绘制直线和矩形】

  • 简介
  • 效果展示
  • 源码
    • mainwindow.h
    • mainwindow.cpp
    • painterwidget.h
    • painterwidget.cpp
    • shape.h (管理)
    • line.h
    • line.cpp
    • rect.h
    • rect.cpp


🙉🙉更多内容 点击:Qt 专栏 🙉🙉



简介

这里我会只做出一个简单的画板程序,大体上就是能够画直线和矩形吧。这样,我计划分成两种实现,一是使用普通的 QWidget 作为画板,第二则是使用 Graphcis View Framework 来实现。因为前面有朋友说不大明白 Graphics View 的相关内容,所以计划如此。

好了,现在先来看看我们的主体框架。我们的框架还是使用 Qt Creator 创建一个 Gui Application工程。

简单的 main()函数就不再赘述。

效果展示

喜欢的伙伴可以自己扩展
图片由以下软件制作
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

源码

mainwindow.h

Shape::Code newshape 枚举变量 画直线 画矩形
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QActionGroup>
#include <QToolBar>
#include <QStatusBar>
#include <QLabel>
#include "shape.h"
#include "painterwidget.h"

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();


signals:
    void sig_changeCurrentShape(Shape::Code newshape);
private slots:
    void drawLine_ActionTriggered();
    void drawRect_ActionTriggered();
};
#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    QToolBar *bar = this->addToolBar("Tools");	//工具栏提示信息
    QActionGroup *group = new QActionGroup(bar); //创建动作组,工具栏是他爸爸

    QAction *act_line = new QAction("Line",bar)//创建动作组,工具栏是他爸爸
    act_line->setIcon(QIcon(":/png/line.png")); //设置图标
    act_line->setToolTip(tr("Draw a Line"));//设置工具提示信息
    act_line->setStatusTip(tr("Draw a Line"));//状态栏提示信息

    act_line->setCheckable(true);
    act_line->setChecked(true);

    group->addAction(act_line); //添加进组
    bar->addAction(act_line);   

    QAction *act_rect = new QAction("Rect",bar);
    act_rect->setIcon(QIcon(":/png/rect.png"));
    act_rect->setToolTip(tr("Draw a Rect"));
    act_rect->setStatusTip(tr("Draw a Rect"));

    act_rect->setCheckable(true);

    group->addAction(act_rect);
    bar->addAction(act_rect);

    QLabel *status = new QLabel;
    statusBar()->addWidget(status); //状态栏添加标签

    //创建绘画现象
    PainterWidget *painterwidget = new PainterWidget(this);
    this->setCentralWidget(painterwidget); //添加绘画容器

    connect(act_line,&QAction::triggered,this,&MainWindow::drawLine_ActionTriggered);
    connect(act_rect,&QAction::triggered,this,&MainWindow::drawRect_ActionTriggered);

    connect(this,&MainWindow::sig_changeCurrentShape,painterwidget,&PainterWidget::SLOT_setCurrentShape);
}

MainWindow::~MainWindow()
{

}

//发射 画直线
void MainWindow::drawLine_ActionTriggered()
{
    emit this->sig_changeCurrentShape(Shape::Line);
}

//发射 画矩形
void MainWindow::drawRect_ActionTriggered()
{
    emit this->sig_changeCurrentShape(Shape::Rect);
}


我们在 MainWindow 类里面声明了一个信号,changeCurrentShape(Shape::Code),用于按钮按下后通知画图板。注意,QActio 的triggered()信号是没有参数的,因此,我们需要在 QAction 的槽函数中重新 emit 我们自己定义的信号。构造函数里面创建了两个 QAction,一个是 drawLineAction,一个是 drawRectAction,分别用于绘制直线和矩形。MainWindow 的中心组件是 PainWidget,也就是我们的画图板。下面来看看PaintWidget 类:

painterwidget.h

#ifndef PAINTERWIDGET_H
#define PAINTERWIDGET_H

#include <QWidget>
#include "QMouseEvent"

#include "QDebug"
#include "line.h"
#include "shape.h"
#include "rect.h"

class PainterWidget : public QWidget
{
    Q_OBJECT
public:
    explicit PainterWidget(QWidget *parent = nullptr);

public slots:
    void  SLOT_setCurrentShape(Shape::Code enumVal) //区分形状
    {
        if (enumVal != this->curenum__value)
             this->curenum__value = enumVal;
    }
protected:
    void paintEvent(QPaintEvent *event) override;
    void mousePressEvent(QMouseEvent *event) override;
    void mouseMoveEvent(QMouseEvent *event) override;
    void mouseReleaseEvent(QMouseEvent *event) override;

signals:

private:
    Shape::Code curenum__value;
    Shape *shape; //形状指针
    bool perm;
    QList<Shape*>shapeList;  //保存形状
};

#endif // PAINTERWIDGET_H

painterwidget.cpp

设置大小策略:可以拖住放大大小。

 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); //设置策略
#include "paintwidget.h" 

PaintWidget::PaintWidget(QWidget *parent) 
        : QWidget(parent), currShapeCode(Shape::Line), shape(NULL), perm(false) 
{ 
        setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); //设置策略
} 

void PaintWidget::paintEvent(QPaintEvent *event) 
{ 
        QPainter painter(this);  //在本Widget上绘图
        painter.setBrush(Qt::white); //画刷白色
        painter.drawRect(0, 0, size().width(), size().height()); //画最外面的大矩形
        foreach(Shape * shape, shapeList) { 
                shape->paint(painter); 
        } 
        if(shape) { 
                shape->paint(painter); 
        } 
} 

void PaintWidget::mousePressEvent(QMouseEvent *event) 
{ 
        switch(currShapeCode) //选中的是直线还是矩形
        { 
        case Shape::Line: 
                { 
                        shape = new Line;  //父类指针指向 子类对象
                        break; 
                } 
        case Shape::Rect: 
                { 
                        shape = new Rect; 
                        break; 
                } 
        } 
        if(shape != NULL) { 
                perm = false; 
                shapeList<<shape;  //保存形状
                shape->setStart(event->pos()); 
                shape->setEnd(event->pos()); 
        } 
} 

void PaintWidget::mouseMoveEvent(QMouseEvent *event) 
{ 
        if(shape && !perm) { 
                shape->setEnd(event->pos());  //鼠标最后的坐标点
                update();  //更新绘图 paintEvent(QPaintEvent *event) 
        } 
} 

void PaintWidget::mouseReleaseEvent(QMouseEvent *event) 
{ 
        perm = true;  //代表画完
} 

PaintWidget 类定义了一个 slot,用于接收改变后的新的 ShapeCode。最主要的是,PaintWidget重定义了三个关于鼠标的事件:mousePressEvent,mouseMoveEvent和mouseReleaseEvent。

我们来想象一下如何绘制一个图形:图形的绘制与鼠标操作息息相关。以画直线为例,首先我们需要按下鼠标,确定直线的第一个点,所以在 mousePressEvent 里面,我们让 shape 保存下 start 点。然后在鼠标按下的状态下移动鼠标,此时,直线就会发生变化,实际上是直线的终止点在随着鼠标移动,所以在 mouseMoveEvent 中我们让 shape 保存下 end 点,然后调用 update()函数,这个函数会自动调用 paintEvent()函数,显示出我们绘制的内容。最后,当鼠标松开时,图形绘制完毕,我们将一个标志位置为 true,此时说明这个图形绘制完毕。

为了保存我们曾经画下的图形,我们使用了一个 List。每次按下鼠标时,都会把图形存入这个 List。可以看到,我们在 paintEvent()函数中使用了 foreach 遍历了这个 List,绘制出历史图形。foreach 是 Qt 提供的一个宏,用于遍历集合中的元素。

最后我们来看看 Shape 类。

shape.h (管理)

#ifndef SHAPE_H
#define SHAPE_H

#include "QPainter"
#include"QPoint"

class Shape
{
public:
    Shape();

    enum Code{Line,Rect}; //枚举
    void setStart(QPoint point_localstart)
    {
        this->point_start = point_localstart;
    }
    void setEnd(QPoint point_localend)
    {
        this->point_end = point_localend;
    }

    QPoint get_point_start()
    {
        return this->point_start;
    }
    QPoint get_point_end()
    {
        return this->point_end;
    }

    void virtual paint(QPainter &painter) = 0; //写一个纯虚函数,让子类去实现自己功能

protected:
    QPoint point_start;
    QPoint point_end;
};

#endif // SHAPE_H

Shape 类最重要的就是保存了 start 和 end 两个点。为什么只要这两个点呢?因为我们要绘制的是直线和矩形。对于直线来说,有了两个点就可以确定这条直线,对于矩形来说,有了两个点作为左上角的点和右下角的点也可以确定这个矩形,因此我们只要保存两个点,就足够保存这两种图形的位置和大小的信息。paint()函数是 Shape 类的一个纯虚函数,子类都必须实现这个函数。我们现在有两个子类:Line和 Rect,分别定义如下:

line.h

#ifndef LINE_H
#define LINE_H

#include "shape.h"

class Line : public Shape
{
public:
    Line();

    void paint(QPainter &painter) override; //重写父类虚函数
};

#endif // LINE_H

line.cpp

#include "line.h"

Line::Line()
{

}

void Line::paint(QPainter &painter)
{
    painter.drawLine(point_start,point_end);//画直线
}

rect.h

#ifndef RECT_H
#define RECT_H

#include "shape.h"

class Rect : public Shape
{
public:
    Rect();

    void paint(QPainter &painter) override;
};

#endif // RECT_H

rect.cpp

#include "rect.h"

Rect::Rect()
{

}

void Rect::paint(QPainter &painter) //画矩形 起始点为左上角 结束点为右下角
{
    painter.drawRect(point_start.x(),point_start.y(),point_end.x()-point_start.x(),point_end.y()-point_start.y());//画矩形
}

使用 paint()函数,根据两个点的数据,Line 和 Rect 都可以绘制出它们自身来。此时就可以看出,我们之所以要建立一个 Shape 作为父类,因为这两个类有几乎完全相似的数据对象,并且从语义上来说,Line、Rect 与 Shape 也完全是一个 is-a 的关系。如果你想要添加颜色等的信息,完全可以在Shape 类进行记录。这也就是类层次结构的好处。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/440700.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

力扣sql中等篇练习(七)

力扣sql中等篇练习(七) 1 查询活跃业务 1.1 题目内容 1.1.1 基本题目信息 1.1.2 示例输入输出 1.2 示例sql语句 # Write your MySQL query statement below # 先求出所有业务的平均发生次数 SELECT t2.business_id FROM (SELECT e.*,IF(e.occurences>t1.A_NUM,1,0) tota…

【C++】vector的简化模拟实现

文章目录 1. 主要结构2. 默认成员函数3. 迭代器4. 容量相关1. size和capacity2. reserve3. resize 5. 数据访问6. 数据修改1. push_back2.pop_back3. insert4.erase5.swap6.clear 1. 主要结构 参照SGI版本的vector实现&#xff0c;使用三个指针来维护这样一段内存空间 templa…

ACL访问控制列表简介和配置演示

一.ACL功能和特点 1.功能 2.特点 二.ACL种类 1.基础ACL&#xff1a; 2.增强ACL&#xff1a; 三.配置演示 1.基础ACL&#xff1a; 2.增强ACL&#xff1a; 一.ACL功能和特点 1.功能 对感兴趣的路由 (控制层面)进行设置策略 对感兴趣的流量 (数据层面)进行设置策略 2.…

Activity启动模式的生命周期

四种启动模式 1.standard android:launchMode"standard" 默认的标准启动模式&#xff0c;每次启动当前Activity&#xff0c;任务栈中都添加一个当前Activity的实例。按返回键时&#xff0c;表现出退出多个当前Activity的现象。 MainActivityOne和MainActivityTwo都…

DPText-DETR原理及源码解读

一、原理 发展脉络&#xff1a;DETR是FACEBOOK基于transformer做检测开山之作&#xff0c;Deformable DETR加速收敛并对小目标改进&#xff0c;TESTR实现了端到端的文本检测识别&#xff0c;DPText-DETR做了精度更高的文字检测。 DETR 2020 FACEBOOK&#xff1a; 原理 https://…

c/c++:函数的作用,分类,随机数,函数定义,调用,申明,exit()函数,多文件编程,防止头文件重复

c/c&#xff1a;函数的作用&#xff0c;分类&#xff0c;随机数&#xff0c;函数定义&#xff0c;调用&#xff0c;申明&#xff0c;exit()函数&#xff0c;多文件编程&#xff0c;防止头文件重复 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大…

Spring启动及Bean实例化过程来看经典扩展接口

目录 一、Spring启动及Bean实例化过程 二、分析其对应经典扩展接口 三、对开发的指导意义 备注&#xff1a;以下总结只是一些基本的总结思路&#xff0c;具体每个扩展接口的应用后续进行分析总结。 一、Spring启动及Bean实例化过程 Spring启动及Bean实例化的过程&#xff0…

6 款顶级 Android 数据恢复软件列表

数据丢失可能会扰乱您的个人/企业生活&#xff0c;如果手动完成&#xff0c;可能很难恢复丢失的数据。Android 数据恢复软件是解决此问题的完美解决方案。这些工具可帮助您快速轻松地从 Android 设备恢复丢失的数据。它可以帮助您恢复照片、视频、笔记、联系人等。 我研究了市…

1. C++使用Thread类创建多线程的三种方式

1. 说明 使用Thread类创建线程对象的同时就会立刻启动线程&#xff0c;线程启动之后就要明确是等待线程结束&#xff08;join&#xff09;还是让其自主运行&#xff08;detach&#xff09;&#xff0c;如果在线程销毁前还未明确上面的问题&#xff0c;程序就会报错。一般都会选…

webserve简介

目录 I/O分类I/O模型阻塞blocking非阻塞 non-blocking&#xff08;NIO&#xff09;IO复用信号驱动异步 webServerHTTP简介概述工作原理HTTP请求头格式HTTP请求方法HTTP状态码 服务器编程基本框架两种高效的事件处理模式Reactor模式Proactor模拟 Proactor 模式 线程池 I/O分类 …

字节岗位薪酬体系曝光,看完感叹:不服真不行

曾经的互联网是PC的时代&#xff0c;随着智能手机的普及&#xff0c;移动互联网开始飞速崛起。而字节跳动抓住了这波机遇&#xff0c;2015年&#xff0c;字节跳动全面加码短视频&#xff0c;从那以后&#xff0c;抖音成为了字节跳动用户、收入和估值的最大增长引擎。 自从字节…

最全MySQL8.0实战教程

文章目录 最全MySQL8.0实战教程一.前言1.计算机语言概述2.SQL的概述2.1 语法特点2.2 MySQL的安装2.2.1 方式1&#xff1a;解压配置方式2.2.2 方式2&#xff1a;步骤安装方式 二. 数据库DDL操作1.DDL概念2.对数据库常用操作 最全MySQL8.0实战教程 长路漫漫&#xff0c;键盘为伴&…

【Linux进阶篇】启动流程和服务管理

目录 &#x1f341;系统启动 &#x1f343;Init和Systemd的区别 &#x1f343;运行级别和说明 &#x1f341;Systemd服务管理 &#x1f343;6和7命令区别 &#x1f343;systemd常用命令 &#x1f341;系统计划调度任务 &#x1f343;一次性任务-at &#x1f343;batch &#x1…

论文 : Multi-Channel EEG Based Emotion Recognition Using TCNBLS

Multi-Channel EEG Based Emotion Recognition Using Temporal Convolutional Network and Broad Learning System 本文设计了一种基于多通道脑电信号的端到端情绪识别模型——时域卷积广义学习系统(TCBLS)。TCBLS以一维脑电信号为输入&#xff0c;自动提取脑电信号的情绪相关…

自然语言处理 —— 01概述

什么是自然语言处理呢? 自然语言处理就是NLP,全名为Natural Language Processing。 一、NLP的困难 1. 歧义 (1)注音歧义 (2)分词歧义 (3)结构歧义 (4)指代歧义 (5)语义歧义 (6)短语歧义

javascript简单学习

简介&#xff1a; javascript 是脚本语言 javascript是轻量级的语言 javascript是可插入html页面的编程代码 javascript插入html页面后&#xff0c;可由所有现代浏览器执行 以下是JavaScript的一些基本概念&#xff1a; 1. 变量&#xff1a;变量用于存储数据值&#xff0…

每日学术速递4.13

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV 1.Slide-Transformer: Hierarchical Vision Transformer with Local Self-Attention(CVPR 2023) 标题&#xff1a;Slide-Transformer&#xff1a;具有局部自注意力的分层视觉变换器 …

一、vue之初体验-两种方式引入vue

一、Vue引入方式-CDN <html lang"en"><head><meta charset"UTF-8" /><meta http-equiv"X-UA-Compatible" content"IEedge" /><meta name"viewport" content"widthdevice-width, initial-s…

开源问答社区软件Answer

什么是 Answer &#xff1f; Answer 是一个开源的知识型社区软件。您可以使用它快速建立您的问答社区&#xff0c;用于产品技术支持、客户支持、用户交流等。 Answer是国内SegmentFault 思否团队开发的技术问答社区&#xff0c;Answer 不仅拥有搭建问答平台&#xff08;Q&A…

界面控件DevExtreme使用指南 - 折叠组件快速入门(一)

DevExtreme拥有高性能的HTML5 / JavaScript小部件集合&#xff0c;使您可以利用现代Web开发堆栈&#xff08;包括React&#xff0c;Angular&#xff0c;ASP.NET Core&#xff0c;jQuery&#xff0c;Knockout等&#xff09;构建交互式的Web应用程序&#xff0c;该套件附带功能齐…