我有一个包含可调整大小的小部件的滚动区域。用户能够递增和递减此内部窗口小部件的整数比例因子(其更改其大小)。当用户递增比例时,左上角可见的内部窗口小部件的部分保持固定。这样可以放大到视图的左上角。
当内部窗口小部件的大小发生变化时,我需要滚动内部窗口小部件,使其看起来像用户正在缩放到视图的中心。我希望在调整大小时将小部件的一部分保持在视图中心固定在中心。
我绘制了一些图表来帮助可视化问题。粉红色矩形是内部小部件。棕色矩形是通过滚动区域到窗口小部件的视图。绿点是内部小部件上的固定点。
正如你所能(希望)从这些粗略绘制的图表中看到的那样。简单地增加滚动区域内小部件的大小会导致缩放到左上角或视图。我必须做更多的事情来放大视图的中心。此外,内部窗口小部件可以比滚动区域小得多。我只想在内部窗口小于滚动区域时移动内部窗口小部件。
这是不良行为的最小示例。按Z(单击内部窗口小部件以更改焦点后)将放大视图的左上角。我想放大视图的中心。
#include <QtGui/qpainter.h>
#include <QtWidgets/qscrollbar.h>
#include <QtWidgets/qscrollarea.h>
#include <QtWidgets/qmainwindow.h>
#include <QtWidgets/qapplication.h>
class InnerWidget final : public QWidget {
public:
explicit InnerWidget(QScrollArea *parent)
: QWidget{parent}, parent{parent} {
updateSize();
setFocusPolicy(Qt::StrongFocus);
}
private:
QScrollArea *parent;
int scale = 1;
void updateSize() {
setFixedSize(256 * scale, 256 * scale);
}
void paintEvent(QPaintEvent *) override {
QPainter painter{this};
const QColor green = {0, 255, 0};
painter.fillRect(0, 0, width(), height(), {255, 255, 255});
painter.fillRect(32 * scale, 32 * scale, 16 * scale, 16 * scale, green);
painter.fillRect(128 * scale, 128 * scale, 16 * scale, 16 * scale, green);
}
void keyPressEvent(QKeyEvent *event) override {
if (event->isAutoRepeat()) return;
QScrollBar *hbar = parent->horizontalScrollBar();
QScrollBar *vbar = parent->verticalScrollBar();
if (event->key() == Qt::Key_Z) {
// need to call bar->setValue and bar->value here
scale = std::min(scale + 1, 64);
updateSize();
} else if (event->key() == Qt::Key_X) {
// here too
scale = std::max(scale - 1, 1);
updateSize();
}
}
};
int main(int argc, char **argv) {
QApplication app{argc, argv};
QMainWindow window;
QScrollArea scrollArea{&window};
InnerWidget inner{&scrollArea};
window.setBaseSize(512, 512);
window.setCentralWidget(&scrollArea);
scrollArea.setAlignment(Qt::AlignCenter);
scrollArea.setWidget(&inner);
window.show();
return app.exec();
}
要重现此问题,请放大几次,然后将其中一个矩形放在窗口中央。缩放将矩形移向右下角。我想缩放以保持矩形在中心。
这感觉就像一个简单的问题,但我似乎无法理解数学。我已经尝试了滚动值的各种计算,但它们似乎没有像我想要的那样表现。
我把这些数字搞砸了一会然后我想出来了。这是完全可行的解决方案。
#include <QtGui/qpainter.h>
#include <QtWidgets/qscrollbar.h>
#include <QtWidgets/qmainwindow.h>
#include <QtWidgets/qscrollarea.h>
#include <QtWidgets/qapplication.h>
class InnerWidget final : public QWidget {
public:
explicit InnerWidget(QScrollArea *parent)
: QWidget{parent}, parent{parent}, scale{1} {
updateSize();
setFocusPolicy(Qt::StrongFocus);
}
private:
QScrollArea *parent;
int scale;
void updateSize() {
setFixedSize(256 * scale, 256 * scale);
}
void paintEvent(QPaintEvent *) override {
QPainter painter{this};
const QColor green = {0, 255, 0};
painter.fillRect(0, 0, width(), height(), {255, 255, 255});
painter.fillRect(32 * scale, 32 * scale, 16 * scale, 16 * scale, green);
painter.fillRect(128 * scale, 128 * scale, 16 * scale, 16 * scale, green);
}
void adjustScroll(const int oldScale) {
if (scale == oldScale) return;
QScrollBar *hbar = parent->horizontalScrollBar();
QScrollBar *vbar = parent->verticalScrollBar();
if (width() >= parent->width()) {
const int halfWidth = parent->width() / 2;
hbar->setValue((hbar->value() + halfWidth) * scale / oldScale - halfWidth);
}
if (height() >= parent->height()) {
const int halfHeight = parent->height() / 2;
vbar->setValue((vbar->value() + halfHeight) * scale / oldScale - halfHeight);
}
}
void keyPressEvent(QKeyEvent *event) override {
if (event->isAutoRepeat()) return;
const int oldScale = scale;
if (event->key() == Qt::Key_Z) {
scale = std::min(scale + 1, 64);
updateSize();
adjustScroll(oldScale);
} else if (event->key() == Qt::Key_X) {
scale = std::max(scale - 1, 1);
updateSize();
adjustScroll(oldScale);
}
}
};
int main(int argc, char **argv) {
QApplication app{argc, argv};
QMainWindow window;
QScrollArea scrollArea{&window};
InnerWidget inner{&scrollArea};
window.setBaseSize(512, 512);
window.setCentralWidget(&scrollArea);
scrollArea.setAlignment(Qt::AlignCenter);
scrollArea.setWidget(&inner);
window.show();
return app.exec();
}