I'm working on Pyside2, python 3.8, windows 10
I have an app that parses a file and show data in QtableView. What I'm trying to implement is a Dialog Window with only one button, the only purpose of this dialog window is to give a minimalistic and simple view to the user, where he can first select the file to be parsed and have a Loading progress barwhile the LoadData() function is runned. The Home Dialog should only be hidden/closed when the parsing is done.
Here's what I've tried so far:
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, file_name,parent=None):
"""
..
__init__ code lines
"""
self.change_val = QtCore.Signal(int)
self.change_val[int].connect(self.set_progress_val)
self.progress = QtWidgets.QProgressDialog('loading...', 'cancel', 0, 100, self)
self.progress.show()
self.LoadData(d.path)
#QtCore.Slot(int)
def set_progress_val(self, val):
self.progress.setValue(val)
def LoadData(self, file_path):
"""
Parsing lines of code
..
self.change_val.emit(30)
..
..
self.change_val.emit(60)
..
..
"""
self.progress.hide()
#Parsing finished -> show the mainWindow
self.show()
class HomeDialog(QtWidgets.QDialog, home_dialog.Ui_Dialog):
def __init__(self, parent=None):
super(HomeDialog, self).__init__(parent)
self.setupUi(self)
self.openB6.clicked.connect(self.get_file_name)
def get_file_name(self):
file_name = QtWidgets.QFileDialog.getOpenFileName(self, 'Open config file',
dir=path.join("/"),
filter="B6 (*.b6)")
if not file_name[0]:
return None
else:
self.path = file_name
self.accept()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
app.setStyle(ProxyStyle())
d = HomeDialog()
if d.exec_():
mainWin = MainWindow(file_name=d.path)
mainWin.show()
sys.exit(app.exec_())
I'm getting the follwoing error on self.change_val[int].connect(self.set_progress_val) line :
'str' object has no attribute 'connect'
The signals are not declared in the class constructor or in the methods but in the static part:
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
change_val = QtCore.Signal(int)
def __init__(self, file_name,parent=None):
"""
..
__init__ code lines
"""
self.change_val[int].connect(self.set_progress_val)
self.progress = QtWidgets.QProgressDialog('loading...', 'cancel', 0, 100, self)
self.progress.show()
self.LoadData(d.path)
Related
when I left-click my icon in the system tray it does not exit the application - I can see it does go through the function though when I set a breakpoint, if however I click Exit from the popup menu then the program exits as expected (runs the same function), why is this please?
It is the on-exit function that should close the application:
self.Bind(wx.adv.EVT_TASKBAR_LEFT_DOWN, self.on_exit)
Thanks very much for any help you can provide.
import wx
import wx.adv
TRAY_TOOLTIP = 'ShutMeDown'
TRAY_ICON = icon.png
def create_menu_item(menu, label, func):
item = wx.MenuItem(menu, -1, label)
menu.Bind(wx.EVT_MENU, func, id=item.GetId())
menu.Append(item)
return item
class TaskBarIcon(wx.adv.TaskBarIcon):
def __init__(self, frame):
self.frame = frame
super(TaskBarIcon, self).__init__()
self.SetIcon(wx.IconFromBitmap(wx.Bitmap(TRAY_ICON)), TRAY_TOOLTIP)
self.Bind(wx.adv.EVT_TASKBAR_LEFT_DOWN, self.on_exit)
def CreatePopupMenu(self):
menu = wx.Menu()
create_menu_item(menu, 'Exit', self.on_exit)
return menu
def on_exit(self, event):
wx.CallAfter(self.Destroy)
self.frame.Close()
class App(wx.App):
def OnInit(self):
frame = wx.Frame(None)
self.SetTopWindow(frame)
TaskBarIcon(frame)
return True
def main():
app = App(False)
app.MainLoop()
if __name__ == '__main__':
main()
I am trying to figure out how to pass variables from (e.g.: an openFile function) inside a QWizardPage class to the main QWizard class. I have also read about signals and slots but can't understand how and if this is the ideal way to do it when using PyQt5.
Here follows a simplified example of the code:
class ImportWizard(QtWidgets.QWizard):
def __init__(self, parent=None):
super(ImportWizard, self).__init__(parent)
self.addPage(Page1(self))
self.setWindowTitle("Import Wizard")
# Trigger close event when pressing Finish button to redirect variables to backend
self.finished.connect(self.closeEvent)
def closeEvent(self):
print("Finish")
# Return variables to use in main
print(self.variable)
class Page1(QtWidgets.QWizardPage):
def __init__(self, parent=None):
super(Page1, self).__init__(parent)
self.openFileBtn = QPushButton("Import Edge List")
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.comboBox)
layout.addWidget(self.openFileBtn)
self.setLayout(layout)
self.openFileBtn.clicked.connect(self.openFileNameDialog)
def openFileNameDialog(self, parent):
options = QFileDialog.Options()
options |= QFileDialog.DontUseNativeDialog
fileName, _ = QFileDialog.getOpenFileName(
self, "QFileDialog.getOpenFileName()", "",
"All Files (*);;Python Files (*.py)", options=options)
# if user selected a file store its path to a variable
if fileName:
self.parent.variable = fileName
if you want to access QWizard from QWizardPage you must use the wizard() method, on the other hand closeEvent() is an event that is triggered when the window is closed that should not be invoked by an additional signal that is not necessary, the correct thing is to create a slot that connects to the finished signal.
from PyQt5 import QtCore, QtWidgets
class ImportWizard(QtWidgets.QWizard):
def __init__(self, parent=None):
super(ImportWizard, self).__init__(parent)
self.addPage(Page1(self))
self.setWindowTitle("Import Wizard")
# Trigger close event when pressing Finish button to redirect variables to backend
self.finished.connect(self.onFinished)
#QtCore.pyqtSlot()
def onFinished(self):
print("Finish")
# Return variables to use in main
print(self.variable)
class Page1(QtWidgets.QWizardPage):
def __init__(self, parent=None):
super(Page1, self).__init__(parent)
self.openFileBtn = QtWidgets.QPushButton("Import Edge List")
self.comboBox = QtWidgets.QComboBox()
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.comboBox)
layout.addWidget(self.openFileBtn)
self.setLayout(layout)
self.openFileBtn.clicked.connect(self.openFileNameDialog)
#QtCore.pyqtSlot()
def openFileNameDialog(self):
options = QtWidgets.QFileDialog.Options()
options |= QtWidgets.QFileDialog.DontUseNativeDialog
fileName, _ = QtWidgets.QFileDialog.getOpenFileName(
self, "QFileDialog.getOpenFileName()", "",
"All Files (*);;Python Files (*.py)", options=options)
# if user selected a file store its path to a variable
if fileName:
self.wizard().variable = fileName
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = ImportWizard()
w.show()
sys.exit(app.exec_())
I am trying to build a Python GUI using PyQt5 which incorporates multi-threading. Using various snippets of code (acquired mainly from Stack Overflow), I have come up with the code below which reads data from a csv file into a pandas dataframe and updates the GUI (triggered from a QTimer).
I would like to implement a 2nd thread which reads from a different csv file and a function which accepts both dataframes and does some computation on them before updating the GUI. I'm not sure how I can do this within the signals/slots framework of PyQt5. Any help is much appreciated!
import time
import traceback
import sys
import pandas as pd
import numpy as np
from PyQt5.QtWidgets import (QWidget, QTabWidget, QGridLayout, QApplication, QMainWindow, QStatusBar, QTableView)
from PyQt5.QtCore import (Qt, QTimer, QAbstractTableModel, QThread, QVariant, QObject, QRect, pyqtSlot, pyqtSignal)
class PandasModel(QAbstractTableModel):
"""
Class to populate a table view with a pandas dataframe
"""
def __init__(self, data, parent=None):
QAbstractTableModel.__init__(self, parent)
self._data = np.array(data.values)
self._cols = data.columns
self.r, self.c = np.shape(self._data)
def rowCount(self, parent=None):
return self.r
def columnCount(self, parent=None):
return self.c
def data(self, index, role=Qt.DisplayRole):
if index.isValid():
if role == Qt.DisplayRole:
return self._data[index.row(), index.column()]
return None
def headerData(self, p_int, orientation, role):
if role == Qt.DisplayRole:
try:
if orientation == Qt.Horizontal:
return self._cols[p_int]
elif orientation == Qt.Vertical:
return p_int
except(IndexError, ):
return QVariant()
else:
return QVariant()
class WorkerSignals(QObject):
"""
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 = pyqtSignal()
error = pyqtSignal(tuple)
result = pyqtSignal(object)
progress = pyqtSignal(int)
class Worker(QObject):
"""
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
# kwargs['progress_callback'] = self.signals.progress
#pyqtSlot()
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 MyTabWidget(QWidget):
def __init__(self, parent):
super(QWidget, self).__init__(parent)
self.layout = QGridLayout(self)
# Initialize tab screen
self.tabs = QTabWidget()
self.tab1 = QWidget()
self.tabs.resize(300, 200)
# Add tabs
self.tabs.addTab(self.tab1, "Tab 1")
# Create tabs
self.tab1.layout = QGridLayout(self)
self.tab1.setLayout(self.tab1.layout)
# Create table widgets
self.table_widget1 = MyTableWidget(self)
# Add tables to tabs
self.tab1.layout.addWidget(self.table_widget1)
# Add tabs to widget
self.layout.addWidget(self.tabs)
self.setLayout(self.layout)
class MyTableWidget(QWidget):
def __init__(self, parent):
super(QWidget, self).__init__(parent)
self.layout = QGridLayout(self)
# Initialize tables
self.table = QTableView()
# Add tabs to widget
self.layout.addWidget(self.table)
self.setLayout(self.layout)
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
# Create a central Widgets
self.central_widget = QWidget()
# Create tab Widgets
self.tab_widget = MyTabWidget(self)
# Create a Layout for the central Widget
self.central_layout = QGridLayout()
# Set the Layout
self.central_widget.setLayout(self.central_layout)
# Set the Widget
self.setCentralWidget(self.central_widget)
self.central_layout.addWidget(self.tab_widget)
self.setGeometry(QRect(0, 100, 2000, 1500))
self.setWindowTitle('CSV viewer')
self.status_bar = QStatusBar()
self.setStatusBar(self.status_bar)
self.proxy = None
self.model = None
self.show()
# Setup worker thread
self.worker_thread = QThread(self)
self.worker = Worker(self.read_csv)
self.worker.moveToThread(self.worker_thread)
self.worker_thread.started.connect(self.worker.run)
# Connect worker signals to appropriate ports
self.worker.signals.result.connect(self.process_csv)
self.worker.signals.finished.connect(self.worker_thread.quit)
self.worker.signals.finished.connect(self.print_refresh_time)
# Setup timer to repetitively trigger start of worker thread e.g. every 5000ms
self.timer = QTimer()
self.timer.timeout.connect(self.worker_thread.start)
self.timer.start(5000)
def process_csv(self, df):
self.model = PandasModel(df)
self.tab_widget.table_widget1.table.setModel(self.model)
self.tab_widget.table_widget1.table.resizeColumnsToContents()
def print_refresh_time(self):
self.status_bar.showMessage('Last updated: ' + time.strftime("%d/%m/%Y %H:%M:%S", time.localtime()))
#staticmethod
def read_csv():
df = pd.read_csv("C:\\Users\\Brian\\Desktop\\E0.csv")
return df
def main():
app = QApplication(sys.argv)
window = MainWindow()
app.exec_()
if __name__ == '__main__':
main()
I'm trying to create simple frame that will run longrun procedure, but will show counting in different window (dialog), but it don't work (staticText1 is not updating)... Here's my code:
Main frame (simple frame with one button)
import wx
import time
from threading import Thread
import Dialog1
def create(parent):
return Frame1(parent)
[wxID_FRAME1, wxID_FRAME1BUTTON1, ] = [wx.NewId() for _init_ctrls in range(2)]
class Frame1(wx.Frame):
def test1(self):
for i in range(self.delays):
time.sleep(1)
def _init_ctrls(self, prnt):
# generated method, don't edit
wx.Frame.__init__(self, id=wxID_FRAME1, name='', parent=prnt,
pos=wx.Point(589, 259), size=wx.Size(178, 128),
style=wx.DEFAULT_FRAME_STYLE, title='Frame1')
self.button1 = wx.Button(id=wxID_FRAME1BUTTON1, label='button1',
name='button1', parent=self, pos=wx.Point(0, 0), size=wx.Size(178,
128), style=0)
self.button1.Bind(wx.EVT_BUTTON, self.OnButton1Button,
id=wxID_FRAME1BUTTON1)
def __init__(self, parent):
self._init_ctrls(parent)
self.delays = 5
self.timer = wx.Timer(self)
def OnButton1Button(self, event):
self.testThread = Thread(target=self.test1)
self.testThread.start()
#self.Show(False)
self.dlg = Dialog1.Dialog1(self)
self.dlg.ShowModal()
self.button1.Disable()
self.Bind(wx.EVT_TIMER, self.PollThread)
self.timer.Start(20, oneShot=True)
event.Skip()
def PollThread(self, event):
if self.testThread.isAlive():
self.Bind(wx.EVT_TIMER, self.PollThread)
self.timer.Start(200, oneShot=True)
self.dlg.staticText1.SetLabel(self.dlg.staticText1.GetLabel()+".")
else:
self.button1.Enable()
self.dlg.Destroy()
Dialog (simple dialog with one statictext):
import wx
def create(parent):
return Dialog1(parent)
[wxID_DIALOG1, wxID_DIALOG1STATICTEXT1, ] = [wx.NewId() for _init_ctrls in range(2)]
class Dialog1(wx.Dialog):
def _init_ctrls(self, prnt):
# generated method, don't edit
wx.Dialog.__init__(self, id=wxID_DIALOG1, name='', parent=prnt,
pos=wx.Point(451, 295), size=wx.Size(544, 59),
style=wx.DEFAULT_DIALOG_STYLE, title='Dialog1')
self.staticText1 = wx.StaticText(id=wxID_DIALOG1STATICTEXT1,
label=u'Please wait', name='staticText1', parent=self,
pos=wx.Point(56, 24), size=wx.Size(95, 17), style=0)
def __init__(self, parent):
self._init_ctrls(parent)
Here is the sample code
import sys, time
from PyQt4 import QtCore, QtGui
class MyApp(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setGeometry(300, 300, 280, 600)
self.setWindowTitle('threads')
self.layout = QtGui.QVBoxLayout(self)
self.testButton = QtGui.QPushButton("test")
self.connect(self.testButton, QtCore.SIGNAL("released()"), self.test)
self.listwidget = QtGui.QListWidget(self)
self.layout.addWidget(self.testButton)
self.layout.addWidget(self.listwidget)
def add(self, text):
print "Add: ", text
self.listwidget.addItems(text)
self.listwidget.sortItems()
# def addBatch(self, text="test", iters=6, delay=0.3):
# for i in range(iters):
# time.sleep(delay)
# self.add(text + " " + str(i))
def test(self):
self.listwidget.clear()
#self.addBatch("_non_thread", iters=6, delay=0.3)
self.workThread = WorkThread()
self.connect(self.workThread, QtCore.SIGNAL("update(QString"), self.add)
self.workThread.start()
class WorkThread(QtCore.QThread):
def __init__(self):
QtCore.QThread.__init__(self)
def __del__(self):
self.wait()
def run(self):
for i in range(6):
time.sleep(0.3)
self.emit(QtCore.SIGNAL('update(QString'), "from work thread " + str(i))
return
app = QtGui.QApplication(sys.argv)
test = MyApp()
test.show()
app.exec_()
Here I have a basic GUI with a listwidget and a push button. When I press the push button, I program should wait a moment and display a string in that listwidget. The WorkThread class does the waiting stuff and after waiting it emits a signal. But when I run the program, only I can see the GUI and nothing is displayed in the listwidget.
Can somebody tell me what is the reason behind this and how to fix this ?
QListWidget.addItems expects a list of items but you're giving it a single QString. You should use .addItem.
There are also a few minor corrections. You don't need to implement __del__ in your thread. You can skip __init__ if you're not doing additional stuff. And you should use new style signals and connections.
Here is the result with all corrections:
import sys, time
from PyQt4 import QtCore, QtGui
class MyApp(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setGeometry(300, 300, 280, 600)
self.setWindowTitle('threads')
self.layout = QtGui.QVBoxLayout(self)
self.testButton = QtGui.QPushButton("test")
self.testButton.clicked.connect(self.test)
self.listwidget = QtGui.QListWidget(self)
self.layout.addWidget(self.testButton)
self.layout.addWidget(self.listwidget)
def add(self, text):
print "Add: ", type(text)
self.listwidget.addItem(text)
self.listwidget.sortItems()
# def addBatch(self, text="test", iters=6, delay=0.3):
# for i in range(iters):
# time.sleep(delay)
# self.add(text + " " + str(i))
def test(self):
self.listwidget.clear()
#self.addBatch("_non_thread", iters=6, delay=0.3)
self.workThread = WorkThread()
self.workThread.update.connect(self.add)
self.workThread.start()
class WorkThread(QtCore.QThread):
update = QtCore.pyqtSignal(str)
def run(self):
for i in range(6):
time.sleep(0.3)
self.update.emit("from work thread " + str(i))
app = QtGui.QApplication(sys.argv)
test = MyApp()
test.show()
sys.exit(app.exec_())
The PyQtWiki contains a pretty elaborate example how to send signals from a background thread to the UI. Apparently, no special magic is necessary as long as you QThread to implement your thread.
But I noticed that you use the signal released() to connect the button with the method test(). Did you try clicked()?