How to pass variables from one QWizardPage to main QWizard - python-3.x

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

Related

Show progressBar while function is running Pyside2

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)

Sending a signal from a button to a parent window

I'm making an app with PyQt5 that uses a FileDialog to get files from the user and saves the file names in a list. However I also want to be able to remove those entries after they are added, so I created a class that has a name(the file name) and a button. The idea is that when this button is clicked the widget disappears and the file entry is removed from the list. The disappearing part works fine but how to I get the widget to remove the entry form the list? How can i send a signal from one widget inside the window to the main app and tell it to remove the entry from the list?
I know the code is very bad, I'm still very new to PyQt and Python in general so any advice would be greatly appreciated.
from PyQt5 import QtWidgets as qw
import sys
class MainWindow(qw.QMainWindow):
def __init__(self):
super().__init__()
# List of opened files
self.files = []
# Main Window layout
self.layout = qw.QVBoxLayout()
self.file_display = qw.QStackedWidget()
self.file_button = qw.QPushButton('Add File')
self.file_button.clicked.connect(self.add_file)
self.layout.addWidget(self.file_display)
self.layout.addWidget(self.file_button)
self.setCentralWidget(qw.QWidget())
self.centralWidget().setLayout(self.layout)
# Open File Dialog and append file name to list
def add_file(self):
file_dialog = qw.QFileDialog()
self.files.append(file_dialog.getOpenFileName())
self.update_stack()
# Create new widget for StackedWidget remove the old one and display the new
def update_stack(self):
new_stack_item = qw.QWidget()
layout = qw.QVBoxLayout()
for file in self.files:
layout.addWidget(FileWidget(file[0]))
new_stack_item.setLayout(layout)
if len(self.file_display) > 0:
temp_widget = self.file_display.currentWidget()
self.file_display.removeWidget(temp_widget)
self.file_display.addWidget(new_stack_item)
class FileWidget(qw.QWidget):
def __init__(self, name):
# This widget is what is added when a new file is opened
# it has a file name and a close button
# my idea is that when the close button is pressed the widget is removed
# from the window and from the files[] list in the main class
super().__init__()
self.layout = qw.QHBoxLayout()
self.file_name = qw.QLabel(name)
self.close_button = qw.QPushButton()
self.close_button.clicked.connect(self.remove)
self.layout.addWidget(self.file_name)
self.layout.addWidget(self.close_button)
self.setLayout(self.layout)
def remove(self):
self.close()
if __name__ == '__main__':
app = qw.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
You need to remove from the list in the remove method of theFileWidget class.
import sys
from PyQt5 import QtWidgets as qw
class FileWidget(qw.QWidget):
def __init__(self, name, files): # + files
super().__init__()
self.files = files # +
self.name = name # +
self.layout = qw.QHBoxLayout()
self.file_name = qw.QLabel(name)
self.close_button = qw.QPushButton("close {}".format(name))
self.close_button.clicked.connect(self.remove)
self.layout.addWidget(self.file_name)
self.layout.addWidget(self.close_button)
self.setLayout(self.layout)
def remove(self):
self.files.pop(self.files.index(self.name)) # <<<-----<
self.close()
class MainWindow(qw.QMainWindow):
def __init__(self):
super().__init__()
# List of opened files
self.files = []
# Main Window layout
self.layout = qw.QVBoxLayout()
self.file_display = qw.QStackedWidget()
self.file_button = qw.QPushButton('Add File')
self.file_button.clicked.connect(self.add_file)
self.layout.addWidget(self.file_display)
self.layout.addWidget(self.file_button)
self.setCentralWidget(qw.QWidget())
self.centralWidget().setLayout(self.layout)
# Open File Dialog and append file name to list
def add_file(self):
file_name, _ = qw.QFileDialog().getOpenFileName(self, 'Open File') # +
if file_name: # +
self.files.append(file_name) # +
self.update_stack()
# Create new widget for StackedWidget remove the old one and display the new
def update_stack(self):
new_stack_item = qw.QWidget()
layout = qw.QVBoxLayout()
for file in self.files:
layout.addWidget(FileWidget(file, self.files)) # + self.files
new_stack_item.setLayout(layout)
if len(self.file_display) > 0:
temp_widget = self.file_display.currentWidget()
self.file_display.removeWidget(temp_widget)
self.file_display.addWidget(new_stack_item)
if __name__ == '__main__':
app = qw.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())

Open another wx.python frame cause the main UI to be blocked

