C++ GUI Qt4编程-事件处理

来源:互联网 发布:csol大刀控制台优化吧 编辑:程序博客网 时间:2024/05/16 04:49

        事件(event)是由窗口系统或者Qt自身产生的,用以响应所发生的各类事情。当用户按下或者松开键盘或者鼠标上的按键时,就可以产生一个键盘或者鼠标事件;当某个窗口第一次显示的时候,就会产生一个绘制事件,用来告知窗口需要重新绘制它本身,从而使得该窗口可见。大多数事件是作为用户动作的响应而产生的,但是也有一些例外,比如像定时器事件,则是有系统独立产生的。

        不应该混淆"事件"和"信号"这两个概念。一般情况下,在使用窗口部件的时候,信号是十分有用的;而在实现窗口部件时,事件则是十分有用的。例如,当使用QPushButton时,我们对于它的click()信号往往更为关注,而很少关心促成发射该信号的底层鼠标或者键盘事件。但是如果要实现的是一个类似于QPushButton的类,就需要编写一定的鼠标和键盘事件的代码,而且在必要的时候还需要发生clicked()信号。

1.重新实现事件处理器

         在Qt中,事件就是QEvent子类的一个实例,Qt处理的事件类型有一百多种,其中的每一种都可以通过一个枚举值来进行识别。例如,QEvent::type()可以返回用于处理鼠标按键事件的QEvent::MouseButtonPress。

        通过继承QObject,事件通过它们的event()函数来通知对象,在QWidget中的event()实现把绝大多数常用类型的事件提前传递给特定的事件处理器,例如mousePressEvent()/keyPressEvent()以及paintEvent()。

        在此将会回顾两种值得详述的常用事件类型:键盘事件和定时器事件。

 

        通过重新实现keyPressEvent()和keyReleaseEvent(),就可以处理键盘事件了。Plotter窗口部件就重新实现了keyPessEvent()。通常,我们只需要重新实现keyPressEvent()就可以了,因为用于表明释放重要性的键只是Ctrl、Shift和Alt这些修饰键,而这些键的状态可以在keyPressEvent()中使用QKeyEvent::modifiers()检测出来。

 

void CodeEditor::keyPressEvent(QKeyEvent *event)

{

        switch(event->key)

       {

       case Qt::Key_Home:

                if(event->modifiers() & Qt::ControlModifier)

                {

                         goToBeginningOfDocument();

                 }

                 else

                 {

                         goToBeginningOfLine();

                 }

                 break;

       case Qt::Key_End:

                 ...

       defanlt:

                QWidget::keyPressEvent();

       }

}

 

        Tab键和BackTab(Shift + Tab)键是两种特殊情况。在窗口部件调用keyPerssEvent()之前,QWidget::event()会先处理它们,它所包含的语义就是用于把焦点传递给焦点序列中的下一个或者上一个窗口部件。这种行为通常正是我们想要的,但是在CodeEditor窗口部件中,我们或许更愿意让Tab键起到缩进文本行的作用。于是,重新实现后的event()看起来应当是下面的样子:

bool CodeEditor::event(QEvent *event)

{

        if(event->type() == QEvent::KeyPress)

        {

                QKeyEvent *keyEvent = static_case<QKeyEvent*>(event);

                if(keyEvent->type() == Qt::Key_Tab)

                {

                        insertAtCurrentPosition('\t');

                        return true;

                }

        }

        return QWidget::event(event);

}

        如果该事件是一个按键事件,那么就把这个QEvent对象强制装换成QkeyEvent并且检查按下的是哪个键。如果按下的键是Tab键,就做一些处理并返回true,告诉Qt已经把这个事件处理完毕了。如果返回的是false,那么Qt将会把这个事件传递给它的父窗口部件来处理。

 

        另外一种常用的事件类型是定时器事件。虽然绝大多数的其他事件类型的发生是因为用户的动作,但是定时器事件允许应用程序可以在一定的时间间隔后执行事件处理。定时器事件可以用来实现光标的闪烁和其他动画的播放,或者只简单地用作显示的刷新。

void Ticker::paintEvent(QPaintEvent event)

{

         QPainter painter(this);

         int textWidth = fontMetrics().width(text());

         if(textWidth < 1)

        {

                return;

        }

        int x = -offset;

        while(x < width())

        {

                painter.drawText(x, 0, textWidth, height(), Qt::AlignLeft | Qt::AlignVCenter, text());

                x += textWidth;

        }

}

void Ticker::showEvent(QShowEvent event)

{

        myTimerId = startTimer(30);

}

 

void Ticker::timerEvent(QTimerEvent * event)

{

        if(event->timerId() == myTimerId)

        {

                ++offset;

               if(offset >= fontMetrics().width(text()))

               {

                       offset = 0;

               }

               scroll(-1, 0);

        }

        else

        {

                QWidget::timerEvent(event);

        }

}

 

void Ticker::hideEvent(QHideEvent event)

{

        killTimer(myTimerId);

        myTimerId = 0;

}

2.安装事件过滤器

        Qt事件模型一个非常强大的功能是:QObject实例在看到它自己的事件之前,可以通过设置另外一个QObject实例监视这些事件。

        假定有一个由几个QLineEdit组成的CustomerInfoDialog窗口部件,并且我们想使用Space键把光标移动到下一个QLineEdit中,这种非标准行为对于公司内部的某个应用程序来讲也许是非常合适的,因为使用它的用户可能都早已训练有素了,一种更为直接的解决方案是子类化QLineEdit并且重新实现keyPressEvent(),由它调用focusNextChild(),就像如下所示的形式:

void MyLineEdit::keyPressEvent(QKeyEvent * event)

{

        if(event->key() == Qt::Key_Space)

        {

                focusNextChild();

        }

        else

        {

                QLineEdit::keyPressEvent(event);

        }

}

这种方法由一个主要的缺点:如果在窗体中使用了好几种不用类型的窗口部件,我们也必须对它们作揖子类化,以便让它们能够实现相同的行为。一个更好的解决方案是让CustomerInfoDialog监视它的子窗口部件中键的按下事件并且在监视代码中实现所需的行为。这种方法可以通过使用事件过滤器来实现。创建一个事件过滤器包括如下两步过程:

1.通过对目标对象调用installEventFilter()来注册监视对象。

        CustomerInfoDialog::CustomerInfoDialog(QWidget *parent)

        {

                //事件一旦注册,发送给firstName窗口部件的事件就会在它们到达目的地之前先被发送给CustomerInfoDialog的eventFilter()函数

                firstNameEdit->installEventFilter(this);

        }

2.在监视对象的eventFilter()函数中处理目标对象的事件。

        bool CustomerInfoDialog::eventFilter(QObject *target, QEvent *event)

        {

                if(target == firstNameEdit)

                {

                        if(event->type() == QEvent::KeyPress)

                        {

                                 QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);

                                 if(keyEvent->key() == Qt::Key_Space)

                                 {

                                         focusNextChild();

                                         return true;

                                 }

                        }

                }

               return QDialog::eventFilter(target, event);

        }

        Qt提供了5个级别的事件处理和事件过滤方法

        1.重新实现特殊的事件处理器

                 重新实现像mousePressEvent()、keyPressEvent()和paintEvent()这样的事件处理器是到现在为止最常用的事件处理方式。

        2.重新实现QObject::event()

                 通过event()函数的重新实现,可以在这些事件到达特定的事件处理器之前处理它们。这种方式常用于覆盖Tab键的默认意义,就像在前面看到的那样。这种方式也可以用于处理那些没有特定事件处理器的不常用类型的事件中。当重新实现event()时,必须对那些没有明确处理的情况调用其基类的event()函数。

        3.在QObject中安装事件过滤器

                对象一旦使用installEventFilter()注册过,用于目标对象的所有事件都会首先发送给这个监视对象的eventFilter()函数。如果在同一个对象上安装了多个事件处理器,那么就会按照安装顺序逆序,从最近安装的到最先安装的,依次激活这些事件处理器。

        4.在QApplication对象中安装事件过滤器

                一旦在qApp(唯一的QApplication对象)中注册了事件过滤器,那么应用程序中每个对象的每个事件都会在发送到其他事件过滤器之前先发送给这个eventFilter()函数。这种处理方式对于调试是非常有用的。它也可以用来处理那些发送给失效窗口部件的鼠标事件,因为QApplication通常都会忽略这些事件。

       5.子类化QApplication并且重新实现notify()

               Qt调用QApplication::notify()来发送一个事件。重新实现这个函数式在事件过滤器得到所有事件之前获得它们的唯一方式。事件过滤器通常更有用,因为可以同时又多个事件过滤器,而notify()函数却只能有一个。

 

3.处理密集时的响应保持

        应用程序把一个文件保存到磁盘的过程中,知道文件保存完毕,才会处理那些有系统产生的事件。在文件保存的过程中,应用程序就不能响应来自窗口系统的重新绘制的请求。

        一种解决方法是使用多线程:一个线程用于处理应用程序的用户界面,另外一个线程则执行文件保存操作,这样的话,在保存文件的时候,应用程序的用户界面仍然可以保持响应。

        一种更为简单的解决方法是在文件保存的代码中频繁的调用QApplication::processEvents()。这个函数告诉Qt处理所有那些还没有被处理的各类事件,然后再将控制权返还给调用者,实际上,QApplication::exec()就是一个不停调用processEvent()函数的while循环,为了更为保险,在调用的时候把qApp->processEvents();替换为qApp->processEvent(QEventLoop::ExcludeUserInputEvents);以告诉Qt忽略鼠标事件和键盘事件。

       在Qt中,通过使用一个0毫秒定时器可以实现这种方法。只要没有其他尚待处理的事件,就可以出发这个定时器,以下就是timerEvent()实现的例子,其中给出了程序空闲时的处理方法:

        void Spreadsheet::timerEvent(QTimerEvent *event)

        {

                if(event->timerId() == myTimerId)

               {

                      while(step < MaxStep && !qApp->hasPendingEvents())

                      {

                              performStep(step);

                              ++step;

                      }

               }

               else

               {

                        QTableWidget::timerEvent(event);

               }

        }

       如果hasPendingEvents()的返回值是true,就停止处理并且把控制权交还给Qt。当Qt处理完所有事件后,就会重新恢复这项操作。

0 0