自学内容网 自学内容网

Qt界面编程04

问题:能否使用循环+sleep+move实现控件的移动效果(动画)?

1、默认的Qt程序是单进程且单线程,该主线程的主要任务是监控响应用户的界面操作。

2、当有事件、信号产生时,主线程会调用事件函数、槽函数,在执行事件函数、槽函数的过程中,界面无法响应(俗称卡住了),只有当事件函数、槽函数执行完毕后,才把执行权还给QApplication对象的exec函数。

3、返回到exec函数后,它会重新显示、渲染界面,在事件函数、槽函数对界面做出改变会一次显示出来,不会有动画效果。

1、Qt控件的动画

QPropertyAnimation 负责实现控件的动态移动效果,俗称动画,播放控件的移动过程,它是基于多线程实现的。

使用方法:

1、创建QPropertyAnimation对象

2、setTargetObject(控件的地址) 设置要播放的控件

3、setPropertyName("geometry") 设置依靠控件的什么属性生成动画

4、setStartValue(QRect(x, y, w, h)) 设置控件的初始位置和大小

5、setEndValue(QRect(x, y, w, h)) 设置控件的结束位置和大小

6、setLoopCount(cnt) 设置动画的播放次数,-1无限播放

7、setDuration(msecs) 设置动画的时长

8、start() 开始播放动画

    /* 创建一个动画对象 */
    QPropertyAnimation *animation = new QPropertyAnimation;
​
    /* 设置动画目标 */
    animation->setTargetObject(ui->label);
​
    /* 设置窗口几何属性(位置,大小)作为动画参考 */
    animation->setPropertyName("geometry");
​
    /* 设置动画开始坐标和大小(QRect) */
    animation->setStartValue(QRect(25, 25, 50, 50));
​
    /* 设置动画结束坐标和大小(QRect) */
    animation->setEndValue(QRect(0, 0, 100, 100));
​
    /* 设置循环次数:-1为无限次 */
    animation->setLoopCount(1);
    /* 设置动画持续时长 */
    animation->setDuration(1000);
    /* 开始动画 */
    animation->start();

练习:

1、给2048游戏,新生成的数字,添加缩放动画。

2、给贪吃蛇游戏,新生成的食物,添加缩放动画。

3、显示图片

1、右击项目->添加新文件->Qt->Qt resource file->输入资源文件名->完成。

2、双击新建的资源文件->添加->添加前缀->Ctrl+s 保存。

3、选中前缀->添加->添加文件->选择要添加的图片文件->Ctrl+s 保存。

4、在Qt设计师中插入一个QLabel控件,右击QLabel控件->改变样式表->添加资源->Image->选择要显示的图片,会自动缩放。

border-image: 把图片按照控件的边框大小进行拉伸

image: 保持图片原有的比例进行缩放

background-image: 把图片平铺满整个控件 ​ 注意:此方法只能用于显示,程序员准备好的图片。

4、QPainter绘图

1、QPainter类只能工作在void paintEvent(QPaintEvent *event)事件函数中,要想使用QPainter绘图必须重载事件函数。

2、创建QPainter类对象,设置要绘制图片的控件。

QPainter painter(this);

3、设置画笔的线条样式,设置画笔颜色。

void setPen(const QColor &color);
QColor(Qt::GlobalColor color);  // 使用Qt自带的颜色
QColor(int r, int g, int b, int a = 255); // 使用RGB三原色调配,a是透明度
​
void setPen(Qt::PenStyle style);
​
Qt::SolidLine:实线,没有间隔。
Qt::DashLine:虚线,由一系列等长的短线段组成。
Qt::DotLine:点线,由一系列等长的点组成。
Qt::DashDotLine:点划线,由一段短线段和一段点交替出现。
Qt::DashDotDotLine:双点划线,由一段短线段和两个点交替出现。
Qt::CustomDashLine:自定义虚线,允许用户指定一个数组来定义短线段和空白的长度。

4、调用drawXxxx系列函数绘制线条、拆线、弧线、图形。

5、绘制图片:

// 打开要绘制的图片
QImage image("../image/1.jpg");
// 创建 QPainter 对象
QPainter painter(this);
// 把图片绘制到窗口
painter.drawImage(ui->label->rect(),image);
​
// 在指定的区域绘制图片(会自动缩放
void QPainter::drawImage(const QRect &r, const QImage &image)
​
// 从指定的点开始绘制图片(平铺)
void QPainter::drawImage(const QPoint &p, const QImage &image)
    
// 获取图片的宽和高
image.width(),image.height()

练习:实现一个图片浏览器,在QScrollArea窗口上显示图片(滚动条)。

5、Qt中的线程

1、在Qt中线程被封装成了QThread类。

2、该类不能直接使用,必须先继承该类,覆盖run成员函数,该函数就是线程的入口函数。

3、主线程创建新的线程对象,调用它的start成员函数,线程就会从run函数启动开始运行。

4、wait()成员函数用于回收子线程,可以指定等待的时间,以毫秒为单位千分之一秒,此功能相当于pthread库中的pthread_join函数。

5、QThread::currentThreadId() 静态成员函数用于获取线程的ID,可以区别主子线程,相当于pthread库中的pthread_self()函数。

6、主线程可以调用线程对象的terminate()成员函数终止线程,相当于pthread库中的pthread_cancel函数,setTerminationEnabled(bool enabled = true)可以设置是否允许被其它线程终止,相当于pthread库中的pthread_setcancelstate函数。

7、线程优先级属性:

priority() 获取线程的优先级

setPriority() 设置线程的优先级

start() 启动线程时设置优先级

8、栈内存大小属性:

setStackSize(uint stackSize) 设置线程的栈内存大小。

stackSize() 获取线程的栈内存大小,如果没有设置过线程栈内存的大小,则返回的是0,Windows系统默认的栈内存使用上限是2Mb,ubuntu系统默认的是8Mb。

9、线程休眠(谁调用谁休眠):

void sleep(unsigned long);      //以秒为单位
void msleep(unsigned long);     //毫秒为单位 1000毫秒=1秒
void usleep(unsigned long);     //微秒为单位 1000000微秒=1秒

10、信号和槽

void finished() 线程自然的执行结束,会发出该信号。
void started() 线程被启动时会出该信号
​
void quit() 到达结束点时结束线程
void start(Priority priority = InheritPriority) 启动线程
void terminate() 强制结束线程(要看操作系统是否支持)

注意:尽量不要用terminate()停止线程。

10、C\C++的思路使用QThread类

1、自定义一个类,继承QThread类。

2、覆盖void run(void) 虚函数,也就是线程的入口函数。

3、定义类对象,调用start成员函数,启动线程。

4、run函数开始执行,在该函数中完成线程的相关操作。

11、Qt模式使用线程

QObject::•connect(const QObject * sender, 
                  const char * signal, 
                  const QObject * receiver,
                  const char * method, 
                  Qt::ConnectionType type = Qt::AutoConnection);
​
enum ConnectionType {
    AutoConnection,
    DirectConnection,
    QueuedConnection,
    BlockingQueuedConnection,
    UniqueConnection =  0x80
};
​
Qt::AutoConnection:自动选择连接方式,根据发送者和接收者是否在同一个线程来决定使用直接连接还是队列连接。
Qt::DirectConnection:直接连接方式,当发送者发出信号时,接收者的槽函数会立即执行。 
Qt::QueuedConnection:队列连接方式,当发送者发出信号时,接收者的槽函数会被放入一个任务队列中等待执行。
Qt::BlockingQueuedConnection:阻塞队列连接方式,当发送者发出信号时,如果接收者的槽函数还没有执行完,那么发送者会被阻塞住直到接收者的槽函数执行完毕。
    
Qt::ConnectionType type
    默认情况下,使用的是Qt::AutoConnection,连接方式,当信号发送者与信号接收者在同一个线程,Qt会选择使用Qt::DirectConnection(当槽函数被调用的,信号发送者会等待槽执行完毕,此时信号发送者会进入阻塞状态,因为此时只有一个线程在运行)。
    当信号发送者与信号接收者们于不同的线程,Qt会选择Qt::QueuedConnection连接方式,当信号发出后,槽函数开始工作(它自己所在线程会停止工作,转而执行槽函数),信号发送者可以继续执行。
    如果手动选择 Qt::BlockingQueuedConnection 连接方式,即使信号发送者与信号接收者们于不同的线程执行效果也会与Qt::DirectConnection一样。

注意:当槽函数的执行时间较长(比较耗时,为了避免界面卡住),要让发送信号的对象与槽函数对象不在一个线程内。

方法1:

1、定义一个工作类,继承QObject类或者它的子类。

2、定义用于工作的槽函数。

3、定义Thread类,并继承QThread。

4、实现Thread类的在run函数,在函数内:

创建工作类对象(让对象位于线程内部),绑定工作槽函数与信号

exec() 让线程一直运行。

5、创建Thread类对象,调用对象的start()函数启动线程。

