PyQt5 QGraphicsViewでホイールでドラッグ、ドラッグで範囲選択する
2020/04/07 categories:PyQt5| tags:Python|PyQt5|QGraphicsView|
QGraphicsViewでスクロールやドラッグなどの動作を切り分けようと考えた場合、マウスボタンで動作の切り分けをする方法が考えられます。
そこで下記の仕様でズームやドラッグ、選択を出来るようなプログラムを作成しました。
- ズームイン、ズームアウト:マウスホイールを回す
- 画像の移動:ホイールクリックでドラッグ
- 画像の範囲選択:左クリックでドラッグ
ウィジェットの作成
テスト用のウィジェットとしてQWidgetにQVBoxLayoutを追加して、QVBoxLayoutにQLabelと自作のGraphicsViewを追加します。そして、widget.show()でウィンドウが表示されます。そのmain関数は下記の通りです。
def main():
app = QtWidgets.QApplication(sys.argv)
widget = QtWidgets.QWidget(None)
leyout = QtWidgets.QVBoxLayout(widget)
leyout.addWidget( QtWidgets.QLabel(widget) )
leyout.addWidget( GraphicsView(widget) )
widget.setLayout(leyout)
widget.show()
app.exec()
mousePress時の座標を表示
mousePressEventとmouseReleaseEventをオーバーライドして、押されたボタンと座標を取得して、QLabelにクリックしたボタンとクリックした座標を表示してみます。座標はevent.pos()で取得できて、ボタンはevent.button()で取得できます。座標はself.mapToScene(event.pos())でシーン内の座標に変換しています。
def mousePressEvent(self, event):
p = self.mapToScene(event.pos())
x, y = round(p.x(), 3), round(p.y(), 3)
if event.button() == QtCore.Qt.MidButton:
text = 'MidButton pressed!! ' + str(x) + ',' + str(y)
elif event.button() == QtCore.Qt.LeftButton:
text = 'LeftButton pressed!! ' + str(x) + ',' + str(y)
self.label.setText(text)
QtWidgets.QGraphicsView.mousePressEvent(self, event)
def mouseReleaseEvent(self, event):
QtWidgets.QGraphicsView.mouseReleaseEvent(self, event)
if event.button() == QtCore.Qt.LeftButton:
p = self.mapToScene(event.pos())
x, y = round(p.x(), 3), round(p.y(), 3)
text = 'LeftButton released!! ' + str(x) + ',' + str(y)
self.label.setText(text)
実行結果
mousePressEventでドラッグモードを指定
setDragModeでドラッグモードを指定できます。モードは下記の3つです。
- QtWidgets.QGraphicsView.NoDrag : ドラッグなし
- QtWidgets.QGraphicsView.RubberBandDrag : 範囲指定
- QtWidgets.QGraphicsView.ScrollHandDrag : シーンを上下左右に移動
通常時はNoDragモードにしておいて、mousePressEvent時にクリックしたマウスボタンの種類ごとに、これらのモードを切り替えます。MidButtonの場合はScrollHandDrag、LeftButtonの場合はRubberBandDragとして、mouseReleaseEventでNoDragに戻します。
def mousePressEvent(self, event):
gv = QtWidgets.QGraphicsView
if event.button() == QtCore.Qt.MidButton:
self.setDragMode(gv.ScrollHandDrag)
elif event.button() == QtCore.Qt.LeftButton:
self.setDragMode(gv.RubberBandDrag)
gv.mousePressEvent(self, event)
def mouseReleaseEvent(self, event):
gv = QtWidgets.QGraphicsView
gv.mouseReleaseEvent(self, event)
self.setDragMode(gv.NoDrag)
ScrollHandDragは左クリックでしか機能しないようです。ホイールクリックで動作させるためには、ホイールクリック時にeventを左クリックのeventとして作り変える必要があります。QtGui.QMouseEvent()でイベントを新規作成して、座標はevent.pos()で取得したもの、ボタンはLeftButtonとします。
def mousePressEvent(self, event):
gv = QtWidgets.QGraphicsView
if event.button() == QtCore.Qt.MidButton:
self.setDragMode(gv.ScrollHandDrag)
event = QtGui.QMouseEvent(
QtCore.QEvent.GraphicsSceneDragMove,
event.pos(),
QtCore.Qt.MouseButton.LeftButton,
QtCore.Qt.MouseButton.LeftButton,
QtCore.Qt.KeyboardModifier.NoModifier
)
elif event.button() == QtCore.Qt.LeftButton:
self.setDragMode(gv.RubberBandDrag)
gv.mousePressEvent(self, event)
実行結果
クリック時の座標をラベルに表示させた結果が下記の通りです。
ソースコード
## -*- coding: utf-8 -*-
import sys
from PyQt5 import QtWidgets, QtCore, QtGui
class GraphicsView(QtWidgets.QGraphicsView):
def __init__(self, *argv, **keywords):
super(GraphicsView, self).__init__(*argv, **keywords)
scene = QtWidgets.QGraphicsScene(self)
image = QtGui.QImage('test.png')
pixmap = QtGui.QPixmap.fromImage(image)
scene.addPixmap(pixmap)
scene.setBackgroundBrush(QtCore.Qt.gray)
self.setScene(scene)
self._numScheduledScalings = 0
self.label = self.parent().findChild(QtWidgets.QLabel)
self.coordinates = []
def wheelEvent(self, event):
numDegrees = event.angleDelta().y() / 8
numSteps = numDegrees / 15
self._numScheduledScalings += numSteps
if self._numScheduledScalings * numSteps < 0:
self._numScheduledScalings = numSteps
anim = QtCore.QTimeLine(350, self)
anim.setUpdateInterval(20)
anim.valueChanged.connect(self.scalingTime)
anim.finished.connect(self.animFinished)
anim.start()
def scalingTime(self, x):
factor = 1.0 + float(self._numScheduledScalings) / 300.0
self.scale(factor, factor)
def animFinished(self):
if self._numScheduledScalings > 0:
self._numScheduledScalings -= 1
else:
self._numScheduledScalings += 1
def mousePressEvent(self, event):
p = self.mapToScene(event.pos())
if event.button() == QtCore.Qt.MidButton:
self.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag)
event = QtGui.QMouseEvent(
QtCore.QEvent.GraphicsSceneDragMove,
event.pos(),
QtCore.Qt.MouseButton.LeftButton,
QtCore.Qt.MouseButton.LeftButton,
QtCore.Qt.KeyboardModifier.NoModifier
)
elif event.button() == QtCore.Qt.LeftButton:
self.setDragMode(QtWidgets.QGraphicsView.RubberBandDrag)
self.coordinates = [p.x(), p.y()]
QtWidgets.QGraphicsView.mousePressEvent(self, event)
def mouseReleaseEvent(self, event):
QtWidgets.QGraphicsView.mouseReleaseEvent(self, event)
self.setDragMode(QtWidgets.QGraphicsView.NoDrag)
if event.button() == QtCore.Qt.LeftButton:
p = self.mapToScene(event.pos())
self.coordinates.extend([p.x(), p.y()])
text = 'select area : ' + ','.join([ str(int(c)) for c in self.coordinates])
self.label.setText(text)
def main():
app = QtWidgets.QApplication(sys.argv)
widget = QtWidgets.QWidget(None)
leyout = QtWidgets.QVBoxLayout(widget)
leyout.addWidget( QtWidgets.QLabel(widget) )
leyout.addWidget( GraphicsView(widget) )
widget.setLayout(leyout)
widget.show()
app.exec()
if __name__ == '__main__':
main()