如何在桌面上弹跳 QWidget
How to bounce a QWidget around the desktop
我试图在屏幕上弹跳 QWidget。这是我试过的代码。
class Window : public QMainWindow {
public:
void moveEvent(QMoveEvent* aEvent) override;
};
void Window::moveEvent(QMoveEvent* aEvent) {
QSizeF screenSize = QGuiApplication::primaryScreen()->screenSize();
QRect oldRect = this->geometry();
QRect newRect = oldRect;
QPoint offset;
if (newRect.left() == 0) {
offset.setX(1);
}
else if (newRect.right() == screenSize.width()) {
offset.setX(-1);
}
if (newRect.top() == 0) {
offset.setX(1);
}
else if (newRect.bottom() == screenSize.height()) {
offset.setX(-1);
}
newRect.setTopLeft(newRect.topLeft() + offset);
newRect.setBottomRight(newRect.bottomRight() + offset);
QTimer::singleShot(1, [this, newRect]() {
setGeometry(newRect);
});
}
int main(int argc, char** argv) {
QApplication app{argc, argv};
Window* w = new Window();
w->show();
w->setGeometry(w->geometry());
return app.exec();
}
然而,window 并没有在屏幕上四处移动,而是在原地有些抖动。当我用鼠标移动 window 并松开时。它偶尔会在桌面上移动,这也不是我想要的。
有人知道这是否可行吗?如果是这样,有谁知道正确的方法吗?
发布的代码有几个问题,包括:
Window
class 没有任何 member-variable 来跟踪其当前的运动方向。如果不保持该状态,则无法正确计算沿该运动方向的下一个位置。
从 moveEvent()
中驱动动画有点棘手,因为 moveEvent()
被调用以响应 setGeometry()
以及响应用户实际用鼠标移动 window;这使得意想不到的反馈循环成为可能,从而导致意想不到的行为。
代码假设屏幕的可用表面积从 (0,0)
开始到 (screenSize.width(),screenSize.height())
结束,这不一定是有效的假设。屏幕的实际可用区域是availableGeometry()
.
给定的一个矩形
当调用setGeometry()
时,您正在设置Qt程序实际可以绘制到的window区域的新位置。然而,这只是 window 实际 on-screen 区域的 99% 子集,因为 window 还包括 non-Qt-controlled 区域,如标题栏和 window-borders。这些部分也需要适合 availableGeometry()
,否则 window 将无法完全定位到您想要的位置,这可能导致异常(例如 window 卡住" 在屏幕的 top-edge 上)
无论如何,这是我重写代码以实现 closer-to-correct“弹跳 window”的尝试。请注意,如果您尝试 mouse-drag 周围的 window 而 window 也在尝试移动自己,它仍然有点小故障;理想情况下,Qt 程序可以检测到标题栏上的 mouse-down-event 并使用它来禁用其 self-animation 直到相应的 mouse-up-event 发生之后,但是 AFAICT 如果不求助于 OS-specific hackery,因为 window-title-bar-dragging 由 OS 处理,而不是由 Qt 处理。因此,我在此保留未实现的逻辑。
#include <QApplication>
#include <QMainWindow>
#include <QMoveEvent>
#include <QShowEvent>
#include <QScreen>
#include <QTimer>
class Window : public QMainWindow {
public:
Window() : pixelsPerStep(5), moveDelta(pixelsPerStep, pixelsPerStep)
{
updatePosition(); // this will get the QTimer-loop started
}
private:
void updatePosition()
{
const QRect windowFrameRect = frameGeometry(); // our on-screen area including window manager's decorations
const QRect windowRect = geometry(); // our on-screen area including ONLY the Qt-drawable sub-area
// Since setGeometry() sets the area not including the window manager's window-decorations, it
// can end up trying to set the window (including the window-decorations) slightly "out of bounds",
// causing the window to "stick to the top of the screen". To avoid that, we'll adjust (screenRect)
// to be slightly smaller than it really is.
QRect screenRect = QGuiApplication::primaryScreen()->availableGeometry();
screenRect.setTop( screenRect.top() + windowRect.top() - windowFrameRect.top());
screenRect.setBottom( screenRect.bottom() + windowRect.bottom() - windowFrameRect.bottom());
screenRect.setLeft( screenRect.left() + windowRect.left() - windowFrameRect.left());
screenRect.setRight( screenRect.right() + windowRect.right() - windowFrameRect.right());
// Calculate where our window should be positioned next, assuming it continues in a straight line
QRect nextRect = geometry().translated(moveDelta);
// If the window is going to be "off the edge", set it to be exactly on the edge, and reverse our direction
if (nextRect.left() <= screenRect.left()) {nextRect.moveLeft( screenRect.left()); moveDelta.setX( pixelsPerStep);}
if (nextRect.right() >= screenRect.right()) {nextRect.moveRight( screenRect.right()); moveDelta.setX(-pixelsPerStep);}
if (nextRect.top() <= screenRect.top()) {nextRect.moveTop( screenRect.top()); moveDelta.setY( pixelsPerStep);}
if (nextRect.bottom() >= screenRect.bottom()) {nextRect.moveBottom(screenRect.bottom()); moveDelta.setY(-pixelsPerStep);}
setGeometry(nextRect);
QTimer::singleShot(20, [this]() {updatePosition();});
}
const int pixelsPerStep;
QPoint moveDelta; // our current positional-offset-per-step in both X and Y direction
};
int main(int argc, char** argv) {
QApplication app{argc, argv};
Window* w = new Window();
w->show();
return app.exec();
}
我试图在屏幕上弹跳 QWidget。这是我试过的代码。
class Window : public QMainWindow {
public:
void moveEvent(QMoveEvent* aEvent) override;
};
void Window::moveEvent(QMoveEvent* aEvent) {
QSizeF screenSize = QGuiApplication::primaryScreen()->screenSize();
QRect oldRect = this->geometry();
QRect newRect = oldRect;
QPoint offset;
if (newRect.left() == 0) {
offset.setX(1);
}
else if (newRect.right() == screenSize.width()) {
offset.setX(-1);
}
if (newRect.top() == 0) {
offset.setX(1);
}
else if (newRect.bottom() == screenSize.height()) {
offset.setX(-1);
}
newRect.setTopLeft(newRect.topLeft() + offset);
newRect.setBottomRight(newRect.bottomRight() + offset);
QTimer::singleShot(1, [this, newRect]() {
setGeometry(newRect);
});
}
int main(int argc, char** argv) {
QApplication app{argc, argv};
Window* w = new Window();
w->show();
w->setGeometry(w->geometry());
return app.exec();
}
然而,window 并没有在屏幕上四处移动,而是在原地有些抖动。当我用鼠标移动 window 并松开时。它偶尔会在桌面上移动,这也不是我想要的。
有人知道这是否可行吗?如果是这样,有谁知道正确的方法吗?
发布的代码有几个问题,包括:
Window
class 没有任何 member-variable 来跟踪其当前的运动方向。如果不保持该状态,则无法正确计算沿该运动方向的下一个位置。从
moveEvent()
中驱动动画有点棘手,因为moveEvent()
被调用以响应setGeometry()
以及响应用户实际用鼠标移动 window;这使得意想不到的反馈循环成为可能,从而导致意想不到的行为。代码假设屏幕的可用表面积从
给定的一个矩形(0,0)
开始到(screenSize.width(),screenSize.height())
结束,这不一定是有效的假设。屏幕的实际可用区域是availableGeometry()
.当调用
setGeometry()
时,您正在设置Qt程序实际可以绘制到的window区域的新位置。然而,这只是 window 实际 on-screen 区域的 99% 子集,因为 window 还包括 non-Qt-controlled 区域,如标题栏和 window-borders。这些部分也需要适合availableGeometry()
,否则 window 将无法完全定位到您想要的位置,这可能导致异常(例如 window 卡住" 在屏幕的 top-edge 上)
无论如何,这是我重写代码以实现 closer-to-correct“弹跳 window”的尝试。请注意,如果您尝试 mouse-drag 周围的 window 而 window 也在尝试移动自己,它仍然有点小故障;理想情况下,Qt 程序可以检测到标题栏上的 mouse-down-event 并使用它来禁用其 self-animation 直到相应的 mouse-up-event 发生之后,但是 AFAICT 如果不求助于 OS-specific hackery,因为 window-title-bar-dragging 由 OS 处理,而不是由 Qt 处理。因此,我在此保留未实现的逻辑。
#include <QApplication>
#include <QMainWindow>
#include <QMoveEvent>
#include <QShowEvent>
#include <QScreen>
#include <QTimer>
class Window : public QMainWindow {
public:
Window() : pixelsPerStep(5), moveDelta(pixelsPerStep, pixelsPerStep)
{
updatePosition(); // this will get the QTimer-loop started
}
private:
void updatePosition()
{
const QRect windowFrameRect = frameGeometry(); // our on-screen area including window manager's decorations
const QRect windowRect = geometry(); // our on-screen area including ONLY the Qt-drawable sub-area
// Since setGeometry() sets the area not including the window manager's window-decorations, it
// can end up trying to set the window (including the window-decorations) slightly "out of bounds",
// causing the window to "stick to the top of the screen". To avoid that, we'll adjust (screenRect)
// to be slightly smaller than it really is.
QRect screenRect = QGuiApplication::primaryScreen()->availableGeometry();
screenRect.setTop( screenRect.top() + windowRect.top() - windowFrameRect.top());
screenRect.setBottom( screenRect.bottom() + windowRect.bottom() - windowFrameRect.bottom());
screenRect.setLeft( screenRect.left() + windowRect.left() - windowFrameRect.left());
screenRect.setRight( screenRect.right() + windowRect.right() - windowFrameRect.right());
// Calculate where our window should be positioned next, assuming it continues in a straight line
QRect nextRect = geometry().translated(moveDelta);
// If the window is going to be "off the edge", set it to be exactly on the edge, and reverse our direction
if (nextRect.left() <= screenRect.left()) {nextRect.moveLeft( screenRect.left()); moveDelta.setX( pixelsPerStep);}
if (nextRect.right() >= screenRect.right()) {nextRect.moveRight( screenRect.right()); moveDelta.setX(-pixelsPerStep);}
if (nextRect.top() <= screenRect.top()) {nextRect.moveTop( screenRect.top()); moveDelta.setY( pixelsPerStep);}
if (nextRect.bottom() >= screenRect.bottom()) {nextRect.moveBottom(screenRect.bottom()); moveDelta.setY(-pixelsPerStep);}
setGeometry(nextRect);
QTimer::singleShot(20, [this]() {updatePosition();});
}
const int pixelsPerStep;
QPoint moveDelta; // our current positional-offset-per-step in both X and Y direction
};
int main(int argc, char** argv) {
QApplication app{argc, argv};
Window* w = new Window();
w->show();
return app.exec();
}