1. 水平、垂直、网格、表单布局中的setSpacing()方法
- 在水平布局QHBoxLayout、垂直布局QVBoxLayout中,setSpacing() 分别用于设置水平方向、垂直方向上的间距。
QHBoxLayout *pHLay = new QHBoxLayout(); pHLay->setSpacing(100); // 设置水平方向上子控件之间的间距为100 QVBoxLayout *pVLay = new QVBoxLayout (); pVLay->setSpacing(200); // 设置垂直方向上子控件之间的间距为200
- 在网格布局QGridLayout 、表单布局QFormLayout中,setSpacing() 方法会同时设置水平间距和垂直间距(即将水平方向与垂直方向的间距统一设置为指定值)。
QGridLayout *pGLay = new QGridLayout (); pGLay->setSpacing(100); // 设置水平和垂直方向上子控件之间的间距为100 QFormLayout *pFLay = new QFormLayout(); pFLay->setSpacing(200); // 设置水平和垂直方向上子控件之间的间距为200
- 若需要分别控制水平、垂直间距,需使用专门方法:
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); // 启动响应超时(故障)定时器
// ...省略无关本问题的其它代码
}
&spm=1001.2101.3001.5002&articleId=152327837&d=1&t=3&u=8e1ef041c6f0467b9f1dffa41d7b66df)
211

被折叠的 条评论
为什么被折叠?



