Essential Qt 第十一章 事件的运用

             这一章将通过一个完成一个完成的程序,这个程序将会运用到多个事件。这个程序被命名为TitleWidget.在界面程序中,滚动播放一些文字非常常见,而这个程序的最用将是能滚动播放默认的字符或输入的文本,完成后大概是这个样子


其中的文字在从左到右不断的循环滚动播出。要实现这个程序,至少要用到3个事件,都是到目前尚未接触到的事件,先看程序头文件TitleWidget.h
#ifndef TITLEWIDGET_H__
#define TITLEWIDGET_H__
#include<QWidget>
#include<QShowEvent>
#include<QPaintEvent>
#include<QTimerEvent>
const QString DEFAULT_TITLE = "This is A TitleWidget For Test Event.         ";//注释1
class TitleWidget:public QWidget
{
  private:
    int TextPoint;
    QString TextInfo;
    int TextTimeID;//注释2
  public:
    TitleWidget(QWidget*parent = 0);
    void setText(const QString& str );
    QString text()const;
    QSize sizeHint()const;//注释3
  protected:
    void showEvent(QShowEvent* event);
    void paintEvent(QPaintEvent* event);
    void timerEvent(QTimerEvent* event);//注释4
};
#endif


注释1: 对于这个程序来说,可以使用接下来的setText()函数来设置他滚动播放的内容,如果没有调用这个函数设置播放内容,则将会播放固定的文本
注释2: 变量TextPoint用于标记文本在窗体中的位置,在绘图中有重要作用,TextInfo为播放文本的内容,TextTimeID将用于标记一个定时器,这个定时器用于控制文本滚动的速度
注释3:这里用setText()函数和text()分别获得和返回播放文本的内容,而sizeHint()函数则用于根据文本内容来觉得窗体大小
注释4:这里将修改三个事件,分别对应显示事件,绘图事件和时间事件,这个窗体的主要功能依靠这三个时间实现

          接下来是TitleWidget.cxx文件,在构造函数中将TextPoint和TextTimeID设为0,这两个值会在下面的事件函数中使用,而TextInfo则等于默认值DEFAULT_TITLE;
void TitleWidget::setText(const QString& str)
{
  TextInfo = str;
  update();//注释1
  updateGeometry();
}
QString TitleWidget::text()const
{
  return TextInfo;
}
QSize TitleWidget::sizeHint()const
{
  return fontMetrics().size(0,text());//注释2
}
注释1 :setText()函数设置滚动播放的文本,然后调用update()函数来强制刷新一次绘图事件,效果等同与窗体重新绘制一次,在Qt3以前版本,这样的操作会造成窗体的闪烁,需要做额外的处理,而从Qt4开始,则不会出现闪烁的情况,另一个函数updateGepmetry()则用于告诉窗体自身的布局管理器,窗体的形状已经改变了,布局管理器也需要做相应的改动。
注释2 :fontMetrics()用于返回一个QFontMetrics对象,这个对象包含了窗体部件字体相关的信息,这个了类的成员函数size()则返回文本的大小,这个QFontMetrics::size()第一个参数用于比特级的细节控制,对于这个例子来说,直接传值0即可,第二个参数为希望获得大小的文本,而上面已经实现了text()函数来获得程序滚动播放的文本内容

绘图事件实现
void TitleWidget::paintEvent(QPaintEvent* event)
{
  QPainter P(this); //注释1
  int TextWidth = fontMetrics().width(text());//注释2
  if(TextWidth < 1)
    return;
  int X = - TextPoint;  //注释3
  while(X < TextWidth)
  {
    P.drawText(X,0,TextWidth,fontMetrics().height(),Qt::AlignLeft|Qt::AlignCenter,text()); //  注释4
    X += TextWidth;
  }
}
注释1: QPainter是一个画笔,他作用是在他的父对象上画画,这里他的父对象是this,所以他的作用就是在窗体上画东西,要使用这个类需要包含头文件#include<QPainter>
注释2: 这里再次利用fontMetrics()函数的返回值来获得文本的大小信息,不过这里是获得文本的长度
注释3: 这里X是一个文本的起始位置,举例,窗体最左段的坐标为0,最初TextPoint的值为0,程序设定文本每个一段时间向左移动一个位置,而TextPoint用于标记文本移动的距离,假设文本向左移动了两次,则TextPoint的值为2,而文本起始的位置则为-2(即向左移动了单位2),当然,从-2到0这段文本不会显示在窗体中
注释4 :这里调用了QPainter的成员函数来“画画”,这里画的是文本,

显示事件实现
void TitleWidget::showEvent(QShowEvent* event)
{
  TextTimeID = startTimer(30);//注释1
  QWidget::showEvent(event);//注释2
}  
注释1: 这个是窗体的显示事件函数,前面说过Qt窗体创建是默认是隐藏的,必须调用show()函数来显示,而调用show()函数就触发了这个显示时间,而在这个程序的显示事件中,需要添加一个功能,这个功能调用基类成员函数startTimer()来生产一个定时器,这个函数参数为定时器的时间间隔,单位为毫秒,例子中这个定时器在窗体显示的时候开始,每隔30毫秒就会触发一次窗体自身的时间事件,这个函数的返回值用于标记创建的定时器,Qt允许一个窗体有多个定时器,创建的时候将会用一个ID来标记,追踪他们
注释2: 由于只是添加功能,所以这里调用下基类的显示事件函数

时间事件实现
void TitleWidget::timerEvent(QTimerEvent* event)
{
  if(event->timerId() == TextTimeID)//注释1
  {
    ++TextPoint;
    if(TextPoint >= fontMetrics().width(text()))//注释2
      TextPoint = 0;
    scroll(-1,0);//注释3
  }
  else
    QWidget::timerEvent(event);//注释4
}
注释1: 在显示事件中,窗体在显示的时候会生成一个定时器,这个定实际每隔30毫秒就会触发一次时间事件,但操作系统也会触发事件时间,所以需要判断是否由显示事件中的那个定时器触发的,可以通过事件的timerId()函数返回触发这次事件的定时器ID来判断
注释2: 每隔一段时间(程序中为30毫秒)TextPoint会记录文本移动距离,当移动具体超过了文本长度,则重置为0,即文本向左移动了一个自身的距离,则可以视为没有移动
注释3: 这里使用了scroll()函数,是的文本向左移动一个像素,注意地一个参数是负代表向左移动,上文中采用update()函数来强制刷新一次绘图之间,而这里由于之需要改变文本位置,不需要改动(重新绘制)整个窗体。

然后程序编译运行后差不多是这个样子的

               到这里实现了滚动播放文本的功能,此外还可以在添加一些功能,比如很多人希望有个暂停和继续滚动的动能,为了演示这个功能,可以添加一个槽来实现,为了程序能(想用户演示)控制空能,可以暂时使用鼠标左键点击来控制,滚动的状态下点击就暂停,暂停的状态下就滚动
              为了实现这样的功能,需要在类中添加一个变量bool值来追踪窗体的滚动状态,在构造函数中设置TitleState = true;因为窗体默认是滚动的,然后还需要修改下窗体的鼠标点击事件,舍得鼠标左键点击的时候会发射一个自定义的信号
