ON-BLOG

CGのこと、あれこれ書いてます。

Pyside勉強 その八

次は、読みこんだUIに、マウスイベントを入れてみます。
色々手法はあるみたいですが
とりあえず、Filterを使ってWidgetのイベントを
オーバーライドする形にしてみます。
まぁはじめこの部分すんごいはまりましたw
まずはマウスイベントだけですが
なかなか文字数増えてますね汗

import os
import sys

from PySide import QtCore, QtGui
from PySide.QtUiTools import QUiLoader
from maya.app.general.mayaMixin import MayaQWidgetBaseMixin

CURRENT_PATH = "D:/"

class GUI(MayaQWidgetBaseMixin, QtGui.QMainWindow):

    def __init__(self, parent=None):
        super(GUI, self).__init__(parent)
        loader = QUiLoader()
        uiFilePath = os.path.join(CURRENT_PATH, 'UI.ui')
        self.UI = loader.load(uiFilePath)
        self.setCentralWidget(self.UI)
        self.UI.BackGound.setPixmap(QtGui.QPixmap("D:/GR.jpg"))
        self._filter = Filter()
        self.UI.widget.installEventFilter(self._filter)
        self.UI.pushButton.clicked.connect(self.test)
        self.setWindowTitle("UI_testWindow")
        self.resize(460, 610)
        
    def test(self):
        print "Push!"

class Filter(QtCore.QObject):
    def eventFilter(self, widget, event):
        if event.type() == QtCore.QEvent.MouseButtonPress:
            self.origin = event.pos()
            self.rubberBand = QtGui.QRubberBand(QtGui.QRubberBand.Rectangle,widget)
            self.origin.setX(self.origin.x())
            self.origin.setY(self.origin.y())
            self.rubberBand.setGeometry(QtCore.QRect(self.origin,QtCore.QSize()))
            self.rubberBand.show()
            print "Click"
            print self.origin.x()
            print self.origin.y()
            
        elif event.type() == QtCore.QEvent.MouseMove:
            if self.rubberBand.isVisible():
                self.movePos = event.pos()
                self.movePos.setX(self.movePos.x()+ widget.x())
                self.movePos.setY(self.movePos.y()+ widget.y())
                self.rubberBand.setGeometry(QtCore.QRect(self.origin,self.movePos).normalized())
                print "Move"
                print event.x()
                print event.y()
            
        elif event.type() == QtCore.QEvent.MouseButtonRelease:
            print "Release"
            print event.x()
            print event.y()
            self.rubberBand.hide()
            rect = self.rubberBand.geometry()
            rect.setX(rect.x()-widget.x())
            rect.setY(rect.y()-widget.y())
            rect.setWidth(rect.width()-widget.x())
            rect.setHeight(rect.height()-widget.y())
            selected = []
            #ウィンドウ内のQPushButtonをすべて取得
            for child in widget.findChildren(QtGui.QPushButton):
            	if rect.intersects(child.geometry()):
                    selected.append(child)
            if selected:
                self.rectSelection(selected)

        return QtGui.QMainWindow.eventFilter(self, widget, event)

    def rectSelection(self,sel):
    	print sel


def main():
    app = QtGui.QApplication.instance()
    ui = GUI()
    ui.show()

if __name__ == '__main__':
    main()

実行するとこんな感じ
f:id:tommy_on:20190428005822p:plain
前回同様に画像が張ってあって、ボタンが真ん中に。
といったUI。
見た目は何も変わってません。

これにマウスイベントが組み込まれています。
実際に矩形選択すると、ログにはこう出ています。
f:id:tommy_on:20190429012126p:plain
マウスの位置と、ボタンが取得できていますね。
成功です。

では解説。

from PySide.QtUiTools import QUiLoader
from maya.app.general.mayaMixin import MayaQWidgetBaseMixin
CURRENT_PATH = "D:/"

UIファイルを読み込むモジュールを
インポートしておきます、
【CURRENT_PATH 】で絶対パスを記載して、あとでファイルと結合して
UIファイルを読み込みます。
この方が管理しやすいので、変更しました。

loader = QUiLoader()

ロードするコマンドをインスタンス化します。

uiFilePath = os.path.join(CURRENT_PATH, 'UI.ui')
self.UI = loader.load(uiFilePath)

上記で宣言したパスと、ファイル名を合体して
ロードを実行。読み込んだUIファイルを変数に格納しておきます。

self.setCentralWidget(self.UI)

継承したウィンドウ(メインウィンドウ)に読み込んだ
UIをセットしています。

self.UI.BackGound.setPixmap(QtGui.QPixmap("D:/GR.jpg"))

前回同様画像ファイルを設定

self._filter = Filter()
self.UI.widget.installEventFilter(self._filter)

この部分が今回の一番の追加点になります。
マウスイベントを【installEventFilter】を
使用して、イベントを管理しています。
【installEventFilter】については以下が、めちゃくちゃわかりやすいです。
ヲドリテヒヅル eventFilter de のっとーり


で、中身ですが、
まずはマウスの処理をまとめたクラスを読み込み
変数self._filterに格納しておきます。
次にeventFilterを使ってイベントを全取得して、上記クラスに処理を投げています。
細かく書くと

self.UI.widget.installEventFilter(self._filter)

(メインウィンドウの子どもにあるパーツにinstallEventFilterで読み込んだクラスを
 割り当てる)といった流れです。
f:id:tommy_on:20190429004958p:plain

