PDA

View Full Version : PyQt: zooming with QGraphicsSceneWheelEvent



danboid
12th February 2017, 18:32
I am new to both (Py)Qt and Python. I am currently trying to write my first PyQt app in the form of a simple image viewer I have dubbed shufti. So far I have got it to load images files from the command line and... well that's about all so far. Now I want to be able to zoom in and out of the shufti QGraphicsView window by scrolling the mousewheel over the window but I'm having trouble figuring out how that should work.

What I have been able to deduce is that I should be able to achieve this using QGraphicsSceneWheelEvent / QGraphicsSceneWheelEvent.delta and then I'd need to hook that up to adjust the self.view.scale() object within the initUI method, somehow.

I've had a search but I've not been able to find any examples of people using QGraphicsSceneWheelEvent / WheelEvent with PyQt so I'm hoping someone could explain how I might get the two interacting?

Thanks



#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# shufti - A WIP, PyQt5 persistent image viewer
#
# By Dan MacDonald
#
# 2017
#
# Usage:
#
# python shufti.py path/to/image

import sys
from PyQt5 import QtCore
from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import QMainWindow, QApplication, QGraphicsScene, QGraphicsView

class Shufti(QMainWindow):

def __init__(self):
super().__init__()
try:
self.file = open(sys.argv[1], 'r')
except IOError:
print('There was an error opening the file')
sys.exit(1)

if (sys.argv[1]).lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.bmp', '.pbm', '.pgm', '.ppm', '.xbm', '.xpm')):
self.initUI()
self.setWindowTitle("shufti")
self.resize(self.img.size())
else:
print("Unsupported file format")
sys.exit(1)

def initUI(self):

self.img = QPixmap(sys.argv[1])
self.scene = QGraphicsScene()
self.scene.addPixmap(self.img)
self.view = QGraphicsView(self.scene, self)
self.view.resize(self.img.width() + 2, self.img.height() + 2)
self.show()

def toggleFullscreen(self):
if self.isFullScreen():
self.showNormal()
else:
self.showFullScreen()

def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_F11 or event.key() == QtCore.Qt.Key_F:
self.toggleFullscreen()


if __name__ == '__main__':

app = QApplication(sys.argv)
shufti = Shufti()
sys.exit(app.exec_())

Royce
12th February 2017, 19:09
I don't really know Python, however in C++ what I did was override the QGraphicsView class itself and use an instance of that overriden class in my program. Then, in my derived class, I just had to declare the wheelEvent method (overriding the regular method). Then scrolling the wheel starting calling my override.

One thing I noticed was the the scale method didn't seem to react well to successive calls. Maybe it was me being dumb, but I wound up doing this:


QTransform scale;
scale.scale(zoom_level, 1); //scale only one dimension
setTransform(scale);


Also the wheel data itself turned up coming from the .y() method of the QPoint that comes from the angleDelta() method of the wheelEvent.

danboid
12th February 2017, 22:36
Thanks for your suggestions Royce!

I've now mostly got mousewheel zoom working. My problem now is that when the scaled QGraphicsView object area exceeds the initial QGV view area (ie when you zoom in), the mousewheel switches from zooming in and out to scrolling the image view vertically.

I presume I need to resize the QGV view area (but not the window size) every time I zoom to stop the view scrollbars appearing but I'm not sure how best to do that yet.



#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# shufti - A WIP, PyQt5 persistent image viewer
#
# By Dan MacDonald
#
# 2017
#
# Usage:
#
# python shufti.py path/to/image

import sys
from PyQt5 import QtCore
from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import QMainWindow, QApplication, QGraphicsScene, QGraphicsView

class Shufti(QMainWindow):

def __init__(self):
super().__init__()
try:
self.file = open(sys.argv[1], 'r')
except IOError:
print('There was an error opening the file')
sys.exit(1)

if (sys.argv[1]).lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.bmp', '.pbm', '.pgm', '.ppm', '.xbm', '.xpm')):
self.zoom = 1
self.initUI()
self.setWindowTitle("shufti")
self.resize(self.img.size())
else:
print("Unsupported file format")
sys.exit(1)

def initUI(self):

