Qt 信号和槽 & 事件处理

一:Qt信号和槽概述              

信号与槽作为QT的核心机制在QT编程中有着广泛的应用,本章介绍了信号与槽的一些基本概念、元对象工具以及在实际使用过程中应注意的一些问题。

信号和槽机制是QT的核心机制,要精通QT编程就必须对信号和槽有所了解。信号和槽是一种高级接口,应用于对象之间的通信,它是QT的核心特性,也是QT区别于其它工具包的重要地方。信号和槽是QT自行定义的一种通信机制,它独立于标准的C/C++语言,因此要正确的处理信号和槽,必须借助一个称为 moc (Meta Object Compiler)的QT工具,该工具是一个C++预处理程序,它为高层次的事件处理自动生成所需要的附加代码。

在所有从 QObject或其子类(例如Qwidget)派生的类都能够包含信号和槽。当对象改变其状态时,信号就由该对象发射(emit)出去,这就是对象所要做的全部事情,它不知道另一端是谁在接收这个信号。这就是真正的信息封装,它确保对象被当作一个真正的软件组件来使用。槽用于接收信号,但它们是普通的对象成员函数。一个槽并不知道是否有任何信号与自己相连接。而且,对象并不了解具体的通信机制。

二:信号和槽示意图

三:信号

当某个信号对其客户或所有者发生的内部状态发生改变,信号被一个对象发射。

只有定义过这个信号的类及其派生类能够发射这个信号。当一个信号被发射时,与其相关联的槽将被立刻执行,就象一个正常的函数调用一样。

信号-槽机制完全独立于任何GUI事件循环。

如果存在多个槽与某个信号相关联,那么,当这个信号被发射时,这些槽将会一个接一个地执行,但是它们执行的顺序将会是随机的、不确定的,我们不能人为地指定哪个先执行、哪个后执行。

四:如何编写信号

信号的声明是在头文件中进行的,QT的signals关键字指出进入了信号声明区,随后即可 声明自己的信号。

下面定义了三个信号:

signals:

  void mySignal();

  void mySignal(int x);

  void mySignalParam(int x,int y);

在上面的定义中,signals是QT的关键字,而非C/C++的。接下来的一行void mySignal() 定义了信号mySignal,这个信号没有携带参数;接下来的一行void mySignal(int x)定义 了重名信号mySignal,但是它携带一个整形参数,这有点类似于C++中的虚函数。从形式上 讲信号的声明与普通的C++函数是一样的,但是信号却没有函数体定义,另外,信号的返回 类型都是void

五:槽

槽是普通的C++成员函数,可以被正常调用,它们唯一的特殊性就是很多信号可以与其相关联。当与其关联的信号被发射时,这个槽就会被调用。槽可以有参数,但槽的参数不能有缺省值。

同普通的C++成员函数一样,槽函数也分为三种类型,即public slots、private slots和protected slots。

槽也能够声明为虚函数,这也是非常有用的。

六:槽函数类型

public slots:在这个区内声明的槽意味着任何对象都可将信号与之相连接。这对于组件编程非常有用,你可以创建彼此互不了解的对象,将它们的信号与槽进行连接以便信息能够正确的传递。

protected slots:在这个区内声明的槽意味着当前类及其子类可以将信号与之相连接。这适用于那些槽,它们是类实现的一部分,但是其界面接口却面向外部。

private slots:在这个区内声明的槽意味着只有类自己可以将信号与之相连接。这适用于联系非常紧密的类。

七:如何编写槽

槽的声明也是在头文件中进行的。例如,

下面声明了三个槽:

public slots:

  void mySlot();

  void mySlot(int x);

          void mySignalParam(int x,int y);

八:QObject::connect

通过调用QObject对象的connect函数来将某个对象的信号与另外一个对象的槽函数相关联,这样当发射者发射信号时,接收者的槽函数将被调用。该函数的定义如下:

bool QObject::connect ( const QObject * sender, const char * signal, const QObject * receiver, const char * member ) [static]

