Qt实战教程三:Qt事件和文件

3 Qt事件和文件

3.1 自定义控件封装

  • 开发中,常需要自定义封装控件,如滑动条和选值框进行封装,实现拖动滑动条,改变选值框的内容;改变选值框,滑动框也跟着改变。

在这里插入图片描述

  • 封装步骤:右击项目->添加新文件->Qt->Qt设计师界面类->选模板为Widget空模板->设类名。

  • 在新类即为Widget类的提升类,将滑动条和选值框拖动到UI界面即可。在项目中添加Widget控件后,右击->提升为->输入刚刚类的名称->如果勾选全局包含下次可以直接提升。

  • 提升后类变为新类名,此时虽然在项目UI界面没有显示,但是实际上存在,下面需要设定新类的一些内部关联,使用信号和槽监听,由于现在在项目中很难访问到封装的新控件中的小控件,也需要设定一些对外的接口

  • 内部关联写在my_first_item.cpp的默认构造中。

    //设定范围
    ui->spinBox->setRange(10,1000);
    ui->horizontalSlider->setRange(10,1000);
    //有重载,需要设函数指针
    void (QSpinBox::* spSignal)(int)=&QSpinBox::valueChanged;
    //当选值框改变时,改变滑动条
    connect(ui->spinBox,spSignal,ui->horizontalSlider,&QSlider::setValue);
    //当滑动条改变时,改变选值框
    connect(ui->horizontalSlider,&QSlider::valueChanged,ui->spinBox,&QSpinBox::setValue);
    //设定选值框初始值
    ui->spinBox->setValue(500);
  • 对外接口写在my_first_item.cpp中。
    //设定选值框数值
    void my_first_item::setNum(int num)
    {
        ui->spinBox->setValue(num);
    }
	//获取选值框数值
    int my_first_item::getNum()
    {
        return ui->spinBox->value();
    }
  • 在项目中可以设定获取值的按钮和设定值的按钮,利用对外接口得到数据。

在这里插入图片描述

	//按下获取值按钮,获取选值框数值并放进QLabel中
	connect(ui->getValue,&QPushButton::clicked,[=](){
        ui->getLabel->setText(QString::number(ui->widget->getNum()));
    });
	//按下值设为按钮,将单行编辑框数值设顶为选值框数值
    connect(ui->setValue,&QPushButton::clicked,[=](){
        ui->widget->setNum(ui->lineEdit->text().toInt());
    });
  • 注意点:QString类型与int类型互转
    • QString->int 使用.toInt()方法。
    • int->QString 使用静态成员函数QString::number(int num)强转。

3.2 鼠标事件

  • 当鼠标在控件上经过、点击的时候,QT都可以记录鼠标的动作,包括哪个键按下,坐标是多少,等等。记录鼠标事件也需要自行封装控件并提升类。

  • 本案例先讲解QWidget类的enterEvent(QEvent *event)leaveEvent(QEvent *event)方法,用于记录鼠标的进入控件离开控件动作。再讲解QLabel类的mouseMoveEvent(QMouseEvent *ev) mousePressEvent(QMouseEvent *ev)mouseReleaseEvent(QMouseEvent *ev)方法,用于记录鼠标的移动点击松开动作。

