|
from PySide2.QtWidgets import QApplication, QMainWindow, QWidget, QStackedWidget, QHBoxLayout, QVBoxLayout |
|
from PySide2.QtWidgets import QPushButton, QLabel, QMenuBar, QMessageBox |
|
#from PySide2.QtGui import * |
|
from PySide2.QtCore import QObject, QRunnable, Signal, Slot, QThreadPool |
|
import sys, traceback, time |
|
|
|
class WorkerSignals(QObject): |
|
"""WorkerSignals obtained from https://www.learnpyqt.com/courses/concurrent-execution/multithreading-pyqt-applications-qthreadpool/ |
|
|
|
Defines the signals available from a running worker thread. |
|
Supported signals are: |
|
finished |
|
No data |
|
error |
|
`tuple` (exctype, value, traceback.format_exc() ) |
|
result |
|
`object` data returned from processing, anything |
|
progress |
|
`int` indicating % progress""" |
|
|
|
finished = Signal() |
|
error = Signal(tuple) |
|
result = Signal(object) |
|
progress = Signal(int) |
|
|
|
class Worker(QRunnable): |
|
"""Worker obtained from https://www.learnpyqt.com/courses/concurrent-execution/multithreading-pyqt-applications-qthreadpool/ |
|
|
|
Worker thread |
|
Inherits from QRunnable to handler worker thread setup, signals and wrap-up. |
|
:param callback: The function callback to run on this worker thread. Supplied args and |
|
kwargs will be passed through to the runner. |
|
:type callback: function |
|
:param args: Arguments to pass to the callback function |
|
:param kwargs: Keywords to pass to the callback function""" |
|
|
|
def __init__(self, fn, *args, **kwargs): |
|
super(Worker, self).__init__() |
|
# Store constructor arguments (re-used for processing) |
|
self.fn = fn |
|
self.args = args |
|
self.kwargs = kwargs |
|
self.signals = WorkerSignals() |
|
# Add the callback to our kwargs |
|
self.kwargs['progress_callback'] = self.signals.progress |
|
|
|
@Slot() |
|
def run(self): |
|
"""Initialise the runner function with passed args, kwargs.""" |
|
# Retrieve args/kwargs here; and fire processing using them |
|
try: |
|
result = self.fn(*self.args, **self.kwargs) |
|
except: |
|
traceback.print_exc() |
|
exctype, value = sys.exc_info()[:2] |
|
self.signals.error.emit((exctype, value, traceback.format_exc())) |
|
else: |
|
self.signals.result.emit(result) # Return the result of the processing |
|
finally: |
|
self.signals.finished.emit() # Done |
|
|
|
|
|
class MainWindow(QMainWindow): |
|
"""MainWindow helped along with |
|
https://stackoverflow.com/questions/22697901/how-do-i-switch-layouts-in-a-window-using-pyqt-without-closing-opening-window""" |
|
def __init__(self, parent=None): |
|
super(MainWindow, self).__init__(parent) |
|
self.PageTitle = APPNAME |
|
self.setWindowTitle(self.PageTitle) |
|
self.setMinimumSize(600, 400) |
|
|
|
self.central_widget = QStackedWidget() |
|
self.setCentralWidget(self.central_widget) |
|
|
|
#login_widget = Page_Login(self) # gets the Page_Login Class, Has the layout, widgets, buttons, and button connects |
|
#self.central_widget.addWidget(login_widget) # Uncomment these lines to skip Disclaimer Page, and comment out disclaimer_widget lines |
|
|
|
disclaimer_widget = Page_Disclaimer(self) |
|
self.central_widget.addWidget(disclaimer_widget) # This makes Disclaimer Page page load first |
|
#self.central_widget.setCurrentWidget(disclaimer_widget) #my v3.8, not needed to show widget on top |
|
|
|
def goto_Page2(self): |
|
new_widget = Page_Two(self) |
|
self.central_widget.addWidget(new_widget) |
|
self.central_widget.setCurrentWidget(new_widget) #my v3.8 |
|
|
|
def goto_Page1(self): |
|
new_widget = Page_One(self) |
|
self.central_widget.addWidget(new_widget) |
|
self.central_widget.setCurrentWidget(new_widget) |
|
|
|
def goto_PageLogin(self): |
|
new_widget = Page_Login(self) |
|
self.central_widget.addWidget(new_widget) |
|
self.central_widget.setCurrentWidget(new_widget) |
|
|
|
def exitapp(self): |
|
sys.exit(0) |
|
|
|
|
|
class Page_Disclaimer(QWidget): #3.8 |
|
"""This class is to contain layout for a disclaimer page that user can agree with and |
|
continue to application or disagree which exits application""" |
|
def __init__(self, parent=None): |
|
super(Page_Disclaimer, self).__init__(parent) |
|
|
|
layout = QVBoxLayout() |
|
|
|
label1str = """Simple Disclaimer Page""" |
|
self.label1 = QLabel(label1str) |
|
layout.addWidget(self.label1) |
|
|
|
self.button1 = QPushButton('Agree') |
|
#self.button1.clicked.connect(self.parent().goto_PageLogin) # Un/Comment option to skip Login Page |
|
self.button1.clicked.connect(self.parent().goto_Page1) # Un/Comment option to skip Login Page |
|
layout.addWidget(self.button1) |
|
|
|
self.button2 = QPushButton('Disagree') |
|
self.button2.clicked.connect(self.parent().exitapp) |
|
layout.addWidget(self.button2) |
|
|
|
self.setLayout(layout) |
|
|
|
|
|
class Page_Login(QWidget): |
|
"""This class is to contain layout for a login page where user can agree with statement and |
|
continue to application or disagree which exits application""" |
|
def __init__(self, parent=None): |
|
super(Page_Login, self).__init__(parent) |
|
self.PageTitle = 'Login' |
|
WINDOW.setWindowTitle(APPNAME + ' - ' + self.PageTitle) |
|
|
|
layout = QHBoxLayout() |
|
|
|
label1str = """Login or Exit""" |
|
self.label1 = QLabel(label1str) |
|
layout.addWidget(self.label1) |
|
|
|
self.button1 = QPushButton('Login') |
|
self.button1.clicked.connect(self.parent().goto_Page1) |
|
layout.addWidget(self.button1) |
|
|
|
self.button2 = QPushButton('Exit') |
|
self.button2.clicked.connect(self.parent().exitapp) |
|
layout.addWidget(self.button2) |
|
|
|
self.setLayout(layout) |
|
|
|
|
|
class Page_One(QWidget): #QWidget, QMainWindow ? |
|
"""This class is to contain layout for the Main Application Window, will hold functions for auto/manual refresh |
|
table buttons, querying data from various APIs""" |
|
def __init__(self, parent=None): |
|
super(Page_One, self).__init__(parent) |
|
self.PageTitle = 'Page 1' |
|
WINDOW.setWindowTitle(APPNAME + ' - ' + self.PageTitle) |
|
|
|
layout = QVBoxLayout() |
|
|
|
self.MyQMenuBar = QMenuBar(self) # The QMenu used in a Widget seems to have less clickable area. |
|
file_menu1 = self.MyQMenuBar.addMenu("&File") # type: QMenu |
|
file_menu1.addAction("Log Out", self.parent().goto_PageLogin) |
|
file_menu1.addAction("Exit", APP.quit) |
|
file_menu2 = self.MyQMenuBar.addMenu("&About") # type: QMenu |
|
file_menu2.addAction("About " + APPNAME, self.about_application) |
|
|
|
#menubar = self.menuBar() |
|
#file_menu = menubar.addMenu('File') |
|
#file_menu.addAction(QAction("Open", self, triggered=self.open)) |
|
#self.viewer = Viewer() |
|
#self.setCentralWidget(self.viewer) |
|
|
|
label1str = """logged into Main Widget!""" |
|
self.label1 = QLabel(label1str) |
|
layout.addWidget(self.label1) |
|
|
|
self.button1 = QPushButton('Start meaningless process') |
|
self.button1.clicked.connect(self.Start_Execution) |
|
layout.addWidget(self.button1) |
|
|
|
self.button2 = QPushButton('To Page 2') |
|
self.button2.clicked.connect(self.parent().goto_Page2) |
|
layout.addWidget(self.button2) |
|
|
|
self.setLayout(layout) |
|
|
|
self.threadpool = QThreadPool() # create Thread pool |
|
print("Available multithreading with maximum %d threads" % self.threadpool.maxThreadCount()) |
|
|
|
def about_application(self): |
|
about_lines = [APPNAME, |
|
"Version: " + CVERSION, |
|
"Author: " + AUTHOR] |
|
for line in about_lines: |
|
about_msg = '\n'.join(line.strip() for line in about_lines) |
|
QMessageBox.question(self, 'About', about_msg, QMessageBox.Ok) |
|
|
|
def progress_fn(self, n): |
|
print("%d%% done" % n) |
|
|
|
def execute_this_fn(self, progress_callback): |
|
print("START THREAD!") |
|
for n in range(0, 5): |
|
time.sleep(1) |
|
progress_callback.emit(n*100/4) |
|
return "Done." |
|
|
|
def print_output(self, s): |
|
print(s) |
|
|
|
def thread_complete(self): |
|
print("THREAD COMPLETE!") |
|
|
|
def Start_Execution(self): |
|
"""Start_Execution and relevant functions obtained from |
|
https://www.learnpyqt.com/courses/concurrent-execution/multithreading-pyqt-applications-qthreadpool/""" |
|
# Pass the function to execute |
|
worker = Worker(self.execute_this_fn) # Any other args, kwargs are passed to the run function |
|
worker.signals.result.connect(self.print_output) |
|
worker.signals.finished.connect(self.thread_complete) |
|
worker.signals.progress.connect(self.progress_fn) |
|
# Execute |
|
self.threadpool.start(worker) |
|
|
|
|
|
class Page_Two(QWidget): |
|
"""This class is to contain layout for a second page that could serve as a template for additional pages""" |
|
def __init__(self, parent=None): |
|
super(Page_Two, self).__init__(parent) |
|
self.PageTitle = 'Page 2' |
|
WINDOW.setWindowTitle(APPNAME + ' - ' + self.PageTitle) |
|
|
|
layout = QVBoxLayout() |
|
|
|
self.button1 = QPushButton('To Page 1') |
|
self.button1.clicked.connect(self.parent().goto_Page1) |
|
layout.addWidget(self.button1) |
|
|
|
self.setLayout(layout) |
|
|
|
|
|
if __name__ == '__main__': |
|
APP = QApplication([]) |
|
APPNAME = 'My GUI App' |
|
CVERSION = 'v0.1' |
|
AUTHOR = '[email protected]' |
|
WINDOW = MainWindow() |
|
WINDOW.show() |
|
APP.exec_() |
More work with this pyside2 code base seems to show that it is creating another new widget within QStackedWidget when changing pages, according to QStackedWidget.count(), instead of switching back to an already created widget page. Not sure if this is an issue, but is something I'm trying to fix.