void TitleWidget::mousePressEvent(QMouseEvent* event)
{
  if(event->button() == Qt::LeftButton)
    emit LeftClick();  //注释1
  QWidget::mousePressEvent(event);
}
注释1: 这个信号在类中定义,用于和槽连接
然后是控制是否滚动的槽函数的实现
void TitleWidget::StopTitleRun()
{
  if(TitleState == true)
  {
    TitleState = false;
    killTimer(TextTimeID); //注释1
    TextTimeID = 0;
  }
  else if(TitleState == false)
  {
    TextTimeID = startTimer(30);//注释2
    TitleState = true;
  }
}
注释1:TitleState值可以判断窗体目前是滚动还是停止状态, 这里使用killTimer()函数来停止并删除显示事件中产生的定时器
注释2: 这里重新生产一个定时器,来刷新时间事件,达到重启滚动的效果
最后在构造函数中将鼠标点击事件中发射的信号和这个槽连接起来
connect(this,SIGNAL(LeftClick()),this,SLOT(StopTitleRun()));
这样鼠标左键点击就可以控制窗体滚动/停止,当这个窗体作为别的窗体的子窗体(比如一个程序的警告信息播出栏),可以删除这个连接和鼠标点击时间函数,让别的程序特定的信号和这个槽链接,这样就能控制这个部件的行为
             这个类的功能顺序可以概括为
             1.窗体启动创建后调用show()函数显示,出发窗体显示事件,在显示事件中创建定时器,触发了时间时间
             2.时间事件函数中每隔30毫秒刷新一次窗体绘图事件(scroll()函数)
             3.绘图函数中重新绘制窗体,使用drawText()函数来重新绘制窗体(的文本)。
             4.成员函数setText()作为接口,包含了强制窗体重绘的代码,调用该函数也会触发绘图事件

             一些细节上的改动:首先关于时间的间隔这个程序设定为30毫秒,而这个值30是直接写在代码中的,这显然不是一个很好的编程习惯,因为很多程序可能需要修改滚动速度,所以在文件中定义一个常量来表示时间间隔会比较合适,可以使用
const int INTERVAL = 30;
然后在实现使用INTERVAL而不是30,这样当有需要修改的时候只要修改下这个值就可以了而不用在逐行查看代码有没有遗漏的地方。
             另一个细节就是在这个类的构造函数中使用了
setWindowFlags(Qt::CustomizeWindowHint);
这个函数是QWidget的成员函数,多数情况下用于自定义窗体,就一这个例子,我在演示是最初的演示截图中窗体并没有系统窗体自带的标题,最大化,最小化等部件,setWindowFlags()函数的参数是枚举Qt::WindowFlags,这个枚举值提供了很多自定义窗体的功能,其中就包括了这里演示的删除本地风格的标题,最大/小化部件等,用和不用这个函数,最后部件外观差别很大




最后需要注意的setWindowFlags()调用后很多外观的改动只有窗体是顶层窗体才会有区别

              到这里完成了一个滚动窗体的制作,这个窗体提供了一个setText()的接口,用于设置滚动的文本内容,另外这个程序还可以用鼠标点击来控制播放/停止,这部分很容易根据需要改成窗体的控制接口代码,但这里还有点小问题,这个程序是以文本的大小作为窗体的大小,这样做是为了演示这个程序的时候显得比较。。。好看,窗体刚好是完美契合文本的大小,但如果这个窗体是用于其他程序的子窗体来说就有些不合适了,程序往往需要经常改变滚动的文本内容,事实上就演示的这个窗体而言,拉升下窗体就会使得滚动的文本出现问题。
              要解决这个问题,最先要做的是删掉sizeHint()这个函数以及他的定义,这个函数在这个程序中的作用是让窗体的大小和文本完美契合,而如果一个程序需要使用这个窗体,往往会根据实际情况自定义窗体的大小,所以就这个窗体而言,没有必要自己定义大小。
              然后就是修改绘图事件函数里的代码。
void TitleWidget::paintEvent(QPaintEvent* event)
{
  QPainter P(this);
  int TextWidth = fontMetrics().width(text());
  int WidgetWidth = width();//注释1
  if(TextWidth < 1)
    return;
  int X = - TextPoint;
  while(X < WidgetWidth)//注释2
  {
    P.drawText(X,0,TextWidth,fontMetrics().height(),Qt::AlignLeft|Qt::AlignCenter,text());
    X += TextWidth;
  }
}
注释1: 这里获取窗体的宽度
注释2: 原先的代码最大的绘制范围(宽度)是文本的宽度,导致拉伸窗体后拉申的部分显示不全或着不显示,这里改用窗体的宽度作为绘制范围(宽度),使得窗体拉伸后仍然能够正常显示

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值