|
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_() |