6、发送信号,负责工作的槽函数就会在子线程内执行。

方法2:

1、定义工作类Worker,在它的类内定义工作槽函数。

2、创建工作类对象worker,和QThread对象thread。

3、把worker对象移动到thread线程的内部,worker->moveToThread(thread);

4、连接信号,然后再启动线程。

5、发送信号,负责工作的槽函数就会在子线程内执行。

6、Qt中的线程同步

1、互斥锁 QMutex

bool isRecursive() 判断当前互斥锁是否被递归使用(连续加锁)
void lock() 加锁,如果该锁已经被锁则阻塞
bool tryLock(int timeout = 0) 尝试加锁,成功返回true,失败返回false
void unlock() 解锁

2、读写锁QReadWriteLock

void lockForRead() 加读锁,如果之前没有加锁或加了读锁则立即返回,如果之前加的是写锁则阻塞。
void lockForWrite() 加写锁,如果之前已经加锁则阻塞。
bool tryLockForRead()
bool tryLockForRead(int timeout)
bool tryLockForWrite()
bool tryLockForWrite(int timeout)
void unlock()
    
    线程A     线程B
    读锁      读锁  OK
    读锁      写锁  NO
    写锁      读锁  NO
    写锁      写锁  NO

3、信号量QSemaphore

void acquire(int n = 1) 信号量减操作
intavailable() const获取信号量的值
void release(int n = 1) 信号量加操作
bool tryAcquire(int n = 1) 尝试信号量减操作
bool tryAcquire(int n, int timeout) 带倒计时尝试信号量减操作

4、条件变量QWaitCondition

bool wait(QMutex * lockedMutex, unsigned long time = ULONG_MAX) 线程睡入条件变量并解锁互斥锁
bool wait(QReadWriteLock * lockedReadWriteLock, unsigned long time = ULONG_MAX) 线程睡入条件变量并解锁读写锁
void wakeAll() 叫醒所有线程
void wakeOne() 叫醒一个线程

5、原子操作还可以继续使用。

type __sync_fetch_and_add (type *ptr, type value);// +
type __sync_fetch_and_sub (type *ptr, type value);// -
type __sync_fetch_and_and (type *ptr, type value);// &
type __sync_fetch_and_or (type *ptr, type value);// |
type __sync_fetch_and_nand (type *ptr, type value);// ~
type __sync_fetch_and_xor (type *ptr, type value);// ^
功能:以上操作返回的是*ptr的旧值

type __sync_add_and_fetch (type *ptr, type value); // +
type __sync_sub_and_fetch (type *ptr, type value);// -
type __sync_and_and_fetch (type *ptr, type value);// &
type __sync_or_and_fetch (type *ptr, type value);// |
type __sync_nand_and_fetch (type *ptr, type value);// ~
type __sync_xor_and_fetch (type *ptr, type value);// ^
功能:以上操作返回的是*ptr与value计算后的值
    
type __sync_lock_test_and_set (type *ptr, type value);
功能:把value赋值给*ptr,并返回*ptr的旧值
    
__sync_lock_release(type *ptr);
功能:将*ptr赋值为0

7、Qt中的Socket通信

由于Qt程序的主线程需要响应界面操作,所以在Socket通信中的阻塞操作(除了读取)都要使用 信号+槽 机制处理。

注意:在Qt中使用Socket进行通信需要在pro文件中添加以下配置。

QT += network

1、QTcpServer

1、创建QTcpServer对象,

2、调用QTcpServer的成员函数 listen 设置监听,需要提供ip地址,端口号,包含原Socket的bind、listen功能。

3、不需要执行accept等待客户端连接,因为该动作是阻塞的,如果执行该操作界面就会卡住,所以Qt没有提供此功能。

当有客户端连接时QTcpServer对象会发出 void newConnection() 信号。

此时我们只需要调用 nextPendingConnection() 成员函数,就会返回一个已经连接成员的 QTcpSocket 对象地址。

4、我们无法使用 阻塞的方式等待接收客户端发送过来数据,原因与等待连接一样。

当有客户端发送过来数据时QTcpSocket 对象会发出 readyRead() 信号,需要绑定该信号,用于读取数据。

5、由于readyRead()信号没有参数,无法知道从哪个客户端读取数据

方法1:遍历所有客户端,调用 bytesAvailable()成员函数 判断是否有数据需要读取。

方法2:在槽函数内,调用sender()函数可以手动获取信号的发送者是谁,但返回的是QObject,需要进行强制转换才能使用。