I am trying to open another wx frame from a main frame UI. I followed the example shown here: https://wiki.wxpython.org/CallAfter but my main UI is still blocked.
Here is the event listener on the main UI:
def testShowGUI(self):
# This process is a long one
# It uses the vtk to read point cloud file and reconstruct the surface
file = "cache/d9c5e8ef-7b7f-485e-8fc8-23098c32afcb.ply"
reader = vtk.vtkPLYReader()
reader.SetFileName(file)
reader.Update()
delaunay = vtk.vtkDelaunay2D()
delaunay.SetAlpha(0.1)
delaunay.SetTolerance(0.0001)
delaunay.SetOffset(1.25)
delaunay.BoundingTriangulationOff()
delaunay.SetInputData(reader.GetOutput())
delaunay.Update()
#Once finish reading and processing the point cloud, pass to the next function for rendering
wx.CallAfter(self.AfterProcess, delaunay)
def AfterProcess(self, data):
meshVisGui = MesVisGUI.MeshVisGui(data)
meshVisGui.Show()
def OnEnter(self, event):
#Event listener when user click on Enter button
my_thread = threading.Thread(target=self.testShowGUI)
my_thread.start()
The code for the separate frame is as below:
class MeshVisGui(wx.Frame):
SPACING = 4
def __init__(self, delaunay, parent=None):
self.delaunayData = delaunay
self.title = "Mesh Visualization"
wx.Frame.__init__(self, None, -1, self.title)
self.Initialize()
def Initialize(self):
self.panel = wx.Panel(self, -1, size=(600, 400), style=wx.BORDER_RAISED)
self.widget3d = wxVTKRenderWindowInteractor(self.panel, -1)
self.widget3d.Enable()
self.render = vtk.vtkRenderer()
self.render.SetBackground(params.BackgroundColor)
self.widget3d.GetRenderWindow().AddRenderer(self.render)
self.interactor = self.widget3d.GetRenderWindow().GetInteractor()
self.interactor.GetInteractorStyle().SetCurrentStyleToTrackballCamera()
self.axesWidget = utils.createAxes(self.interactor)
self.meshActor = utils.build_actor(self.delaunayData)
self.render.AddActor(self.meshActor)
self.render.ResetCamera()
box = wx.BoxSizer(wx.VERTICAL)
box.Add(self.widget3d, 1, wx.EXPAND, self.SPACING)
self.panel.SetSizer(box)
self.Layout()
However, on my main UI, it is still showing a spinning icon and block the UI while it is trying to process the point cloud data. Can someone help me spot what I have done wrong?
Wxpython version: 4.0.1
Python version: 3.6.5
Following on from Mike Driscoll's answer and comment, here is a sample of a threaded task running from another panel.
The thread reports back to the 2nd panel (it's parent) using an event. This allows a progress bar to be updated.
The 2nd panel includes a "Cancel" option for the threaded task, whilst the main frame has a button to test if it is not frozen.
The use of wx.GetApp().Yield() may be slightly old fashioned but I have always found it to be reliable.
import time
import wx
from threading import Thread
import wx.lib.newevent
progress_event, EVT_PROGRESS_EVENT = wx.lib.newevent.NewEvent()
class ThreadFrame(wx.Frame):
def __init__(self, title, parent=None):
wx.Frame.__init__(self, parent=parent, title=title)
panel = wx.Panel(self)
self.btn = wx.Button(panel,label='Stop Long running process', size=(200,30), pos=(10,10))
self.btn.Bind(wx.EVT_BUTTON, self.OnExit)
self.progress = wx.Gauge(panel,size=(300,10), pos=(10,50), range=300)
#Bind to the progress event issued by the thread
self.Bind(EVT_PROGRESS_EVENT, self.OnProgress)
#Bind to Exit on frame close
self.Bind(wx.EVT_CLOSE, self.OnExit)
self.Show()
self.mythread = TestThread(self)
#Enable the GUI to be responsive by briefly returning control to the main App
while self.mythread.isAlive():
time.sleep(0.1)
wx.GetApp().Yield()
continue
try:
self.OnExit(None)
except:
pass
def OnProgress(self, event):
self.progress.SetValue(event.count)
#or for indeterminate progress
#self.progress.Pulse()
def OnExit(self, event):
if self.mythread.isAlive():
self.mythread.terminate() # Shutdown the thread
self.mythread.join() # Wait for it to finish
self.Destroy()
class TestThread(Thread):
def __init__(self,parent_target):
Thread.__init__(self)
self.parent = parent_target
self.stopthread = False
self.time = time.time()
self.start() # start the thread
def run(self):
# A loop that will run for 5 minutes then terminate
while self.stopthread == False:
curr_loop = int(time.time() - self.time)
if curr_loop < 300:
time.sleep(1)
evt = progress_event(count=curr_loop)
#Send back current count for the progress bar
try:
wx.PostEvent(self.parent, evt)
except: # The parent frame has probably been destroyed
self.terminate()
else:
self.terminate()
def terminate(self):
self.stopthread = True
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.text_count = 0
self.parent=parent
btn = wx.Button(self, wx.ID_ANY, label='Start Long running process', size=(200,30), pos=(10,10))
btn.Bind(wx.EVT_BUTTON, self.Thread_Frame)
btn2 = wx.Button(self, wx.ID_ANY, label='Is the GUI still active?', size=(200,30), pos=(10,50))
btn2.Bind(wx.EVT_BUTTON, self.AddText)
self.txt = wx.TextCtrl(self, wx.ID_ANY, style= wx.TE_MULTILINE, pos=(10,90),size=(400,100))
def Thread_Frame(self, event):
frame = ThreadFrame(title='Threaded Task', parent=self.parent)
def AddText(self,event):
self.text_count += 1
txt = self.txt.GetValue()
txt += "More text " + str(self.text_count)+"\n"
self.txt.SetValue(txt)
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='Main Frame', size=(600,400))
panel = MyPanel(self)
self.Show()
if __name__ == '__main__':
app = wx.App(False)
frame = MainFrame()
app.MainLoop()
You do not need threads to open new frames / windows in your wxPython application. You just need to create a sub-class of wx.Frame to hold the code of your other frame. Then from your main application's frame, you can instantiate the other frame and show it. You use the same concept when you create a wx.Dialog or a wx.MessageDialog.
Here is a simple example:
import wx
class OtherFrame(wx.Frame):
"""
Class used for creating frames other than the main one
"""
def __init__(self, title, parent=None):
wx.Frame.__init__(self, parent=parent, title=title)
self.Show()
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
btn = wx.Button(self, label='Create New Frame')
btn.Bind(wx.EVT_BUTTON, self.on_new_frame)
def on_new_frame(self, event):
frame = OtherFrame(title='SubFrame',
parent=wx.GetTopLevelParent(self))
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='Main Frame')
panel = MyPanel(self)
self.Show()
if __name__ == '__main__':
app = wx.App(False)
frame = MainFrame()
app.MainLoop()
In this example, I set the other frame's parent to the MainFrame instance by using wx.GetTopLevelParent(self). The benefit of setting a parent for the sub-frames is that if I close the main frame, it will cause the other frames to also be closed.

