Linux下移动顶级QWidget时如何实时接收moveEvent()?
问题:Qt输入透明窗口移动时无法实时接收moveEvent及局部可响应区域实现
我正在开发一个基于Qt的应用,该应用使用Qt::WindowTransparentForInput属性,同时需要内部控件可响应点击操作,原型代码如下:
MainWindow::MainWindow() : QWidget( nullptr, Qt::WindowStaysOnTopHint | Qt::WindowDoesNotAcceptFocus | Qt::WindowTransparentForInput ) { setWindowTitle( tr( "GIF Recorder" ) ); setAttribute( Qt::WA_TranslucentBackground, true ); m_w = new QWidget( nullptr, Qt::WindowStaysOnTopHint | Qt::CustomizeWindowHint ); auto vlayout = new QVBoxLayout( m_w ); vlayout->setContentsMargins( 0, 0, 0, 0 ); auto layout = new QHBoxLayout; layout->setContentsMargins( 0, 0, 0, 0 ); auto button = new QToolButton( this ); button->setText( tr( "Record" ) ); layout->addWidget( button ); layout->addItem( new QSpacerItem( 10, 0, QSizePolicy::Expanding, QSizePolicy::Fixed ) ); vlayout->addLayout( layout ); vlayout->addItem( new QSpacerItem( 0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding ) ); m_w->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed ); m_w->resize( 800, button->sizeHint().height() ); m_w->move( geometry().x(), geometry().y() ); m_w->show(); } MainWindow::~MainWindow() { delete m_w; } void MainWindow::moveEvent( QMoveEvent * e ) { m_w->move( geometry().x(), geometry().y() ); e->accept(); } void MainWindow::resizeEvent( QResizeEvent * e ) { m_w->resize( e->size().width(), m_w->height() ); e->accept(); } void MainWindow::closeEvent( QCloseEvent * e ) { m_w->close(); e->accept(); }
目前功能正常,但存在问题:点击窗口标题栏移动MainWindow时,只有在鼠标松开时才会收到一次moveEvent()。我尝试通过nativeEvent观察事件:
bool MainWindow::nativeEvent( const QByteArray & eventType, void * message, qintptr * result ) { static int i = 0; qDebug() << eventType << ++i; return false; }
发现移动过程中没有中间原生事件,怀疑没有直接解决方案,想请教如何实现带局部可响应区域的Qt输入透明窗口?补充:我使用Linux系统,目前考虑放弃系统标题栏,实现自定义无边框Widget并设置掩码,还未验证可行性。
解决方案
问题根源
Qt::WindowTransparentForInput属性会让窗口将所有输入事件传递给下层窗口,包括窗口拖拽的交互事件。此时窗口管理器负责处理拖拽过程,不会将中间的位置更新事件发送给Qt窗口,只有拖拽结束时才会发送最终位置的moveEvent()。
可行实现方案
1. 自定义无边框窗口(推荐)
放弃系统标题栏,完全自定义窗口拖拽逻辑和输入响应区域:
- 移除
Qt::WindowTransparentForInput属性,改用Qt::FramelessWindowHint创建无边框窗口,结合Qt::WA_TranslucentBackground实现透明背景。 - 通过设置窗口掩码(
setMask())或者重写eventFilter(),只让需要响应点击的控件区域接收输入,其他区域保持“输入透明”。 - 自定义拖拽逻辑:在标题栏区域(或指定区域)处理
mousePressEvent、mouseMoveEvent、mouseReleaseEvent,实时更新窗口位置并同步m_w的位置。
示例代码片段:
void CustomTitleBar::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { m_dragStartPos = event->globalPos() - parentWidget()->geometry().topLeft(); event->accept(); } } void CustomTitleBar::mouseMoveEvent(QMouseEvent *event) { if (event->buttons() & Qt::LeftButton) { parentWidget()->move(event->globalPos() - m_dragStartPos); // 同步m_w的位置 m_w->move(parentWidget()->geometry().x(), parentWidget()->geometry().y()); event->accept(); } }
2. 监听X11原生ConfigureNotify事件(Linux专属)
在Linux系统下,直接监听X11的窗口配置改变事件,实时获取窗口位置:
- 在
nativeEvent中处理XCB的ConfigureNotify事件,该事件会在窗口位置或大小变化时实时触发。 - 从事件中提取窗口的新位置,手动更新
m_w的位置,或者触发自定义的位置更新逻辑。
示例代码片段:
bool MainWindow::nativeEvent(const QByteArray &eventType, void *message, qintptr *result) { if (eventType == "xcb_generic_event_t") { xcb_generic_event_t *event = static_cast<xcb_generic_event_t*>(message); if ((event->response_type & 0x7F) == XCB_CONFIGURE_NOTIFY) { xcb_configure_notify_event_t *cfgEvent = reinterpret_cast<xcb_configure_notify_event_t*>(event); // 获取新的窗口位置 int newX = cfgEvent->x; int newY = cfgEvent->y; // 同步m_w的位置 m_w->move(newX, newY); } } return QWidget::nativeEvent(eventType, message, result); }
3. 使用QWindow的positionChanged信号
如果改用QWindow作为顶层窗口,可以监听positionChanged信号,该信号会在窗口位置变化时实时触发:
- 将MainWindow改为继承
QWindow,或者获取现有QWidget对应的底层QWindow对象。 - 连接
positionChanged信号到槽函数,实时更新m_w的位置。
示例代码片段:
MainWindow::MainWindow() { // ... 其他初始化代码 QWindow *window = windowHandle(); connect(window, &QWindow::positionChanged, this, [this](const QPoint &pos) { m_w->move(pos); }); }
内容的提问来源于stack exchange,提问作者Igor Mironchik
相关产品推荐
相关产品推荐

