2 信号和槽
约 7065 字大约 24 分钟
2025-06-22
信号和槽机制是 QT 的核心机制, 所有从 QObject 或其子类 ( 例如 Qwidget) 派生的类都能够包含信号和槽。 当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会事先使用连接(connect)函数,意思是,用自己的一个函数(成为槽(slot))进行注册以便于处理这个信号。也就是说,当信号发出时,被连接的槽函数会自动被回调。这样的设计可能在某些地方会有些不便,但却杜绝了紧耦合,于总体设计有利。
在我们所熟知的很多 GUI 工具包中,窗口小部件 (widget) 都有一个回调函数用于响应它们能触发的每个动作,这个回调函数通常是一个指向某个函数的指针。但是,在 QT 中信号和槽取代了这些凌乱的函数指针,使得我们编写这些通信程序更为简洁明了。信号和槽能携带任意数量和任意类型的参数,他们是类型完全安全的,不会像回调函数那样产生 core dumps。
- 当对象改变其状态时,信号就由该对象发射 (emit) 出去,这就是对象所要做的全部事情,它不知道另一端是谁在接收这个信号。这就是真正的信息封装,它确保对象被当作一个真正的软件组件来使用。
- 槽(slot)用于接收信号,但它们是普通的对象成员函数。一个槽并不知道是否有任何信号与自己相连接。而且,对象并不了解具体的通信机制。
虽然信号/反应槽机制有很多优点,使用也很方便,但它也不是没有缺点。最大的缺点在于要稍微牺牲一点性能。根据Trolltech公司的自测,在CPU为Intel PentiumII 500 Mhz的PC机上,对于一个信号对应一个反应槽的连接来说,一秒钟可以调用两百万次;对于一个信号对应两个反应槽的连接来说,一秒钟可以调用一百二十万次。这个速度是不经过连接而直接进行回调的速度的十分之一。请注意这里的十分之一速度比是调用速度的比较,而不是一个完整函数执行时间的比较。事实上一般情况下一个函数的总执行时间大部分是在执行部分,只有小部分是在调用部分,因些这个速度是可以接受的。这就象面向对象的编程和早些年的结构化编程相比一样:
程序的执行效率并没有提高,反而是有所下降的,但现在大家都在用面向对象的方法编写程序。用一部分执行效率换回开发效率与维护效率是值得的,况且现在已是P4为主流的时代。
信号函数和槽函数可以相互传数据 发送数据的信号函数可以不给出实现,二者的参数相同,便可以通过connect把a的send 的参数传递到b的recv的参数
A a;
B b;
connect(a,&A::send,b,&B::recv);
void A::send(int n){}
void B::recv(int n){cout<<n<<endl;}
元对象工具
元对象编译器 moc(meta object compiler)对 C++ 文件中的类声明进行分析并产生用于初始化元对象的 C++ 代码,元对象包含全部信号和槽的名字以及指向这些函数的指针。
moc 读 C++ 源文件,如果发现有 Q_OBJECT 宏声明的类,它就会生成另外一个 C++ 源文件,这个新生成的文件中包含有该类的元对象代码。例如,假设我们有一个头文件 mysignal.h,在这个文件中包含有信号或槽的声明,那么在编译之前 moc 工具就会根据该文件自动生成一个名为 mysignal.moc.h 的 C++ 源文件并将其提交给编译器;类似地,对应于 mysignal.cpp 文件 moc 工具将自动生成一个名为 mysignal.moc.cpp 文件提交给编译器。
元对象代码是 signal/slot 机制所必须的。用 moc 产生的 C++ 源文件必须与类实现一起进行编译和连接,或者用 #include
语句将其包含到类的源文件中。moc 并不扩展#include
或者 #define
宏定义 , 它只是简单的跳过所遇到的任何预处理指令。
Q_Object宏将由 moc(我们会在后面章节中介绍 moc。这里你可以将其理解为一种预处理器,是比 C++ 预处理器更早执行的预处理器。) 做特殊处理,不仅仅是宏展开这么简单。moc 会读取标记了 Q_OBJECT的头文件,生成以 moc_为前缀的文件,比如 newspaper.h将生成 moc_newspaper.cpp。你可以到构建目录查看这个文件,看看到底增加了什么内容。注意,moc 只处理头文件中的标记了Q_OBJECT的类声明,不会处理 cpp 文件中的类似声明。,所以Q_Object宏必须放在头文件里,否则会报错
信号
当某个信号对其客户或所有者发生的内部状态发生改变,信号被一个对象发射。只有定义过这个信号的类及其派生类能够发射这个信号。
当一个信号被发射时,与其相关联的槽将被立刻执行,就象一个正常的函数调用一样。信号-槽机制完全独立于任何 GUI 事件循环。只有当所有的槽返回以后发射函数(emit)才返回。
如果存在多个槽与某个信号相关联,那么,当这个信号被发射时,这些槽将会一个接一个地执行,但是它们执行的顺序将会是随机的、不确定的,我们不能人为地指定哪个先执行、哪 个后执行。 信号的声明是在头文件中进行的,QT 的 signals 关键字指出进入了信号声明区,随后即可声明自己的信号。例如,下面定义了三个信号:
signals:
void mySignal();
void mySignal(int x);
void mySignalParam(int x,int y);
signals 是 QT 的关键字,而非 C/C++ 的。 siganls 没有 public、private、protected 等属性,这点不同于 slots。
信号的声明与普通的 C++ 函数是一样的,但信号只有定义没有实现,另外,信号的返回类型都是 void,不要指望能从信号返回什么有用信息。信号由 moc 自动产生,它们不应该在 .cpp 文件中实现。
槽
槽是普通的 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);
signals、slots 关键字是 QT 自己定义的,不是 C++ 中的关键字。
emit宏
emit 是 Qt 对 C++ 的扩展,是一个关键字(其实也是一个宏)。emit 的含义是发出 一般使用emit的时候都是用户自定义的一个信号 emit只是显式的声明一下信号要被发送,没有特殊含义
class Newspaper : public QObject {
Q_OBJECT
private:
QString m_name;
public:
Newspaper(const QString &name) :m_name(name) {}
void send() { emit newPaper(m_name); }
signals:
void newPaper(const QString &name);
};
class Reader : public QObject{
Q_OBJECT
public:
Reader() {}
void receiveNewspaper(const QString & name){qDebug() << "Receives Newspaper: " << name;}
};
int main(int argc, char *argv[]) {
QCoreApplication app(argc, argv);
Newspaper newspaper("Newspaper A");
Reader reader;
QObject::connect(&newspaper, &Newspaper::newPaper,&reader, &Reader::receiveNewspaper);
newspaper.send();
return app.exec();
}
应注意的问题
信号与槽机制是比较灵活的,但有些局限性我们必须了解,这样在实际的使用过程中做到有的放矢,避免产生一些错误。下面就介绍一下这方面的情况。
- 信号与槽的效率是非常高的,但是同真正的回调函数比较起来,由于增加了灵活性,因此在速度上还是有所损失,当然这种损失相对来说是比较小的,通过在一台 i586-133 的机器上测试是 10 微秒(运行 Linux),可见这种机制所提供的简洁性、灵活性还是值得的。但如果我们要追求高效率的话,比如在实时系统中就要尽可能的少用这种机制。
- 信号与槽机制与普通函数的调用一样,如果使用不当的话,在程序执行时也有可能产生死循环。因此,在定义槽函数时一定要注意避免间接形成无限循环,即在槽中再次发射所接收到的同样信号。例如 , 在前面给出的例子中如果在 mySlot() 槽函数中加上语句 emit mySignal() 即可形成死循环。
- 如果一个信号与多个槽相联系的话,那么,当这个信号被发射时,与之相关的槽被激活的顺序将是随机的。
- 宏定义不能用在 signal 和 slot 的参数中。既然 moc 工具不扩展
#define
,因此,在 signals 和 slots 中携带参数的宏就不能正确地工作,如果不带参数是可以的。 - 构造函数不能用在 signals 或者 slots 声明区域内。
- 函数指针不能作为信号或槽的参数。
- 信号与槽不能有缺省参数。
- 信号与槽也不能携带模板类参数。如果将信号、槽声明为模板类参数的话,即使 moc 工具不报告错误,也不可能得到预期的结果。
- 嵌套的类不能位于信号或槽区域内,也不能有信号或者槽。
- 友元声明不能位于信号或者槽声明区内。
常用的函数
函数 | 作用 |
---|---|
cick() | 表示是否被点击 |
clicked(bool) | 表示是否被选中(点击过) |
setObjectName("name") | 用来设置对象名字 |
objectName() | 用来查询名字 |
deleteLater() | 稍后删除当窗口使用exec()函数阻塞窗口退出时使用这个deleteLater()会在窗口关闭后删除 |
exec() | 阻塞函数,消息驱动循环、时间驱动循环,使窗口阻塞一直运行,点击关闭才会跳出循环停止运行 |
bool QObject::blockSignals(bool block) | 阻断信号,参数填true就是阻断信号,填false就是不阻断 |
connect()函数:
有四种重载版本 第一种 Qt 4 使用宏,主要通过connect + 宏的方式进行通信连接。 connect(发送对象,信号,接收对象,槽函数),其中发送信号和槽函数需要用 SIGNAL() 和 SLOT() 来进行明确的声明。 对于信号或槽函数名称是唯一的,不存在歧义的情况下可以省略参数
static QMetaObject::Connection QObject:connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member,Qt::ConnectionType =Qt::Auto
第一个参数:发送者 第二个参数:信号 第三个参数:接收者 第四个参数:要执行的槽 第五个参数:表示信号与槽连接的方式一般使用默认值,在满足某些特殊需求的时候可能需要手动设置。
- QtDirectConnection类型。如果接收者和发送者不在一个线程,则自动使用Qt::QueuedConnection类型。
- Qt::DirectConnection:槽函数会在信号发送的时候直接被调用,槽函数运行于信号发送者所在线程。效果看上去就像是直接在信号发送位置调用了槽函数。这个在多线程环境下比较危险,可能会造成奔溃。 不在同一个线程用这个,也可能导致另一个线程槽函数始终不响应。
- Qt::QueuedConnection:槽函数在控制回到接收者所在线程的事件循环时被调用,槽函数运行于信号接收者所在线程。发送信号之后,槽函数不会立刻被调用,等到接收者的当前函数执行完,进入事件循环之后,槽函数才会被调用。多线程环境下一般用这个。
- Qt:BlockingQueueConnection:槽函数的调用时机与Qt::QueuedConnection一致,不过发送完信号后发送者所在线程会阻塞,直到槽函数运行完。接收者和发送者绝对不能在一个线程,否则程序会死锁。在多线程间需要同步的场景可能需要这个。
- Qt::UniqueConnection:这个flag可以通过按位或(|)与以上四个结合在一起使用。当这个flag设置时,当某个信号和槽已经连接时,再进行重复的连接就会失败。也就是避免重复连接。 因为connect中信号和槽的参数都是字符串类型,而发出信号或执行的槽的函数返回值不一定时字符串类型。因此需要使用SIGNAL()和SLOT()宏将参数转化为const char * 类型
先自定义一个 Button,然后定义两个信号
class MyButton : public QWidget{
Q_OBJECT
public:
explicit MyButton(QWidget *parent = nullptr);
signals:
void sigClicked();
void sigClicked(bool check); //重载信号
};
//使用
connect(m_pBtn,SIGNAL(sigClicked()),this,SLOT(onClicked()));
connect(m_pBtn,SIGNAL(sigClicked(bool)),this,SLOT(onClicked(bool)));
这种写法比较麻烦,但优点是一眼就能看出来是将哪个信号连接到哪个槽。
第二种 Qt 5 推出了新的 connect 函数,不需要使用 SIGNAL() 和 SLOT() 宏,可以在编译时做类型检查: connect函数原型如下:
static QMetaObject::Connection QObject::connect(const QObject *sender, PointerToMemberFunction signal, const QObject *context, Functor functor, Qt::ConnectionType type = Qt::AutoConnection)
用 connect 将信号与槽函数连接,不需要再使用 SIGNAL() 和 SLOT() 宏:
connect(m_pBtn,&MyButton::sigClicked,this,&Widget::onClicked);
这种写法看起来很简洁,但是存在一些坑需要注意,这句写法如果用在上面的示例中,会报错 当信号和槽函数唯一时可以省略小括号直接使用函数名 如果信号和槽函数都存在重载的情况,则需要使用qOverload<参数类型>进行指定
connect(m_pBtn, static_cast<void (MyButton::*)(bool)>(&MyButton::sigClicked), this, &Widget::onClicked);
如果我的onClicked槽也是重载的话,还是会报同样的错误。 这种情况写法反而比第一种情况还要麻烦
虽然当出现信号或槽函数发生重载时这种写法更加复杂,但还是推荐使用Qt5中的写法 因为前一种写法使用SIGNAL()和SLOT()进行的宏替换,会把函数名和参数列表转换成一个字符串,转换过程中是直接进行一个宏替换,宏替换是不会进行错误检查。
第三种
一种最新的写法,主要针对重载信号的连接做了调整,会更简单些:
connect(m_pBtn,QOverload<bool>::of(&MyButton::sigClicked),this,&Widget::onClicked);
很显然这种写法相对于第二种会比较简单些,但依然不能连接到重载的槽函数,如果连接重载槽函数,还是会报之前的错误。
第四种重载 前两种形式都是静态函数版本,无法获得对象本身的this指针 第三种形式是成员函数版本
inline QMetaObject::Connection connect(const QObject *sender, const char *signal,const char *member, Qt::ConnectionType type = Qt::AutoConnection) const;
Lambda 函数进行连接
如果槽函数中的内容比较简单的话,没必要再去单独定义一个槽来连接, 直接用Lambda 函数会更简单。
connect(m_pBtn, QOverload<bool>::of(&MyButton::sigClicked), this, [=](bool check){
qDebug() << "do something";
});
connect(m_pBtn, static_cast<void (MyButton::*)(bool)>(&MyButton::sigClicked), this, [=](bool check){
qDebug() << "do something";
});
connect(ui->lineEdit, &QLineEdit::textEdited, this, [=](QString s){
qDebug() << s;
});
disconnect()
函数原型:
connect(sender, signal, receiver, slot);
一般有以下四种用法:
- 解除与一个发送者所有信号的连接
disconnect(myobject,nullptr,nullptr,nullptr);//静态函数
myobject->disconnect();//成员函数
- 解除与一个特定信号的所有连接:
disconnect(myobject, SIGNAL(mySignal(), nullptr, nullptr);
myObject->disconnect(SIGNAL (mySignal()));
- 解除与一个特定接收者的所有连接:
disconnect(myobject, nullptr, hyReceiver,nullptr);
myObject->disconnect(myReceiver);
- 解除一对特定的信号和槽的连接:
disconnect(lineEdit, &QLineEdit:textChanged,label, &QLabel:setText);
sender()
在槽函数中使用QObject::sender()可以获取信号发射者的指针,返回类型为QObject * 得到的QObject指针需要进行对应的类型转换 有可能多个Object的signal会连接到同一个signal(例如多个Button可能会connect到一个slot函数onClick()),因此这是就需要判断到底是哪个Object emit了这个signal,根据sender的不同来进行不同的处理
函数原型:
QObject *sender() const;
使用:
QPushButton *a = new QPushButton(this);
connect(a, SIGNAL(Click()), this, SLOT(Ona());
void Ona(){
QPushButton *c = (QPushButton*) sender();
}
事件
对窗口的任何操作都会产生对应的事件 我们使用的基于窗口的应用程序都是基于事件,其目的主要是用来实现回调(因为只有这样程序的效率才是最高的)。所以在Qt框架内部为我们提供了一些列的事件处理机制,当窗口事件产生之后,事件会经过: 事件派发 -> 事件过滤->事件分发->事件处理 几个阶段。Qt窗口中对于产生的一系列事件都有默认的处理动作,如果我们有特殊需求就需要在合适的阶段重写事件的处理动作。
事件(event)是由系统或者 Qt 本身在不同的场景下发出的。当用户按下/移动鼠标、敲下键盘,或者是窗口关闭/大小发生变化/隐藏或显示都会发出一个相应的事件。一些事件在对用户操作做出响应时发出,如鼠标/键盘事件等;另一些事件则是由系统自动发出,如计时器事件。
每一个Qt应用程序都对应一个唯一的 QApplication应用程序对象,然后调用这个对象的exec()函数,这样Qt框架内部的事件检测就开始了(程序将进入事件循环来监听应用程序的事件)。
QEvent是qt中事件类的基类,QKeyEvent和QMouseEvent分别是键盘和鼠标事件 事件在Qt中产生之后,的分发过程是这样的:
当事件产生之后,Qt使用用应用程序对象调用notify()函数将事件发送到指定的窗口:
//第一个参数是事件的接收者,第二个参数是产生的事件
[override virtual] bool QApplication::notify(QObject *receiver, QEvent *e);
事件在发送过程中可以通过事件过滤器进行过滤,默认不对任何产生的事件进行过滤。
// 需要先给窗口安装过滤器, 该事件才会触发
//第一个参数是过滤哪一个对象的事件,第二个参数是要被过滤的事件
[virtual] bool QObject::eventFilter(QObject *watched, QEvent *event)
当事件发送到指定窗口之后,窗口的事件分发器会对收到的事件进行分类:
[override virtual protected] bool QWidget::event(QEvent *event);
事件分发器会将分类之后的事件(鼠标事件、键盘事件、绘图事件。。。)分发给对应的事件处理器函数进行处理,每个事件处理器函数都有默认的处理动作(我们也可以重写这些事件处理器函数),比如:鼠标事件:
// 鼠标按下
[virtual protected] void QWidget::mousePressEvent(QMouseEvent *event);
// 鼠标释放
[virtual protected] void QWidget::mouseReleaseEvent(QMouseEvent *event);
// 鼠标移动
[virtual protected] void QWidget::mouseMoveEvent(QMouseEvent *event);
事件分发
qt中在触发事件时会将其事件传递给它的event函数即事件分发器,由它判断是什么类型的事件然后发送给对应的事件处理器函数 可以对对应的事件调用type
函数判断对应的事件,返回的时一个QEvent
枚举值,其中包含了各类事件 也可以重写事件分发器事件 示例
bool MainWindow::event(QEvent *event)override{
if(event->type()==QEvent::MouserButtonPress){
qDebug()<<"鼠标按下了";
}
return QMainWindow::event(event);
}
在此示例中重写了事件分发器,当出现鼠标按下的事件时就打印输出信息并转发给其父类的事件分发器做默认处理,如果想要拦截事件可以不转发事件, 自己处理不向下分发
事件处理器函数
Qt提供的这些事件处理器函数都是回调函数,也就是说作为使用者我们只需要指定函数的处理动作,关于函数的调用是不需要操心的,当某个事件被触发,Qt框架会调用对应的事件处理器函数。
QObject中的事件函数
virtual void timeEvent(QTimerEvent *event);
定时器事件,和QTimer是一样的
QWidget中的事件函数 QWidget类是Qt中所有窗口类的基类,在这个类里边定义了很多事件处理器函数,它们都是受保护的虚函数。我们可以在Qt的任意一个窗口类中重写这些虚函数来重定义它们的行为。
//关闭事件,点击窗口右上角的叉触发
virtual void closeEvent(QCloseEvent *event)
//处理窗口优先菜单,可以设置优先菜单的策略,设置好后点击鼠标右键这个事件就会自动被调用
virtual void contextMenuEvent(QContextMenuEvent *event)
//拖拽某个控件进入窗口时调用
virtual void dragEnterEvent(QDragEnterEvent *event)
//拖拽控件离开窗口时调用
virtual void dragLeaveEvent(QDragLeaveEvent *event)
//是一个持续的事件,鼠标拖拽某一控件移动的过程中持续调用
virtual void dragMoveEvent(QDragMoveEvent *event)
//拖拽控件释放时调用
virtual void dropEvent(QDropEvent *event)
//鼠标进入窗口边缘时调用,只调用一次,光标在窗口里面不会被调用
virtual void enterEvent(QEvent *event)
//光标离开窗口时调用
virtual void leaveEvent(QEvent *event)
//当窗口获得焦点时调用
virtual void focuslnEvent(QFocusEvent *event)
//窗口失去焦点时调用
virtual void focusOutEvent(QFocusEvent *event)
//
virtual bool focusNextPrevchild(bool next)
//窗口被隐藏时调用
virtual void hideEvent(QHideEvent *event)
//窗口隐藏后又被显示出来时被调用
virtual void showEvent(QShowEvent *event)
//
virtual void inputMethodEvent(QInputMethodEvent *event)
//键盘上某一个键被按下后调用
virtual void keyPressEvent(QKeyEvent *event)
//按下键盘上的某一个键释放时调用
virtual void keyReleaseEvent(QKeyEvent *event)
//双击鼠标调用
virtual void mouseDoubleClickEvent(QMouseEvent *event)
//按着鼠标进行移动或者不按鼠标进行移动调用
virtual void mouseMoveEvent(QMouseEvent *event)
//按下鼠标键时调用(包括左右键和中键)
virtual void mousePressEvent(QMouseEvent *event)
//释放鼠标键调用
virtual void mouseReleaseEvent(QMouseEvent *event)
//移动窗口时调用
virtual void moveEvent(QMoveEvent *event)
//
virtual bool nativeEvent(const QByteArray &eventType,void *message,long *result)
//绘图事件,重绘当前窗口如缩放最小化后显示出来
virtual void paintEvent(QPaintEvent *event)
//窗口大小发生变化的时候调用
virtual void resizeEvent(QResizeEvent *event)
//
virtual void tabletEvent(QTabletEvent *event)
//滚动鼠标滚轮时调用
virtual void wheelEvent(QWheelEvent *event)
timeEvent
timeEvent
需要调用startTimer()
startTimer
每隔指定的时间间隔发送一次信号,同时timerEvent也会被自动调用一次.
自定义事件
重写事件处理函数后其父类的信号槽函数就不会执行了: 如:子类继承了一个QPushButton类并重写了其事件处理函数在点击它后就不会有clicked等信号了,如果想要父类中的功能可以:
- 声明对应的信号槽函数并手动调用
- 在重写的事件处理函数中手动调用父类的事件处理函数(可以调用不同的事件)
调用父类的事件函数
如果重写了父类的事件函数后还想要调用父类事件函数的执行可以在重写的事件函数中手动调用父类的事件函数
void MyButton::mouseReleaseEvent (QMouseEvent* event){
Q_UNUSED(ev); // 如果函数中用不到event参数编译器会警告,使用此宏可以消除警告
m_pixmap.load(":/ghost-1.png");
update();
Qwidget::mousePressEvent(event); //调用父类的事件处理函数
}
绘图事件
指定绘画设备
painter(this)
绘画
draw
设置画笔 设置画笔颜色 QPen pen
设置画笔风格 如:虚线 setStyle
让画家使用画笔 setPen
设置画刷 用来填充封闭图形的颜色 QBrush
让画家使用画刷 setBrush
抗锯齿setRenderHint
画图形
画线 drawLine(QPointe* start,QPoint* end)
画椭圆(包含圆) drawEllipse(QPoint* cecnter,a,b)
指定中心(不是焦点,如果圆的话就是圆心)和长度宽度
画矩形 drawRect
画文字 drawText
以矩形形式指定文本框大小
修改画家参考系原点translate(int x, int y)
保存画家状态save
保存后其参考系原点画笔等也保存了,下次再次绘画就从已经修改并保存状态后开始
还原状态restore
还原到最近保存的一次状态
直接画图片
drawPixmap
如果手动调用绘图时间需要调用update
来更新
绘图设备
绘图设备是继承QPainterDevice的子类包含以下四个:
- QPixmap: 专门为图像在屏幕上的显示做了优化
- QBitmap: 是QPixmap的一个子类,它的色深限定为1 ( 黑白图片 ) ,可以使用QPixmap的isQBitmap(函数来确定这个QPixmap是不是一个QBitmap。
- QImage专门为图像的像素级访间做了优化。可以更改像素的颜色
- QPicture则可以记录和重现QPainter的各条命令。
QPixmap
//Pixmap绘图设备
QPixmap pix(300,300);
//填充白色
pix.fill(Qt::white);
//声明画家QPainter
painter(&pix);
painter.setPen(QPen(Qt::green));
painter.drawEllipse(QPoint(150,150)),100,100);
//保存到磁盘
pix.save("D:\\pix.png");
QImagesetPixel(int x,int y,QRgb value)
对指定像素点更改对应的颜色
QPicture 记录和重现指令 通过QPicture将画家类绘画的结果保存到文件中 ( 文件可以是任意后缀名,包括不存在的 ) ,再次读取时再次使用QPicture可以重现之前操作的结果
鼠标和键盘事件
鼠标事件
鼠标点击(左右键都有)事件对应qt中的枚举Qt::MouseButton
点击事件存在于QMouseEvent中,可以使用button()
方法获取; buttons()
方法和button()
类似的,button产生的事件是一瞬间,buttons产生的事件是持续的
贡献者
版权所有
版权归属:PinkDopeyBug