qt事件处理

目录

(一). Qt 的事件系统

一. 事件的产生和派发

1.事件的产生

2.事件的派发

二. 事件类和事件类型

三. 事件的处理

1.事件处理的基本过程

2.QWidget 类的典型事件处理函数

四. 典型事件处理示例

1.事件处理函数 paintEvent()  

2.事件处理函数 closeEvent()

3.事件处理函数 mousePressEvent()

4.事件处理函数 keyPressEvent()或 keyReleaseEvent()

5.事件处理函数 showEvent()和 hideEvent()

(二). 事件与信号

 一. 函数 event()的作用

二. 事件与信号编程示例

1.示例功能概述

2.设计新的标签类

3.示例程序设计

(三). 事件过滤器

一. 事件过滤器工作原理

二. 事件过滤器编程示例

(四). 拖放事件与拖放操作

一. 拖放操作相关事件

二. 外部文件拖放操作示例

1.示例功能和窗口界面可视化设计

2.窗口类定义和初始化

3.函数 dragEnterEvent()的实现

4.函数 dropEvent()的实现

5.函数 resizeEvent()的实现

(五).  具有拖放操作功能的组件

一. 示例窗口类定义和初始化

二. 拖放操作属性的显示

三. 拖放操作属性的设置

四. 通过事件过滤器实现项的删除


GUI 应用程序是由事件(event)驱动的,点击鼠标、按下某个按键、改变窗口大小、最小化窗口等都会产生相应的事件,应用程序对这些事件进行相应的处理以实现程序的功能。

(一). Qt 的事件系统

窗口系统是由事件驱动的,Qt 为事件处理编程提供了完善的支持。QWidget 类是所有界面组件类的基类,QWidget 类定义了大量与事件处理相关的数据类型和接口函数。

一. 事件的产生和派发

1.事件的产生

事件表示应用程序中发生的操作或变化,如移动鼠标、点击鼠标、按下按键等。在 Qt 中,事件是对象,是 QEvent 类或其派生类的实例,例如 QKeyEvent 是按键事件类,QMouseEvent 是鼠标事件类,QPaintEvent 是绘制事件类,QTimerEvent 是定时器事件类。

按事件的来源,可以将事件划分为 3 类: 

自生事件(spontaneous event):是由窗口系统产生的事件。例如,QKeyEvent 事件、QMouseEvent 事件。自生事件会进入系统队列,然后被应用程序的事件循环逐个处理。

发布事件(posted event):是由 Qt 或应用程序产生的事件。例如,QTimer 定时器发生定时溢 出时 Qt 会自动发布 QTimerEvent 事件。应用程序使用静态函数 QCoreApplication::postEvent() 产生发布事件。发布事件会进入 Qt 事件队列,然后由应用程序的事件循环进行处理。

发送事件(sent event):是由 Qt 或应用程序定向发送给某个对象的事件。应用程序使用静态函数 QCoreApplication::sendEvent()产生发送事件,由对象的 event()函数直接处理。

窗口系统产生的自生事件自动进入系统队列,应用程序发布的事件进入 Qt 事件队列。自生事件和发布事件的处理是异步的,也就是事件进入队列后由系统去处理,程序不会在产生事件的地方停止 进行等待。

应用程序使用静态函数 QCoreApplication::postEvent()发布事件,这个函数的原型定义如下:

void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority = Qt::NormalEventPriority) 

其中,receiver 是接收事件的对象,event 是事件对象,priority 是事件的优先级。在程序中调用 QCoreApplication::postEvent()发布一个事件后,这个函数立刻就会退出,不会等到事件处理完之后再退出,所以,发布事件的处理是异步的。

应用程序使用静态函数 QCoreApplication::sendEvent()向某个对象定向发送事件,函数定义如下:

bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event)

其中,receiver 是接收事件的对象,event 是事件对象。这个函数是以同步模式运行的,也就是它 需要等待对象处理完事件后才退出。

2.事件的派发

GUI 应用程序的 main()函数代码一般是下面这样的结构:

int main(int argc, char *argv[]) 
{ 
 QApplication a(argc, argv); 
 Widget w; 
 w.show(); 
 return a.exec(); 
} 

这段代码创建了一个 QApplication 对象 a,还创建了一个窗口 w,运行 w.show()显示窗口,最后运行 a.exec(),开始应用程序的事件循环。

函数QApplication::exec()的主要功能就是不断地检查系统队列和Qt事件队列里是否有未处理的自生事件和发布事件,如果有事件就派发(dispatch)给接收事件的对象去处理。应用程序的事件循 环还可以对队列中的相同事件进行合并处理,例如如果队列中有一个界面组件的多个 QPaintEvent 事件(绘制事件),应用程序就只派发一次 QPaintEvent 事件,因为界面只需要绘制一次。

注意,应用程序的事件循环只处理自生事件和发布事件,而不会处理发送事件,因为发送事件由应用程序直接派发给某个对象,是以同步模式运行的。

一般情况下,应用程序都能及时处理队列里的事件,用户操作时不会感觉到响应迟滞。但是在某些情况下,例如执行一个大的循环,并且在循环内进行大量的计算或数据传输,同时又要求更新界面显示内容,这时就可能出现界面响应迟滞甚至无响应的情况,这是因为事件队列未能被及时处理。

要解决这样的问题可以采用多线程方法,例如一般的涉及网络大量数据传输的程序都会使用多线程,将界面更新与网络数据传输分别用两个线程去处理,这样就不会出现界面无响应的情况。

另外一种简单的处理方法是在长时间占用 CPU 的代码段中,偶尔调用 QCoreApplication 的静态函数 processEvents(), 将事件队列里未处理的事件派发出去,让事件接收对象及时处理,这样程序就不至于出现停滞的现象。这个函数的原型定义如下:

void QCoreApplication::processEvents(QEventLoop::ProcessEventsFlags flags = QEventLoop::AllEvents) 

参数flags是标志类型QEventLoop::ProcessEventsFlags,它是枚举类型QEventLoop::ProcessEventsFlag 的枚举值的组合。参数 flags 的默认值是 QEventLoop::AllEvents,表示处理队列中的所有事件。枚举类型 QEventLoop::ProcessEventsFlag 有以下几种枚举值:

• QEventLoop::AllEvents:处理所有事件。

• QEventLoop::ExcludeUserInputEvents:排除用户输入事件,如键盘和鼠标的事件。

• QEventLoop::ExcludeSocketNotifiers:排除网络 socket 的通知事件。

• QEventLoop::WaitForMoreEvents:如果没有未处理的事件,等待更多事件。

QCoreApplication 还有一个派发事件的静态函数 sendPostedEvents,定义如下:

void QCoreApplication::sendPostedEvents(QObject *receiver = nullptr, int event_type = 0) 

参数 receiver 是接收事件的对象,event_type 是事件类型。这个函数的功能是把前面用静态函数 QCoreApplication::postEvent()发送到 Qt 事件队列里的事件立刻派发出去。如果不指定 event_ type,只指定 receiver,就派发所有给这个接收者的事件;如果 event_type 和 receiver 都不指定, 就派发所有用 QCoreApplication::postEvent()发布的事件。

二. 事件类和事件类型

事件是 QEvent 类或其派生类的实例,大多数的事件有其专门的类。QEvent 是所有事件类的基类,但它不是一个抽象类,它也可以用于创建事件。QEvent 有以下几个主要的接口函数:

void accept() //接受事件,设置事件对象的接受标志(accept flag)
void ignore() //忽略事件,清除事件对象的接受标志
bool isAccepted() //是否接受事件,true 表示接受,false 表示忽略
bool isInputEvent() //事件对象是不是 QInputEvent 或其派生类的实例
bool isPointerEvent() //事件对象是不是 QPointerEvent 或其派生类的实例
bool isSinglePointEvent() //事件对象是不是 QSinglePointEvent 或其派生类的实例
bool spontaneous() //是不是自生事件,也就是窗口系统的事件
QEvent::Type type() //事件类型

函数 type()返回事件的类型,返回值是枚举类型 QEvent::Type。每个事件都有唯一的事件类型, 也有对应的事件类,但是有的事件类可以处理多种类型的事件。例如,QMouseEvent 是鼠标事件 类,用它创建的事件的类型可以是鼠标双击事件 QEvent::MouseButtonDblClick 或鼠标移动事件 QEvent::MouseMove 等。

