我正在尝试创建一个可拆卸类型样式的小部件,就像 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 事件。
如有任何建议,我们将不胜感激!
有点棘手的解决方法。我没有测试过,这只是一个想法。
当鼠标悬停在小部件的可拖动部分时,您可以使用
Qt::FramelessWindowHint
(也可以使用 Qt::WA_TranslucentBackground
)创建最顶层的小部件(我们称之为 Shade)。您可以通过重新实现 paintEvent
来操纵 Shade 外观。例如 - 绘制原始小部件的内容,或者绘制一些透明预览等
然后您可以在拖动过程中调整 Shade 的大小,以向用户显示小部件将被分离。您不会失去鼠标捕获。
当用户释放鼠标时 - 您会记住 Shade 的位置,销毁它并分离+移动原始小部件。
如果您想了解更多详情,请随时询问。
这是类似的问题。
因此,您应该使用 QDocWidget 并使用 tabifyDockWidget 强制堆叠该小部件。
我遇到了这个问题,或者一个非常相似的问题:
// 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");
}
}