您的位置:首页 > 编程语言 > Qt开发

在QT中通过键盘事件过滤,改变QTableWidget的键盘导航行为

2013-09-04 10:57 2351 查看
  用QTableWidget做个表格,当单元格在编辑状态的时候,键盘左右方向键是在单元格内移动文本插入点光标。但是我想让左右键结束编辑并直接选中相邻的单元格,效果和上下键一样。也许在高手眼里一点也不困难,不过本人刚接触QT,摸索了两天才解决,期间也有一些心得,所以记下来以免忘记。

  QT中所有事件都要先送到qApp,也就是QApplication的实例,从这里再进行分发。分发到哪里呢?对于键盘事件要看当前是谁获得了焦点,这个事件就发给那个对象。如果这个对象处理了事件,那么事件的传递就到此为止;如果这个对象不处理这个事件,事件就会传递给它的父对象,如果父对象也不处理,就继续传给父对象的父对象,直到主窗口为止。

  之所以用方向键可以在表格的各单元格间移动,是因为QTableWidget的实例处理了键盘事件。但当有个单元格处于编辑状态的时候,键盘事件就首先分发给单元格里的编辑控件了,这个控件对左右键进行了处理,左右键的事件就不会再传递到QTableWidget的实例了。这个编辑控件的类型是QExpandingLineEdit,怎么知道的呢?这是个内部使用的类,手册上是没有描述的,我在捕获到键盘事件的时候把object->metaObject()->className()打印出来才知道的。

  接下来要怎么做呢?

  1、给QExpandingLineEdit对象注册事件过滤器?这个不行,给一个对象注册事件过滤器,只对这个对象有效,同一类型的其他对象还是不受影响的。要给表格里的每一个单元格全都注册上过滤器?天啊!

  2、创建QExpandingLineEdit的子类并重写事件处理函数?不行,这是个内部使用的类,自己创建的子类没有人用。

  3、直接修改QT源代码,在QExpandingLineEdit中实现这个功能,然后重新编译和发布QT库?这样倒是能实现,但是感觉太野蛮了。

  4、给QApplication的对象注册过滤器,过滤所有的事件,见到目标对象的类型是QExpandingLineEdit就进行处理?这个看起来可以,下面就按这个思路做。

#include "mainwindow.h"
#include <QApplication>
#include <QKeyEvent>        // static_cast 所需要的
#include "ui_mainwindow.h"

Ui::MainWindow ui;

// 实现过滤器
// 由于 eventFilter() 是QObject的成员函数,因此需要在一个QObject的子类中实现它,
// 任何一个QObject都可以,不过这里新建了一个类。
class NavigationKeyFilter:public QObject
{
bool eventFilter(QObject *object, QEvent *event)
{
int key;

switch(event->type())
{
// 只需要过滤按键事件
case QEvent::KeyPress:
case QEvent::KeyRelease:
// 对QEvent进行强制转换,并获得键值
key = (static_cast<QKeyEvent *>(event))->key();
// 需要过滤的按键
if (key == Qt::Key_Left || key == Qt::Key_Right || key == Qt::Key_Up ||
key == Qt::Key_Down || key == Qt::Key_Tab)
{
// 当目标对象的类型是QExpandingLineEdit,则把消息转发给QTableWidget
if (! strcmp(object->metaObject()->className(), "QExpandingLineEdit"))
{
qApp->sendEvent(ui.tableWidget, event);
return true;
}
}
default:
return false;
}
return false;
}
};

int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
ui.setupUi(&w);

// 创建过滤器并安装给QApplication
NavigationKeyFilter filter;
a.installEventFilter(&filter);

w.show();

return a.exec();
}


  关于过滤器的返回值,返回false表示事件在过滤器中没有处理,需要继续由目标对象处理。返回true表示在过滤器里已经处理了,目标对象不需要再处理。

  关于 sendEvent 与 postEvent:sendEvent 直接将消息发给目标对象,是一个同步操作,因此消息可以从堆栈上分配。postEvent 是将消息入队,待后续再分发处理,因此消息必须在heap上分配,QT系统会在用完后自动释放。

  关于判断对象的类型,可以借助QT中的metaObject。不过用C++本身的typeid也可以实现,例如:

#include <typeinfo>
......
if (typeid(*object) == typeid(QExpandingLineEdit))


  后来,我发现在上述代码中用sendEvent重新分发消息还不是最好的,因为这样必须知道表格对象的指针,如果有多个表格的话就更麻烦。更好的做法是使用ignore(),即把qApp->sendEvent(ui.tableWidget,event); 这一行换成event->ignore(); 它表示目标对象将忽略这个事件,这个事件会发给它的父对象。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: