QGraphicsView Увеличение и уменьшение изображения при помощи колесика мыши

23

У меня есть приложение с окном QGraphicsView в середине экрана. Я хочу, чтобы можно было увеличивать и уменьшать масштаб с помощью прокрутки колесика мыши.

В настоящее время я повторно выполнил QGraphicsView и переопределил функцию прокрутки мыши, чтобы она не прокручивала изображение (как это делает по умолчанию).

void MyQGraphicsView::wheelEvent(QWheelEvent *event)
{
    if(event->delta() > 0)
    {
        emit mouseWheelZoom(true);
    }
    else
    {
        emit mouseWheelZoom(false);
    }
}

поэтому, когда я прокручиваю, я излучаю сигнал true, если колесико мыши ложно, если колесо мыши обратно.

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

Итак, я сделал это, но это не идеально, поэтому я ищу, чтобы это было немного изменено или для рабочего примера, используя масштаб в функции события колеса.

Я инициализирую m_zoom_level до 0 в конструкторе.

void Display::zoomfunction(bool zoom)
{
    QMatrix matrix;

    if(zoom && m_zoom_level < 500)
    {
        m_zoom_level = m_zoom_level + 10;
        ui->graphicsView->setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
        matrix.scale(m_zoom_level, m_zoom_level);

        ui->graphicsView->setMatrix(matrix);
        ui->graphicsView->scale(1,-1);
    }
    else if(!zoom)
    {
        m_zoom_level = m_zoom_level - 10;
        ui->graphicsView->setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
        matrix.scale(m_zoom_level, m_zoom_level);

        ui->graphicsView->setMatrix(matrix);
        ui->graphicsView->scale(1,-1);
    }
}

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

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

  • 2
    Было бы неплохо просто получить окончательный ответ на этот вопрос, потому что есть слишком много вопросов, подобных этому, все с разными ответами, и большинство кажется только наполовину отработанным, прежде чем их просто оставили в покое ... конечно, это не слишком сложно, это больше просто вопрос правильного масштабирования и как его применить ..
Теги:
qt
qgraphicsview

12 ответов

34
Лучший ответ

Такое масштабирование немного сложно. Позвольте мне поделиться своим собственным классом для этого.

Заголовок:

#include <QObject>
#include <QGraphicsView>

/*!
 * This class adds ability to zoom QGraphicsView using mouse wheel. The point under cursor
 * remains motionless while it possible.
 *
 * Note that it becomes not possible when the scene's
 * size is not large enough comparing to the viewport size. QGraphicsView centers the picture
 * when it smaller than the view. And QGraphicsView scrolls boundaries don't allow to
 * put any picture point at any viewport position.
 *
 * When the user starts scrolling, this class remembers original scene position and
 * keeps it until scrolling is completed. It better than getting original scene position at
 * each scrolling step because that approach leads to position errors due to before-mentioned
 * positioning restrictions.
 *
 * When zommed using scroll, this class emits zoomed() signal.
 *
 * Usage:
 *
 *   new Graphics_view_zoom(view);
 *
 * The object will be deleted automatically when the view is deleted.
 *
 * You can set keyboard modifiers used for zooming using set_modified(). Zooming will be
 * performed only on exact match of modifiers combination. The default modifier is Ctrl.
 *
 * You can change zoom velocity by calling set_zoom_factor_base().
 * Zoom coefficient is calculated as zoom_factor_base^angle_delta
 * (see QWheelEvent::angleDelta).
 * The default zoom factor base is 1.0015.
 */
class Graphics_view_zoom : public QObject {
  Q_OBJECT
public:
  Graphics_view_zoom(QGraphicsView* view);
  void gentle_zoom(double factor);
  void set_modifiers(Qt::KeyboardModifiers modifiers);
  void set_zoom_factor_base(double value);

private:
  QGraphicsView* _view;
  Qt::KeyboardModifiers _modifiers;
  double _zoom_factor_base;
  QPointF target_scene_pos, target_viewport_pos;
  bool eventFilter(QObject* object, QEvent* event);

signals:
  void zoomed();
};

Источник:

#include "Graphics_view_zoom.h"
#include <QMouseEvent>
#include <QApplication>
#include <QScrollBar>
#include <qmath.h>

Graphics_view_zoom::Graphics_view_zoom(QGraphicsView* view)
  : QObject(view), _view(view)
{
  _view->viewport()->installEventFilter(this);
  _view->setMouseTracking(true);
  _modifiers = Qt::ControlModifier;
  _zoom_factor_base = 1.0015;
}

