分享

重新实现事件处理函数(Reimplementing Event Handlers)

 成长的园地.a 2012-04-28
在Qt中,一个事件是QEvent的子类的对象。Qt能够处理上百种类型的事件,每一类型的事件由一个枚举值确定。例如,对鼠标点击事件,QEvent::type()返回的值为QEvent::MouseButtonPress
很多情况下,一个QEvent对象不能保存有关事件的所有信息,例如,鼠标点击事件需要保存是左键还是右键触发了这个信息,还要知道事件发生时鼠标指针的位置,这些额外的信息储存在QEvent的子类QMouseEvent中。
通知到对象的事件是由QObject::event()得到。QWidget::event()提供了很多普通类型的事件,实现了很多事件处理函数,例如mousePressEvent(),keyPressEvent(),paintEvent()等等。
在前面的章节中,我们已经在MainWindow 类,IconEditor类,Plotter类中看到了很多事件处理函数,在QEvent参考文档中,还列举了很多类型的事件。我们还可以定义自己的事 件,把事件分派出去。这里,我们讨论一下两种最常用的事件:键盘事件和定时器事件。
通过重写函数keyPressEvent()keyReleaseEvent()可以处理键盘事件。Plotter控件就重写了keyPressEvent()函数。通常,我们只需要重写keyPressEvent(),需要处理键盘释放事件的只有修改键(Ctrl, Shift, Alt),而这些键的信息可以通过QKeyEvent::modifiers()得到。例如,如果我们重写了控件CodeEditor的KeyPressEvent()函数,区分Home键和Ctrl+Home键:
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:
        ...
    default:
        QWidget::keyPressEvent(event);
    }
}
Tab键和Backtab(Shift+Tab)键很特殊,它们是在控件调用keyPressEvent()之前,由QWidget::event()处理的,这两个键的作用是把输入焦点转到前一控件或者下一个控件上,但在CodeEditor中,希望Tab键的作用是缩进,可以这样重写event():
bool CodeEditor::event(QEvent *event)
{
    if (event->type() == QEvent::KeyPress) {
        QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
        if (keyEvent->key() == Qt::Key_Tab) {
            insertAtCurrentPosition('\t');
            return true;
        }
    }
    return QWidget::event(event);
}
如果这个事件是一个键盘敲击事件,我们把QEvent对象转换成QKeyEvent,然后确定是哪个键敲击了,如果是Tab键,进行处理后返回true,通知Qt我们已经对事件进行了处理。如果返回false,Qt还会把这个事件交给父类控件处理。
 
响应键盘事件的更好的方法是使用QAction。例 如,goToBeginningOfLine()和goToBeginningOfDocument()是CodeEditor的两个公有槽函 数,CodeEditor是MainWindow的中央控件,下面的代码实现了键盘和槽函数的绑定:
MainWindow::MainWindow()
{
    editor = new CodeEditor;
    setCentralWidget(editor);
    goToBeginningOfLineAction =
            new QAction(tr("Go to Beginning of Line"), this);
    goToBeginningOfLineAction->setShortcut(tr("Home"));
    connect(goToBeginningOfLineAction, SIGNAL(activated()),
            editor, SLOT(goToBeginningOfLine()));
    goToBeginningOfDocumentAction =
            new QAction(tr("Go to Beginning of Document"), this);
    goToBeginningOfDocumentAction->setShortcut(tr("Ctrl+Home"));
    connect(goToBeginningOfDocumentAction, SIGNAL(activated()),
            editor, SLOT(goToBeginningOfDocument()));
    ...
}
这样可以很容易把一个键盘敲击的命令加入到菜单或者工具条中。如果命令没有出现在用户界面中,可用QShortcut对象代替QAction对象,在QAction内部就是使用这个类实现键盘的绑定。
 
通常情况下,只要窗口中有激活的控件,控件上用QAction或QShortcut设置的键盘绑定都是可用的。绑定的键可用QAction::setShortcutContext()或者QShortcur::setContext()进行修改。
 
