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

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

新規登録して質問してみよう
ただいま回答率
85.48%
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Q&A

解決済

2回答

1708閲覧

PyQt5でのウィンドウ外でのマウスイベント監視方法

nahon

総合スコア13

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

0グッド

0クリップ

投稿2022/02/12 10:51

編集2022/02/12 12:19

イメージ説明

前提・実現したいこと

PyQt5を使い、ウィンドウを細長くしたもの(エッジバー)を左右上に配置し、クリックしたときにアクションをさせています。
この時、エッジバー以外をクリックしたときにもアクションをさせたいのですが、どのように実装すればよいでしょうか?
例えばこの画像でいうと灰色のウィンドウをクリックしたときに、アクションをさせたいです。

よろしくお願いいたします。

該当のソースコード

Python

1''' 2メインウィンドウの左右、上に太さ1pixelのエッジバーを表示する 3エッジバーをクリックすると、hello()を呼ぶ 4エッジバーを右クリックすると終了する 5 6エッジバー以外の領域をクリックしたときにbye()を呼びたい 7''' 8 9from re import S 10from PyQt5.QtWidgets import QApplication, QWidget 11from PyQt5.QtCore import Qt, QEvent 12import sys 13 14class EdgeWindow(QWidget): 15 16 def __init__( self, p_wnd, ): 17 super().__init__(p_wnd) 18 19 self.setWindowFlags(Qt.Window | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint ) 20 self.setWindowOpacity(1) 21 self.installEventFilter(self) 22 self.show() 23 24 def eventFilter(self, obj, event): 25 if event.type() == QEvent.MouseButtonPress: 26 if event.button() == Qt.LeftButton: 27 self.func() 28 return True 29 if event.button() == Qt.RightButton: 30 sys.exit() 31 return False 32 33class EdgeBar(QWidget): 34 def __init__( self, LT=None, LB=None, RT=None,RB=None, TL=None, TR=None, DEFAULT=None ): 35 super().__init__() 36 37 self.hide() 38 39 scr = QApplication.primaryScreen().availableGeometry() 40 41 if LT != None: #LeftTop 42 self.LTwin = EdgeWindow(self) 43 self.LTwin.func = LT 44 self.LTwin.setGeometry( 0, 0, 1, int(scr.height()/2)) 45 if LB != None: #LeftBottom 46 self.LBwin = EdgeWindow(self) 47 self.LBwin.func = LB 48 self.LBwin.setGeometry( 0, int((scr.top()+scr.height())/2), 1, int(scr.height()/2)) 49 if RT != None: #RightTop 50 self.RTwin = EdgeWindow(self) 51 self.RTwin.func = RT 52 self.RTwin.setGeometry( scr.right(), scr.top(), scr.right(), int(scr.height()/2)) 53 if RB != None: #RightBottom 54 self.RBwwin = EdgeWindow(self) 55 self.RBwwin.func = RB 56 self.RBwwin.setGeometry( scr.right(), int((scr.top()+scr.height())/2), scr.right(), int(scr.height()/2)) 57 if TL != None: #TopLeft 58 self.TLwin = EdgeWindow(self) 59 self.TLwin.func = TL 60 self.TLwin.setGeometry( 0, 0, int(scr.width()/2), 1) 61 if TR != None: #TopRight 62 self.TRwin = EdgeWindow(self) 63 self.TRwin.func = TR 64 self.TRwin.setGeometry( int((scr.left()+scr.width())/2), 0, int(scr.width()/2), 1 ) 65 if DEFAULT != None: 66 ''' 67 self.xxx.func = DEFAULT 68 69 ここの処理がわからないです 70 ''' 71 72def hello(): 73 print("hello!") 74 75def bye(): 76 print("bye!") 77 78if __name__ == '__main__': 79 app = QApplication(sys.argv) 80 edgebar = EdgeBar(LT=hello, LB=hello, RT=hello,RB=hello, TL=hello, TR=hello, DEFAULT=bye ) 81 sys.exit(app.exec_())

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

Qtの提供するイベントの仕組みの枠内では、
ウィンドウ外のイベントを捕捉することはできません。
(一応、バックグラウンドで常時監視する方法もありますが、
実行効率は良くないのでお勧めはしません)

マウスやキーボードイベントの監視方法は、プラットフォーム毎に異なります。
検索のヒント「Hook」

