Skip to content

Instantly share code, notes, and snippets.

@SantosJMM
Forked from collinxsmith1/README.md
Created March 22, 2021 01:19
Show Gist options
  • Save SantosJMM/2485e1799632ae309ca887ea8640a96e to your computer and use it in GitHub Desktop.
Save SantosJMM/2485e1799632ae309ca887ea8640a96e to your computer and use it in GitHub Desktop.
Evaluation of PySide2 GUI Program code base

Python GUI Program using Pyside2

Main Objective:

  1. To use Pyside2 as a framework to build a Windows GUI Program.

  2. Build pages as class containers of pages simliar to how Sentdex has done this beautifully with Tkinter here https://www.youtube.com/watch?v=A0gaXfM1UN0&list=PLQVvvaa0QuDclKx-QpC9wntnURXVJqLyk&index=2

The first listed resource is a Stack Overflow answer from a while ago and mostly what my code is based on.

  1. Utilize multithreadability so no hang ups in page detail processing

I'm wondering if any newer method available in PySide2 simplifies this code base, or if there is any other method I could use to clean up my current code.

Resources used and followed are:

https://stackoverflow.com/questions/22697901/how-do-i-switch-layouts-in-a-window-using-pyqt-without-closing-opening-window

https://www.learnpyqt.com/courses/concurrent-execution/multithreading-pyqt-applications-qthreadpool/

Notes

Inspiration taken from Sentdex GUI with Tkinter playlist here https://www.youtube.com/playlist?list=PLQVvvaa0QuDclKx-QpC9wntnURXVJqLyk

My main focus with this code base if functionality. I can worry about making it pretty later.

Running with Windows and Python 3.7.2

Build/Activate new virtual environment

python -m venv winvenv

./winvenv/Scripts/activate.ps1

Install Requirements

python -m pip install -r requirements.txt

Run

python ./pyside2_share.py

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 = 'test@gmail.com'
WINDOW = MainWindow()
WINDOW.show()
APP.exec_()
PySide2==5.13.2
shiboken2==5.13.2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment