Найти - Пользователи
Полная версия: pyqt5 не могу нормально прикрутить окно загрузки
Начало » GUI » pyqt5 не могу нормально прикрутить окно загрузки
1
@cckyi_boxxx
Короче проблема такая, есть приложуха которая ну оооооочень долго грузит гуй и модули, сек 30 точно, поэтому было решено юзеру отобразить весь процесс загрузки, заодно в случае зависания можно будет глянуть на каком модуле оно случилось.

для реализации этой задачи состряпал два класса , один легкий - собсна окно загрузки, второй мощный (в оригинале около 900 строк , сюда естественно все ненужное вырезал , оставил основняк, см код ниже.

проблема в том что я не могу заставить приложуху постоянно работать и обрабатывать сигналы до вызова app.exec_() , без этого максимум что я могу это резко перемещать прогрессбар, а так что-бы класс основного окна по мере прогрузки давал данные окну загрузки и оно работало как самостоятельное приложение - не выходит.

уже вторые сутки бьюсь не могу запилить это дерьмо

 # -*- coding: utf-8 -*-
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QFrame, QPushButton, QHBoxLayout, QMainWindow, QAction, \
QGridLayout, QRadioButton, QLabel, QCheckBox, QTableWidgetItem, QTableWidget, QCompleter, QComboBox, QLineEdit, QToolBar, \
QComboBox, QStatusBar, QSizePolicy, QProgressBar, QMessageBox
from PyQt5.QtGui import QFocusEvent, QIcon, QPixmap, QCursor
from PyQt5.QtCore import pyqtSignal , QEventLoop , QUrl , QThread , QObject , QTimer, QStringListModel, QEvent, Qt, QRect, \
QSize
import sys, os, importlib, time
import _thread as thread
class signal_inf:
    def __init__(self, msg = '', state = 5):
        self.msg = msg
        self.state = state
    
class loading_window(QWidget):
    # лучше и безопаснее передавать сигналами, этим сигналом главная приложуха говорит насколько она загрузилась
    updater = pyqtSignal(signal_inf) 
    # В эту переменную позже переместим обьект класса основного окна (наследника QMainWindow),пока пусть висит что-б автодополнение в ИДЕ было
    mainobj = QMainWindow
    # не помню почему м не показалось удобнее использовать свою переменную нежели стандартные ср-ва qt, да и пох как показометр работает
    notshow = True
    def __init__(self, parrent = None):
        """
        тут ваще ничего интересного обычное обьявлялово виджетов, см след коммент
        """
        QWidget.__init__(self, parrent)
        self.click_count = 0
        self.current_loading = 5
        self.updater.connect(self.updater_handler)
        self.setObjectName("loading")
        self.resize(400, 200)
        self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint)
        sizePolicy1 = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        sizePolicy1.setHorizontalStretch(0)
        sizePolicy1.setVerticalStretch(0)
        sizePolicy1.setHeightForWidth(self.sizePolicy().hasHeightForWidth())
        self.setSizePolicy(sizePolicy1)
        self.setMinimumSize(QSize(400, 200))
        self.setMaximumSize(QSize(400, 200))
        self.setCursor(QCursor(Qt.WaitCursor))
        self.setContextMenuPolicy(Qt.NoContextMenu)
        self.setStyleSheet("background-color: rgb(0, 0, 0);\n border-color: rgb(0, 0, 0);\n alternate-background-color: rgb(0, 0, 0);")
        self.verticalLayout = QVBoxLayout()
        self.verticalLayout.setObjectName("verticalLayout")
        self.setLayout(self.verticalLayout)
        self.progressBar = QProgressBar(self)
        self.progressBar.setProperty("value", self.current_loading)
        self.progressBar.setObjectName("progressBar")
        self.progressBar.setFixedHeight(10)
        self.progressBar.setFixedWidth(380)
        self.progressBar.setTextVisible(False)
        self.verticalLayout.addWidget(self.progressBar, Qt.AlignBottom|Qt.AlignLeft)
        self.label = QLabel(self)
        sizePolicy2 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
        sizePolicy2.setHorizontalStretch(0)
        sizePolicy2.setVerticalStretch(0)
        sizePolicy2.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth())
        self.label.setSizePolicy(sizePolicy2)
        self.label.setCursor(QCursor(Qt.WaitCursor))
        self.label.setContextMenuPolicy(Qt.NoContextMenu)
        self.label.setStyleSheet("color: rgb(170, 170, 255);")
        self.label.setTextInteractionFlags(Qt.NoTextInteraction)
        self.label.setObjectName("label")
        self.verticalLayout.addWidget(self.label, Qt.AlignBottom|Qt.AlignLeft)
        self.verticalLayout.setAlignment(self.progressBar, Qt.AlignBottom|Qt.AlignLeading|Qt.AlignLeft)
        self.label.setText("loading...")
        
        # этот таймер отвечает за плавное перемещение полосы загрузки, для наглядности стоит 100мсек, для боевого режима лучше 10 - 15
        self.utimer = QTimer(self)
        self.utimer.timeout.connect(self.update_progress_bar)
        self.utimer.start(100)
    
    def update_progress_bar(self):
        curval = self.progressBar.property('value')
        if curval < self.current_loading:
            self.progressBar.setProperty('value' , curval + 1)
        if curval == 100:
            self.mainobj.showMaximized()
            self.exit_loader()
        """
        # одна из попыток заставить все работать плавно, эх как-же зае*ло отсутствие готового инструмента для типовой задачи
        try:
            self.mainobj.appl.processEvents()
            print('ok self.appl.processEvents()')
        except:
            print('err self.appl.processEvents()')
        #"""
            
    # если юзер клацает по нашему окну загрузки значит что-то не нравится, предложим закрыть все
    def mouseDoubleClickEvent(self, event): 
        self.__exit_all()
        
    # если юзер клацает по нашему окну загрузки значит что-то не нравится, предложим закрыть все
    def mousePressEvent(self, *args, **kwargs):
        self.__exit_all()
    
    def showEvent(self, event):
        self.notshow = False
    
    # если юзер клацает по нашему окну загрузки значит что-то не нравится, предложим закрыть все
    def ok_exit(self):
        msg = QMessageBox()
        msg.setWindowTitle("STOP")
        msg.setText("Are you sure you want to close the program?")
        msg.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
        retval = msg.exec_()
        return retval == QMessageBox.Ok
    
    def updater_handler(self, data):
        """сюда при помощи сигнала вызванного главным окном (которое еще грузится) передаем его текущее состояние"""
        self.current_loading = data.state
        if data.msg != '':
            self.label.setText(data.msg)
    
    def exit_loader(self):
        self.utimer.stop()
        self.close()
    
    def __exit_all(self):
        if self.click_count > 5:
            if self.ok_exit():
                self.mainobj.close()
                self.exit_loader()
            else:
                self.click_count = 0
        else:
            self.click_count += 1
            
