teratail header banner
teratail header banner
質問するログイン新規登録

回答編集履歴

7

追記: 2つのイベントループを結合する方法

2020/12/10 08:52

投稿

teamikl
teamikl

スコア8817

answer CHANGED
@@ -95,4 +95,70 @@
95
95
 
96
96
  注意点: 終了時に tk/qt 側で何か処理を行う場合は、
97
97
  daemon=False にして join メソッドでプロセスの終了を待ちます。
98
- その場合、安全に各GUIのイベントループを終了する仕組みが必要になります。
98
+ その場合、安全に各GUIのイベントループを終了する仕組みが必要になります。
99
+
100
+
101
+ ----
102
+ qt/tk のイベントループを結合する方法
103
+
104
+ 独自のイベントループを使う為、終了時のイベント処理は未対応。
105
+
106
+ デメリット:
107
+ - Qt の aboutToQuit シグナル等は期待通りに処理されない等、他への影響があります。
108
+ - 片方の遅延がもう片方に影響する。(tk のイベント処理が遅れると qt も遅くなる → 動作が重く)
109
+
110
+ 別プロセスにした方が良いと思いますが、2つ以上のイベントループを使いたい場合、
111
+ このようなアプローチにすることも有ります。(例: asyncio + GUI 等)
112
+
113
+ ```python
114
+ import sys
115
+ import time
116
+ from functools import partial
117
+ from types import SimpleNamespace
118
+
119
+ import tkinter as tk
120
+ from PyQt5.QtCore import pyqtSignal
121
+ from PyQt5.QtWidgets import QApplication, QMainWindow
122
+
123
+ def my_quit(app, root, shared):
124
+ shared.stop_request = True
125
+
126
+ def my_event_loop(app, root, shared):
127
+ while not shared.stop_request:
128
+ root.update()
129
+ app.processEvents()
130
+ time.sleep(0.1)
131
+ app.quit()
132
+ root.destroy()
133
+
134
+ def main():
135
+ app = QApplication(sys.argv)
136
+ root = tk.Tk()
137
+ shared = SimpleNamespace(
138
+ stop_request = False,
139
+ )
140
+ cleanup = partial(my_quit, app, root, shared)
141
+
142
+ # tk widgets
143
+ root.protocol("WM_DELETE_WINDOW", cleanup)
144
+ button = tk.Button(root, text="OK")
145
+ button.pack()
146
+
147
+ # qt widgets
148
+ class MyMainWindow(QMainWindow):
149
+ closed = pyqtSignal()
150
+ def closeEvent(self, event):
151
+ self.closed.emit()
152
+ event.accept()
153
+ win = MyMainWindow()
154
+ win.closed.connect(cleanup)
155
+ win.show()
156
+
157
+ # XXX: app.aboutToQuit does not work
158
+
159
+ my_event_loop(app, root, shared)
160
+
161
+
162
+ if __name__ == '__main__':
163
+ main()
164
+ ```

6

説明補足

2020/12/10 08:52

投稿

teamikl
teamikl

スコア8817

answer CHANGED
@@ -27,10 +27,6 @@
27
27
  ----
28
28
 
29
29
  ```python
30
-
31
-
32
-
33
-
34
30
  def tk_main(shared):
35
31
  import tkinter as tk
36
32
  from tkinter import ttk
@@ -95,4 +91,8 @@
95
91
 
96
92
  qt <-> tk 間の連携には「プロセス間通信」を調べて見て下さい、
97
93
  qt 側で読み取る Queue や tk 側で読み取る Queue を増やし、
98
- Queue を通じてメッセージのやり取りをすることになります。
94
+ Queue を通じてメッセージのやり取りをすることになります。
95
+
96
+ 注意点: 終了時に tk/qt 側で何か処理を行う場合は、
97
+ daemon=False にして join メソッドでプロセスの終了を待ちます。
98
+ その場合、安全に各GUIのイベントループを終了する仕組みが必要になります。

5

コード修正

2020/12/10 08:36

投稿

teamikl
teamikl

スコア8817

answer CHANGED
@@ -28,6 +28,9 @@
28
28
 
29
29
  ```python
30
30
 
31
+
32
+
33
+
31
34
  def tk_main(shared):
32
35
  import tkinter as tk
33
36
  from tkinter import ttk
@@ -51,14 +54,16 @@
51
54
  app = QApplication(shared.argv)
52
55
  win = QMainWindow()
53
56
  button = QPushButton("Quit", win)
54
- button.clicked.connect(lambda: shared.queue.put(None))
57
+ button.clicked.connect(win.close)
55
58
  win.show()
56
59
 
