Многопоточность в python
Пытаюсь создать окно tkinter, через которое через кнопку on
/off
можно воспроизводить файл с гифкой, но есть проблема.
Окно запускается, кнопка нажимается, гифка проигрывается, а после этого окно не отвечает, с ним невозможно взаимодействовать.
Подскажите пожалуйста что не так?
from tkinter import *
from tkinter import ttk
import threading
from PyQt5.QtWidgets import QApplication, QLabel
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QMovie
def window():
global is_on, gif_thread
root = Tk()
w = root.winfo_screenwidth() // 3
h = root.winfo_screenheight() // 3
root.geometry(f'300x300+{w}+{h}')
root.resizable(False, False)
# Определяем глобальные переменные
is_on = False
gif_thread = None
def switch():
global is_on, gif_thread
if is_on:
on_button.config(image=off)
is_on = False
# Останавливаем поток
gif_thread.do_run = False
gif_thread.join() # Ждем завершения потока
else:
on_button.config(image=on)
is_on = True
# Запускаем новый поток для проигрывания гифки
gif_thread = threading.Thread(target=gifff)
gif_thread.do_run = True
gif_thread.start()
# Определяем изображения
on = PhotoImage(file="Group 3.png")
off = PhotoImage(file="Group 2.png")
# Создаем кнопку
on_button = Button(root, image=off, bd=0, command=switch)
on_button.pack(pady=50)
root.mainloop()
def gifff():
# Пример функции, которая проигрывает гифку
while getattr(gif_thread, "do_run", True):
app = QApplication(sys.argv)
w = QLabel()
w.setWindowFlags(Qt.FramelessWindowHint) # transparent window
w.setAttribute(Qt.WA_TranslucentBackground)
w.setWindowFlags(Qt.Window | Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint)
movie = QMovie('gifka.gif')
w.setMovie(movie)
movie.start()
# center
w.adjustSize() # update w.rect() now
w.move(1500, 900)
w.show()
sys.exit(app.exec_())
win = threading.Thread(target=window)
win.start()
Ответы (1 шт):
Я не знаю сами вы придумали или вам кто-то подсказал,
но никогда не объединяйте
tkinter и PyQt,
да еще и используя модуль threading - это гремучая смесь.
tkinter и PyQt5 используют свой собственный цикл событий.
Никакие виджеты пользовательского интерфейса не должны быть доступны или созданы вне основного потока Qt, это справедливо также для элементов пользовательского интерфейса. Вы не можете установить фильм в отдельный поток, но и не можете создать там QMovie.
Я не знаю что вы планируете делать в дополнительном потоке и нужен ли он вам вообще.
Если реализовать, из того что я понял, на PyQt,
то
используйте сигналы и слоты - они предназначены для работы
между потоками и выглядит это примерно так:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.Qt import *
class WorkThread(QtCore.QThread):
threadSignal = QtCore.pyqtSignal(int)
def __init__(self, numStart, numEnd):
super().__init__()
self.numStart = numStart
self.numEnd = numEnd
self.threadStop = False
def run(self):
# имитируем какую-то работу в потоке
for i in range(self.numStart, self.numEnd):
self.msleep(100)
if self.threadStop:
break
self.threadSignal.emit(i)
self.finished.emit()
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.thread = None
self.movie = None
self.numStart = 0
self.numEnd = 1_000
self.centralWidget = QtWidgets.QWidget()
self.centralWidget.setObjectName("centralWidget")
self.setCentralWidget(self.centralWidget)
self.button = QPushButton(self)
self.button.setObjectName("button")
self.button.setCheckable(True)
self.button.setIcon(QIcon('off.png'))
self.button.setIconSize(QSize(70, 70))
self.button.clicked.connect(self.button_clicked)
self.label = QtWidgets.QLabel(alignment=Qt.AlignCenter)
self.label.setObjectName("label")
self.layout = QGridLayout(self.centralWidget)
self.layout.addWidget(self.label, 0, 0, 3, 1)
self.layout.addWidget(self.button, 1, 1, 1, 1)
self.layout.setColumnStretch(0, 1)
def button_clicked(self):
if self.button.isChecked():
self.button.setIcon(QIcon('on.png'))
self.show_movie()
else:
self.button.setIcon(QIcon('off.png'))
if self.thread: self.thread.threadStop = True
def show_movie(self):
self.label_movie = QtWidgets.QLabel()
self.label_movie.setAttribute(
QtCore.Qt.WA_TranslucentBackground, True)
self.label_movie.setWindowFlags(
self.windowFlags() |
QtCore.Qt.FramelessWindowHint |
QtCore.Qt.WindowStaysOnTopHint
)
self.label.setText('Ожидайте заавершения операции ...')
self.movie = QtGui.QMovie('loading.gif') # тут ваша .gif
self.label_movie.setMovie(self.movie)
self.movie.start()
self.label_movie.show()
if self.thread is None:
self.thread = WorkThread(self.numStart, self.numEnd)
self.thread.threadSignal.connect(self.on_threadSignal)
self.thread.finished.connect(self.threadFinished)
self.thread.start()
else:
self.label_movie.close()
self.thread.terminate()
self.thread = None
def on_threadSignal(self, num):
self.label.setText(f'Ожидайте заавершения операции... {num}')
self.numStart = num + 1
def threadFinished(self):
self.movie.stop()
self.label_movie.close()
self.thread = None
self.button.setIcon(QIcon('off.png'))
if self.numStart+1 == self.numEnd:
self.numStart = 0
self.button.click()
def closeEvent(self, event):
if self.movie:
self.movie.stop()
self.label_movie.close()
self.thread = None
Stylesheet = '''
#centralWidget {
background-color: rgb(54, 54, 54);
}
#label {
background-color: rgb(84, 74, 54);
color: #D9AC00;
font-size: 24px;
}
#button {
border: rgb(0, 0, 0);
}
'''
if __name__ == "__main__":
app = QApplication(sys.argv)
app.setStyle(QStyleFactory.create("fusion"))
app.setStyleSheet(Stylesheet)
w = MainWindow()
w.resize(600, 400)
w.show()
sys.exit(app.exec_())
off.png
on.png