这个函数的作用就是将发射者sender对象中的信号signal与接收者receiver中的member槽函数联系起来。当指定信号 signal时必须使用QT的宏SIGNAL(),当指定槽函数时必须使用宏SLOT()。

如果发射者与接收者属于同一个对象的话,那么在connect 调用中接收者参数可以省略。

九:带有信号和槽的类

class TsignalApp:public QMainWindow
{
	Q_OBJECT
	//信号声明区
	signals: 
		//声明信号mySignal()
		void mySignal();
		//声明信号mySignal(int)
		void mySignal(int x);
		//声明信号mySignalParam(int,int)
		void mySignalParam(int x,int y);
	//槽声明区
	public slots: 
		//声明槽函数mySlot()
		void mySlot();
		//声明槽函数mySlot(int)
		void mySlot(int x);
		//声明槽函数mySignalParam (int,int)
		void mySignalParam(int x,int y);
}

十:一个信号与另一个信号相关联

class MyWidget : public QWidget
 { public: MyWidget(); 
	... 
	signals: void aSignal();
	 ... 
	private:
	 ... 
	QPushButton *aButton; 
}; 
MyWidget::MyWidget() 
{ aButton = new QPushButton( this ); 
connect( aButton, SIGNAL(clicked()), SIGNAL(aSignal()) ); 
} 

十一:信号和槽例子

下面定义了两个对象:标签对象label和滚动条对象scroll,并将valueChanged()信号与标签对象的setNum()相关联,另外信号还携带了一个整形参数,这样标签总是显示滚动条所处位置的值。

QLabel     *label  = new QLabel;
QScrollBar *scroll = new QScrollBar;
QObject::connect( scroll, SIGNAL(valueChanged(int)),
                           label,  SLOT(setNum(int)) );

 十二:QObject::disconnect

当信号与槽没有必要继续保持关联时,我们可以使用disconnect函数来断开连接。

bool QObject::disconnect ( const QObject * sender, const char * signal, const Object * receiver, const char * member ) [static]

十三:槽和信号限制

限制1:类型宏不能被用于信号和槽的参数

#ifdef ultrix 
#define SIGNEDNESS(a) unsigned a 
#else 
#define SIGNEDNESS(a) a 
#endif 
class Whatever : public QObject 
{ 
	... 
	signals: void someSignal( SIGNEDNESS(int) );//错误
	 ... 
}; 

限制2:构造函数不能用于信号部分和槽部分

class SomeClass : public QObject 
{ 
	Q_OBJECT 
	public slots: 
	SomeClass( QObject *parent, const char *name ) : QObject( parent, name ) //错误
	
	... }; 

限制3:函数指针不能作为信号或槽的参数。 

class SomeClass : public QObject
	{ 
	    Q_OBJECT
	     ... 
	     public slots:
	     void apply( void (*apply)(List *, void *), char * ); //错误
	 }; 

限制4:信号和槽不能被升级  public  protected private

             QButtonGroup::buttonPressed()槽是保护的。

class Whatever : public QButtonGroup
{ ... 
	public slots: 
		void QButtonGroup::buttonPressed; // 错的 
	... 
}; 

其他限制如下:

如果一个信号与多个槽相联系的话,那么,当这个信号被发射时,与之相关的槽被激活的顺序将是随机的。

信号与槽也不能携带模板类参数。

嵌套的类不能位于信号或槽区域内也不能有信号或者槽。

友元声明不能位于信号或者槽声明区内。

十四:元对象工具

元对象编译器moc(meta object compiler)对C++文件中的类声明进行分析并产生用于初始化元对象的C++代码,元对象包含全部信号和槽的名字以及指向这些函数的指针。

元对象代码是signal/slot机制所必须的。用moc产生的C++源文件必须与类实现一起进行编译和连接,或者用#include语句将其包含到类的源文件中。moc并不扩展#include或者#define宏定义,它只是简单的跳过所遇到的任何预处理指令。

十五:moc编译

Moc元对象编译器的功能是把包含有信号和槽的源文件编译成特殊的文件名为moc_+实际源文件名称