class onload_event_proc(QThread):
    appl = QApplication # что-б работало автодополнение в IDE, использоваться будет в __main__ заменой на обьект класса QApplication()
    def __init__(self, parent = None):
        QThread.__init__(self)
        self.ss = True
    
    def run(self):
        while self.ss:
            self.msleep(1)
            self.appl.processEvents()
            
    def stop_ld_events(self):
        self.ss = False
        cnt = 0
        while self.isRunning():
            time.sleep(0.001)
            if cnt > 20:
                self.terminate()
            else:
                cnt += 1
class callbacker_thread(QThread):
    def __init__(self , task , parent = None):
        QThread.__init__(self , parent)
        self.task = task
    
    def run(self):
        self.task()
    
    
class acc(QMainWindow):
    loader = loading_window # что-б работало автодополнение в IDE, использоваться будет в __main__ заменой на обьект класса loading_window()
    appl_ev = onload_event_proc()
    appl = QApplication # та-же история что и с loading_window
    def __init__(self, parrent = None):
        QMainWindow.__init__(self, parrent)
        """
        перенес всю инициализацию в метод linit, для того что-бы перед и нициализацией всего дерьма
        иметь возможность поместить сюда ссылки на объекты классов loading_window, QApplication и прочих
        а так-же вести инициализацию из другого потока
        """
        
    
    def linit(self):
        while self.loader.notshow:
            time.sleep(0.05)
        self.loader.updater.emit(signal_inf('loading graphical components', 10))
        #self.appl.processEvents()
        
        # для форума большую часть виджетов вынес, на этот блок можно не обращать внимания
        self.centralwidget = QWidget(self)
        self.centralwidget.setObjectName("centralwidget")
        self.setCentralWidget(self.centralwidget)
        self.central_layout = QVBoxLayout(self.centralwidget)
        self.centralwidget.setLayout(self.central_layout)
        self.toolBar = QToolBar(self)
        self.toolBar.setObjectName("toolBar")
        self.toolBar.setMovable(False)
        self.addToolBar(Qt.TopToolBarArea, self.toolBar)
        self.config_button = QPushButton('Config', self) 
        self.toolBar.addWidget(self.config_button)
        
        
        time.sleep(1)
        self.loader.updater.emit(signal_inf('loading controll triggers', 20))
        #self.appl.processEvents()
        
        time.sleep(5)
        self.loader.updater.emit(signal_inf('loading global settings', 30))
        
        
        time.sleep(5)
        self.loader.updater.emit(signal_inf('loading stage', 40))
        #self.appl.processEvents()
        
        self.loader.updater.emit(signal_inf('', 100))
    
    def run_app_event_processor(self, qapplic):
        self.appl_ev.appl = qapplic
        self.appl_ev.start()