60
+ app.aboutToQuit.connect(lambda: shared.queue.put(None))
57
61
  app.exec_()
58
62
 
59
63
 
60
64
  def main():
61
65
  import sys
66
+ from types import SimpleNamespace
62
67
  from multiprocessing import Process, Queue
63
68
 
64
69
  shared = SimpleNamespace(
@@ -78,6 +83,7 @@
78
83
  if __name__ == '__main__':
79
84
  main()
80
85
 
86
+
81
87
  ```
82
88
 
83
89
  tkinter と pyqt のウィンドウを同時に開くサンプル

4

追記: Process を使う方法

2020/12/10 08:32

投稿

teamikl
teamikl

スコア8817

answer CHANGED
@@ -22,4 +22,71 @@
22
22
  ```
23
23
 
24
24
  同時にイベントループを稼働する方法は、終了時に同期を取る方法が難しい為、
25
- 別プロセスを検討した方が良さそうです。
25
+ 別プロセスを検討した方が良さそうです。
26
+
27
+ ----
28
+
29
+ ```python
30
+
31
+ def tk_main(shared):
32
+ import tkinter as tk
33
+ from tkinter import ttk
34
+ from functools import partial
35
+
36
+ main_quit = partial(shared.queue.put, None)
37
+
38
+ root = tk.Tk()
39
+ button = ttk.Button(root, text="Quit")
40
+ button.config(command=main_quit)
41
+ button.pack()
42
+
43
+ root.protocol("WM_DELETE_WINDOW", main_quit)
44
+
45
+ root.mainloop()
46
+
47
+
48
+ def qt_main(shared):
49
+ from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton
50
+
51
+ app = QApplication(shared.argv)
52
+ win = QMainWindow()
53
+ button = QPushButton("Quit", win)
54
+ button.clicked.connect(lambda: shared.queue.put(None))
55
+ win.show()
56
+
57
+ app.exec_()
58
+
59
+
60
+ def main():
61
+ import sys
62
+ from multiprocessing import Process, Queue
63
+
64
+ shared = SimpleNamespace(
65
+ argv = sys.argv,
66
+ queue = Queue(),
67
+ )
68
+ procTk = Process(target=tk_main, args=(shared,), daemon=True)
69
+ procQt = Process(target=qt_main, args=(shared,), daemon=True)
70
+
71
+ procTk.start()
72
+ procQt.start()
73
+
74
+ for item in iter(shared.queue.get, None):
75
+ queue.task_done()
76
+
77
+
78
+ if __name__ == '__main__':
79
+ main()
80
+
81
+ ```
82
+
83
+ tkinter と pyqt のウィンドウを同時に開くサンプル
84
+ このコードでは合計3つのプロセスを使いますが、最少は qt と tk の2つでも良いです。
85
+ 終了時の処理を解りやすくするために3つにしました。
86
+
87
+ メインのプロセスではキューへのメッセージを待ち、
88
+ None が入れられるとプログラムを終了します。
89
+
90
+ qt <-> tk 間の連携には「プロセス間通信」を調べて見て下さい、
91
+ qt 側で読み取る Queue や tk 側で読み取る Queue を増やし、
92
+ Queue を通じてメッセージのやり取りをすることになります。

3

文章の修正

2020/12/10 08:30

投稿

teamikl
teamikl

スコア8817

answer CHANGED
@@ -22,4 +22,4 @@
22
22
  ```
23
23
 
24
24
  同時にイベントループを稼働する方法は、終了時に同期を取る方法が難しい為、
25
- 別プロセスで使う方を検討した方が良さそうです。
25
+ 別プロセスを検討した方が良さそうです。

2

修正

2020/12/10 07:12

投稿

teamikl
teamikl

スコア8817

answer CHANGED
@@ -17,8 +17,8 @@
17
17
  # 双方のイベントループを動かす独自のイベントループを実装する
18
18
  def my_event_loop():
19
19
  while True:
20
- print("tk") # update
20
+ print("tk") # root.update() ... tk のイベント処理
21
- print("qt") # processEvents
21
+ print("qt") # app.processEvents() ... qt のイベント処理
22
22
  ```
23
23
 
24
24
  同時にイベントループを稼働する方法は、終了時に同期を取る方法が難しい為、

1

説明補足

2020/12/10 07:02

投稿

teamikl
teamikl

スコア8817

answer CHANGED
@@ -19,4 +19,7 @@
19
19
  while True:
20
20
  print("tk") # update
21
21
  print("qt") # processEvents
22
- ```
22
+ ```
23
+
24
+ 同時にイベントループを稼働する方法は、終了時に同期を取る方法が難しい為、
25
+ 別プロセスで使う方を検討した方が良さそうです。