它的具体使用格式如下:

moc – [option] <filename>

moc   mysingal.h  -o moc_mysingal.h

 option包含如下参数:

-o <file> 
•把编译结果写到一个文件中而不是标志输出
-p  -p path
•使元对象编译器生成的(如果有生成的)#include声明的文件名称中预先考虑到 path/。
-q path
•使元对象编译器在生成的文件中的qt #include文件的名称中预先考虑到 path/。         

十六:类声明位置

类的声明放在一个头文件(.h文件)中

如果在上述的文件myclass.h中发现类的声明,元对象编译器的输出文件将会被放在一个叫moc_myclass.cpp的文件中。这个文件将会像通常情况一样被编译,作为对象文件的结果是moc_myclass.o

类的声明放在一个实现文件(.cpp文件)中

如果上述的文件myclass.cpp中发现类的声明,元对象编译器的输出文件将会被放在一个叫myclass.moc的文件中。这个文件需要被实现文件包含(#include),也就是说myclass.cpp需要包含下面这行 #include "myclass.moc" 放在所有的代码之后。

十七:事件机制

Qt中定义的事件是一个从QEvent类继承下来的,它表示应用程序内部或者外部发生某些应用程序必须知道的事情

Qt 使用一个事件队列对所有发出的事件进行维护,当新的事件产生时,会被追加到事件队列的尾部。前一个事件完成后,取出后面的事件进行处理。但是,必要的时候,Qt 的事件也可以不进入事件队列,而是直接处理

在Qt内部,Qt通过由函数QApplication::exec()函数启动的主事件循环从系统事件队列中抓取属于本程序事件并转化为QEvent对象

十八:事件类图

十九:事件来源分类

基于事件如何被产生与分发,可以把事件分为三类:

1. Spontaneous 事件,由窗口系统产生,它们被放到系统队列中,通过事件循环逐个处理

    鼠标,键盘,移动

2. Posted 事件,由Qt或是应用程序产生,它们被Qt组成队列,再通过事件循环处理。update(),paintEvent()

3. Sent  事件,由Qt或是应用程序产生,但它们被直接发送到目标对象。 repaint()

二十:QEvent对象

QEvent对象是所有事件对象的基类,因此有必要了解该对象构成函数:

enum Type :标识事件类型

Type type () const   :调用该函数,返回事件发生事件的类型(该类型为枚举)

二十一:常用的事件类型

通过调用QEvent::type函数可以返回具体事件类型,Qt已经为具体的事件类型事先定义了:

QEvent::MouseButtonPress - 鼠标按下,QMouseEvent

QEvent::MouseButtonRelease - 鼠标抬起,QMouseEvent

QEvent::MouseButtonDblClick - 鼠标再次按下

QEvent::MouseMove - 鼠标移动,QMouseEvent

QEvent::Close - 窗口部件被关闭(永久性地) QCloseEvent

QEvent::KeyPress - 键按下(举例,包括Shift)QKeyEvent

QEvent::KeyRelease - 键抬起,QKeyEvent

QEvent::FocusIn - 窗口部件获得键盘焦点QFocusEvent

QEvent::FocusOut - 窗口部件失去键盘焦点,QFocusEvent

 QEvent::Timer - 规则的定时器事件,QTimerEvent

二十二:常用的Qt事件类

Qt为多数事件定义了特殊的类:

QMouseEvent 鼠标事件
QKeyEvent  键盘事件
QResizeEvent窗体宿放事件
QPaintEvent 窗体重绘事件
QCloseEvent 窗体关闭事件
QFocusEvent 部件获得焦点事件

二十三:事件处理方法

Qt提供了几种处理事件的方法:

重新实现特定的事件处理器
重新实现QObject::event()函数
继承QApplication并重新实现notify()函数

二十四:实现特定的事件处理器

最常见的事件处理办法就是重载象mousePressEvent(), keyPressEvent(), paintEvent() 这样的特定事件处理函数. 以按键事件为例  

二十五:QKeyEvent对象

QKeyEvent对象是Qt的键盘事件的包装对象,我们也经常需要捕获键盘事件.下面是该对象主要成员函数:

int key () const
int ascii () const
ButtonState state () const
ButtonState stateAfter () const
bool isAccepted () const
QString text () const
bool isAutoRepeat () const
int count () const
void accept ()

                 void ignore ()

二十六:Qt键盘常量

为了方便程序员捕获特殊键.qt已经对所有的键盘都进行了宏定义,下面列出常用的键值:

二十七:QMouseEvent对象

在Qt系统中对所有鼠标事件都被封装为QMouseEvent对象,有必要了解下该对象成员函数

Qt::MouseButton button () const

返回发生鼠标事件时按下的鼠标按钮

const QPoint & globalPos () const
返回发生鼠标事件时,鼠标在屏幕上的位置指针
int globalX () const
返回发生鼠标事件时,鼠标在屏幕上x轴位置
int globalY () const
返回发生鼠标事件时,鼠标在屏幕上y轴位置
int x () const
返回发生鼠标事件时,相对与本窗体的x轴位置
int y () const

                            返回发生鼠标事件时,相对与本窗体的x轴位置

二十八:事件处理器实现

void imageView::keyPressEvent(QKeyEvent * event)
{
	switch (event->key()) {
	case Key_Plus:
	zoomIn();
	break;
	case Key_Minus:
	zoomOut();
	break;
	case Key_Left:
	// … 
	default:
	QWidget::keyPressEvent(event);
	}
}

二十九:重载event()函数

通过重载event()函数,我们可以在事件被特定的事件处理函数处理之前(象keyPressEvent())处理它. 比如, 当我们想改变tab键的默认动作时,一般要重载这个函数. 在处理一些不常见的事件当我们重载event()函数时, 需要调用父类的event()函数来处理我们不需要处理或是不清楚如何处理的事件

三十:重载event实现

下面这个例子演示了如何重载event()函数, 改变Tab键的默认动作: (默认的是键盘焦点移动到下一个控件上)

bool CodeEditor::event(QEvent * event)
{
	if (event->type() == QEvent::KeyPress)
	 {
		QKeyEvent *keyEvent = (QKeyEvent *) event;
		if (keyEvent->key() == Key_Tab)
	 	{
			insertAtCurrentPosition('\t');
			return true;
		}
	}
return QWidget::event(event);
}

三十一:QApplication::notify()

Qt是用QApplication::notify()函数来分发事件的.想要在任何事件过滤器查看任何事件之前先得到这些事件,重载这个函数是唯一的办法. 通常来说事件过滤器更好用一些, 因为不需要去继承QApplication类. 而且可以给QApplication对象安装任意个数的事件过滤器, 相比之下, notify()函数只有一个

三十二:事件过滤器

Qt事件模型中有一项非常强大的功能是一个Object实例可以监视另外一个QObject实列中的事件.实现方法就是在目标对象安装事件过滤器.这样事件到打目标对象之前首先获得该事件.从而起到监视作用

三十三:安装事件过滤器

安装事件过滤器有两个步骤: (假设要用A来监视过滤B的事件)

首先调用B的installEventFilter( const QOject *obj ), 以A的指针作为参数. 这样所有发往B的事件都将先由A的eventFilter()处理.
然后, A要重载QObject::eventFilter()函数, 在eventFilter() 中书写对事件进行处理的代码

三十四:事件过滤器实现                 

MainWidget::MainWidget()
{  // …
	CodeEditor * ce = new CodeEditor( this, “code editor”);
	ce->installEventFilter( this );
	// …
}
bool MainWidget::eventFilter( QOject * target , QEvent * event )
{
       if( target == ce ){
              if( event->type() == QEvent::KeyPress ) {
                     QKeyEvent *ke = (QKeyEvent *) event;
                     if( ke->key() == Key_Tab ){
	ce->insertAtCurrentPosition('\t');
	return true;
                     }
              } }
       return false;
}

猜你喜欢

转载自blog.csdn.net/m0_56051805/article/details/127197341