if __name__ == '__main__':
    
    app = QApplication(sys.argv)
    wnd = acc() # создаем основное окно
    wnd.loader = loading_window() # создаем окно загрузчика и помещаем его внутрь обьекта главного окна что-бы иметь к нему доступ оттуда
    wnd.appl = app # даем главному окну доступ к обьекту QApplication
    #wnd.loader.setParent(wnd)
    
    # даем окну загрузчика ссылку на главное окно что-бы он по достижении прогрессбаром 100% мог вызвать show у главного окна
    wnd.loader.mainobj = wnd 
    wnd.loader.show() 
    
    
    #wnd.run_app_event_processor(app) # как-то совсем плохо все при таком способе
    """
    когда загрузку виджетов, плагинов и прочего отправляю в поток, в этой версии (для форума),
    где вырезана целая куча виджетов и модулей просто не прогружается гуй (нет тулбара и кнопки на нем)
    в более сложной версии получаю вылет (скорее всего потому что пытаюсь использовать не размещенные виджеты)
    
    так отрабатывает как в случае с модулем thread так и с QThread (см 3 строки ниже)
    """
    #thread.start_new_thread(wnd.linit, () ) 
    
    #fuck = callbacker_thread(wnd.linit , wnd) 
    #fuck.start()
    
    
    """
    если вызывать wnd.linit() так как это сделано ниже и раскоментить все вызовы self.appl.processEvents() внутри 
    wnd.linit() то все работает но криво, прогресс бар не идет плавно до установленной точки, а делает лтишь один рывок, 
    и плавно идет лишь после вызова app.exec_()
    """
    wnd.linit()
    
    #wnd.showMaximized() позднее принял решение вызывать из окна "загрузчика" по достижении на прогресс баре 100%
    sys.exit(app.exec_())
    
PEHDOM
@cckyi_boxxx
class loading_window(QWidget):
# лучше и безопаснее передавать сигналами, этим сигналом главная приложуха говорит насколько она загрузилась
updater = pyqtSignal(signal_inf)

эээ если вы передаете сигнал из главной прилжухи в статусбар, то накой вы этот сигнал обьявляете в статусбаре?
Проблема в том что пока у вас выполняется какаято долгоиграющая функция, time.sleep(5) например, обработчик событий блокируется, окно не обновляетьсяи тд.. поэтому не выйдет в одном потоке и окно перерисовывать 10 раз в секунду и одновременно выполнять “тяжелую” функцию.
Как вариант нужно или запускать два процесса одно с окном загрузки, другое с главной приложухой, или применить вот такой “обман”
  # -*- coding: utf-8 -*-