类名方法功能
QWidgetvoid enterEvent(QEvent *event)记录鼠标进入窗体
void leaveEvent(QEvent *event)记录鼠标离开窗体
QLabelvoid mouseMoveEvent(QMouseEvent *ev)记录鼠标在标签中移动
void mousePressEvent(QMouseEvent *ev)记录鼠标在标签中点击
void mouseReleaseEvent(QMouseEvent *ev)记录鼠标在标签中松开
    labelTracking::labelTracking(QWidget *parent) : QLabel(parent)
    {
        //设定一直监控鼠标移动(默认是鼠标点击后才会监听)
        //setMouseTracking(true);
    }

    void labelTracking::enterEvent(QEvent *event)
    {
        qDebug()<<"鼠标进入了~";
    }

    void labelTracking::leaveEvent(QEvent *event)
    {
        qDebug()<<"鼠标离开了~";
    }

    void labelTracking::mouseMoveEvent(QMouseEvent *ev)
    {
        if(ev->buttons()&Qt::LeftButton)
        {
        QString str=QString("鼠标移动到 x=%1  y=%2  globalX=%3  globalY=%4").arg(ev->x()).arg(ev->y()).arg(ev->globalX()).arg(ev->globalY());
        qDebug()<<str;
        }
    }

    void labelTracking::mousePressEvent(QMouseEvent *ev)
    {
        if(ev->button()==Qt::LeftButton)
        {
        QString str=QString("鼠标点击到 x=%1  y=%2  globalX=%3  globalY=%4").arg(ev->x()).arg(ev->y()).arg(ev->globalX()).arg(ev->globalY());
        qDebug()<<str;
        }
    }

    void labelTracking::mouseReleaseEvent(QMouseEvent *ev)
    {
        if(ev->button()==Qt::LeftButton)
        {
        QString str=QString("鼠标离开到 x=%1  y=%2  globalX=%3  globalY=%4").arg(ev->x()).arg(ev->y()).arg(ev->globalX()).arg(ev->globalY());
        qDebug()<<str;
        }
    }
  • 注意点

    • 输出时使用了QString("%1 %2...").arg(...).arg(...)的方法,类似链式编程,其中%1对应第一个.arg()%2对应第二个.arg(),以此类推,可以很方便构建字符串。

    • QMouseEvent类的x()属性是返回该控件中鼠标的x坐标,globalX()属性是返回整个窗体中鼠标的x坐标。

    • QMouseEvent类的button()属性返回一个枚举值,用于记录鼠标瞬时发生的动作是由什么按键产生的,如点击,松开。

    • QMouseEvent类的buttons()属性返回一个枚举值,用于记录鼠标持久发生的动作是由什么按键产生的,如移动。

    • 换句话来说,鼠标只有在点击那一刹那,button()属性才会为左键和右键的枚举值,在移动状态下button()属性为Qt::NoButton,相反,buttons()属性记录了鼠标每时每刻的键值。

    • 本例中如果要记录是鼠标左键执行点击和松开操作的,只需要比对button()属性和Qt::LeftButton枚举值即可,但如果要记录是鼠标左键执行移动操作的,需要比对buttons()属性和Qt::LeftButton枚举值,可以用位操作符&,此时只要移动的时候包含左键(如左右键一起移动)都可以触发。

      ConstantValue
      Qt::NoButton0x00000000
      Qt::LeftButton0x00000001
      Qt::RightButton0x00000002
      Qt::MidButton0x00000004

3.3 定时器1

  • 如制作闹钟软件需要用到定时器,本案例讲解的是virtual void QTimer::timerEvent(QTimerEvent *e) override方法,通过在窗口中重写该函数并设定时间间隔,实现读秒。上面是每1秒+1,下面是每2秒+1。

在这里插入图片描述

    MainWindow::MainWindow(QWidget *parent)
        : QMainWindow(parent)
        , ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
        id1=startTimer(1000); //单位为ms
        id2=startTimer(2000);
    }

    void MainWindow::timerEvent(QTimerEvent *e)
    {
        static int num_1=0;
        static int num_2=0;
        if(e->timerId()==id1)
        ui->timeLabel_1->setText(QString::number(num_1++));
        if(e->timerId()==id2)
        ui->timeLabel_2->setText(QString::number(num_2++));
    }
  • 注意点
    • 在函数中设定了两个成员变量id1id2,在程序开始运行时,设定两个计时器开始动作,并分配好各计时器id值,在每次发生timerEvent事件时,比对id号是否为当前事件的timerId
    • 两个读秒值需要设定为静态成员变量,避免多次定义无法增加值。

3.4 定时器2

  • 第二种定时器的实现相对简单,需要添加<QTimer>类,并在主窗口的构造函数中声明类对象。
    //第二种定时器
    QTimer *timer=new QTimer(this);
    static int num_3;
	//开始计时,间隔为0.5秒
    timer->start(500); 
	//每到间隔计时器会发送信号,通过信号和槽连接
    connect(timer,&QTimer::timeout,[=](){
        ui->timeLabel_3->setText(QString::number(num_3++));
    });
    //按下暂停键暂停计数
    connect(ui->btn,&QPushButton::clicked,[=](){
        timer->stop();
    });
  • 功能也较第一种定时器有提升,设置了暂停功能,且无需记录定时器序号,对于不同的定时器可以声明不同的类对象即可。