※ 注意点: 環境によっては権限やセキュリティ関連の設定が必要。
他アプリでの操作等の情報を取れる為、マルウェアと判断される可能性があります。

Pythonからは、外部ライブラリを利用する方法が簡単です。
幾つか候補がありますが、クロスプラットフォームなものは「pynput」「mouse」等。

そして、Qtアプリケーションと併用する場合、マウスイベントの監視を別スレッドで動かす事になる為、
GUIを動かしているメインスレッドとの通信が必要になります。
(GUI関連のコードは、必ずメインスレッド側で呼び出さなければなりません)
Qtでは、これをシグナル・スロットの仕組みが使えます。

先日の質問にも、イベント関連の実装方法を追記しましたが、
Qtではイベントには、「シグナル・スロット」の仕組みを使うようにしてください。

  • Qtのウィジェット・オブジェクトの提供するシグナル・スロットを使う
  • 対応したシグナルがなければ、サブクラスを作りevent関連のメソッドをオーバーライド
    必要なイベントを捕まえて、「シグナルを発呼するよう」に実装します。
  • installEventFilter は全てのイベントを捕捉する手段。通常は、上記の方法を先に検討します。
    イベント関連の処理は、ほぼ何でもできる代わりに、コードが冗長になります。

追記: 2022/02/14 9:30


追記2

エッジバー以外の領域をクリックしたときにbye()を呼びたい

目的と合うか分かりませんが、もし1度きりでよいのなら
前提として「ウィンドウがアクティブでフォーカスを持っている時」であれば、
eventFilter内で「非アクティブになった時(QEvent.WindowDeactivate)」や、
「フォーカスが外れた時(QEvent.FocusOut)」 の検知は可能です。

この方法ではマウスのクリックイベントは所得できないので、
マウスボタンの左右の判別ができない等、制限はありますが
もし、用途がウィンドウ外をクリックすると消失するタイプの一時的なダイアログであれば
マウス監視を導入しなくても実装できます。

非アクティブの時は判別できないので、限定的な場合のみ使えるかもしれない方法です。

投稿2022/02/13 03:33

編集2022/02/14 13:24
teamikl

総合スコア8664

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nahon

2022/02/14 12:56 編集

teamiklさん、こんばんは。 pynputのコードありがとうございます、早速拝見させていただきました。 teamiklさんのコードは見るたびに新しい発見があり、Pythonをより深く理解するためにとても助かっています。 QObjectでのラップ、ロギングモジュール、プロパティ、などなど積極的に使って行きたいです。 非アクティブになったときに検知する、という手もあるのですね。 質問のために少し表現を変えて投稿してしまいましたが、 今作ろうとしているのがウィンドウ端をクリックすると表示されるアプリケーションランチャーで、 アプリケーションランチャー以外をクリックすると消える、という動作を実現したいと思っています。 コメント欄での画像の貼り方が分からなかったので、別サイトで失礼します。 https://i.imgur.com/t57U3wp.png ウィンドウ外をクリックする=ウィンドウが非アクティブになるという発想がなく、こうして教えていただくまで気が付きませんでした。 考え方を変えると、また違った実装方法もあるのですね。 QEvent.WindowDeactivate、QEvent.FocusOutでの実装方法も模索してみようと思います。 ウィンドウが複数あるので少しひねりが必要かもしれませんが、そういったところを考えるのもまた楽しいですね。 自分だけではここまで素敵なアイデアを出すことはできませんでした。 改めまして、ありがとうございます。 追記2022/02/14 21:44 今後この質問を見る方のためにも追記です。 ブックマークしていたサイトに、PyQtのウィンドウのオーバーライドすればメッセージを受け取れる関数の一覧がありました。 その中にフォーカス関連のものもあり、こちらも使えるかもしれません。 http://dorafop.my.coocan.jp/Qt/Qt104.html >focusInEvent(QFocusEvent *event) フォーカスを持った >focusNextPrevChild(bool next) フォーカスが移った >focusOutEvent(QFocusEvent *event) フォーカスを失った 追記2022/02/14 21:55 すみません、ウキウキして書いてしまいましたが、 上のFocus系、QLineEditなどでしか使えないようです。 QWidgetを継承したクラスでは反応しませんでした。
teamikl

2022/02/14 13:22

filterEvent 内で QEvent.WindowDeactivate の判別となるのかな。 マウス監視なしでも実装できそうですね。 仰るとおり、focus系のイベントはこの場合は使えませんでした。 回答訂正しておきます。
teamikl