常见的事件类型及其所属的事件类如表所示:

常见的事件类型及其所属的事件类
事件类事件类型事件描述
QMouseEventQEvent::MouseButtonDblClick鼠标双击
QEvent::MouseButtonPress鼠标按键按下,可以是左键或右键
QEvent::MouseButtonRelease鼠标按键释放,可以是左键或右键
QEvent::MouseMove鼠标移动
QWheelEventQEvent::QWheelEvent鼠标滚轮滚动
QHoverEventQEvent::HoverEnter鼠标光标移动到组件上方并悬停(hover),组件需要设置 Qt::WA_Hover 属性才会产生悬停类的事件
QEvent::HoverLeave鼠标光标离开某个组件上方
QEvent::HoverMove鼠标光标在组件上方移动
QEnterEventQEvent::Enter鼠标光标进入组件或窗口边界范围内
QEventQEvent::Leave鼠标光标离开组件或窗口边界范围,注意这个事件类型使用 的事件类就是 QEvent
QKeyEventQEvent::KeyPress键盘按键按下
QEvent::KeyRelease键盘按键释放
QFocusEventQEvent::FocusIn组件或窗口获得键盘的输入焦点
QEvent::FocusOut组件或窗口失去键盘的输入焦点
QEvent::FocusAboutToChange组件或窗口的键盘输入焦点即将变化
QShowEventQEvent::Show窗口在屏幕上显示出来,或组件变得可见
QHideEventQEvent::Hide窗口在屏幕上隐藏(例如窗口最小化),或组件变得不可见
QMoveEventQEvent::Move组件或窗口的位置移动
QCloseEventQEvent::Close窗口被关闭,或组件被关闭,例如QTabWidget的一个页面被关闭
QPaintEventQEvent::Paint界面组件需要更新重绘
QResizeEventQEvent::Resize窗口或组件改变大小
QStatusTipEventQEvent::StatusTip请求显示组件的 statusTip 信息
QHelpEventQEvent::ToolTip请求显示组件的 toolTip 信息
QEvent::WhatsThis请求显示组件的 whatsThis 信息
QDragEnterEventQEvent::DragEnter在拖放操作中,鼠标光标移动到组件上方
QDragLeaveEventQEvent::DragLeave在拖放操作中,鼠标光标离开了组件
QDragMoveEventQEvent::DragMove拖放操作正在移动过程中
QDropEventQEvent::Drop拖放操作完成,即放下拖动的对象
QTouchEventQEvent::TouchBegin开始一个触屏事件序列(sequence)
QEvent::TouchCancel取消一个触屏事件序列
QEvent::TouchEnd结束一个触屏事件序列
QEvent::TouchUpdate触屏事件
QGestureEventQEvent::Gesture手势事件,能识别的手势有轻触、放大、扫屏等
QNativeGestureEventQEvent::NativeGesture操作系统检测到手势而产生的事件
QActionEventQEvent::ActionAdded运行 QWidget::addAction()函数时会产生这种事件
QEvent::ActionChangedAction 改变时触发的事件
QEvent::ActionRemoved移除 Action 时触发的事件

从表的内容可以看出,Qt 的事件类型非常丰富,除了常见的鼠标事件、键盘事件、窗口事件,还有拖放操作事件、触屏操作事件、手势事件等。还有一些用于图形/视图架构的事件类型没有在表 中列出。

QEvent 中有几个函数会判断事件是不是某种类的实例,例如函数 isPointerEvent()会判断事件对象是不是 QPointerEvent 或其派生类的实例。QInputEvent 类及其派生类比较多,这些类的继承关系如图所示。

三. 事件的处理

1.事件处理的基本过程

任何从 QObject 派生的类都可以处理事件,但其中主要是从 QWidget 派生的窗口类和界面组件类需要处理事件,因为大多数事件都是通过界面操作产生的,例如鼠标事件、按键事件等。

一个类接收到应用程序派发来的事件后,首先会由函数 event()处理。event()是 QObject 类中定义的一个虚函数,其函数原型定义如下:

bool QObject::event(QEvent *e) 

其中,参数 e 是事件对象,通过 e->type()就可以得到事件的具体类型。

任何从 QObject 派生的类都可以重新实现函数 event(),以便在接收到事件时进行处理。如果一个类重新实现了函数 event(),需要在函数 event()的实现代码里设置是否接受事件。QEvent 类有两个函数,函数 accept()接受事件,表示事件接收者会对事件进行处理;函数 ignore()忽略事件, 表示事件接收者不接受此事件。被接受的事件由事件接收者处理,被忽略的事件则传播到事件接收者的父容器组件,由父容器组件的 event()函数去处理,这称为事件的传播(propagation), 事件最后可能会传播给窗口。

2.QWidget 类的典型事件处理函数

QWidget 类是所有界面组件类的基类,它重新实现了函数 event(),并针对一些典型类型的事件定义了专门的事件处理函数,函数 event()会根据事件类型自动去运行相应的事件处理函数。例如,如果事件类型是 QEvent::MouseMove,对应的事件处理函数是 mouseMoveEvent();如果事件类型是 QEvent::Paint,对应的事件处理函数是 paintEvent()。这两个事件处理函数的定义如下:

void QWidget::mouseMoveEvent(QMouseEvent *event) //对应 QEvent::MouseMove 类型事件
void QWidget::paintEvent(QPaintEvent *event) //对应 QEvent::Paint 类型事件

这两个函数中的参数 event 就是具体事件类的对象。QWidget 中定义的典型事件处理函数都是受保护的虚函数,所以不能被外部类调用,但是可以被派生类重新实现。

如果一个自定义的类从 QWidget 派生而来,例如基于 QWidget 的窗口类 Widget,如果我们不重新实现函数 event(),而只是要对一个典型事件进行处理,就可以重新实现 QWidget 类中定义的典型事件的处理函数。例如,我们要在窗口上绘制背景图片,就可以在窗口类 Widget 中重新定义事件处理函数 paintEvent(),在这个函数的代码里实现绘制窗口背景图片。

QWidget 类中定义了很多典型事件的处理函数,这些函数都有一个参数 event,它是具体事件类的对象。这些典型事件的处理函数如表所示,一个函数对应一个类型的事件,但是多个函数的参数 event 的类型可能是一样的,因为一个事件类可能会处理多个类型的事件。

QWidget 类中定义的典型事件的处理函数
事件处理函数名对应的事件类型参数 event 的类型事件描述
mouseDoubleClickEvent()QEvent::MouseButtonDblClickQMouseEvent鼠标双击
mousePressEvent()QEvent::MouseButtonPressQMouseEvent鼠标按键按下,可以是左键或右键
mouseReleaseEvent()QEvent::MouseButtonReleaseQMouseEvent鼠标按键释放,可以是左键或右键
mouseMoveEvent()QEvent::MouseMoveQMouseEvent鼠标移动
wheelEvent()QEvent::QWheelEventQWheelEvent鼠标滚轮滚动
enterEvent()QEvent::EnterQEnterEvent鼠标光标进入组件或窗口边界范围内
leaveEvent()QEvent::LeaveQEvent鼠标光标离开组件或窗口边界范围
keyPressEvent()QEvent::KeyPressQKeyEvent键盘按键按下
keyReleaseEvent()QEvent::KeyReleaseQKeyEvent键盘按键释放
focusInEvent()QEvent::FocusInQFocusEvent组件或窗口获得键盘的输入焦点
focusOutEvent()QEvent::FocusOutQFocusEvent组件或窗口失去键盘的输入焦点
showEvent()QEvent::ShowQShowEvent窗口在屏幕上显示出来,或组件变得可见
hideEvent()QEvent::HideQHideEvent窗口在屏幕上隐藏(例如窗口最小化), 或组件变得不可见
moveEvent()QEvent::MoveQMoveEvent组件或窗口的位置移动
closeEvent()QEvent::CloseQCloseEvent窗口被关闭,或组件被关闭
paintEvent()QEvent::PaintQPaintEvent界面组件需要更新重绘
resizeEvent()QEvent::ResizeQResizeEvent窗口或组件改变大小
dragEnterEvent()QEvent::DragEnterQDragEnterEvent在拖放操作中,鼠标光标移动到组件上方
dragLeaveEvent()QEvent::DragLeaveQDragLeaveEvent 在拖放操作中,鼠标光标离开了组件
dragMoveEvent()QEvent::DragMoveQDragMoveEvent拖放操作正在移动过程中
dropEvent()QEvent::DropQDropEvent拖放操作完成,即放下拖动的对象