另一个常用的事件类型是定时器事件。其他事件都是由用户的某种动作引发的,而定时器事件则使程序按照一定的时间间隔执行特定的任务。定时器事件一般用来使光标闪烁,或者播放动画,或者只是刷新显示界面或者控件。
为了介绍定时器事件,我们将实现一个Ticker控件。这个控件显示一条标语,每隔30毫秒钟向左移动一个象素。如果控件比标语要宽,标语的文本重复的显示在控件上,填满整个控件。
Figure 7.1. The Ticker widget
 
头文件如下:
#ifndef TICKER_H
#define TICKER_H
#include <QWidget>
class Ticker : public QWidget
{
    Q_OBJECT
    Q_PROPERTY(QString text READ text WRITE setText)
public:
    Ticker(QWidget *parent = 0);
    void setText(const QString &newText);
    QString text() const { return myText; }
    QSize sizeHint() const;
protected:
    void paintEvent(QPaintEvent *event);
    void timerEvent(QTimerEvent *event);
    void showEvent(QShowEvent *event);
    void hideEvent(QHideEvent *event);
private:
    QString myText;
    int offset;
    int myTimerId;
};
#endif
在头文件中,我们声明了Ticker的四个事件处理函数,其中三个timeEvent(),showEvent()和hideEvent()是我们以前没有见过的。
 
下面是实现文件:
#include <QtGui>
#include "ticker.h"
Ticker::Ticker(QWidget *parent)
    : QWidget(parent)
{
    offset = 0;
    myTimerId = 0;
}
在构造函数中,设置offset为0,这个变量是文本要显示的x坐标值。时间ID总是非0的,这里设置myTimerId为0说明我们还没有启动任何定时器。
 
void Ticker::setText(const QString &newText)
{
    myText = newText;
    update();
    updateGeometry();
}
函数setText()设置要显示的文本。调用update()引发绘制事件重新显示文本,updateGeometry()通知布局管理器根据size hint的变化改变控件的大小。
 
QSize Ticker::sizeHint() const
{
    return fontMetrics().size(0, text());
}
函数sizeHint()返回的是控件的理想大小作为文本显示所需的尺寸。QWidget::fontMetrics()返回一个QFontMetrics对象,得到控件所用的字体的信息。在这里我们需要得到的是文本的大小。(在QFontMetrics::size()中,第一个参数是一个标识,对字符串来讲并不需要,所以赋了0值)。
 
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;
    }
}
 
函数paintEvent()使用QPainter::drawText()绘制文本。调用fontMetrics()得到文本所需要的水平空间,然后多次绘制文本,直至填满整个控件,当然考虑了offset
 
void Ticker::showEvent(QShowEvent * /* event */)
{
    myTimerId = startTimer(30);
}
showEvent()启动了一个定时器。调用QObject::startTimer()返回一个ID值,这个ID值可以帮助我们识别这个定时器。QObject能够支持多个独立的不同的时间间隔的定时器。调用startTimer()以后,Qt大约每30毫秒产生一个事件,时间的准确与否取决于不同的操作系统。
我们也可以在Ticker的构造函数中调用startTimer()。但是在控件可见以后再启动定时器事件,能够节省一些资源。
 
void Ticker::timerEvent(QTimerEvent *event)
{
    if (event->timerId() == myTimerId) {
        ++offset;
        if (offset >= fontMetrics().width(text()))
            offset = 0;
        scroll(-1, 0);
    } else {
        QWidget::timerEvent(event);
    }
}
函数timerEvent()由系统以一定间隔进行调用的。把offset增加1来模仿文字的移动,增加到标语的宽度时文字的宽度是重新设置为0。然后调用QWidget::scroll()把控件向左滚动一个象素。也可以调用update(),但是scroll()更加高效,因为它只对屏幕上可见的象素进行移动,且只是对需要新绘制的地方调用绘制事件(在这个例子中,只是一个象素宽的区域)。
如果定时器事件不是我们感兴趣的定时器需要处理的,则把它传递给基类。
 
void Ticker::hideEvent(QHideEvent * /* event */)
{
    killTimer(myTimerId);
}
在hideEvent()中,调用QObject::killTimer()停止定时器。
 
定时器事件的优先级很低,如果需要多个定时器,那么跟踪每一个定时器的ID是很费时的。这种情况下,较好的方法是为每一个定时器创建一个QTimer对象。在每一个时间间隔内,QTimer发出一个timeout()信号。QTimer还支持一次性定时器(只发出一次timeout()信号的定时器)。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多