void Graphics_view_zoom::gentle_zoom(double factor) {
  _view->scale(factor, factor);
  _view->centerOn(target_scene_pos);
  QPointF delta_viewport_pos = target_viewport_pos - QPointF(_view->viewport()->width() / 2.0,
                                                             _view->viewport()->height() / 2.0);
  QPointF viewport_center = _view->mapFromScene(target_scene_pos) - delta_viewport_pos;
  _view->centerOn(_view->mapToScene(viewport_center.toPoint()));
  emit zoomed();
}

void Graphics_view_zoom::set_modifiers(Qt::KeyboardModifiers modifiers) {
  _modifiers = modifiers;

}

void Graphics_view_zoom::set_zoom_factor_base(double value) {
  _zoom_factor_base = value;
}

bool Graphics_view_zoom::eventFilter(QObject *object, QEvent *event) {
  if (event->type() == QEvent::MouseMove) {
    QMouseEvent* mouse_event = static_cast<QMouseEvent*>(event);
    QPointF delta = target_viewport_pos - mouse_event->pos();
    if (qAbs(delta.x()) > 5 || qAbs(delta.y()) > 5) {
      target_viewport_pos = mouse_event->pos();
      target_scene_pos = _view->mapToScene(mouse_event->pos());
    }
  } else if (event->type() == QEvent::Wheel) {
    QWheelEvent* wheel_event = static_cast<QWheelEvent*>(event);
    if (QApplication::keyboardModifiers() == _modifiers) {
      if (wheel_event->orientation() == Qt::Vertical) {
        double angle = wheel_event->angleDelta().y();
        double factor = qPow(_zoom_factor_base, angle);
        gentle_zoom(factor);
        return true;
      }
    }
  }
  Q_UNUSED(object)
  return false;
}

Пример использования:

Graphics_view_zoom* z = new Graphics_view_zoom(ui->graphicsView);
z->set_modifiers(Qt::NoModifier);
  • 0
    Ура, это работает очень хорошо, были некоторые ошибки, которые были помечены, когда я пытался скомпилировать его QMouseEvent* mouse_event = static_cast<QMouseEvent*>(event); мне пришлось перейти на QMouseEvent* mouse_event = (QMouseEvent *) event ; кроме этого все работает спасибо. Вы не могли бы объяснить, для чего предназначены некоторые из жестко закодированных значений, чтобы я мог точно понять, что он делает и почему, еще раз спасибо, скоро приму
  • 0
    Большое спасибо @Pavel!
Показать ещё 3 комментария
19

Вот решение, использующее PyQt:

def wheelEvent(self, event):
    """
    Zoom in or out of the view.
    """
    zoomInFactor = 1.25
    zoomOutFactor = 1 / zoomInFactor

    # Save the scene pos
    oldPos = self.mapToScene(event.pos())

    # Zoom
    if event.angleDelta().y() > 0:
        zoomFactor = zoomInFactor
    else:
        zoomFactor = zoomOutFactor
    self.scale(zoomFactor, zoomFactor)

    # Get the new position
    newPos = self.mapToScene(event.pos())

    # Move scene to old position
    delta = newPos - oldPos
    self.translate(delta.x(), delta.y())
  • 0
    Это отлично сработало для меня. Спасибо за это!
10

Здесь версия python работает для меня. Исходя из сочетания ответов от @Stefan Reinhardt и @rengel.

class MyQGraphicsView(QtGui.QGraphicsView):

def __init__ (self, parent=None):
    super(MyQGraphicsView, self).__init__ (parent)

def wheelEvent(self, event):
    # Zoom Factor
    zoomInFactor = 1.25
    zoomOutFactor = 1 / zoomInFactor

    # Set Anchors
    self.setTransformationAnchor(QtGui.QGraphicsView.NoAnchor)
    self.setResizeAnchor(QtGui.QGraphicsView.NoAnchor)

    # Save the scene pos
    oldPos = self.mapToScene(event.pos())

    # Zoom
    if event.delta() > 0:
        zoomFactor = zoomInFactor
    else:
        zoomFactor = zoomOutFactor
    self.scale(zoomFactor, zoomFactor)

    # Get the new position
    newPos = self.mapToScene(event.pos())

    # Move scene to old position
    delta = newPos - oldPos
    self.translate(delta.x(), delta.y())
  • 0
    да, эти якоря решали проблему.
6

Здесь приведенный вариант решения выше; с помощью всего лишь кода, который нужно ввести в колесо. Это работает с/без полос прокрутки в моем тестировании, отлично;)