self.img = QPixmap(sys.argv[1])
self.scene = QGraphicsScene()
self.scene.addPixmap(self.img)
self.view = QGraphicsView(self.scene, self)
self.view.resize(self.img.width() + 2, self.img.height() + 2)
self.show()

def toggleFullscreen(self):
if self.isFullScreen():
self.showNormal()
else:
self.showFullScreen()

def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_F11 or event.key() == QtCore.Qt.Key_F:
self.toggleFullscreen()

def wheelEvent(self, event):
self.zoom += event.angleDelta().y()/2880
self.view.scale(self.zoom, self.zoom)

if __name__ == '__main__':

app = QApplication(sys.argv)
shufti = Shufti()
sys.exit(app.exec_())

danboid
13th February 2017, 11:43
I have read that QGraphicsView is supposed to automatically expand when any of its components spill over its edges, if I understand correctly but this is not what I'm seeing. When I zoom into an image, the view scrollbars appear and then its game over for the zoom.

I've tried various things with itemsBoundingRect, sceneRect(), fitInView, BoundingRectViewportUpdate but nothing has improved the situation from the code I've already posted.

danboid
13th February 2017, 14:45
I have also tried setting

self.view.setVerticalScrollBarPolicy(QtCore.Qt.Scr ollBarAlwaysOff)
self.view.setHorizontalScrollBarPolicy(QtCore.Qt.S crollBarAlwaysOff)

Whilst that hides the scrollbars, it doesn't disable scrolling of the QGV area so it doesn't fix my problem.

danboid
13th February 2017, 19:56
I've spent all day trying to crack this and seeing as no-one else knows the answer it looks like I'll have to live without the scrollwheel zoom and just do with keyboard shortcuts :(

Other things I've tried that I thought might work included setting setInteractive() to False for the QGV object and I also tried overriding the scrollContentsBy method:



#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# shufti - A WIP, PyQt5 persistent image viewer
#
# By Dan MacDonald
#
# 2017
#
# Usage:
#
# python shufti.py path/to/image

import sys
from PyQt5 import QtCore
from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import QMainWindow, QApplication, QGraphicsScene, QGraphicsView

class GraphicsView(QGraphicsView):

def __init__(self):
super().__init__()

def scrollContentsBy(self, blah, blah):
pass

class Shufti(QMainWindow):

def __init__(self):
super().__init__()
try:
self.file = open(sys.argv[1], 'r')
except IOError:
print('There was an error opening the file')
sys.exit(1)

if (sys.argv[1]).lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.bmp', '.pbm', '.pgm', '.ppm', '.xbm', '.xpm')):
self.zoom = 1
self.initUI()
self.setWindowTitle("shufti")
self.resize(self.img.size())
else:
print("Unsupported file format")
sys.exit(1)

def initUI(self):

self.img = QPixmap(sys.argv[1])
self.scene = QGraphicsScene()
self.scene.addPixmap(self.img)
self.view = GraphicsView(self.scene, self)
self.view.resize(self.img.width() + 2, self.img.height() + 2)
self.show()

def toggleFullscreen(self):
if self.isFullScreen():
self.showNormal()
else:
self.showFullScreen()

def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_F11 or event.key() == QtCore.Qt.Key_F:
self.toggleFullscreen()

def wheelEvent(self, event):
self.zoom += event.angleDelta().y()/2880
self.view.scale(self.zoom, self.zoom)

if __name__ == '__main__':

app = QApplication(sys.argv)
shufti = Shufti()
sys.exit(app.exec_())


None of it has stopped the scrolling of the QGV clashing with mousewheel zoom.

Royce
17th February 2017, 23:01
In C++ I have to call the accept() method of the QWheelEvent to let Qt know I'm handling the event. ( Or something) Maybe Python isn't doing that for you automatically? I could see that causing an issue like this maybe. Do you have access to accept()? Give it a shot.

danboid
21st February 2017, 21:07
Thanks for your suggestion Royce!

I did get it working in the end thanks to a tip-off from Qt overlord Rui Nuno Capella aka RNCBC and so now I've created my new favourite image viewing app, shufti. Its main feature, and the reason it was created, is that it automatically saves and restores the zoom level, rotation, window size, desktop location and the scrollbar positions (ie viewing area) for every image it loads, on a per-image/location basis.

https://github.com/danboid/shufti