Многопоточность в 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 шт):

Автор решения: S. Nick

Я не знаю сами вы придумали или вам кто-то подсказал, но никогда не объединяйте
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

введите сюда описание изображения

→ Ссылка