如果从 QWidget 或其派生类继承自定义了一个类,需要对表中的某种类型的事件进行处理, 那么只需重新实现表中与事件类型对应的事件处理函数。如果需要处理的事件在 QWidget 中没有定义事件处理函数,就需要重新实现函数 event(),判断事件类型后调用自己定义的事件处理函数。

四. 典型事件处理示例

创建一个 GUI 应用程序,窗口基类选择 QWidget。在 UI 可视化设计时,在窗体上放置了一个 QPushButton 和一个 QLabel,不使用任何布局。创建资源文件 res.rc,加载一个图片文件用于绘制窗口背景。在窗口类 Widget 中重新定义了一些事件处理函数,运行时界面如图所示。

程序在函数 paintEvent()里将资源文件中的图片绘制在窗口上;在函数 keyPressEvent()里判断按键,用 W、S、A、D 键或上、下、左、右方向键移动按钮;在窗口上点击鼠标时,标签会移动到鼠标光标处,并显示事件的4 个坐标函数的返回值。

为了实现这些功能,在 Widget 类的 protected 部分重新定义几个事件处理函数,定义如下:

class Widget : public QWidget 
{ 
 Q_OBJECT 
protected: 
 void paintEvent(QPaintEvent *event); 
 void closeEvent(QCloseEvent *event); 
// void keyReleaseEvent(QKeyEvent *event); 
 void keyPressEvent(QKeyEvent *event); 
 void showEvent(QShowEvent *event); 
 void hideEvent(QHideEvent *event); 
 void mousePressEvent(QMouseEvent *event); 
public: 
 Widget(QWidget *parent = 0); 
private: 
 Ui::Widget *ui; 
}; 

在 Widget 类的构造函数里不需要添加代码进行处理,下面介绍各事件处理函数的代码。

1.事件处理函数 paintEvent()  

在窗口需要重绘时,应用程序会向窗口发送 QEvent::Paint 类型的事件,窗口对象会自动运行事件处理函数 paintEvent()。我们重新实现这个函数,在窗口上绘制背景图片,代码如下:

void Widget::paintEvent(QPaintEvent *event) 
{ 
 Q_UNUSED(event); 
 QPainter painter(this); 
 painter.drawPixmap(0,0,this->width(), this->height(), 
 QPixmap(":/pics/images/background.jpg")); 
// QWidget::paintEvent(event); 
} 

这个函数的功能是将资源文件中的图片 background.jpg 绘制到窗口的整个区域,绘图时使用了窗口的画笔对象 painter.

被注释的语句表示运行父类的 paintEvent()函数,以便父类执行其内建的一些操作。如果父类的事件函数里没有特殊的处理,可以不运行这行代码。

2.事件处理函数 closeEvent()

当窗口被关闭,例如点击窗口右上角的关闭按钮或调用 QWidget 的 close()函数时,系统会产生 QEvent::Close 类型的事件,事件处理函数 closeEvent()会被自动运行。我们重新实现这个函数, 使用一个对话框询问是否关闭窗口,代码如下:

void Widget::closeEvent(QCloseEvent *event) 
{ 
 QString dlgTitle= "消息框"; 
 QString strInfo = "确定要退出吗?"; 
 QMessageBox::StandardButton result=QMessageBox::question(this, dlgTitle, strInfo, 
 QMessageBox::Yes|QMessageBox::No |QMessageBox::Cancel); 
 if (result == QMessageBox::Yes) 
 event->accept(); //接受事件,窗口可以被关闭
 else 
 event->ignore(); //忽略事件,窗口不能被关闭
} 

这里使用了 QMessageBox 对话框询问是否关闭窗口。 关闭窗口时就会触发 closeEvent()函数.

这个函数中调用的 accept()和 ignore()是 QCloseEvent 的父类 QEvent中定义的函数。accept()表示接受事件,窗口可以被关闭;ignore() 表示不接受事件,事件被传播到父容器,但是窗口不再有父容器,所以是忽略事件,窗口不会被关闭。

3.事件处理函数 mousePressEvent()

在窗口上点击鼠标按键时,会触发运行事件处理函数 mousePressEvent()。函数代码如下:

void Widget::mousePressEvent(QMouseEvent *event) 
{ 
 if (event->button() == Qt::LeftButton) //鼠标左键
 { 
 QPoint pt= event->pos(); //点击点在窗口上的相对坐标
 QPointF relaPt= event->position(); //相对坐标
 QPointF winPt= event->scenePosition(); //相对坐标
 QPointF globPt= event->globalPosition(); //屏幕或虚拟桌面上的绝对坐标
 QString str= QString::asprintf("pos()=(%d,%d)", pt.x(),pt.y()); 
 str= str + QString::asprintf("\nposition()=(%.0f,%.0f)", relaPt.x(),relaPt.y()); 
 str= str + QString::asprintf("\nscenePosition()=(%.0f,%.0f)", 
 winPt.x(),winPt.y()); 
 str= str + QString::asprintf("\nglobalPosition()=(%.0f,%.0f)", 
 globPt.x(),globPt.y()); 
 ui->labMove->setText(str); 
 ui->labMove->adjustSize(); //自动调整组件大小
 ui->labMove->move(event->pos()); //标签移动到鼠标光标处
 } 
 QWidget::mousePressEvent(event); 
} 

参数 event 是 QMouseEvent 类型,QMouseEvent 有几个接口函数表示按下的按键的信息和鼠标坐标信息。除了函数 pos(),其他函数都是 QMouseEvent 的父类 QSinglePointEvent 中定义的。

• 函数 button():返回值是枚举类型 Qt::MouseButton,表示被按下的是哪个鼠标按键,有 Qt::LeftButton、Qt::RightButton、Qt::MiddleButton 等多种枚举值。

• 函数 buttons():返回值是标志类型 Qt::MouseButtons,也就是枚举类型 Qt::MouseButton 的枚举值组合,可用于判断多个按键被按下的情况,例如判断鼠标左键和右键同时被按下的 if 语句如下所示:

if ((event->buttons() & Qt::LeftButton) && (event->buttons() & Qt::RightButton))

• 函数 pos():返回值是 QPoint 类型,是鼠标光标在接收此事件的组件上的相对坐标。

• 函数 position():返回值是 QPointF 类型,是鼠标光标在接收此事件的组件上的相对坐标。

• 函数 scenePosition():返回值是 QPointF 类型,是鼠标光标在接收此事件的窗口或场景上的相对坐标。

• 函数 globalPosition():返回值是 QPointF 类型,是鼠标光标在屏幕或虚拟桌面上的绝对坐标。

在本示例中,接收 QMouseEvent 事件的是一个窗口,所以 pos()、position()和 scenePosition() 返回的结果是相同的,都表示鼠标光标在窗口上的相对坐标,也就是相对于窗口左上角的位置。

4.事件处理函数 keyPressEvent()或 keyReleaseEvent()

函数 keyPressEvent()在键盘上的按键按下时被触发运行,函数 keyReleaseEvent()在按键释放时 被触发运行。为函数 keyPressEvent()编写如下代码:

void Widget::keyPressEvent(QKeyEvent *event) 
{ 
 QPoint pt= ui->btnMove->pos(); 
 if ((event->key()==Qt::Key_A) || (event->key()==Qt::Key_Left)) 
 ui->btnMove->move(pt.x()-20, pt.y()); 
 else if((event->key()==Qt::Key_D) || (event->key()==Qt::Key_Right)) 
 ui->btnMove->move(pt.x()+20, pt.y()); 
 else if((event->key()==Qt::Key_W) || (event->key()==Qt::Key_Up)) 
 ui->btnMove->move(pt.x(), pt.y()-20); 
 else if((event->key()==Qt::Key_S) || (event->key()==Qt::Key_Down)) 
 ui->btnMove->move(pt.x(), pt.y()+20); 
 
 event->accept(); //接受事件,不会再传播到父容器组件
} 

这个函数的参数 event 是 QKeyEvent 类型,它有两个主要的接口函数反映了按下的按键的信息。 • 函数 key():返回值类型是 int 类型,表示被按下的按键,与枚举类型 Qt::Key 的枚举值对应。枚举类型 Qt::Key 包括键盘上所有按键的枚举值,如 Qt::Key_Escape、Qt::Key_Delete、Qt::Key_Alt、Qt::Key_F1、Qt::Key_A 等.

• 函数 modifiers():返回值是枚举类型 Qt::KeyboardModifier 的枚举值组合,表示一些用于组合使用的按键,如 Ctrl、Alt、Shift 等按键。例如,判断 Ctrl+Q 快捷键是否被按下的语句如下:

if ((event->key()==Qt::Key_Q) && (event->modifiers() & Qt::ControlModifier))

这段程序是期望在按下 W、S、A、D 键或上、下、左、右方向键时,窗口上的按钮 btnMove 能上、下、左、右地移动位置。但是我们发现在使用函数 keyPressEvent()时,只有按下 W、S、A、 D 键有效,如果使用的是函数 keyReleaseEvent(),则按下 W、S、A、D 键和上、下、左、右方向键都有效。这说明按下上、下、左、右方向键时不会产生 QEvent::KeyPress 类型的事件,只会在按键释放时产生 QEvent::KeyRelease 类型的事件。

5.事件处理函数 showEvent()和 hideEvent()

在窗口显示/隐藏或组件的 visible 属性变化时,事件处理函数 showEvent()或 hideEvent()会被触发运行。重新实现这两个函数,代码如下:

void Widget::showEvent(QShowEvent *event) 
{ 
 Q_UNUSED(event); 
 qDebug("showEvent()函数被触发"); 
} 
void Widget::hideEvent(QHideEvent *event) 
{ 
 Q_UNUSED(event); 
 qDebug("hideEvent()函数被触发"); 
} 

程序运行时我们会发现,应用程序最小化或窗口关闭时会触发函数 hideEvent(),在系统任务栏上点击应用程序重新显示其窗口时,会触发函数 showEvent()。

(二). 事件与信号

事件和信号的区别在于, 事件通常是由窗口系统或应用程序产生的, 信号则是 Qt 定义或用户自定义的。Qt 为界面组件定义的信号通常是对事件的封装,例如 QPushButton 的 clicked()信号可以看作对 QEvent::MouseButtonRelease 类型事件的封装。

在使用界面组件为交互操作编程的时候,我们通常选择合适的信号,为该信号编写槽函数。 但是Qt 的界面组件只将少数事件封装成了信号,对于某些事件可能缺少对应的信号。例如, 下图所示的 QLabel 的 Go to slot 对话框,这里显示了 QLabel 所有 可用的信号,其中没有与鼠标双击事件对应的信号。 这种情况下,我们可以从 QLabel 继承定义一个新的标签类,通过自定义信号和事件处理,使新的标签类具有处理鼠标双击事件的信号。

 一. 函数 event()的作用

应用程序派发给界面组件的事件首先会由其函数 event()处理,如果函数 event()不做任何处理,组件就会自动调用 QWidget 中与事件类型对应的默认事件处理函数。从 QWidget 派生的界面组件类一般不需要重新实现函数 event(), 如果要对某种类型事件进行处理,可以重新实现对应的事件处理函数。

QWidget 类针对一些典型事件编写了事件处理函数,但是某些类型的事件没有对应的事件处理函数。例如,对于 QEvent::HoverEnter 和 QEvent::HoverLeave 类型的事件,QWidget 类中就没有对应的事件处理函数。这种情况下,如果要对QEvent::HoverEnter和QEvent::HoverLeave 类型的事件进行处理,就需要自定义一个类,重新实现函数 event(),判断事件类型,针对 QEvent::HoverEnter 和 QEvent::HoverLeave 类型的事件进行相应的处理。

二. 事件与信号编程示例

1.示例功能概述

示例项目演示如何针对事件设计自定义信号,以及如何针对事件设计自定义的事件处理函数。示例运行时界面如图所示:

我们设计一个标签类 TMyLabel,它从 QLabel 继承而来。TMyLabel 为鼠标双击事件定义了信号 doubleClicked(),并且对 QEvent::HoverEnter 和 QEvent::HoverLeave 类型的事件进行了处理。鼠标光标移动到标签上(HoverEnter 事件)时,标签的文字变为红色;鼠标光标离开标签 (HoverLeave 事件)时,标签的文字变为黑色。

2.设计新的标签类

新建一个 GUI 项目,窗口基类选择 QWidget。然后创建一个 C++类 TMyLabel,设置基类为 QLabel,勾选 Add Q_OBJECT 复选框。Qt Creator 会自动创建文件 tmylabel.h 和 tmylabel.cpp,并将其添加到项目里。TMyLabel 类的定义代码如下:

class TMyLabel : public QLabel 
{ 
 Q_OBJECT 
public: 
 TMyLabel(QWidget *parent = nullptr); //构造函数需要按此参数改写
 bool event(QEvent *e); //重新实现 event()函数
protected: 
 void mouseDoubleClickEvent(QMouseEvent *event); //重新实现鼠标双击事件的默认处理函数
signals: 
 void doubleClicked(); //自定义信号
};

TMyLabel 类里重新定义了函数 event()和 mouseDoubleClickEvent(),定义了一个信号doubleClicked()。

注意,TMyLabel 的构造函数需要改写为代码中的参数形式。使用创建 C++类向导自动生成的 TMyLabel 的构造函数没有任何参数,那样是有问题的,因为界面组件必须有一个父容器组件。

文件 tmylabel.cpp 里 TMyLabel 类的实现代码如下:

TMyLabel::TMyLabel(QWidget *parent):QLabel(parent) 
{ 
 this->setAttribute(Qt::WA_Hover,true); //必须设置这个属性,才能产生 hover 事件
} 
bool TMyLabel::event(QEvent *e) 
{ 
 if(e->type()== QEvent::HoverEnter) //鼠标光标移入
 { 
 QPalette plet= this->palette(); 
 plet.setColor(QPalette::WindowText, Qt::red); 
 this->setPalette(plet); 
 } 
 else if (e->type()== QEvent::HoverLeave) //鼠标光标移出
 { 
 QPalette plet= this->palette(); 
 plet.setColor(QPalette::WindowText, Qt::black); 
 this->setPalette(plet); 
 } 
 return QLabel::event(e); //运行父类的 event(),处理其他类型事件
} 
void TMyLabel::mouseDoubleClickEvent(QMouseEvent *event) 
{ 
 Q_UNUSED(event); 
 emit doubleClicked(); //发射信号
} 

在构造函数里,我们将 TMyLabel 的 Qt::WA_Hover 属性设置为 true(默认值是 false)。这样,鼠标光标移入和移出 TMyLabel 组件时,才会分别产生 QEvent::HoverEnter 和 QEvent::HoverLeave类型的事件。

函数 event()里会判断事件的类型,如果事件类型是 QEvent::HoverEnter,就把组件的文字颜色设置为红色,如果事件类型是 QEvent::HoverLeave,就把组件的文字颜色设置为黑色。注意,函数 event()里的最后一行代码是必需的,它表示要运行父类 QLabel 的 event()函数,因为在 TMyLabel的 event()函数里只对两个事件进行了处理,对于其他典型事件,还需要交给父类去处理。

mouseDoubleClickEvent()是鼠标双击事件的默认处理函数,重新实现的这个函数里就发射了自定义信号 doubleClicked()。这样,我们就把鼠标双击事件转换为发射一个信号,如果要对 TMyLabel 组件的鼠标双击事件进行处理,只需为其 doubleClicked()信号编写槽函数即可。

3.示例程序设计

设计好 TMyLabel 类的程序后,我们再设计窗口的程序。在 UI 可视化设计时,在窗体上放置一个 QLabel 组件,设置其对象名称为 lab,需要用提升法将它提升成 TMyLabel 类。在组件 lab 的快捷菜单中点击 Promote to 菜单项,会出现下图所示的对话框。