eventFilterはイベントを全部取ってくることができる、とても便利な機能です。
他のやり方もあるみたいですが、私はこのやり方しかやったことがないので、割愛。

self.UI.pushButton.clicked.connect(self.test)
self.setWindowTitle("UI_testWindow")
self.resize(460, 610)

ボタンクリックの挙動と、ウィンドウの名前
UIサイズの変更を行っています。

def test(self):
	print "Push!"

ボタン挙動の関数です。
今見ると、押すとPush!!ってアホですねw


では次に、追加したクラスの中をみていきます。

class Filter(QtCore.QObject):
    def eventFilter(self, widget, event):

マウス処理を記載したクラスです。
ここで少しハマったので自信ありませんw
とりあえず動いているので進めます苦笑
クラスの名前は適当です。
関数名も同様です。
引数でウィジェットとイベントを指定しています。

if event.type() == QtCore.QEvent.MouseButtonPress:

event.type()なので、クラスに渡されたイベントが
マウスボタンが押されたという、イベントだったら
という分岐になります。

self.origin = event.pos()

イベントが発生したポジションを取得しています。
今回でいうと、マウスボタンが押された場所です。

self.rubberBand = QtGui.QRubberBand(QtGui.QRubberBand.Rectangle,widget)

widget上で、QRubberBandで矩形選択時に出るコレ↓を描画しています。
f:id:tommy_on:20190429005033p:plain

self.origin.setX(self.origin.x())
self.origin.setY(self.origin.y())

マウスボタンが押された場所をXとYで分解して
矩形選択の開始位置として、セットしています。

self.rubberBand.setGeometry(QtCore.QRect(self.origin,QtCore.QSize()))

矩形のジオメトリなので、線の部分かと思います
QtCore.QRectについては以下が一番わかり易いです。
QRect - qtmemo @ ウィキ - アットウィキ
あとここも
http://melpystudio.blog82.fc2.com/blog-entry-191.htmlQRect - qtmemo @ ウィキ - アットウィキ
引用すると
""
QtCore.QRect(x, y, width, height)
でQRectを作成します。xが矩形の左上X座標、yが左上Y座標、width/heightが矩形の幅/矩形の高さをそれぞれ表しています。
""
なので、QtCore.QRect(self.origin,QtCore.QSize())では
x, y,をself.originで。
width, heightQtCore.QSize()で指定います。

self.rubberBand.show()

最後に実勢の表示をこのコマンドで実行。

print "Click"
print self.origin.x()
print self.origin.y()

こちらはデバッグ用です。特に意味はありません。

elif event.type() == QtCore.QEvent.MouseMove:

マウスが動いたら。という分岐になります。

if self.rubberBand.isVisible():

クリック時に表示していた、矩形オブジェクトが表示状態なら=クリックが実行されて
いたらという意味になります。

self.movePos = event.pos()
self.movePos.setX(self.movePos.x()+ widget.x())
self.movePos.setY(self.movePos.y()+ widget.y())

一行目で現在のポジションを取得し
2、3行目でXYそれぞれの最新の値を更新しています。
(self.movePos.x()+ widget.x())この部分ですが
self.movePos.x()(現在位置) + widget.x()(ウィジェットのX開始位置)
と言うことになるので、ウィジェットの開始位置からの移動値を
取得しているといったながれになります。

self.rubberBand.setGeometry(QtCore.QRect(self.origin,self.movePos).normalized())

上記で取得した位置を元に、ジオメトリを更新しています。
最後のnormalized()は正規化です。
正しい値にしているだけですので、実はなくても動きます。
保険かと思います。
サンプルに記載していたので、つけてますw


次に

elif event.type() == QtCore.QEvent.MouseButtonRelease:

マウスボタンが離されたらという分岐。

self.rubberBand.hide()

リリースなので、ジオメトリを非表示にしています。

rect = self.rubberBand.geometry()
rect.setX(rect.x()-widget.x())
rect.setY(rect.y()-widget.y())
rect.setWidth(rect.width()-widget.x())
rect.setHeight(rect.height()-widget.y())

この部分ですが、今までと逆のことを行っています。
1行目では、矩形を取得し
2,3、4,5行目で
ウィジェットのローカル空間のXY位置と
横幅のサイズを取得しています。

selected = []

あとで使う空の配列

for child in widget.findChildren(QtGui.QPushButton):
	if rect.intersects(child.geometry()):
		selected.append(child)

widgetの子どもにあるボタンをまず全取得しておきます。
2行目で rect.intersectsで矩形の中にある(厳密には矩形に当たっている)
ジオメトリ(パーツ)なのか?と分岐を噛ましています。
矩形内なら、配列にボタンを追加しています。
まとめると、まずは該当ウィジェットの子どもにある
ボタンを全取得し、それを回し
矩形内に入っていたら(矩形座標内に)配列に足す。という
流れになります。

if selected:
	self.rectSelection(selected)

配列が空じゃなかったら、関数に飛びます。

return QtGui.QMainWindow.eventFilter(self, widget, event)

ここ重要です。
eventFilterはリターンが必要です。
処理を返さないといけないので上記を指定しています。
ここはIFで分岐しているので
return Falseでもいけます。

def rectSelection(self,sel):
	print sel

最後です。
配列にボタンが入っていたら飛ぶ関数です。
特段今は意味がありません。


という感じになります。
eventFilter便利なんですが、調べるのに結構時間かかりました…
ふぅ。。

次はボタンが矩形内にあったときの挙動を作ってみます。

以上です。