void MyGraphicsView::wheelEvent(QWheelEvent* pWheelEvent)
{
    if (pWheelEvent->modifiers() & Qt::ControlModifier)
    {
        // Do a wheel-based zoom about the cursor position
        double angle = pWheelEvent->angleDelta().y();
        double factor = qPow(1.0015, angle);

        auto targetViewportPos = pWheelEvent->pos();
        auto targetScenePos = mapToScene(pWheelEvent->pos());

        scale(factor, factor);
        centerOn(targetScenePos);
        QPointF deltaViewportPos = targetViewportPos - QPointF(viewport()->width() / 2.0, viewport()->height() / 2.0);
        QPointF viewportCenter = mapFromScene(targetScenePos) - deltaViewportPos;
        centerOn(mapToScene(viewportCenter.toPoint()));

        return;
    }
6

После долгих разочарований, похоже, это работает. Проблема заключается в том, что QGraphicsView transform не имеет ничего общего с ее положением прокрутки, поэтому поведение QGraphicsView::mapToScene(const QPoint&) const зависит как от положения прокрутки, так и от преобразования. Мне нужно было посмотреть на источник mapToScene, чтобы понять это.

С учетом этого, вот что сработало: запомните точку сцены, на которую указывает мышь, масштабируйте, сопоставьте точку сцены с координатами мыши, затем отрегулируйте полосы прокрутки, чтобы подвести эту точку под мышью:

void ZoomGraphicsView::wheelEvent(QWheelEvent* event)
{
   const QPointF p0scene = mapToScene(event->pos());

   qreal factor = std::pow(1.01, event->delta());
   scale(factor, factor);

   const QPointF p1mouse = mapFromScene(p0scene);
   const QPointF move = p1mouse - event->pos(); // The move
   horizontalScrollBar()->setValue(move.x() + horizontalScrollBar()->value());
   verticalScrollBar()->setValue(move.y() + verticalScrollBar()->value());
}
  • 0
    На самом деле я вообще не использую scrollbr, поэтому, к сожалению, просто последний бит, который фактически сбрасывает позиции, не сработает, мне не нравится перепост, но я реализовал то, что сначала мне показалось работающим, но на самом деле не думал, что это стоило нового вопроса, не так ли? знаете, может быть, у вас есть идеи? stackoverflow.com/questions/21134446/...
  • 0
    ну, это не сработало для меня. Ответ от Veslem сделал однако.
5

Вы можете просто использовать встроенную функциональность AnchorUnderMouse или AnchorViewCenter для поддержания фокуса под мышью или в центре. Это работает для меня в Qt 5.7

void SceneView::wheelEvent(QWheelEvent *event)
    {
        if (event->modifiers() & Qt::ControlModifier) {
            // zoom
            const ViewportAnchor anchor = transformationAnchor();
            setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
            int angle = event->angleDelta().y();
            qreal factor;
            if (angle > 0) {
                factor = 1.1;
            } else {
                factor = 0.9;
            }
            scale(factor, factor);
            setTransformationAnchor(anchor);
        } else {
            QGraphicsView::wheelEvent(event);
        }
    }
  • 0
    Это реальное решение, позволяющее Qt делать работу, а не изобретать колесо. Большой вклад!
5

Немного поздно но я прошел через то же самое сегодня только с Pyside, но должен быть тем же...

Подход "очень прост", хотя мне стоило немного времени... Сначала установите все якоря на NoAnchor, затем возьмите точку хребта, сопоставьте его со сценой, перевести сцену на это значение, масштаб и, наконец, перевести его обратно:

def wheelEvent(self, evt):
    #Remove possible Anchors
    self.widget.setTransformationAnchor(QtGui.QGraphicsView.NoAnchor)
    self.widget.setResizeAnchor(QtGui.QGraphicsView.NoAnchor)
    #Get Scene Pos
    target_viewport_pos = self.widget.mapToScene(evt.pos())
    #Translate Scene
    self.widget.translate(target_viewport_pos.x(),target_viewport_pos.y())
    # ZOOM
    if evt.delta() > 0:
        self._eventHandler.zoom_ctrl(1.2)
    else:
        self._eventHandler.zoom_ctrl(0.83333)
    # Translate back
    self.widget.translate(-target_viewport_pos.x(),-target_viewport_pos.y())

Это было единственное решение, которое сработало для моей цели. ИМХО это тоже самое логичное решение...

3

Более плавное масштабирование

void StatusView::wheelEvent(QWheelEvent * event)
{
    const QPointF p0scene = mapToScene(event->pos());

    qreal factor = qPow(1.2, event->delta() / 240.0);
    scale(factor, factor);

    const QPointF p1mouse = mapFromScene(p0scene);
    const QPointF move = p1mouse - event->pos(); // The move
    horizontalScrollBar()->setValue(move.x() + horizontalScrollBar()->value());
    verticalScrollBar()->setValue(move.y() + verticalScrollBar()->value());

}
0

 void GraphicsView::wheelEvent(QWheelEvent* event) { switch (event->modifiers()) { case Qt::ControlModifier: if (event->angleDelta().x() != 0) QAbstractScrollArea::horizontalScrollBar()->setValue(QAbstractScrollArea::horizontalScrollBar()->value() - (event->delta())); else QAbstractScrollArea::verticalScrollBar()->setValue(QAbstractScrollArea::verticalScrollBar()->value() - (event->delta())); break; case Qt::ShiftModifier: QAbstractScrollArea::horizontalScrollBar()->setValue(QAbstractScrollArea::horizontalScrollBar()->value() - (event->delta())); break; case Qt::NoModifier: if (abs(event->delta()) == 120) { if (event->delta() > 0) zoomIn(); else zoomOut(); } break; default: QGraphicsView::wheelEvent(event); return; } event->accept(); } const double zoomFactor = 1.5; void GraphicsView::zoomIn() { scale(zoomFactor, zoomFactor); } void GraphicsView::zoomOut() { scale(1.0/zoomFactor, 1.0/zoomFactor); }

0

Простой пример:

class CGraphicsVew : public QGraphicsView
{
    Q_OBJECT

protected:
    void wheelEvent(QWheelEvent *event)
    {
        qreal deltaScale = 1;
        deltaScale += event->delta() > 0 ? 0.1 : -0.1;
        setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
        scale(deltaScale, deltaScale);
    }
};
0

В Mac OS приведенные здесь решения иногда терпят неудачу при использовании QGraphicsView :: setTransformationAnchor (AnchorUnderMouse):

1 - Qt не обновляет lastMouseMoveScenePoint, когда окна не имеют фокуса. Из-за этого масштабирование выполняется с использованием позиции мыши, когда она потеряла фокус, а не текущей. (https://bugreports.qt.io/browse/QTBUG-73033)

2 - Qt иногда останавливает распространение событий перемещения мыши при переключении окон с помощью управления полетом, поэтому масштабирование также ведет себя не так, как в # 1. (https://bugreports.qt.io/browse/QTBUG-73067). Я сделал это видео, где чипы не подсвечиваются во второй раз, когда я щелкаю окно, потому что mouseMoveEvent не вызывается. Я знаю, что это не ошибка в моем приложении, потому что это пример 40000 чипов, предоставленных Qt. Я разместил решение этой проблемы здесь.

3 - setInteractive (false) нельзя использовать с AnchorUnderMouse, поскольку позиция мыши, используемая в качестве центра преобразования, не обновляется: https://bugreports.qt.io/browse/QTBUG-60672

Кажется, что Qt SDK не был хорошо протестирован на события перемещения мыши в необычных сценариях, таких как масштабирование колесом мыши.

0

Объединение решения @veslam: s с кодом Smooth Zoom из QT Wiki (https://wiki.qt.io/Smooth_Zoom_In_QGraphicsView), кажется, работает очень хорошо:

Источник:

QGraphicsViewMap::QGraphicsViewMap(QWidget *parent) : QGraphicsView(parent)
{
    setTransformationAnchor(QGraphicsView::NoAnchor);
    setResizeAnchor(QGraphicsView::NoAnchor);
}

void QGraphicsViewMap::wheelEvent(QWheelEvent* event)
{
    wheelEventMousePos = event->pos();

    int numDegrees = event->delta() / 8;
    int numSteps = numDegrees / 15; // see QWheelEvent documentation
    _numScheduledScalings += numSteps;
    if (_numScheduledScalings * numSteps < 0) // if user moved the wheel in another direction, we reset previously scheduled scalings
        _numScheduledScalings = numSteps;

    QTimeLine *anim = new QTimeLine(350, this);
    anim->setUpdateInterval(20);

    connect(anim, SIGNAL (valueChanged(qreal)), SLOT (scalingTime(qreal)));
    connect(anim, SIGNAL (finished()), SLOT (animFinished()));
    anim->start();
 }

void QGraphicsViewMap::scalingTime(qreal x)
{
    QPointF oldPos = mapToScene(wheelEventMousePos);

    qreal factor = 1.0+ qreal(_numScheduledScalings) / 300.0;
    scale(factor, factor);

    QPointF newPos = mapToScene(wheelEventMousePos);
    QPointF delta = newPos - oldPos;
    this->translate(delta.x(), delta.y());
}

void QGraphicsViewMap::animFinished()
{
    if (_numScheduledScalings > 0)
        _numScheduledScalings--;
    else
        _numScheduledScalings++;

    sender()->~QObject();
}

Заголовок:

class QGraphicsViewMap : public QGraphicsView
{
    Q_OBJECT

private:
    qreal _numScheduledScalings = 0;
    QPoint wheelEventMousePos;
public:
    explicit QGraphicsViewMap(QWidget *parent = 0);

signals:

public slots:
    void wheelEvent(QWheelEvent* event);
    void scalingTime(qreal x);
    void animFinished();
};

Ещё вопросы

Сообщество Overcoder
Наверх
Меню