Форум сайта python.su
Короче проблема такая, есть приложуха которая ну оооооочень долго грузит гуй и модули, сек 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_())
Офлайн
@cckyi_boxxxэээ если вы передаете сигнал из главной прилжухи в статусбар, то накой вы этот сигнал обьявляете в статусбаре?
class loading_window(QWidget):
# лучше и безопаснее передавать сигналами, этим сигналом главная приложуха говорит насколько она загрузилась
updater = pyqtSignal(signal_inf)
# -*- 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_())
[code python][/code]
Офлайн
Едрить твою налево ! Вот я тупицца ! Не догадался вместо таймера поставить обычный цикл while и заставить юзера подождать еще пару секунд в качестве платы за красивый нескачкообразный прогрессбар! Двое суток я совокуплялся с ежиком пытаясь прикрутить вызов QApplication.processEvents() из другого потока или прикрутить qeventloop в различные места, а тут такое простое решение! Категорическое вам спасибо!!!
ps: кстати в два потока пробовал, не получилось
Отредактировано @cckyi_boxxx (Фев. 29, 2020 23:09:29)
Офлайн
@cckyi_boxxxне потоки, процессы, вплоть до того что это будут два разных питонячих процесса, тогда у каждого будет свой event loop.
ps: кстати в два потока пробовал, не получилось
[code python][/code]
Отредактировано PEHDOM (Март 1, 2020 13:49:16)
Офлайн
пока оставлю как есть, а потом думаю ради спортивного интереса попробовать два qapplication запилить и один из них отправить в питонопоток, по идее процессов питона будет 1 а кутэхи 2 и каждый со своим эвентлупом, главное что-бы GIL не насрал мне в карман)))
Офлайн