2022/02/14 16:48 編集

画像拝見しました。 目的の挙動が「自分以外がクリックされた時に閉じる」であれば、 メニュー等のポップアップ・ウィンドウが該当しそうです。 独自にQWidget から作る場合は、 Window flags の Qt.Window の所を Qt.Popup とするとポップアップとなり、 ウィンドウ外がクリックされた時に、自動的に閉じられるような挙動になります。 ※ 但し、この場合の EdgeWindow 自体は複数あるのでポップアップにはできません。 非アクティブ時に消えるので、どれかひとつしか表示出来ない為。 既存のウィジェットを使う場合は QMenu EdgeWindow 自体の挙動は分かりませんが、アプリのリストを表示する部分は 例えば、メニューならポップアップとして利用できるので、 「コンテキストメニュー」「ポップアップメニュー」を検討してみてはどうでしょう。 非アクティブになった時に非表示となるので、任意の関数を実行したい場合は メニュー(QMenu)非表示時のシグナル(aboutToHide)を使うことができます。 マウス監視が必要になるかどうかは edge window の挙動次第です。 ■ 何もないところで画面端にマウスが近づいたら表示する  → 非アクティブ時はイベントを検知できないため、マウス監視が必要 追記・訂正: ほぼ透過(opacity 0.01)ウィンドウにすれば目立たなくできるので、  マウス監視なしでも実現可能でした。 ■ 1pixelの細いウィンドウを常に表示しておく場合  → アクティブ時にイベントでも良い場合は、マウス監視は省けます
nahon

2022/02/15 13:08

teamiklさん、こんばんは。 Popup、たしかに思っているようなものとは異なる動きをしました。 そしてもう一つ教えていただいたQMenuをごく短いコードで試してみました。 これは…! 最初からこれでよかったかもしれませんね… ここまで自分で作ってしまってから移るのも少し悩みますが、 せっかくなのでまた別のものとして楽しむ気持ちでQMenuを使ったランチャーを作ってみようと思います。 本当にPyQtはいろいろな機能があるのですね。 ありがとうございます。
guest

0

自己解決

teamikiさん、ありがとうございます。

Qtウィンドウが表示されるときに呼ばれるshowEventスロットで「pynput」「mouse」等を開始する、という方向性で作ってみます。
このシグナル・スロットの仕組みをうまく使えば、より効率的なソースを書けるのではないかと期待しております。

teamikiさん、2度も丁寧なご回答いただきまして、ありがとうございました。
PyQtはtkinterとくらべ日本語での解説サイトが少なく困っておりましたが、おかげさまでなんとか作っていけそうです。

追記:操作を誤って自己解決扱いとなってしまいました。申し訳ありません。

投稿2022/02/13 07:13

編集2022/02/13 07:15
nahon

総合スコア13

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

teamikl

2022/02/13 10:27

> showEventスロットで「pynput」「mouse」等を開始する、という方向性で作ってみます。 方向性は良いと思います。幾つか細かな点を補足・訂正すると showEvent は「スロット」ではなく、イベント関連のメソッドです。 ウィンドウが表示される時に「毎回」呼び出されます。(最小化して再表示した後、等) そして、シグナル・スロットの仕組みを使うならここは、間に一手間 showEvent 内ではシグナルを発呼(emit) のみして、 マウスの監視を開始するスロットもしくは関数へ接続(connect)するようにします。 但し、厳密なタイミングが必要な場面ではないので、 余程起動に時間のかかるGUIでなければ、 show() を呼び出した後に開始でも問題有りません。簡易的には QTimer.singleShot で数秒後に実行とすれば、起動後に呼び出すことができます。 後、先の話になるかもしれませんが、シグナルの使い所としては マウスの監視はアプリケーションの終了時に必ず正しく終了しておきたいので、 aboutToQuit シグナルを使い、監視スレッドの終了処理が呼び出されるようにします。 ウィンドウが閉じられるときに呼び出される closeEvent メソッド を利用することもできますが、シグナルが使えるケース。 > 追記:操作を誤って自己解決扱いとなってしまいました。申し訳ありません。 誤操作に関してはお気になさらずに。 もう少し具体的なところ(実行できるソースコード)迄できるかなと思ったので、 後々回答に追記するかもしれません。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問