Using dynamically added widgets in PyQt/Pyside

I have modified the answer given here as written below. The code is basically creating pushbuttons with a counter as pushButton_0, pushButton_1..
Here, I know that when I press to self.addButton I am creating widgets named like self.pushButton_0, self.pushButton_1 etc. So, my question is, how I'm supposed to use this pushbuttons? Because when I'm trying to do something like self.pushButton_0.clicked.connect(self.x), it' s telling me that "there is no attribute named 'pushButton_0'".
Thanks!
from PyQt4 import QtGui, QtCore
import sys
class Main(QtGui.QMainWindow):
def __init__(self, parent = None):
super(Main, self).__init__()
self.GUI()
def GUI(self):
self.count = 0
# main button
self.addButton = QtGui.QPushButton('button to add other widgets')
self.addButton.clicked.connect(self.addWidget)
# scroll area widget contents - layout
self.scrollLayout = QtGui.QFormLayout()
# scroll area widget contents
self.scrollWidget = QtGui.QWidget()
self.scrollWidget.setLayout(self.scrollLayout)
# scroll area
self.scrollArea = QtGui.QScrollArea()
self.scrollArea.setWidgetResizable(True)
self.scrollArea.setWidget(self.scrollWidget)
# main layout
self.mainLayout = QtGui.QVBoxLayout()
# add all main to the main vLayout
self.mainLayout.addWidget(self.addButton)
self.mainLayout.addWidget(self.scrollArea)
# central widget
self.centralWidget = QtGui.QWidget()
self.centralWidget.setLayout(self.mainLayout)
# set central widget
self.setCentralWidget(self.centralWidget)
def addWidget(self):
self.scrollLayout.addRow(Test(self))
self.count = self.count + 1
print(self.count)
class Test(QtGui.QWidget):
def __init__( self, main):
super(Test, self).__init__()
self.Main = main
self.setup()
def setup(self):
print(self.Main.count)
name = "pushButton_"+str(self.Main.count)
print(name)
self.name = QtGui.QPushButton('I am in Test widget '+str(self.Main.count))
layout = QtGui.QHBoxLayout()
layout.addWidget(self.name)
self.setLayout(layout)
app = QtGui.QApplication(sys.argv)
myWidget = Main()
myWidget.show()
app.exec_()
After hours, I found the problem!
You have to declare the signal while creating the pushbutton!
To fix this, I rewrote the setup function as below;
def setup(self):
print(self.Main.count)
name = "pushButton_"+str(self.Main.count)
print(name)
self.name = QtGui.QPushButton('I am in Test widget '+str(self.Main.count))
self.name.clicked.connect(self.x) # self.x is any function
layout = QtGui.QHBoxLayout()
layout.addWidget(self.name)
self.setLayout(layout)
So know, you will run function x whenever you push the new created pushbuttons!

QThread workaround in a Basic GUI

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

Resources