组件的基类名称是 QLabel,这是标签组件原来的类名称。在 Promoted class name 编辑框里输入TMyLabel,也就是需要提升成的类名称,Header file 编辑框里会自动显示头文件名。勾选 Global include 复选框,点击 Add 按钮,会将此提升类的设置添加到上面的列表里,这样项目里其他 QLabel 组件提升为 TMyLabel 类时就可以直接应用此设置。最后, 点击 Promote 按钮,此对话框会关闭,在属性编辑器里会看到,组件 lab 的类名称变成了 TMyLabel。

将组件 lab 提升为 TMyLabel 类后,打开 lab 的 Go to slot 对话框,可以发现对话框里并没有在TMyLabel 中自定义的信号 doubleClicked()。使用提升法提升组件的类后,提升成的类里新定义的 属性、信号等不会在 Qt Creator 环境里显示出来。

在窗口类 Widget 里增加两项定义,Widget 类的定义代码如下:

class Widget : public QWidget 
{ 
 Q_OBJECT 
public: 
 Widget(QWidget *parent = nullptr); 
protected: 
 void mouseDoubleClickEvent(QMouseEvent *event); //在窗口上双击时的响应
private slots: 
 void do_doubleClick(); //与 lab 的 doubleClicked()信号关联
private: 
 Ui::Widget *ui; 
}; 

Widget 类重新定义了函数 mouseDoubleClickEvent(),这是双击窗口时的事件响应函数。设计了一个自定义槽函数 do_doubleClick(),用于与界面组件 lab 的 doubleClicked()信号关联。

文件 widget.cpp 中的实现代码如下:

Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) 
{ 
 ui->setupUi(this); 
 connect(ui->lab, SIGNAL(doubleClicked()), this,SLOT(do_doubleClick())); 
} 
void Widget::mouseDoubleClickEvent(QMouseEvent *event) 
{//双击窗口时的响应
 Q_UNUSED(event); 
 ui->lab->setText("窗口被双击了"); 
 ui->lab->adjustSize(); 
} 
void Widget::do_doubleClick() 
{//双击标签时的响应
 ui->lab->setText("标签被双击了,信号的槽函数响应"); 
 ui->lab->adjustSize(); 
} 

在 Widget 类的构造函数里,标签 lab 的 doubleClicked()信号与自定义槽函数 do_doubleClick() 关联。双击窗口时会触发窗口的事件处理函数 mouseDoubleClickEvent(),双击标签 lab 时会触发槽函数 do_doubleClick(),它们在标签 lab 上显示不同的文字。

程序运行时,把鼠标光标移动到标签上,标签的文字会变成红色;鼠标光标移出标签时,标签的文字会变成黑色,这是因为TMyLabel类先后对QEvent::HoverEnter和QEvent::HoverLeave 两种类型的事件进行了处理。

(三). 事件过滤器

一个界面组件如果要对事件进行处理,需要从父类继承定义一个新类,在新类里编写程序直接处理事件,或者将事件转换为信号。

如果不想定义一个新的类,可以用事件过滤器(event filter)对界面组件的事件进行处理。事件过滤器是QObject 提供的一种处理事件的方法,它可以将一个对象的事件委托给另一个对象来监视并处理。

一. 事件过滤器工作原理

前两部分已经介绍了事件产生、派发、传播和处理的基本原理,产生的事件会被派发给接收者,由接收者的 event()函数去处理。从(二)的示例可以看到,如果要对一个标签的某些事件进行处理,需要重新定义一个标签类,在标签类里重新实现函数 event()或对应的事件处理函数。

QObject 还提供了另一种处理事件的方法:事件过滤器。它可以将一个对象的事件委托给另一个对象来监视并处理。例如, 一个窗口可以作为其界面上的 QLabel 组件的事件过滤器,派发给 QLabel 组件的事件由窗口去处理,这样,就不需要为了处理某种事件而新定义一个标签类。

要实现事件过滤器功能,需要完成两项操作:

(1)被监视对象使用函数 installEventFilter()将自己注册给监视对象,监视对象就是事件过滤器。 (2)监视对象重新实现函数 eventFilter(),对监视到的事件进行处理。

installEventFilter()和 eventFilter()都是 QObject 类定义的公有函数。函数 installEventFilter()的原型定义如下:

void QObject::installEventFilter(QObject *filterObj) 

被监视的对象调用函数 installEventFilter(),将对象 filterObj 设置为自己的事件过滤器。

函数 eventFilter()的原型定义如下:

bool QObject::eventFilter(QObject *watched, QEvent *event) 

作为事件过滤器的监视对象需要重新实现函数 eventFilter(),参数 watched 是被监视对象,event是产生的事件。这个函数有一个返回值,如果返回 true,事件就不会再传播给其他对象,事件处理结束;如果返回 false,事件会继续传播给事件接收者做进一步处理。

二. 事件过滤器编程示例

使用事件过滤器可以比较灵活地实现对事件的处理,一般是用窗口对象作为界面上的一些组件的事件过滤器,这样就不需要为了处理某个事件而新定义一个类。

示例运行时界面如图所示,UI 可视化设计时就是用两个 QLabel 组件在窗口上垂直布局。窗口的基类是 QWidget,在窗口类 Widget 中重新定义了函数 eventFilter(),无须进行其他定义。Widget 类的构造函数以及函数 eventFilter()的代码如下:

Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) 
{ 
 ui->setupUi(this); 
 ui->labHover->installEventFilter(this); //安装事件过滤器
 ui->labDBClick->installEventFilter(this); //安装事件过滤器
} 
bool Widget::eventFilter(QObject *watched, QEvent *event) 
{ 
//上面的 QLabel 组件的事件处理
 if (watched == ui->labHover) 
 { 
 if (event->type()== QEvent::Enter) //鼠标光标移入
 ui->labHover->setStyleSheet("background-color: rgb(170, 255, 255);"); 
 else if (event->type()== QEvent::Leave) //鼠标光标离开
 { 
 ui->labHover->setStyleSheet(""); 
 ui->labHover->setText("靠近我,点击我"); 
 } 
 else if (event->type()== QEvent::MouseButtonPress) //鼠标键按下
 ui->labHover->setText("button pressed"); 
 else if (event->type()== QEvent::MouseButtonRelease) //鼠标键释放
 ui->labHover->setText("button released"); 
 } 
//下面的 QLabel 组件的事件处理
 if (watched == ui->labDBClick) 
 { 
 if (event->type()== QEvent::Enter) //鼠标光标移入
 ui->labDBClick->setStyleSheet("background-color: rgb(85, 255, 127);"); 
 else if (event->type()== QEvent::Leave) //鼠标光标离开
 { 
 ui->labDBClick->setStyleSheet(""); 
 ui->labDBClick->setText("可双击的标签"); 
 } 
 else if (event->type()== QEvent::MouseButtonDblClick) //鼠标双击
 ui->labDBClick->setText("double clicked"); 
 } 
 return QWidget::eventFilter(watched,event); //运行父类的 eventFilter()函数
// return true; //有问题,不能直接返回 true 
} 

在 Widget 类的构造函数里,两个 QLabel 组件运行了函数 installEventFilter(),将窗口对象设置为自己的事件过滤器。这样, 应用程序派发给这两个QLabel组件的事件就会被窗口对象监视和处理。

Widget 类重新实现了函数 eventFilter(),对被监视对象的事件进行处理。如果被监视对象 watched 是标签对象 labHover,在鼠标光标移入时设置它的背景色为亮蓝色,在鼠标光标移出时恢复默认背景色,在鼠标键按下时显示“button pressed”,在鼠标键释放时显示“button released”。 设置标签的背景色时,我们使用了 QWidget 的 setStyleSheet()函数,这个函数用于设置样式表。

当被监视对象 watched 是标签对象 labDBClick 时,代码的功能是相似的。

函数 eventFilter()最后一行代码是运行父类的 eventFilter()函数。不能用 return true 替代这一行代码。如果函数 eventFilter()直接返回 true,事件过滤器拦截的事件将不会传播给被监视对象,而在这个类的 eventFilter()函数里,我们只处理了被监视对象的少数几个事件,例如 QEvent::Paint 类型的事件就没有处理。如果直接返回 true,程序运行时界面上根本就不显示标签的文字。

(四). 拖放事件与拖放操作

