Qt知识点散记(持续更新……)

1. 水平、垂直、网格、表单布局中的setSpacing()方法

  1. 在水平布局QHBoxLayout、垂直布局QVBoxLayout中,setSpacing() 分别用于设置水平方向、垂直方向上的间距
QHBoxLayout *pHLay = new QHBoxLayout();
pHLay->setSpacing(100); 		// 设置水平方向上子控件之间的间距为100
QVBoxLayout *pVLay = new QVBoxLayout ();
pVLay->setSpacing(200); 		// 设置垂直方向上子控件之间的间距为200
  1. 在网格布局QGridLayout 、表单布局QFormLayout中,setSpacing() 方法会同时设置水平间距和垂直间距(即将水平方向与垂直方向的间距统一设置为指定值)。
QGridLayout *pGLay = new QGridLayout ();
pGLay->setSpacing(100); 		// 设置水平和垂直方向上子控件之间的间距为100
QFormLayout *pFLay = new QFormLayout();
pFLay->setSpacing(200); 		// 设置水平和垂直方向上子控件之间的间距为200
  1. 若需要分别控制水平、垂直间距,需使用专门方法:
void setHorizontalSpacing(int spacing);		// 设置水平间距
void setVerticalSpacing(int spacing);		// 设置垂直间距

2. QPainter::begin: Painter already active

问题代码:

QPainter painter(this);     // begin方法会激活画家,此处不能重复指定父对象
painter.begin(this);
// ...其它代码
painter.end();

修改为:

QPainter painter;     // 不再重复指定父对象,QPainter::begin: Painter already active 问题消失
painter.begin(this);
// ...其它代码
painter.end();

3. QLayout: Attempting to add QLayout “xxx” to CSidebarStackedWidget “xxx”, which already has a layout

问题根本原因是:试图给一个已经设置了布局(layout)的 QWidget 对象再次设置另一个布局。

错误举例:

QVBoxLayout *pVLayTitleAndStack = new QVBoxLayout(this);
// 写完上面这句,Qt 会自动调用 this->setLayout(...),
// 将这个新创建的 QVBoxLayout 设置为 this 的布局!
// ...
// ... 其它代码
// ...
this->setLayout(pMainLayout); // 运行到这就会报以上警告信息
// 这就导致了:先通过 new QVBoxLayout(this) 隐式设置了布局,
// 再通过 setLayout(pMainLayout) 显式设置另一个布局 → Qt 检测到重复设置布局,
// 于是报出警告。

所以再使用布局时,要注意布局的父对象设置。
加入A布局会被B布局addLayout(),建议就不要指定A布局的父对象了,当B布局析构时,A布局也会被析构。

4. 快捷添加一个固定间距(空白占位)

// 函数原型
void QBoxLayout::addSpacing(int size)

作用:在布局中添加一段固定像素值的空白区域,空白大小不会随窗口缩放变化。
相比在布局中添加一个 QSpacerItem 实例,省去了手动实例化的步骤,更简洁高效。


5. 解决QObject::startTimer: Timers cannot be started from another thread问题

程序运行后,Qt Creator控制台输出以下错误信息:

QObject::startTimer: Timers cannot be started from another thread
QObject::startTimer: Timers cannot be started from another thread

出问题的代码:

ModbusRtuUdp::ModbusRtuUdp(QObject *parent)
    : QObject(parent)   // 父类构造函数
    , m_pCommCycleCtrlTimer(new QTimer(this))           // 通信周期定时器,用于控制通信周期
    , m_pRespondWarnTimer(new QTimer(this))             // 响应超时警告定时器,用于警告响应超时
    , m_pRespondFaultTimer(new QTimer(this))            // 响应超时故障定时器,用于故障响应超时
	// .. 省略无关本问题的其它代码
{
	// ..省略无关本问题的其它代码
}
// 发送请求
void ModbusRtuUdp::sendRequest()
{
	// ... 省略无关本问题的其它代码
	//! 问题就出在下面这两句,定时器调用 start()方法这里
	m_pRespondWarnTimer->start(m_nRespondTimeout_Warn);     // 启动响应超时(警告)定时器
	m_pRespondFaultTimer->start(m_nRespondTimeout_Fault);   // 启动响应超时(故障)定时器
	// ...省略无关本问题的其它代码
}

问题分析:

先说个知识点:
在 Qt 中,对象的线程亲和性(thread affinity)与其父对象密切相关。如果你为一个 QObject 指定了父对象(parent),那么这个子对象必须和父对象在同一个线程中。
也就是说如果创建定时器时指定了父对象, 那么可能导致无法将其转移到子线程。


在 Qt 中,定时器(QTimer)必须在其所属线程中启动。 原代码中,QTimer 通过 new QTimer(this) 创建,将其父对象设为 ModbusRtuUdp 实例。当该实例被 moveToThread() 移动到子线程后,定时器的线程亲和性仍受父对象约束,但其实际创建发生在主线程, 导致后续在子线程中调用 start() 时违反了“不能从其他线程启动定时器”的规则,从而触发错误。

根本原因:带有父对象的子对象无法安全地随父对象迁移线程,尤其像 QTimer 这类依赖线程事件循环的对象。

解决关键:创建 QTimer 时不指定父对象,确保其能随宿主对象正确迁移至目标线程后再启动。

正确代码(创建 QTimer 时不指定父对象)

ModbusRtuUdp::ModbusRtuUdp(QObject *parent)
    : QObject(parent)   // 父类构造函数
    , m_pCommCycleCtrlTimer(new QTimer())           // 关键修改!!!
    , m_pRespondWarnTimer(new QTimer())             // 关键修改!!!
    , m_pRespondFaultTimer(new QTimer())            // 关键修改!!!
	// .. 省略无关本问题的其它代码
{
	// ..省略无关本问题的其它代码
}
ModbusRtuUdp::~ModbusRtuUdp{}
{
	// 创建对象时不能指定父对象,进而无法使用Qt对象树,因此只能手动删除
    delete m_pCommCycleCtrlTimer;   // 通信周期定时器
    m_pCommCycleCtrlTimer = nullptr;
    delete m_pRespondWarnTimer;     // 响应超时警告定时器
    m_pRespondWarnTimer = nullptr;
    delete m_pRespondFaultTimer;    // 响应超时故障定时器
    m_pRespondFaultTimer = nullptr;
}
// 发送请求
void ModbusRtuUdp::sendRequest()
{
	// ... 省略无关本问题的其它代码
	//! 经过以上修改,下面两行代码不会再报错
	m_pRespondWarnTimer->start(m_nRespondTimeout_Warn);     // 启动响应超时(警告)定时器
	m_pRespondFaultTimer->start(m_nRespondTimeout_Fault);   // 启动响应超时(故障)定时器
	// ...省略无关本问题的其它代码
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

油炸自行车

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值