Размещение виджета с изменяемым размером внутри области прокрутки

#c #qt #qt5

#c #qt #qt5

Вопрос:

У меня есть область прокрутки, которая содержит виджет с изменяемым размером. Пользователь может увеличивать и уменьшать целочисленный масштабный коэффициент этого внутреннего виджета (который изменяет его размер). Когда пользователь увеличивает масштаб, часть внутреннего виджета, видимая в верхнем левом углу, остается неизменной. Это дает эффект увеличения в верхнем левом углу представления.

Когда размер внутреннего виджета изменяется, мне нужно прокрутить внутренний виджет, чтобы он выглядел так, как будто пользователь приближает масштаб к центру представления. Я хочу, чтобы часть виджета в центре представления была зафиксирована в центре при изменении размера.

Я нарисовал несколько диаграмм, чтобы помочь визуализировать проблему. Розовый прямоугольник является внутренним виджетом. Коричневый прямоугольник — это вид виджета через область прокрутки. Зеленые точки — это фиксированные точки на внутреннем виджете.

Перед масштабированием
Перед масштабированием

После масштабирования (текущее нежелательное поведение)
После масштабирования (текущее нежелательное поведение)

После масштабирования (желаемое поведение)
После масштабирования (желаемое поведение)

Как вы можете (надеюсь) видеть из этих грубо нарисованных диаграмм. Простое увеличение размера виджета внутри области прокрутки приводит к масштабированию в верхний левый угол или вид. Я должен сделать что-то еще, чтобы приблизить изображение к центру. Кроме того, внутренний виджет может быть намного меньше области прокрутки. Я хочу сдвигать внутренний виджет только тогда, когда он больше области прокрутки.

Это минимальный пример нежелательного поведения. Нажатие 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{amp;window};
  InnerWidget inner{amp;scrollArea};
  window.setBaseSize(512, 512);
  window.setCentralWidget(amp;scrollArea);
  scrollArea.setAlignment(Qt::AlignCenter);
  scrollArea.setWidget(amp;inner);
  window.show();
  return app.exec();
}
  

Чтобы воспроизвести проблему, увеличьте масштаб пару раз, затем расположите один из прямоугольников в центре окна. Масштабирование переместит прямоугольник в нижний правый угол. Я хочу, чтобы масштабирование сохраняло прямоугольник в центре.

Кажется, что это простая задача, но, похоже, я не могу разобраться в математике. Я пробовал различные вычисления для значений прокрутки, но, похоже, ни одно из них не работает так, как я хочу.

Ответ №1:

Я некоторое время возился с числами, а затем разобрался. Вот полностью рабочее решение.

 #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{amp;window};
  InnerWidget inner{amp;scrollArea};
  window.setBaseSize(512, 512);
  window.setCentralWidget(amp;scrollArea);
  scrollArea.setAlignment(Qt::AlignCenter);
  scrollArea.setWidget(amp;inner);
  window.show();
  return app.exec();
}