質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.91%

PyQt5で既存のQGraphicViewにマウスのドラッグ&ドロップで矩形を描画したい。

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 274

isc

score 2

前提・実現したいこと

QGraphicViewに先にjpg形式の画像を貼っておき、その上から矩形を描画したいと考えています。
矩形の描画は一時的でよく、ドラッグ&ドロップをやり直すと矩形が再描画できるのが理想です。

該当のソースコード

import sys,re
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPainter
from PyQt5.QtWidgets import QApplication,QWidget 

class Widget(QWidget):

    global start_x,start_y,end_x,end_y

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.points = []
        self.psets = []
        self.points_a = []
        self.setMinimumSize(500, 500)
        self.setMaximumSize(500, 500)

    def mousePressEvent(self, event):
        self.points.append(event.pos())

        #無知が故の文字列処理です##################
        arr = [str(i) for i in self.points]
        moji =','.join(arr)
        global start_x ,start_y
        result = moji.replace('PyQt5.QtCore.QPoint','')
        result = result.strip('()')
        result = result.replace(',','')
        l_result = result.split()
        #######################################
        start_x = l_result[0]
        start_y = l_result[1]



    def mouseMoveEvent(self, event):
        self.points_a =[]
        self.points_a.append(event.pos())
        arr = [str(i) for i in self.points_a]
        moji =','.join(arr)
        global end_x ,end_y
        result = moji.replace('PyQt5.QtCore.QPoint','')
        result = result.strip('()')
        result = result.replace(',','')
        l_result = result.split()
        end_x = l_result[0]
        end_y = l_result[1]
        painter = QPainter(self)
        painter.setPen(Qt.black)
        painter.drawRect(int(start_x),int(start_y),int(end_x)-int(start_x),int(end_y)-int(start_y))
        self.update()

    def mouseReleaseEvent(self, event):
        self.psets = []
        self.psets.append(event.pos())



        arr = [str(i) for i in self.psets]
        moji =','.join(arr)
        global end_x ,end_y
        result = moji.replace('PyQt5.QtCore.QPoint','')
        result = result.strip('()')
        result = result.replace(',','')
        l_result = result.split()
        end_x = l_result[0]
        end_y = l_result[1]


        self.points = []
        self.update()



    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setPen(Qt.black)

        for points in self.psets:
            painter.drawRect(int(start_x),int(start_y),int(end_x)-int(start_x),int(end_y)-int(start_y))
            print(start_x,start_y,end_x,end_y)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = Widget()
    w.show()
    sys.exit(app.exec_())

試したこと

参考プログラムをもとに上記のプログラムを書きました(適当に書きすぎて本当に汚いです…)
マウスのドラッグ&ドロップで矩形を描画するのはこちらのサイトでも例があります。
これをQGraphicViewの画像の上で再現することができません。
mainwindowなどでやると動作はしますが、画像が入らないという問題が出てきました。

補足情報(FW/ツールのバージョンなど)

python 3.6

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

2つの異なる方法を混在させて使おうとしてるのが原因です。

GGraphicsViewクラスは、paintEventをオーバーライドして
Scene で管理する Item を描画するという独自の描画の枠組みを提供しているので、

元の機能を保ったまま独自に拡張する場合は、
親クラスのpaintEvent を呼び出す必要があります。(暫定的な解決策)

(↑ QGraphicViewを使ったコードは解らない為、ここは推測ですが
QGraphicViewを継承し拡張しようとしている場合)

それよりも、GGraphicsView で出来る事ならば
その枠組み内でやってしまった方が良いです。

QGraphicsView 上の場合、矩形の描画には
QGraphicsRectItem等を用います。
マウスのイベントでアイテムの表示非表示/移動・リサイズを行うことで
選択範囲の枠描画は実現できます。


mainwindowなどでやると動作はしますが、画像が入らないという問題が出てきました。

通常のwidgetの場合
QPainter で画像を描画する場合は、drawImage や drawPixmap を使います。


