マウスイベントに応じて。
成果物
マウス左ボタンで描画。マウス右ボタンで消す。
コード
main.py
#!/usr/bin/python3 # -*- coding: utf-8 -*- import sys, numpy from PySide2 import QtCore, QtGui, QtWidgets class Window(QtWidgets.QWidget): def __init__(self): super(self.__class__, self).__init__() view = GraphicView() scroller = QtWidgets.QScrollArea() scroller.setWidget(view) layout = QtWidgets.QGridLayout() layout.addWidget(scroller, 0, 0) self.setLayout(layout) self.resize(view.width(), view.height()) self.setWindowTitle("QGraphics View Scene Item + QScrollArea") self.show() class GraphicView(QtWidgets.QGraphicsView): def __init__(self): QtWidgets.QGraphicsView.__init__(self) self.setWindowTitle("QGraphicsScene draw Grid") self.setScene(EditorScene(self)) def mousePressEvent(self, event): super(self.__class__, self).mousePressEvent(event) if self.scene(): self.scene().update() def mouseMoveEvent(self, event): super(self.__class__, self).mouseMoveEvent(event) if self.scene(): self.scene().update() class EditorScene(QtWidgets.QGraphicsScene): def __init__(self, *args, **kwargs): super(self.__class__, self).__init__(*args, **kwargs) self.size = 16 self.scale = 32 self.setSceneRect(0, 0, self.size*self.scale, self.size*self.scale) grid = GridItem() self.addItem(grid) bg = BackgroundItem() self.addItem(bg) drawable = DrawableItem() self.addItem(drawable) bg.setZValue(0) drawable.setZValue(1) grid.setZValue(9999) def mousePressEvent(self, event): for item in self.items(): item.mousePressEvent(event) super(self.__class__, self).mousePressEvent(event) def mouseMoveEvent(self, event): for item in self.items(): item.setAcceptHoverEvents(True) item.mouseMoveEvent(event) super(self.__class__, self).mouseMoveEvent(event) class DrawableItem(QtWidgets.QGraphicsRectItem): def __init__(self, *args, **kwargs): super(self.__class__, self).__init__(*args, **kwargs) self.setAcceptHoverEvents(True) self.size = 16 self.scale = 32 self.pixels = numpy.zeros(self.size*self.size).reshape(self.size, self.size) def paint(self, painter, option, widget): painter.fillRect(widget.rect(), QtGui.QBrush( QtGui.QColor(0,0,0,0), QtCore.Qt.SolidPattern)) for y in range(self.size): for x in range(self.size): if 1 == self.pixels[y][x]: painter.fillRect(x*self.scale, y*self.scale, self.scale, self.scale, QtGui.QBrush( QtGui.QColor(255,0,0,128), QtCore.Qt.SolidPattern)) def mouseMoveEvent(self, event): pos = event.scenePos() x = int(pos.x()//self.scale) y = int(pos.y()//self.scale) print('Move', str(pos.x()//self.scale), str(pos.y()//self.scale)) if event.buttons() & QtCore.Qt.LeftButton: self.pixels[y][x] = 1 print('L DRAG!!', x, y) if event.buttons() & QtCore.Qt.RightButton: self.pixels[y][x] = 0 print('R DRAG!!', x, y) def mousePressEvent(self, event): pos = event.scenePos() x = int(pos.x()//self.scale) y = int(pos.y()//self.scale) print(type(pos.x())) print('Press', str(pos.x()//self.scale), str(pos.y()//self.scale)) if event.buttons() & QtCore.Qt.LeftButton: self.pixels[y][x] = 1 print('L PRESS!!', x, y) if event.buttons() & QtCore.Qt.RightButton: self.pixels[y][x] = 0 print('R PRESS!!', x, y) def mouseReleaseEvent(self, event): pass def mouseDoubleClickEvent(self, event): pass class BackgroundItem(QtWidgets.QGraphicsRectItem): def __init__(self, *args, **kwargs): super(self.__class__, self).__init__(*args, **kwargs) self.size = 16 self.scale = 32 self.colors = [QtGui.QColor(196,196,196,255), QtGui.QColor(232,232,232,255)] def paint(self, painter, option, widget): for i in range(self.size*self.size): x = (i % self.size) y = (i // self.size) color = QtGui.QColor(128,128,128,255) if 0 == (i % 2) and 0 == (x % 2) else QtGui.QColor(196,196,196,255) painter.fillRect(x * (self.scale), y * (self.scale), self.scale//2, self.scale//2, self.colors[0]) painter.fillRect(x * (self.scale)+self.scale//2, y * (self.scale)+self.scale//2, self.scale//2, self.scale//2, self.colors[0]) painter.fillRect(x * (self.scale)+self.scale//2, y * (self.scale), self.scale//2, self.scale//2, self.colors[1]) painter.fillRect(x * (self.scale), y * (self.scale)+self.scale//2, self.scale//2, self.scale//2, self.colors[1]) class GridItem(QtWidgets.QGraphicsRectItem): def __init__(self, *args, **kwargs): super(self.__class__, self).__init__(*args, **kwargs) self.size = 16 self.scale = 32 def paint(self, painter, option, widget): painter.fillRect(widget.rect(), QtGui.QBrush(QtGui.QColor(0,0,0,0), QtCore.Qt.SolidPattern)) lines = [] for y in range(self.size+1): lines.append(QtCore.QLine(0, y*self.scale, self.size*self.scale, y*self.scale)) for x in range(self.size+1): lines.append(QtCore.QLine(x*self.scale, 0, x*self.scale, self.size*self.scale)) painter.drawLines(lines) if __name__ == "__main__": app = QtWidgets.QApplication(sys.argv) window = Window() sys.exit(app.exec_())
python3 main.py
- マウス左ボタンで描く
- マウス右ボタンで消す
苦労した所
マウスのMove
イベント
マウスを移動するたびに座標を取得したいとき、どう実装すればいいかわからなかった。要点は以下2つのAPI。
コード例は以下。
class DrawableItem(QtWidgets.QGraphicsRectItem): def __init__(self, *args, **kwargs): super(self.__class__, self).__init__(*args, **kwargs) self.setAcceptHoverEvents(True) def mouseMoveEvent(self, event): print(event.scenePos())
惑わされたのがhover
系イベント。普通にmouseMoveEvent
だけを使うと、クリックしたときだけしか反応しなかった。そこでGraphicsItem.setAcceptHoverEvents()にTrue
を指定する必要がある。ならばQGraphicsItem.hoverMoveEvent()を使うのだろうと思う所だが違う。使うのはQGraphicsItem.mouseMoveEvent()だった。わけわからん。ググってもわからんかったので苦労した。
イベント・バブリング
QGraphicsItem
で描画イベント処理を実装した。クリックされたらドットが描かれるように。しかし描画されなかった。なぜかクリック後にウインドウを拡大・縮小すると表示された。つまりクリック後に再描画されていなかったのである。
改善方法としては、親クラスでイベントハンドラをオーバーライドして、子ウィジェットのイベント呼出を実装してやる必要があった。超面倒くさい……。
class GraphicView(QtWidgets.QGraphicsView): def __init__(self): QtWidgets.QGraphicsView.__init__(self) self.setScene(EditorScene(self)) def mousePressEvent(self, event): super(self.__class__, self).mousePressEvent(event) if self.scene(): self.scene().update() def mouseMoveEvent(self, event): super(self.__class__, self).mouseMoveEvent(event) if self.scene(): self.scene().update() class EditorScene(QtWidgets.QGraphicsScene): def __init__(self, *args, **kwargs): super(self.__class__, self).__init__(*args, **kwargs) grid = GridItem(); self.addItem(grid); bg = BackgroundItem(); self.addItem(bg); drawable = DrawableItem(); self.addItem(drawable) bg.setZValue(0); drawable.setZValue(1); grid.setZValue(9999); def mousePressEvent(self, event): for item in self.items(): item.mousePressEvent(event) super(self.__class__, self).mousePressEvent(event) def mouseMoveEvent(self, event): for item in self.items(): item.setAcceptHoverEvents(True) item.mouseMoveEvent(event) super(self.__class__, self).mouseMoveEvent(event) class DrawableItem(QtWidgets.QGraphicsRectItem): def __init__(self, *args, **kwargs): super(self.__class__, self).__init__(*args, **kwargs) self.setAcceptHoverEvents(True) def mousePressEvent(self, event): pass def mouseMoveEvent(self, event): pass
イベントのコード書いてんだから察してくれよ。
所感
やっとそれっぽくできた!
しかしイベント処理のコードを書いたら一気に汚くなってきた……。
対象環境
- Raspbierry pi 4 Model B
- Raspbian buster 10.0 2019-09-26 ※
- bash 5.0.3(1)-release
$ uname -a Linux raspberrypi 4.19.97-v7l+ #1294 SMP Thu Jan 30 13:21:14 GMT 2020 armv7l GNU/Linux