拖放(drag and drop)操作是 GUI 应用程序中经常使用的一种操作,例如将视频文件拖放到一个视频播放软件上,软件就可以播放此文件。

一. 拖放操作相关事件

拖放由两个操作组成:拖动(drag)和放置(drop)。被拖动的组件称为拖动点 (drag site),  接收拖动操作的组件称为放置点(drop site)。拖动点与放置点可以是不同的组件,甚至是不同的应用程序,也可以是同一个组件,例如一个目录树内的节点的拖放操作。

整个拖放操作可以分解为两个过程。

(1)拖动点启动拖动操作。被拖动组件通过 mousePressEvent()和 mouseMoveEvent()这两个事件处理函数的处理,检测到鼠标左键按下并移动时就可以启动拖动操作。启动拖动操作需要创建一个 QDrag 对象描述拖动操作,以及创建一个 QMimeData 类的对象用于存储拖动操作的格式信息和数据,并将其赋值为 QDrag 对象的 mimeData 属性。

(2)放置点处理放置操作。当拖动操作移动到放置点范围内时,首先触发 dragEnterEvent()事件处理函数,在此函数里一般要通过 QDrag 对象的 mimeData 数据判断拖动操作的来源和参数,以 决定是否接受此拖动操作。只有被接受的拖动操作才可以被放置,并触发 dropEvent()事件处理函数。 函数 dropEvent()用于处理放置时的具体操作,例如根据拖动来的文件类型执行相应的操作。

从这个过程可以看到,要实现完整的拖放操作需要对各种事件进行处理,拖动点和放置点最好是各自实现相关事件处理的类,如果要在同一个窗口上实现这些事件的处理,需要用到事件过滤器。

QWidget 类有一个属性 acceptDrops,如果设置为 true,那么对应的这个组件就可以作为一个放置点。属性 acceptDrops 的默认值为 false。QWidget 类中没有定义拖动操作相关的函数,所以一般的界面组件是不能作为拖动点的。QAbstractItemView类定义了更多与拖动操作相关的函数, 所以,QListWidget、QTreeWidget、QTableWidget 等组件既可以作为拖动点,也可以作为放置点

二. 外部文件拖放操作示例

1.示例功能和窗口界面可视化设计

示例项目演示一个放置点功能的实现。如图所示,从 Windows资源管理器中将一个JPG图片文件拖动到程序窗口上,程序会显示拖动事件的 mimeData 数据,并显示图片。程序窗口只接受 JPG 文件,其他格式文件一律不接受。

窗口基类是 QWidget。在 UI 可视化设计时,在窗体上只放置一个 QPlainTextEdit 组件和一个QLabel 组件,没有使用水平布局,而是固定大小。QLabel 组件的 scaledContents 属性设置为 true,使图片适应 QLabel 组件的大小。

2.窗口类定义和初始化

在窗口类 Widget 中的 protected 部分定义了需要重新实现的 3 个事件处理函数,定义如下:

protected: 
 void dragEnterEvent(QDragEnterEvent *event); //拖动文件进入窗口时触发的事件处理函数
 void resizeEvent(QResizeEvent *event); //窗口改变大小时触发的事件处理函数
 void dropEvent(QDropEvent *event); //拖动文件在窗口上放置时触发的事件处理函数
窗口类 Widget 的构造函数代码如下:
Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) 
{ 
 ui->setupUi(this); 
 ui->labPic->setScaledContents(true); //图片适应组件大小
 this->setAcceptDrops(true); //由窗口接受放置操作
 ui->plainTextEdit->setAcceptDrops(false); //不接受放置操作,由窗口去处理
 ui->labPic->setAcceptDrops(false); //不接受放置操作,由窗口去处理
} 

注意,我们将界面上的组件 plainTextEdit 和 labPic 的 acceptDrops 属性都设置为 false,但是窗口的 acceptDrops 属性设置为 true,这样,在这两个组件上的拖放操作事件都会自动传播给窗口, 由窗口去处理。所以,我们只需在 Widget 类中定义事件处理函数,而不需要将窗口注册为这两个界面组件的事件过滤器,从而简化了处理流程。

3.函数 dragEnterEvent()的实现

从 Windows 资源管理器拖动一个 JPG 文件到本示例窗口上时,会触发窗口的 dragEnterEvent()事件处理函数,该函数的代码如下:

void Widget::dragEnterEvent(QDragEnterEvent *event) 
{ 
 //显示 MIME 信息
 ui->plainTextEdit->clear(); 
 ui->plainTextEdit->appendPlainText("dragEnterEvent 事件 mimeData()->formats()"); 
 for(int i=0; i<event->mimeData()->formats().size(); i++) 
 ui->plainTextEdit->appendPlainText(event->mimeData()->formats().at(i)); 
 
 ui->plainTextEdit->appendPlainText("\n dragEnterEvent 事件 mimeData()->urls()"); 
 for(int i=0; i<event->mimeData()->urls().size(); i++) 
 { 
 QUrl url= event->mimeData()->urls().at(i); //带路径文件名
 ui->plainTextEdit->appendPlainText(url.path()); 
 } 
 
 if (event->mimeData()->hasUrls()) 
 { 
 QString filename= event->mimeData()->urls().at(0).fileName(); //获取文件名
 QFileInfo fileInfo(filename); //获取文件信息
 QString ext= fileInfo.suffix().toUpper(); //获取文件后缀
 if (ext == "JPG") 
 event->acceptProposedAction(); //接受拖动操作
 else 
 event->ignore(); //忽略事件
 } 
 else 
 event->ignore(); 
}

事件处理函数 dragEnterEvent()的主要功能一般是通过读取拖动事件的 mimeData 属性的内容,判断该拖动操作是不是所需的来源,以决定是否允许此拖动被放置。

函数 dragEnterEvent()的输入参数 event 是 QDragEnterEvent 类型指针,event->mimeData()返回一个 QMimeData 对象,这个对象记录了拖动操作数据源的一些关键信息。

多用途互联网邮件扩展(multipurpose internet mail extensions,MIME)被设计的最初目的是在发送电子邮件时附加多媒体数据,使邮件客户端程序能根据其类型进行处理。QMimeData 是对MIME 数据的封装,在拖放操作和剪贴板操作中都用 QMimeData 类描述传输的数据。

一个 QMimeData 对象可能用多种格式存储同一数据。函数 QMimeData::formats()返回对象支持的MIME 格式的字符串列表。示例程序将函数 formats()返回的格式全部显示出来。在本示例程序运行时, 从 Windows 资源管理器中将一个 JPG 文件拖放到窗口上时,函数 formats()返回的格式列表如下:

application/x-qt-windows-mime;value="Shell IDList Array" 
application/x-qt-windows-mime;value="UsingDefaultDragImage" 
application/x-qt-windows-mime;value="DragImageBits" 
application/x-qt-windows-mime;value="DragContext" 
application/x-qt-windows-mime;value="DragSourceHelperFlags" 
application/x-qt-windows-mime;value="InShellDragLoop" 
text/uri-list 
application/x-qt-windows-mime;value="FileName" 
application/x-qt-windows-mime;value="FileContents" 
application/x-qt-windows-mime;value="FileNameW" 
application/x-qt-windows-mime;value="FileGroupDescriptorW" 

其中,application/x-qt-windows-mime 是 Windows 平台上自定义的 MIME 格式;text/uri-list 是标准 的 MIME 格式,表示 URL 或本机上的文件来源。本示例接收从 Windows 资源管理器拖动来的一 个 JPG 文件,所以 MIME 的格式中有 text/uri-list。

知道 MIME 的数据格式是 text/uri-list 后,就可以用 QMimeData 的函数 urls()获取一个列表。程序中用代码显示了函数 urls()返回的列表的内容。函数 QMimeData::urls()返回的结果是QUrl 类的列表数据,函数 QUrl::path()返回 URL 的路径,对于本机上的文件就是带路径的文件名。本示 例在 Windows 上运行时返回的文件名类似于下面的字符串,在字符串开头有一个额外的“/”:

/C:/Users/wwb/Pictures/Saved Pictures/IMG_110946.jpg 

QMimeData 对常见的 MIME 格式有相应的判断函数、获取数据的函数和设置函数,如表所示。表中仅列出函数名,省略了函数的输入输出参数。