在这里插入图片描述

3.5 事件分发器bool event(QEvent *e)

  • 由QT到各事件的处理中间要交由事件分发器bool event(QEvent *e)处理,如果返回值是true则代表用户处理这个事件,不向下分发了,即被拦截;如果返回值是false,分发到对应事件处理程序。
  • 本案例中重写了event函数,拦截了鼠标按下操作。
  • 通过e->type()属性可以判断用户的事件类型,是一个枚举值。
    bool labelTracking::event(QEvent *e)
    {
        if(e->type()==QEvent::MouseButtonPress)
        {
            QMouseEvent *ev=static_cast<QMouseEvent *>(e);
            QString str=QString("拦截到鼠标点击 x=%1  y=%2  globalX=%3  globalY=%4").arg(ev->x()).arg(ev->y()).arg(ev->globalX()).arg(ev->globalY());
            qDebug()<<str;
            //拦截事件,不向下分发
            return true;
        }
        //否则交由父类处理
        return QLabel::event(e);
    }
  • 注意点
    • 拦截的事件类型为QEvent,需要使用static_cast<>()函数强转为其子类QMouseEvent,再使用其坐标属性。
    • 一般很少自定义函数来拦截。

3.6 事件过滤器bool eventFilter(QObject* obj,QEvent* e)

  • 通过事件过滤器,可以在程序分发到event事件分发器之前再做一次高级拦截。
  • 步骤:给控件安装事件过滤器->重写eventFilter事件。
    //1.给label控件安装事件过滤器
    ui->label->installEventFilter(this);

    //2.重写eventFilter事件
    bool MainWindow::eventFilter(QObject* obj,QEvent* e)
    {
        //判断是否为监听的label控件
        if(obj==ui->label)
        {
            //判断事件是否为鼠标点击
            if(e->type()==QEvent::MouseButtonPress)
            {
                QMouseEvent *ev=static_cast<QMouseEvent *>(e);
                QString str=QString("eventFilter拦截到鼠标点击 x=%1  y=%2  globalX=%3  globalY=%4").arg(ev->x()).arg(ev->y()).arg(ev->globalX()).arg(ev->globalY());
                qDebug()<<str;
                //拦截事件,不向下分发
                return true;
            }
        }
        //否则交由父类处理
        return QWidget::eventFilter(obj,e);
    }

在这里插入图片描述

  • 与事件分发器不同的是,这次的安装事件过滤器和重写eventFilter事件都是写在mainWindow.cpp下的,而event事件分发器拦截是写在对应控件中的。

3.7 绘图事件

  • QT可以在界面上绘图,如直线椭圆文字等等,都会自动在paintEvent(QPaintEvent* )中调用,需要在主窗口函数中重写该函数。

在这里插入图片描述

void MainWindow::paintEvent(QPaintEvent* )
{
    //this指的是绘图设备
    QPainter *painter=new QPainter(this);
    //定义画笔
    QPen *pen=new QPen;
    //设定画笔颜色
    pen->setColor(QColor(0,0,255));
    //设定画笔风格(枚举值)
    pen->setStyle(Qt::DotLine);
    //设定笔宽
    pen->setWidth(3);
    //画家用画笔
    painter->setPen(*pen);

    //定义画刷
    QBrush *brush=new QBrush;
    //设定画刷颜色(枚举值)
    brush->setColor(Qt::magenta);
    //设定画刷风格(枚举值)
    brush->setStyle(Qt::DiagCrossPattern);
    //画家用画刷
    painter->setBrush(*brush);

    //画直线
    painter->drawLine(QPoint(0,0),QPoint(100,100));
    //画椭圆
    painter->drawEllipse(QPoint(100,100),100,50);
    //画矩形
    painter->drawRect(200,200,100,100);
    //写文字
    painter->drawText(QPoint(300,100),"好好学习,天天向上");
}
  • 注意点

    • 设定完画刷就会自动填充封闭区域。

    • 本例中将画家设定为一个指针却没有释放,可能会导致内存泄露,因此最好将画家对象直接实例化,放在堆区即可。

