Issue
I have a Qt window where all widgets have been inserted in a centralwidget, therefore when the window is resized the widgets size do not change (Original Window):
Sometimes, I do not need to see some widgets on the right side or left side, So I try to crop the Windows as those widget are not visible (Right-cropped window):
However, this is just true for the widgets on the right side of Qt window and when I try to crop the window from the left, the widgets on the left are not hidden (Right-cropped window):
Is there any way/trick to be able to crop from the left edge as the PushButton1 is not visible?
Solution
I must admit that I was not fully sure whether I had fully understood the intention of OP. Though, the comment of @musicamente raised my attention:
there's no direct and safe way to know "what" border of the widget has actually caused the resize
I believe that this is not so complicated. To prove me right or wrong, I just started an MCVE to check this out.
Thereby, I started from the assumption that a resize by dragging the left and/or top window border should result in changed window coordinates as well. So, it should be possible to determine this, and to keep the positions of the resp. window children constant in relation to the desktop (instead of the top/left window corner as it is usual in Qt).
This can be done by simply compensating movements of the window by negative movements of the resp. window children. This may (or may not) be limited to window resizing only. (If it's applied to window movement as well this results in another funny effect making the window a peephole-like.)
The placement of child windows is in Qt usually subject of layout managers (derived from QLayout
). While there are a variety of layout managers built-in (which cover the needs of the daily business), there is also the option of to make a Custom Layout Manager. Another option is to not to use any layout manager but placing and resizing child widgets directly. To keep the MCVE simple and short, I used the latter option.
While I was fiddling with this I noticed some effects I was not aware about before and tried to handle this respectively.
When the current window position is retrieved in a
resizeEvent()
it provides the old position. The current position cannot be retrieved from the call data (of typeQResizeEvent
). The solution is to defer the compensating child widget movements until the processing of resizing is completed. This can be conveniently achieved with a single-shot timer (with delay 0).When dragging the left or top window border, the
resizeEvent()
was accompanied by amoveEvent()
. (Makes sense to me somehow.) So, I have to distinguishmoveEvents()
resulting from window movement frommoveEvents()
resulting from window resizing. I noticed that (in my case) theresizeEvent()
s were always sent before themoveEvent()
s. In good hope, that this might be something reliable, I used a flag to ignoremoveEvent()
s from aresizeEvent()
until my deferred processing of child movement.
(I must admit that Python is only my second (or third) language while my first is C++. So, I wrote a proof-of-concept in C++, deferring the port to Python/PyQt until later.)
This is what I ended up with:
#include <QtWidgets>
class Window: public QWidget {
private:
QVector<QWidget*> _pQWidgetsMove;
int _x, _y;
bool _updateXY = true;
public:
Window(QWidget *pQParent = nullptr): QWidget(pQParent) { }
virtual ~Window() = default;
Window(const Window&) = delete;
Window& operator=(const Window&) = delete;
void setMoveWidgets(QVector<QWidget*> pQWidgetsMove)
{
_pQWidgetsMove = pQWidgetsMove;
}
protected:
virtual void showEvent(QShowEvent *pQEvent) override;
virtual void resizeEvent(QResizeEvent *pQEvent) override;
virtual void moveEvent(QMoveEvent *pQEvent) override;
};
void Window::showEvent(QShowEvent *pQEvent)
{
QWidget::showEvent(pQEvent);
_x = x(); _y = y();
}
void Window::moveEvent(QMoveEvent* pQEvent)
{
QWidget::moveEvent(pQEvent);
if (_updateXY) {
_x += pQEvent->pos().x() - pQEvent->oldPos().x();
_y += pQEvent->pos().y() - pQEvent->oldPos().y();
}
}
void Window::resizeEvent(QResizeEvent* pQEvent)
{
QWidget::resizeEvent(pQEvent);
_updateXY = false;
QTimer::singleShot(0, // 0 ms -> idle callback
[this, x = x(), y = y()]() {
for (QWidget* pQWidget : _pQWidgetsMove) {
pQWidget->move(pQWidget->x() + _x - x, pQWidget->y() + _y - y);
}
_x = x; _y = y;
_updateXY = true;
});
}
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
// config GUI
const int wMain = 320, hMain = 240; // initial size of main window
const int nBtns = 4; // number of buttons
// setup GUI
Window qWinMain;
qWinMain.setWindowTitle("Fun with Resize");
qWinMain.resize(wMain, hMain);
QPushButton qBtns[nBtns];
{ int i = 0;
for (QPushButton& qBtn : qBtns) {
const int xBtn = i * wMain / nBtns, wBtn = wMain / nBtns;
qBtn.setParent(&qWinMain);
qBtn.move(xBtn, 0);
qBtn.resize(wBtn, hMain);
qBtn.setText(QString("Button %1").arg(i + 1));
++i;
}
}
QCheckBox qTglStick("Stick Buttons to Desktop", &qWinMain);
qTglStick.move(2, 2);
qWinMain.show();
// install signal handlers
QObject::connect(&qTglStick, &QCheckBox::toggled,
[&](bool checked) {
QVector<QWidget*> pQWidgetsMove;
if (checked) {
for (QPushButton& qBtn : qBtns) pQWidgetsMove.push_back(&qBtn);
}
qWinMain.setMoveWidgets(pQWidgetsMove);
});
// runtime loop
return app.exec();
}
Output (Windows, Visual Studio 2019):
This doesn't look that bad.
There is a little shaking in the buttons while the window is resized with dragging the top and/or left border. I assume that this results from the sequential processing of events resulting from calls of QWidget::move()
. I have no real idea how to work-around this. (Suppressing paintEvent()
s for a resp. duration?) So, I decided to live with this for now.
While it's working on Windows, how about Linux?
So, I rebuilt and tested in Debian (in a VM).
Finally, the port of the above MCVE to Python3 / PyQt5:
#!/usr/bin/python3
import sys
from PyQt5.QtCore import QT_VERSION_STR
from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QCheckBox
class Window(QWidget):
def __init__(self, parent = None):
QWidget.__init__(self, parent)
self.widgetsMove = list()
self.x, self.y = (0, 0)
self.updateXY = True
def setMoveWidgets(self, widgets):
self.widgetsMove = widgets
self.x, self.y = QWidget.x(self), QWidget.y(self)
def moveEvent(self, event):
QWidget.moveEvent(self, event)
if self.updateXY:
self.x += event.pos().x() - event.oldPos().x()
self.y += event.pos().y() - event.oldPos().y()
def moveWidgets(self, xOld, yOld):
for widget in self.widgetsMove:
widget.move( \
widget.x() + self.x - xOld, \
widget.y() + self.y - yOld)
self.x, self.y = xOld, yOld
self.updateXY = True
def resizeEvent(self, event):
QWidget.resizeEvent(self, event)
self.updateXY = False
QTimer.singleShot(0, \
lambda x = QWidget.x(self), y = QWidget.y(self): \
Window.moveWidgets(self, x, y))
if __name__ == '__main__':
print("Qt Version: {}".format(QT_VERSION_STR))
app = QApplication(sys.argv)
# config GUI
wMain, hMain = 320, 240
nBtns = 4
# setup GUI
qWinMain = Window()
qWinMain.setWindowTitle("Fun with Resize (PyQt5)")
qWinMain.resize(wMain, hMain)
qBtns = list()
for i in range(0, nBtns):
xBtn = i * wMain / nBtns
wBtn = wMain / nBtns
qBtn = QPushButton(qWinMain)
qBtn.move(xBtn, 0)
qBtn.resize(wBtn, hMain)
qBtn.setText("Button {}".format(i + 1))
qBtns.append(qBtn)
qTglStick = QCheckBox("Stick Buttons to Desktop", qWinMain)
qTglStick.move(2, 2)
qWinMain.show()
# install signal handlers
qTglStick.toggled.connect(lambda checked: \
qWinMain.setMoveWidgets(qBtns if checked else list()))
# runtime loop
sys.exit(app.exec_())
Output (Debian, Python3, PyQt5):
Answered By - Scheff's Cat
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.