QMimeData 对常见 MIME 格式的判断、获取数据和设置函数
MIME 格式判断函数获取数据的函设置函数
text/plainhasText()text()setText()
text/htmlhasHtml()html()setHtml()
text/uri-listhasUrls()urls()setUrls()
image/*hasUrls()imageData()setImageData()
application/x-colorhasColor()colorData()setColorData()

我们在函数 dragEnterEvent()中判断 MIME 数据格式以及来源文件是否为 JPG 文件,然后调用 QDragEnterEvent 的 acceptProposedAction()函数或 ignore()函数进行相应的处理。

• 函数 acceptProposedAction()表示接受拖动操作,允许后续的放置操作。

• 函数 ignore()表示不接受拖动操作,不允许后续的放置操作。

程序中用到了 QFileInfo 类,这个类用于获取文件信息。

4.函数 dropEvent()的实现

当一个被接受的拖动操作在窗口上放置时,会触发事件处理函数 dropEvent(),函数的代码如下:

void Widget::dropEvent(QDropEvent *event) 
{ 
 QString filename= event->mimeData()->urls().at(0).path(); //完整文件名
 filename= filename.right(filename.length()-1); //去掉最左边的“/”
 QPixmap pixmap(filename); 
 ui->labPic->setPixmap(pixmap); 
 event->accept(); 
}

在函数 dragEnterEvent()中被接受的拖动操作在放置时才会触发事件处理函数 dropEvent(),所以在此函数里无须再进行 MIME 格式判断。程序的关键是通过下面的代码获取拖动操作的源文件的完整文件名:

QString filename= event->mimeData()->urls().at(0).path(); //完整文件名

在 Windows 平台上,返回的字符串 filename 的开头有一个额外的“/”,通过字符串处理去掉此字符以得到正确的文件名,然后在窗口上的标签组件 labPic 上显示此图片。

5.函数 resizeEvent()的实现

在窗口改变大小时,会触发事件处理函数 resizeEvent(),该函数代码如下:

void Widget::resizeEvent(QResizeEvent *event) 
{ 
 QSize sz= ui->plainTextEdit->size(); 
 ui->plainTextEdit->resize(this->width()-10,sz.height()); //只改变宽度
 ui->labPic->resize(this->width()-10,this->height()-sz.height()-20); 
 //改变宽度和高度
 event->accept(); 
}

在可视化设计本示例的窗口界面时,我们没有使用布局管理,那么在改变窗口大小时,界面上的两个组件不能自动改变大小。所以,我们重新实现了事件处理函数 resizeEvent(), 在窗口大小改变时, 根据窗口的宽度和高度来改变组件 plainTextEdit 的宽度以及改变组件 labPic 的宽度和高度,使它们随着窗口大小的改变而改变。

(五).  具有拖放操作功能的组件

Qt 类库中的一些类实现了完整的拖放操作功能,例如 QLineEdit 、 QAbstractItemView 、 QStandardItem 等类都有一个函数 setDragEnabled (bool),当设置参数为 true 时,组件就可以作为一个拖动点,具有默认的启动拖动的操作。 QAbstractItemView 类定义了拖放操作相关的各种函数,通过这些函数的设置, QListView 、 QTableView、QTreeView 及其对应的便利类都会具有非常方便的节点拖放操作功能。

示例项目演示 QListWidget、QTableWidget、QTreeWidget 的拖放操 作功能。示例运行时界面如图所示,窗口上有 4 个具有拖放操作功能的界面组件。

• 标题为“listSource”的分组框里是一个 QListWidget 组件,对象名称为 listSource,在 UI 可视化设计时就设计好了十几个带图标的项。

• 标题为“listWidget”的分组框里是一个 QListWidget 组件,对象名称是 listWidget,在 UI 可视化设计时其内容为空。

• 标题为“treeWidget”的分组框里是一个 QTreeWidget 组件,对象名称是 treeWidget,在 UI 可视化设计时只设计了“编辑”和“格式”两个节点。

• 标题为“tableWidget”的分组框里是一个 QTableWidget 组件,对象名称是 tableWidget, 在 UI 可视化设计时其内容为空。

在窗口上方还可以对这 4 个组件进行拖放操作相关的设置。在 “设置对象” 分组框里选择一个对象后,在“拖放参数设置”分组框里会显示这个组件的 4 个属性的值,也可以设置组件的这 4 个拖放操作属性。这 4 个属性影响组件的拖放操作特性,后面会结合代码具体解释这些属性的作用。

一. 示例窗口类定义和初始化

本示例的窗口基类是 QWidget, 在窗口类 Widget 中增加了一些定义,Widget 类的定义代码如下:

class Widget : public QWidget 
{ 
 Q_OBJECT 
private: 
 int getDropActionIndex(Qt::DropAction actionType); //将枚举值转换为 index 
 Qt::DropAction getDropActionType(int index); //将 index 转换为枚举值
 QAbstractItemView *m_itemView= nullptr; //当前设置属性的组件
 void refreshToUI(QGroupBox *curGroupBox); //将组件的属性显示到界面上
protected: 
 bool eventFilter(QObject *watched, QEvent *event); 
public: 
 Widget(QWidget *parent = nullptr); 
private: 
 Ui::Widget *ui; 
} 

Widget 类里定义了一个 QAbstractItemView 类型的指针 m_itemView,用于指向界面上的 4 个项数据组件中的一个,作为当前设置属性的组件。Widget 类重新定义了事件过滤器函数 eventFilter(),这是为了将窗口作为 4 个项数据组件的事件过滤器,来处理它们的 QEvent::KeyPress 类型事件,在按下 Delete 键时删除组件中的当前项。Widget 类的构造函数代码如下:

Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) 
{ 
 ui->setupUi(this); 
 //安装事件过滤器,由窗口处理 4 个项数据组件的事件
 ui->listSource->installEventFilter(this); 
 ui->listWidget->installEventFilter(this); 
 ui->treeWidget->installEventFilter(this); 
 ui->tableWidget->installEventFilter(this); 
 //设置 4 个项数据组件的拖放操作相关属性
 ui->listSource->setAcceptDrops(true); 
 ui->listSource->setDragDropMode(QAbstractItemView::DragDrop); 
 ui->listSource->setDragEnabled(true); 
 ui->listSource->setDefaultDropAction(Qt::CopyAction); 
 ui->listWidget->setAcceptDrops(true); 
 ui->listWidget->setDragDropMode(QAbstractItemView::DragDrop); 
 ui->listWidget->setDragEnabled(true); 
 ui->listWidget->setDefaultDropAction(Qt::CopyAction);
 ui->treeWidget->setAcceptDrops(true); 
 ui->treeWidget->setDragDropMode(QAbstractItemView::DragDrop); 
 ui->treeWidget->setDragEnabled(true); 
 ui->treeWidget->setDefaultDropAction(Qt::CopyAction); 
 ui->tableWidget->setAcceptDrops(true); 
 ui->tableWidget->setDragDropMode(QAbstractItemView::DragDrop); 
 ui->tableWidget->setDragEnabled(true); 
 ui->tableWidget->setDefaultDropAction(Qt::MoveAction); 
}

构造函数主要完成了如下操作。

• 4 个可拖放操作组件调用函数 installEventFilter(),将窗口作为事件过滤器。

• 4 个可拖放操作组件分别设置了拖放操作的属性,其中 setAcceptDrops(true)使组件可以作为放置点接受放置操作,setDragEnabled(true)使组件可以作为拖动点启动拖动操作。

二. 拖放操作属性的显示

在窗口上的“设置对象”分组框里点击某个单选按钮时,右侧的“拖放参数设置”分组框里就会显示选中组件的拖放操作属性。这 4 个单选按钮的 clicked()信号的槽函数以及相关的两个自定义函数的代码如下:

void Widget::on_radio_Source_clicked() 
{//listSource 单选按钮
 m_itemView= ui->listSource; //当前设置属性的组件
 refreshToUI(ui->groupBox_1); //属性刷新显示到界面上
} 
void Widget::on_radio_List_clicked() 
{//listWidget 单选按钮
 m_itemView= ui->listWidget; 
 refreshToUI(ui->groupBox_2); 
} 
void Widget::on_radio_Tree_clicked() 
{//treeWidget 单选按钮
 m_itemView= ui->treeWidget; 
 refreshToUI(ui->groupBox_3); 
} 
void Widget::on_radio_Table_clicked() 
{//tableWidget 单选按钮
 m_itemView= ui->tableWidget; 
 refreshToUI(ui->groupBox_4); 
} 

void Widget::refreshToUI(QGroupBox * curGroupBox) 
{//组件的属性显示到界面上
 ui->chkBox_AcceptDrops->setChecked(m_itemView->acceptDrops()); //acceptDrops 复选框
 ui->chkBox_DragEnabled->setChecked(m_itemView->dragEnabled()); //dragEnabled 复选框
 ui->combo_Mode->setCurrentIndex((int)m_itemView->dragDropMode()); //dragDropMode下拉列表框
 int index= getDropActionIndex(m_itemView->defaultDropAction()); 
 ui->combo_DefaultAction->setCurrentIndex(index); //defaultDropAction下拉列表框
 QFont font= ui->groupBox_1->font(); 
 font.setBold(false); 
 ui->groupBox_1->setFont(font); 
 ui->groupBox_2->setFont(font); 
 ui->groupBox_3->setFont(font); 
 ui->groupBox_4->setFont(font); 
 font.setBold(true); 
 curGroupBox->setFont(font); //当前设置属性的组件所在分组框文字用粗体
} 
int Widget::getDropActionIndex(Qt::DropAction actionType) 
{//根据 Qt::DropAction 的枚举值,获取下拉列表框中的索引
 switch (actionType) 
 { 
 case Qt::CopyAction: 
 return 0; 
 case Qt::MoveAction: 
 return 1; 
 case Qt::LinkAction: 
 return 2; 
 case Qt::IgnoreAction: 
 return 3; 
 default: 
 return 0; 
 } 
} 

在某个单选按钮被点击时,设置私有变量 m_itemView 指向对应的界面组件,然后调用自定义函数 refreshToUI(),将所选组件的 4 个拖放操作相关属性值显示到界面上。获取属性值的 4 个函数中,函数 acceptDrops()是在 QWidget 类中定义的,其他 3 个函数都是在 QAbstractItemView 类中定义的。

• 函数 acceptDrops():返回一个 bool 类型的值,表示组件是否可以作为放置点接受放置操作。

• 函数 dragEnabled():返回一个 bool 类型的值,表示组件是否可以作为拖动点启动拖动操作。

• 函数 dragDropMode():返回结果是枚举类型 QAbstractItemView::DragDropMode,表示拖放操作模式,各枚举值如下表所示。

枚举类型 QAbstractItemView::DragDropMode 的枚举值
枚举值数值描述
QAbstractItemView::NoDragDrop0组件不支持拖放操作
QAbstractItemView::DragOnly1组件只支持拖动操作
QAbstractItemView::DropOnly2组件只支持放置操作
QAbstractItemView::DragDrop3组件支持拖放操作
QAbstractItemView::InternalMove4组件只支持内部项的移动操作,例如目录树内节点的移动操作

• 函数 defaultDropAction():返回结果是枚举类型 Qt::DropAction。当组件作为放置点时,它表示在完成拖放时数据操作的模式,各枚举值如下表所示。

枚举类型 Qt::DropAction 的枚举值
枚举值数值描述
Qt::CopyAction1将数据复制到放置点组件处
Qt::MoveAction2将数据从拖动点组件处移动到放置点组件处
Qt::LinkAction4在拖动点组件和放置点组件之间建立数据连接
Qt::IgnoreAction0对数据不进行任何操作

窗口上的 defaultDropAction 下拉列表框中列出了上表所示的 4 个枚举值,因为枚举值对应的数值不是连续的,所以用一个自定义函数 getDropActionIndex()将枚举值转换为下拉列表框中对应项的索引。

三. 拖放操作属性的设置

选择一个设置对象后,就可以通过窗口上“拖放参数设置”分组框里的 4 个组件设置所选组件的拖放操作属性,相关代码如下:

void Widget::on_chkBox_AcceptDrops_clicked(bool checked) 
{//acceptDrops 复选框
 m_itemView->setAcceptDrops(checked); 
} 
void Widget::on_chkBox_DragEnabled_clicked(bool checked) 
{//dragEnabled 复选框
 m_itemView->setDragEnabled(checked); 
} 
void Widget::on_combo_Mode_currentIndexChanged(int index) 
{//dragDropMode 下拉列表框
 QAbstractItemView::DragDropMode mode= (QAbstractItemView::DragDropMode)index; 
 m_itemView->setDragDropMode(mode); 
} 
void Widget::on_combo_DefaultAction_currentIndexChanged(int index) 
{//defaultDropAction 下拉列表框
 Qt::DropAction actionType= getDropActionType(index); 
 m_itemView->setDefaultDropAction(actionType); 
} 

Qt::DropAction Widget::getDropActionType(int index) 
{//根据下拉列表框的索引,返回 Qt::DropAction 类型的枚举值
 switch (index) 
 { 
 case 0: 
 return Qt::CopyAction; 
 case 1: 
 return Qt::MoveAction; 
 case 2: 
 return Qt::LinkAction; 
 case 3: 
 return Qt::IgnoreAction; 
 default: 
 return Qt::CopyAction; 
 } 
} 

变量 m_itemView 指向需要设置属性的组件,可以通过 4 个接口函数设置拖放操作的属性。

• 函数 setAcceptDrops(bool):设置为 true 时,组件作为放置点,可接受放置操作。

• 函数 setDragEnabled(bool):设置为 true 时,组件作为拖动点,可以启动拖动操作。

• 函数 setDragDropMode(mode):参数 mode 是枚举类型 QAbstractItemView::DragDropMode, 用于设置拖放操作模式。使用函数 setDragDropMode()设置拖放操作模式时,相当于用不同的组合调用了 setAcceptDrops()和 setDragEnabled(),例如运行下面的语句:

setDragDropMode(QAbstractItemView::DragOnly)

就相当于运行了 setAcceptDrops(false)和 setDragEnabled(true)。

• 函数 setDefaultDropAction(dropAction):参数 dropAction 是枚举类型 Qt::DropAction,用于设置完成拖放操作时源组件的数据操作方式。

四. 通过事件过滤器实现项的删除

在 Widget 类的构造函数中,4 个可拖放操作组件通过函数 installEventFilter()将窗口作为自己的事件过滤器。Widget 类重新实现了函数 eventFilter(),对这 4 个组件的事件进行处理。

bool Widget::eventFilter(QObject *watched, QEvent *event) 
{ 
 if (event->type() != QEvent::KeyPress) //不是 KeyPress 事件,退出
 return QWidget::eventFilter(watched,event); 
 QKeyEvent *keyEvent= static_cast<QKeyEvent *>(event); 
 if (keyEvent->key() != Qt::Key_Delete) //按下的不是 Delete 键,退出
 return QWidget::eventFilter(watched,event); 
 if (watched == ui->listSource) 
 { 
 QListWidgetItem *item= ui->listSource->takeItem(ui->listSource->currentRow()); 
 delete item; 
 } 
 else if (watched == ui->listWidget) 
 { 
 QListWidgetItem *item= ui->listWidget->takeItem(ui->listWidget->currentRow()); 
 delete item; 
 } 
 else if (watched == ui->treeWidget) 
 { 
 QTreeWidgetItem *curItem= ui->treeWidget->currentItem(); 
 if (curItem->parent() != nullptr) 
 { 
 QTreeWidgetItem *parItem= curItem->parent(); 
 parItem->removeChild(curItem); 
 } 
 else 
 { 
 int index= ui->treeWidget->indexOfTopLevelItem(curItem); 
 ui->treeWidget->takeTopLevelItem(index); 
 } 
 delete curItem; 
 } 
 else if (watched == ui->tableWidget) 
 { 
 QTableWidgetItem *item= ui->tableWidget->takeItem( 
 ui->tableWidget->currentRow(), 
 ui->tableWidget->currentColumn()); 
 delete item; 
 } 
 return true; //表示事件已经被处理
} 

这个函数只处理了 4 个项数据组件的 QEvent::KeyPress 类型的事件,且在按下的是 Delete 键时才处理。程序通过输入参数 watched 判断是哪个界面组件,然后删除该组件的当前项。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值