イメージ説明

出典: https://stackoverflow.com/questions/52728462/pyqt-add-rectangle-in-qgraphicsscene

サンプルコードを数カ所改変しました。

  • 画像との重ね合わせを考慮して、透過色 QtGui.QColor(255, 0, 0, 128)
  • 短形は一つのみ表示 self.removeItem(self._current_rect_item)
  • 不要な設定の削除 ItemIsMovable フラグ
from PyQt5 import QtCore, QtGui, QtWidgets

class GraphicsScene(QtWidgets.QGraphicsScene):
    def __init__(self, parent=None):
        super(GraphicsScene, self).__init__(QtCore.QRectF(-500, -500, 1000, 1000), parent)
        self._start = QtCore.QPointF()
        self._current_rect_item = None

    def mousePressEvent(self, event):
        if self._current_rect_item:
            self.removeItem(self._current_rect_item)
        if self.itemAt(event.scenePos(), QtGui.QTransform()) is None:
            self._current_rect_item = QtWidgets.QGraphicsRectItem()
            self._current_rect_item.setBrush(QtGui.QColor(255, 0, 0, 128))
            # アイテムを移動可能にする -> すぐに削除する為不要
            # self._current_rect_item.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
            self.addItem(self._current_rect_item)
            self._start = event.scenePos()
            r = QtCore.QRectF(self._start, self._start)
            self._current_rect_item.setRect(r)
        super(GraphicsScene, self).mousePressEvent(event)

    def mouseMoveEvent(self, event):
        if self._current_rect_item is not None:
            r = QtCore.QRectF(self._start, event.scenePos()).normalized()
            self._current_rect_item.setRect(r)
        super(GraphicsScene, self).mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):
        # self._current_rect_item = None
        super(GraphicsScene, self).mouseReleaseEvent(event)


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        scene =GraphicsScene(self)
        view = QtWidgets.QGraphicsView(scene)
        self.setCentralWidget(view)


if __name__ == '__main__':
    import sys

    app = QtWidgets.QApplication(sys.argv)
    w = MainWindow()
    w.resize(640, 480)
    w.show()
    sys.exit(app.exec_())

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/07/12 04:27

    最小限の動作テストしかしてなかったので、そのままの適応は難しかったようですね。

    > マウスでドラッグをして矩形を描く最中にQGraphicsViewの外枠に当たると拡張?(もしくはスクロールバーが追加されてしまう)ような状態になります。

    上記のサンプルコードに画像を追加しただけでは、
    こちらでは再現できませんでした。

    他のQGraphicsView のウィジェットの配置方法の影響でしょうか、
    どの部分の setMaximumSizeやsetFixed~などか解らないので
    質問を編集して問題を再現できるコードの記載をお願いします。

    ----
    もしくは、GraphicsView を使わない方法になりますが、
    既に選択範囲の短形描画迄は出来てるようなので、
    そちらに画像を表示する解決策の方が良いでしょうか?

    GraphicsViewの機能が必要かどうかで、どちらが適切かが変わってきますが、
    差し支えなければ、どのような機能を実装したいか教えていただけますか。

    例えば、画像の選択範囲の切り抜きならどちらでも実装可能ですが、
    配置した画像を動かしてレイアウトを組んだりする画像編集のようなものを作るなら、
    GraphicsView の方が都合が良かったりします。

    キャンセル

  • 2020/07/12 10:38 編集

    scene.setSceneRect(QtCore.QRectF(view.rect()))
    こちらが有用でした。ありがとうございました。

    キャンセル

  • 2020/07/13 06:44

    なるほど、自分の試していたサンプルでは、
    ウィンドウサイズに合わせてたので発生しなかったようです。

    余談になりますが、GraphicsView を使わない方での画像表示(質問の上のコード差分)

    # __init__ で
    self.image = QImage("sample.png")

    # paintEvent内で
    painter.drawImage(100, 100, self.image)

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.91%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る