3.8 绘图高级设置

  • 绘图抗锯齿设置,可以使图像边缘更加柔和,但效率较低。setRenderHint方法里的参数可以从枚举值中查到。

在这里插入图片描述

    painter->drawEllipse(QPoint(200,200),100,100);
    //设置抗锯齿能力,但效率较低
    painter->setRenderHint(QPainter::QPainter::Antialiasing);
    painter->drawEllipse(QPoint(400,200),100,100);
  • 移动画家位置方法translate(),保存和复原画家位置方法save() restore()

在这里插入图片描述

	painter->drawRect(50,50,100,100);

    //将画家向右移动150个单位
    painter->translate(150,0);
    painter->drawRect(50,50,100,100);
    //保存画家当前位置(150,0)
    painter->save();

    //将画家向再右移动150个单位
    painter->translate(150,0);
    //复原画家保存的位置
    painter->restore();
    //事实上这个矩形和第二个矩形重合了,所以只显示两个矩形
    painter->drawRect(50,50,100,100);

3.9 手动调用绘图事件

  • 绘图事件可以画资源图片。

在这里插入图片描述

    void MainWindow::paintEvent(QPaintEvent* )
    {
        QPainter painter(this);
        painter.drawPixmap(xPos,20,QPixmap(":/Image/aite.png"));
    }
    //实现按下按钮图片向右移动50单位,如果超出屏幕在最左侧重新显示
	connect(ui->btn,&QPushButton::clicked,[=](){ 
        xPos+=50;
        if(xPos>width())
            xPos=0;
        update();
    });
  • 注意点
    • 手动调用绘图事件方法即update(),重新执行绘图事件中的函数。
    • 可设置定时器+手动绘图事件 使图片自动移动。
    QTimer *timer=new QTimer(this);
    timer->start(5);
	connect(timer,&QTimer::timeout,[=]{
        xPos+=1;
        if(xPos>width())
            xPos=0;
        update();
    });

3.10 绘图设备PainterDevice

  • 绘图设备指继承PainterDevice的子类。上文提到的绘图设备都选的是当前窗口,本案例中介绍四种新的绘图设备:QPixmap QBitmap QImage QPicture,在这几种绘图设备上绘图可以保存到磁盘上。

    类名介绍
    QPixmap专门为图像在屏幕上的显示做了优化
    QBitmap是QPixmap的一个子类,它的色深限定为1
    QImage可以对图像的各个像素进行访问
    QPicture可以记录和重现QPainter的各条命令

QPixmap

  • 无需在绘图事件中编写代码,直接在主窗口cpp里就可以绘制并保存。
    //QPixmap绘图设备
    QPixmap pix(300,300);
    pix.fill(Qt::white);
    QPainter painter(&pix);
    painter.setPen(QPen(Qt::blue));
    painter.drawEllipse(QPoint(150,150),100,100);
    pix.save("E:/pix.png");

QImage

  • QPixmap不同的是QImage绘图设备在实例化时需要指定格式(枚举值),比较常用的是32位RGBQImage::Format_RGB32
    //QImage绘图设备
    QImage img(300,300,QImage::Format_RGB32);
    img.fill(Qt::white);
    QPainter painter(&img);
    painter.setPen(QPen(Qt::magenta));
    painter.drawEllipse(QPoint(150,150),100,100);
    img.save("E:/img.png");
  • QImage还独有对图像的各个像素进行访问功能,使用.setPixelColor(int x,int y,QColor col)方法即可。
    //利用QImage对像素进行修改
    QImage img;
    img.load(":/Image/aite.png");
    QPainter painter(this);
    //修改像素点
    for(int i=50;i<100;i++)
    {
        for (int j=50;j<100;j++)
        {
            img.setPixelColor(i,j,QColor(255,255,0));
        }
    }
    img.save("E:/edited.png");