qint64 read(char *data, qint64 maxlen);
功能:从客户端读取不超过maxlen个字节的数据
data:存储数据的内存地址
maxlen:data内存块的大小
返回值:实际读取的数据量
注意:一般数据包大小固定的时候使用该函数读取数据(防止粘包)

QByteArray read(qint64 maxlen);
功能:从客户端读取不超过maxlen个字节的数据,存储数据的内存由read自己创建。
返回值:二进制的数组类对象
注意:最好不要使用引用接收
    
QByteArray readAll();
功能:把客户端缓冲区里的所有数据全局读取完毕
返回值:二进制的数组类对象
    
qint64 readLine(char *data, qint64 maxlen);
功能:从客户端读取不超过maxlen个字节的数据,遇到\n会提前停止
返回值:实际读取的数据量
    
QByteArray readLine(qint64 maxlen = 0);
功能:从客户端读取一行数据,遇到\n才会提前停止。

6、给客户返回数据,QTcpSocket对象的发送数据的成员函数,不会阻塞,只是把发送任务交给write成员函数,会立即返回不管有没有发送完毕,发送成员。

qint64 write(const char *data, qint64 len);
功能:发送len个字节的数据给客户端
data:存储要发送的数据的首地址
len:data内存块的大小
返回值:实际发送了多少个字节数
    
qint64 write(const char *data);
功能:发送一个字符串的数据给客户端
返回值:实际发送了多少个字节数
    
qint64 write(const QByteArray &data)
功能:发送一个二进制数组对象给客户端
返回值:实际发送了多少个字节数
注意:出了发送数据的函数,data不能失效
2、QTcpSocket

1、QTcpSocket对象使用connectToHost成员函数连接服务端,由于网速的不同,连接所需要的时间不同,所以为了避免界面卡住,该函数也不能阻塞,当连接成功时会发出 connected() 信号。

void connectToHost(const QHostAddress &address, quint16 port, OpenMode mode = ReadWrite);
address:服务端的ip
port:服务端的端口号
ReadWrite:连接模式
     ReadOnly = 0x0001,
     WriteOnly = 0x0002,
     ReadWrite = ReadOnly | WriteOnly,

2、当连接断开时会发出 disconnected() 信号,接收数据时也不能阻塞,需要readyRead()发出后才去读取数据。

3、当窗口关闭时,应该主动调用 disconnect() 函数,否则服务端不会产生disconnected() 信号。

4、使用QTcpSocket类的SocketState成员变量判断连接是否关闭

QAbstractSocket::UnconnectedState0The socket is not connected.
QAbstractSocket::HostLookupState1The socket is performing a host name lookup.
QAbstractSocket::ConnectingState2The socket has started establishing a connection.
QAbstractSocket::ConnectedState3A connection is established.
QAbstractSocket::BoundState4The socket is bound to an address and port.
QAbstractSocket::ClosingState6The socket is about to close (data may still be waiting to be written).
QAbstractSocket::ListeningState5For internal use only.

3、QUdpSocket

1、接收者需要提前做的是绑定操作。

bool bind(const QHostAddress &address, quint16 port = 0, BindMode mode = DefaultForPlatform);
功能:使用ip和端口号绑定QUdpSocket对象

bool bind(quint16 port = 0, BindMode mode = DefaultForPlatform);
功能:使用端口号+本机地址绑定QUdpSocket对象

2、发送者可以先调用connectToHost连接接收者,也可以直接发送数据。

void connectToHost(const QString &hostName, quint16 port, OpenMode mode = ReadWrite, NetworkLayerProtocol protocol = AnyIPProtocol);
void connectToHost(const QHostAddress &address, quint16 port, OpenMode mode = ReadWrite);

注意:连接的好处就是之后在发送数据时不需要再提供接收者的ip地址和端口号,可以直接调用write函数发送数据。
qint64 writeDatagram(const char *data, qint64 len, const QHostAddress &host, quint16 port);
qint64 writeDatagram(const QByteArray &datagram, const QHostAddress &host, quint16 port);
功能:UDP专用的发送数据的函数,需要提供接收者的ip地址和端口号。

3、接收数据时也不能进行阻塞,所以要等到readyRead()发出后才去读取数据。

qint64 readDatagram(char *data, qint64 maxlen, QHostAddress *host = 0, quint16 *port = 0);
功能:udp专用的数据接收函数,因为udp的数据协议,不需要考虑粘包,可以接收发送者的ip地址和端口号。


原文地址:https://blog.csdn.net/m0_63127040/article/details/142726415

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!