from ... # импорты тут такиеже
class loading_window(QWidget):
    def __init__(self, parrent = None):
        """
        тут ваще ничего интересного обычное обьявлялово виджетов, см след коммент
        """
        QWidget.__init__(self, parrent)
        self.click_count = 0
        self.current_loading = 5
        self.setObjectName("loading")
        self.resize(400, 200)
        self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint)
        sizePolicy1 = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        sizePolicy1.setHorizontalStretch(0)
        sizePolicy1.setVerticalStretch(0)
        sizePolicy1.setHeightForWidth(self.sizePolicy().hasHeightForWidth())
        self.setSizePolicy(sizePolicy1)
        self.setMinimumSize(QSize(400, 200))
        self.setMaximumSize(QSize(400, 200))
        self.setCursor(QCursor(Qt.WaitCursor))
        self.setContextMenuPolicy(Qt.NoContextMenu)
        self.setStyleSheet("background-color: rgb(0, 0, 0);\n border-color: rgb(0, 0, 0);\n alternate-background-color: rgb(0, 0, 0);")
        self.verticalLayout = QVBoxLayout()
        self.verticalLayout.setObjectName("verticalLayout")
        self.setLayout(self.verticalLayout)
        self.progressBar = QProgressBar(self)
        self.progressBar.setProperty("value", self.current_loading)
        self.progressBar.setObjectName("progressBar")
        self.progressBar.setFixedHeight(10)
        self.progressBar.setFixedWidth(380)
        self.progressBar.setTextVisible(False)
        self.verticalLayout.addWidget(self.progressBar, Qt.AlignBottom|Qt.AlignLeft)
        self.label = QLabel(self)
        sizePolicy2 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
        sizePolicy2.setHorizontalStretch(0)
        sizePolicy2.setVerticalStretch(0)
        sizePolicy2.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth())
        self.label.setSizePolicy(sizePolicy2)
        self.label.setCursor(QCursor(Qt.WaitCursor))
        self.label.setContextMenuPolicy(Qt.NoContextMenu)
        self.label.setStyleSheet("color: rgb(170, 170, 255);")
        self.label.setTextInteractionFlags(Qt.NoTextInteraction)
        self.label.setObjectName("label")
        self.verticalLayout.addWidget(self.label, Qt.AlignBottom|Qt.AlignLeft)
        self.verticalLayout.setAlignment(self.progressBar, Qt.AlignBottom|Qt.AlignLeading|Qt.AlignLeft)
        self.label.setText("loading...")
        self.show()
    def update_progress_bar(self, val):
        curval = self.progressBar.property('value')
        while curval < val:
            curval +=1
            self.progressBar.setProperty('value' , curval)
            time.sleep(0.1)
            QApplication.processEvents()
class acc(QMainWindow):
    loaded = pyqtSignal(int)
    def __init__(self, loader , parrent = None):
        self.loader = loader
        QMainWindow.__init__(self, parrent)
        """
        перенес всю инициализацию в метод linit, для того что-бы перед и нициализацией всего дерьма
        иметь возможность поместить сюда ссылки на объекты классов loading_window, QApplication и прочих
        а так-же вести инициализацию из другого потока
        """
    def linit(self):
        self.loaded.emit(1)
        time.sleep(2)
        print('import modules')
        self.loaded.emit(20)
        time.sleep(5)
        print('load GUI')
        self.loaded.emit(40)
        time.sleep(5)
        print('import data')
        self.loaded.emit(60)
        time.sleep(3)
        print('other shit')
        self.loaded.emit(100)
        self.show()
        self.loader.close()
if __name__ == '__main__':
    app = QApplication(sys.argv)
    loader = loading_window() #создаем окно прогреса заргузки
    wnd = acc(loader) # создаем основное окно
    wnd.loaded.connect(loader.update_progress_bar) # соединяем сигналом
    wnd.linit() #запускаем инициализацию главного окна.
    sys.exit(app.exec_())
@cckyi_boxxx
Едрить твою налево ! Вот я тупицца ! Не догадался вместо таймера поставить обычный цикл while и заставить юзера подождать еще пару секунд в качестве платы за красивый нескачкообразный прогрессбар! Двое суток я совокуплялся с ежиком пытаясь прикрутить вызов QApplication.processEvents() из другого потока или прикрутить qeventloop в различные места, а тут такое простое решение! Категорическое вам спасибо!!!

ps: кстати в два потока пробовал, не получилось
PEHDOM
@cckyi_boxxx
ps: кстати в два потока пробовал, не получилось
не потоки, процессы, вплоть до того что это будут два разных питонячих процесса, тогда у каждого будет свой event loop.
@cckyi_boxxx
пока оставлю как есть, а потом думаю ради спортивного интереса попробовать два qapplication запилить и один из них отправить в питонопоток, по идее процессов питона будет 1 а кутэхи 2 и каждый со своим эвентлупом, главное что-бы GIL не насрал мне в карман)))
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Powered by DjangoBB