QPicture

  • QPicture设备可以记录和重现QPainter的各条命令。
  • 在开始时,需要使用begin()方法,绘图结束时需要使用end()方法。
    //QPicture绘图设备
    QPicture pic;
    QPainter painter;
    painter.begin(&pic);
    painter.setPen(QPen(Qt::cyan));
    painter.drawEllipse(QPoint(150,150),100,100);
    painter.end();
    pic.save("E:/pic.lh");
  • 在保存时,可以保存为任意格式,这样在下次读取时,再读取该文件,可以重现上次的所有命令。
void MainWindow::paintEvent(QPaintEvent* )
{
    //重现QPicture绘图指令
    QPainter painter(this);
    QPicture pic;
    pic.load("E:/pic.lh");
    painter.drawPicture(0,0,pic);
}

3.11 QFile文件读写操作

读文件

  • QT中读取文件的方式与C++类似,需要包含<QFile>类,本案例中要求实现点击“选择文件”按钮弹出选择文件对话框,选择完成后在单行文字显示文字路径,在下方文字框中显示读取内容。

在这里插入图片描述

    //点击按钮弹出文件对话框
    connect(ui->pushButton,&QPushButton::clicked,[=]{
        QString path=QFileDialog::getOpenFileName(this,"打开文件","C:\\Users\\Divin\\Desktop");
        //将路径放到lineEdit中
        ui->lineEdit->setText(path);

        //QFile默认支持的格式是utf_8
        QFile file(path);
        //设置对文件操作类型(枚举值)
        file.open(QIODevice::ReadOnly);
        //将所有数据读到手
        QByteArray arr=file.readAll();
        //将读取到的数据放到textEdit
        ui->textEdit->setText(arr);
        file.close();
    });
  • 注意点
    • 读取前需要设置读的操作类型(枚举值)。读取到的数据类型为QByteArray,而setText方法需要的格式是QString,因此这里包含一个隐式类型转换QByteArray->QString
    • 上面读取的方法是readAll()全部读到手,也可使用readLine()方法逐行读,再用atEnd()方法判断是否读到末尾。
    //逐行读
    QByteArray arr;
    while(!file.atEnd())
    {
    	arr+=file.readLine();
    }
  • QFile默认支持的格式是utf_8,如果txt文件的编码格式是gbk,需要使用编码格式类QTextCodec进行转换。
    //编码格式类
    QTextCodec* codec=QTextCodec::codecForName("gbk");
    //QFile默认支持的格式是utf_8
    QFile file(path);
    file.open(QIODevice::ReadOnly);
    //将所有数据读到手
    QByteArray arr=file.readAll();
    //将读取到的数据经转码后放到textEdit
    ui->textEdit->setText(codec->toUnicode(arr));

写文件

  • 写文件的方法更加简单,设定好写的操作类型,再使用write方法即可。
    //用追加方式写文件
    file.open(QIODevice::Append);
    file.write("\n啦啦啦");
    file.close();

3.12 QFileInfo文件信息读取

  • 利用QFileInfo类可以获取文件的一些详细信息,如后缀名、创建时间、修改时间等。

    常用接口方法名数据类型
    大小(字节)size()qint64
    后缀名suffix()Qstring
    文件路径filePath()Qstring
    文件名fileName()Qstring
    创建日期birthTime()QDateTime
    最后修改日期lastModified()QDateTime

在这里插入图片描述

	QFileInfo info(path);
    qDebug()<<"文件大小:"<<info.size()<<"\n后缀名:"<<info.suffix();
    qDebug()<<"文件路径:"<<info.filePath()<<"\n文件名:"<<info.fileName();
    qDebug()<<"创建日期:"<<info.birthTime().toString("yyyy/MM/dd hh:mm:ss")<<"\n修改日期:"<<info.lastModified().toString("yyyy/MM/dd hh:mm:ss");
  • 注意点
    • 创建日期和最后修改日期的数据类型都是QDateTime,需要先包含该头文件,再使用toString方法,格式可以查帮助文档,如toString("yyyy/MM/dd hh:mm:ss")可以完整输出文件的年月日,时秒分的信息。
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值