在鼠标事件期间更改 QWidget 父级

问题描述 投票:0回答:3

我正在尝试创建一个可拆卸类型样式的小部件,就像 Chrome 选项卡可拆卸的方式一样(类称为 Tab)。我一切正常,除了一个错误,有时(也许 50% 的时间),Tab 对象永远不会获取鼠标释放事件,并且停止获取鼠标移动事件。

本质上,分离系统的工作原理是允许在鼠标按下/移动/释放功能中进行拖动,就像正常情况一样。

mouseMoveEvent
检查从开始移动的总距离,如果超过一定量,将启动“分离”过程。分离过程涉及将父窗口小部件设置为 0(顶级窗口小部件,未修饰的窗口),因此 Tab 对象几乎漂浮在鼠标下方的所有内容之上,并继续与其一起拖动直至释放。

我检查了所有正在传递的

QEvent
项目,我发现当发生此问题时,QEvent::MouseMove 项目(以及此后的所有鼠标事件)将被发送到 TabBar(Tab 对象的原始父级)。这会在选项卡上调用
setParent(0)
后立即发生。

基本鼠标处理概述:

void Tab::mousePressEvent(*) {
    [set up some boolean, start positions, etc]
}
void Tab::mouseMoveEvent(*) {
    [track the updated position]
    if (positionChange > STATIC_AMOUNT)
        detachTab();
}
void Tab::mouseReleaseEvent(*) {
    [return the Tab to its original position, and set the parent back to the TabBar]
}
void Tab::detachTab() {
    QPoint mappedPos = mapToGlobal(0, 0);
    setParent(0);    //The loss of MouseMove events occurs when this returns.
    move(mappedPos);
    show();
    raise();
}

这里是Tab对象接收到的事件(第一行是QEvent类型,第二行是名称)

[Tab::detachTab() started]
[setParent(0) started]
QEvent::Hide
QEvent::Leave
qApp QEvent::MouseMove [ TabBar ]    <--  now the TabBar is soaking up the mouse events
QEvent::HideToParent
QEvent::ParentAboutToChange
QEvent::ParentChange
[setParent(0) returned]
....

总结:我的可拖动 QWidget 在其父级设置为 0 后丢失了 QEvent::MouseMove 和 QEvent::MouseButtonRelease 事件。

如有任何建议,我们将不胜感激!

c++ qt qt5 qwidget
3个回答
2
投票

有点棘手的解决方法。我没有测试过,这只是一个想法。

当鼠标悬停在小部件的可拖动部分时,您可以使用

Qt::FramelessWindowHint
(也可以使用
Qt::WA_TranslucentBackground
)创建最顶层的小部件(我们称之为 Shade)。您可以通过重新实现
paintEvent
来操纵 Shade 外观。例如 - 绘制原始小部件的内容,或者绘制一些透明预览等

然后您可以在拖动过程中调整 Shade 的大小,以向用户显示小部件将被分离。您不会失去鼠标捕获。

当用户释放鼠标时 - 您会记住 Shade 的位置,销毁它并分离+移动原始小部件。


如果您想了解更多详情,请随时询问。


0
投票

这是类似的问题
因此,您应该使用 QDocWidget 并使用 tabifyDockWidget 强制堆叠该小部件。


0
投票

我遇到了这个问题,或者一个非常相似的问题:

  // viewport_ is leaving the main window, reparent it
  QPoint lastPos    = viewport_->pos();
  QPoint windowPos  = oldViewportParent_->mapToGlobal( QPoint( 0,0 ) );

  lastPos += windowPos;

  viewport_->setParent( NULL );
  viewport_->window()->move( lastPos );
  viewport_->window()->setWindowTitle( title_ );
  viewport_->show(); // Mouse events STOP.

此时它会停止接收鼠标事件直到您释放鼠标,或将鼠标移回到小部件上。

为了解决这个问题,我使用 QApplication::qInstallMessageHandler 来捕获 Qt 日志记录,并导出 QT_LOGGING_RULES:

export QT_LOGGING_RULES="*.debug=true;qt.qpa.input.mouse=true;qt.qpa.cocoa.notifications=true;qt.qpa.input.events=true;qt.nativeinterface=true;qt.qpa.input.devices=true"

我发现,正如预期的那样,Qt 正在重新设置 Widget 窗口的父级 - 实际上是一个 Cocoa 窗口。当时我并没有想到这个小部件已经有一个 Cocoa 窗口很奇怪,因为它只是一个小部件(或者我是这么认为的)

然后我写了一个小玩具应用程序来做同样的事情,当然它没有失败

但是,在玩具应用程序中,当显示新的父窗口小部件时,Qt 实际上创建了一个 Cocoa 窗口并将 QWidget“移植”到其中。

Sooo,稍后在 qwindow.cpp 和 qwidget.cpp 中进行大量调试和断点。

结果是在 QWidget (viewport_) 调用 QWidget::winId() 之后的某个时刻。此时,Qt 将在内部调用 QWidgetPrivate::createWinId(),这将为 QWidget 创建一个 Cocoa 窗口,即使它只是一个普通的小部件。

尝试在上面的代码中将该小部件移出会导致鼠标跟踪问题。

如果我消除了该调用(它只是用于日志记录),或者改为调用 QWidget::window()::winId (这是正确的)解决了问题

显然,在小部件上调用 winId() 会导致 Qt 创建一个 Cocoa 窗口,这可能没有错,但会产生其他副作用,从而导致此错误。

所以这个故事的寓意不要调用QWidget::winId(),而是调用QWidget::window::winId()

附注这是我的消息记录器:

    void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    QByteArray localMsg = msg.toLocal8Bit();
    const char *file = context.file ? context.file : "";
    const char *function = context.function ? context.function : "";
    const char *filename = (strrchr(file, '/') ? strrchr(file, '/') + 1 : file);
    switch (type) {
    case QtDebugMsg:
        qDebug( "%s:%d %s Debug: %s",
               filename, context.line, function, localMsg.constData());
        break;
    case QtInfoMsg:
        qDebug( "%s:%d %s Info: %s",
               filename, context.line, function, localMsg.constData());
        break;
    case QtWarningMsg:
        qDebug( "%s:%d %s Warning: %s",
               filename, context.line, function, localMsg.constData());
        break;
    case QtCriticalMsg:
        qDebug( "%s:%d %s Critical: %s",
               filename, context.line, function, localMsg.constData());
        break;
    case QtFatalMsg:
        qDebug( "%s:%d %s Fatal: %s",
               filename, context.line, function, localMsg.constData());
        break;
    default:
        assert("Unknown Message Type");
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.