'm trying to figure out how to use my own custom signals in combination with QStateMachine. I started with a simple example from here. Now I'm trying to create a new signal mysignal and trigger a transition off of it. But I can't figure out how to structure the call to addtransition, or how to use the SIGNAL("clicked()") syntax to refer to mysignal.
from PyQt4.QtGui import *
from PyQt4.QtCore import *
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
button = QPushButton()
machine = QStateMachine()
off = QState()
off.assignProperty(button, 'text', 'Off')
off.setObjectName('off')
on = QState()
on.setObjectName('on')
on.assignProperty(button, 'text', 'On')
mysignal = pyqtSignal()
off.addTransition(mysignal, on)
# Let's use the new style signals just for the kicks.
on.addTransition(button.clicked, off)
machine.addState(off)
machine.addState(on)
machine.setInitialState(off)
machine.start()
mysignal.emit()
button.resize(100, 50)
button.show()
sys.exit(app.exec_())
A custom signal must be defined as a class attribute (see New-style Signal and Slot Support in the PyQt docs). So the code in your example needs to be refactored so that all the setup happens in the __init__ of a widget subclass.
Below is a demo that does that (in order to trigger the custom signal and its state transition, you must type "foo" in the line-edit whenever the button text shows "Foo"):
from PyQt4 import QtCore, QtGui
class Window(QtGui.QWidget):
customSignal = QtCore.pyqtSignal()
def __init__(self):
QtGui.QWidget.__init__(self)
self.edit = QtGui.QLineEdit(self)
self.edit.textChanged.connect(self.handleTextChanged)
self.button = QtGui.QPushButton(self)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.edit)
layout.addWidget(self.button)
self.machine = QtCore.QStateMachine()
self.off = QtCore.QState()
self.off.assignProperty(self.button, 'text', 'Off')
self.on = QtCore.QState()
self.on.assignProperty(self.button, 'text', 'On')
self.foo = QtCore.QState()
self.foo.assignProperty(self.button, 'text', 'Foo')
self.off.addTransition(self.button.clicked, self.on)
self.on.addTransition(self.button.clicked, self.foo)
self.foo.addTransition(self.customSignal, self.off)
self.machine.addState(self.off)
self.machine.addState(self.on)
self.machine.addState(self.foo)
self.machine.setInitialState(self.off)
self.machine.start()
def handleTextChanged(self, text):
if text == 'foo':
self.edit.clear()
self.customSignal.emit()
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
window.setGeometry(500, 300, 100, 100)
sys.exit(app.exec_())
Related
This question already has answers here:
Argument 1 has unexpected type 'NoneType'?
(2 answers)
Closed 1 year ago.
I am trying to use function when I clicked the button with below source codes:
from PySide2.QtWidgets import QApplication
from ui_interface import *
from Custom_Widgets.Widgets import *
from PyQt5.QtGui import QIcon
# from PyQt5.QtWidgets import *
from Custom_Widgets.Widgets import QCustomSlideMenu
import sys
class MainWindow(QMainWindow):
def __init__(self,parent = None):
QMainWindow.__init__(self)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
########################################################################
# APPLY JSON STYLESHEET
########################################################################
# self = QMainWindow class
# self.ui = Ui_MainWindow / user interface class
loadJsonStyle(self, self.ui)
########################################################################
self.show()
self.ui.pushButton_2.clicked.connect(self.my_text())
#pyqtSlot()
def on_button1(self):
print("Button #1")
def my_text(self):
index = 1
print("{0} button clicked".format(index))
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
# app.setWindowIcon(QIcon(':/icons/home.ico'))
window.show()
sys.exit(app.exec_())
When I using like this:
self.ui.pushButton_2.clicked.connect(self.my_text())
When I clicked the button, does not show anything.
But if I use like this:
self.ui.pushButton_2.clicked.connect(lambda: self.my_text())
It works.
And Also when I use like this:
self.ui.pushButton_2.clicked.connect(self.on_button1())
it works.
But I dont understand why the first step does not working?
try this
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
class Window(QMainWindow):
def __init__(self):
super().__init__()
# setting title
self.setWindowTitle("Python ")
# setting geometry
self.setGeometry(100, 100, 600, 400)
# calling method
self.UiComponents()
# showing all the widgets
self.show()
# method for widgets
def UiComponents(self):
# creating a push button
button = QPushButton("CLICK", self)
# setting geometry of button
button.setGeometry(200, 150, 100, 30)
# adding action to a button
button.clicked.connect(self.clickme)
# action method
def clickme(self):
print("pressed")
# create pyqt5 app
App = QApplication(sys.argv)
# create the instance of our Window
window = Window()
# start the app
sys.exit(App.exec())
is you're looking for this???
If I click Back from the second window, the program will just exit. How do I go back to mainwindow in this case? I assume I will need some more code in that clickMethodBack function.
import os
import PyQt5
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QMainWindow, QWidget, QLabel, QPushButton
import time
from PyQt5.QtCore import QSize
class GUI_Window():
def __init__(self):
self.main_window()
return
def main_window(self):
app = PyQt5.QtWidgets.QApplication(sys.argv)
self.MainWindow = MainWindow_()
self.MainWindow.show()
app.exec_()
return
class MainWindow_(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.TestAButton = QPushButton("TestA", self)
self.TestAButton.clicked.connect(self.TestA_clickMethod)
self.TestAButton.move(20, 0)
self.CloseButton = QPushButton("Close", self)
self.CloseButton.clicked.connect(self.Close_clickMethod)
self.CloseButton.move(20, 40)
self.TestingA = TestA_MainWindow()
def TestA_clickMethod(self):
self.TestAButton.setEnabled(False)
time.sleep(0.2)
self.TestingA.show()
self.hide()
try:
if self.TestingA.back == True:
self.show()
except:
None
def Close_clickMethod(self):
self.Test_Choice = 'Exit'
self.close()
class TestA_MainWindow(QWidget):
def __init__(self):
super().__init__()
self.setMinimumSize(QSize(980,700))
self.setWindowTitle("TestA")
self.Back_Button = False
self.closeButton = QPushButton("Close", self)
self.closeButton.clicked.connect(self.clickMethodClose)
self.returnButton = QPushButton("Back", self)
self.returnButton.clicked.connect(self.clickMethodBack)
self.returnButton.move(0,30)
def clickMethodClose(self):
self.Back_Button = False
self.close()
def clickMethodBack(self):
self.returnButton.setEnabled(False)
time.sleep(0.5)
self.back = True
self.close()
# Run if Script
if __name__ == "__main__":
main = GUI_Window() # Initialize GUI
Your code has two very important issues.
you're using a blocking function, time.sleep; Qt, as almost any UI toolkit, is event driven, which means that it has to be able to constantly receive and handle events (coming from the system or after user interaction): when something blocks the event queue, it completely freezes the whole program until that block releases control;
you're checking for the variable too soon: even assuming the sleep would work, you cannot know if the window is closed after that sleep timer has ended;
The solution is to use signals and slots. Since you need to know when the second window has been closed using the "back" button, create a custom signal for the second window that will be emitted whenever the function that is called by the button is closed.
from PyQt5 import QtCore, QtWidgets
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
central = QtWidgets.QWidget()
layout = QtWidgets.QHBoxLayout(central)
self.testButton = QtWidgets.QPushButton('Test A')
self.closeButton = QtWidgets.QPushButton('Close')
layout.addWidget(self.testButton)
layout.addWidget(self.closeButton)
self.setCentralWidget(central)
self.testButton.clicked.connect(self.launchWindow)
self.closeButton.clicked.connect(self.close)
def launchWindow(self):
self.test = TestA_MainWindow()
self.test.backSignal.connect(self.show)
self.hide()
self.test.show()
class TestA_MainWindow(QtWidgets.QWidget):
backSignal = QtCore.pyqtSignal()
def __init__(self):
super().__init__()
layout = QtWidgets.QHBoxLayout(self)
self.closeButton = QtWidgets.QPushButton('Close')
self.backButton = QtWidgets.QPushButton('Back')
layout.addWidget(self.closeButton)
layout.addWidget(self.backButton)
self.closeButton.clicked.connect(self.close)
self.backButton.clicked.connect(self.goBack)
def goBack(self):
self.close()
self.backSignal.emit()
def GUI_Window():
import sys
app = QtWidgets.QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
sys.exit(app.exec_())
if __name__ == '__main__':
GUI_Window()
Notes:
I removed the GUI_Window class and made a function, as using a class for that is not really useful;
you should always prefer layout managers instead of setting manual geometries;
widgets should not be added to a QMainWindow as direct children, and a central widget should always be used (see the creation and use of central in the example); read more about it in the documentation;
only classes and constants should be capitalized, while variables, attributes and functions should always have names starting with a lowercase letter;
I have a few actions in a QMenu that I'm trying to connect to a single method; there are additional actions that are unrelated.
import sys
from PyQt5.QtWidgets import *
class Window(QWidget):
def __init__(self):
super(Window, self).__init__()
layout = QGridLayout(self)
self.menu_bar = QMenuBar()
self.menu = QMenu('MENU', self)
self.menu_action = QAction('Option #1', self)
self.menu_action.setData('option1')
self.menu_action.triggered.connect(self.actionClicked)
self.menu.addAction(self.menu_action)
self.menu_bar.addMenu(self.menu)
layout.addWidget(self.menu_bar)
def actionClicked(self, action):
print(action)
try:
print(action.text())
except AttributeError as e:
print(e)
try:
print(action.data())
except AttributeError as e:
print(e)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window()
window.setGeometry(600, 100, 300, 100)
window.show()
sys.exit(app.exec_())
I was wondering how I can tell which of the actions was clicked when the method is called. Currently I'm trying to use self.menu_action.setData() to give the action a cleaner name for the receiving method, but that does not seem to be working properly.
A possible solution is to use the sender() method that returns the QObject that emits the signal, in this case the QAction:
import sys
from PyQt5 import QtCore, QtWidgets
class Window(QtWidgets.QWidget):
def __init__(self):
super(Window, self).__init__()
layout = QtWidgets.QGridLayout(self)
menubar = QtWidgets.QMenuBar()
filemenu = menubar.addMenu('MENU')
filemenu.addAction('Option #1', self.actionClicked)
filemenu.addAction('Option #2', self.actionClicked)
layout.addWidget(menubar)
#QtCore.pyqtSlot()
def actionClicked(self):
action = self.sender()
print('Action: ', action.text())
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.setGeometry(600, 100, 300, 100)
window.show()
sys.exit(app.exec_())
If you are going to connect all the QActions of the QMenu then you can use the triggered signal of the QMenu, this sends the QAction that was pressed.
import sys
from PyQt5 import QtCore, QtWidgets
class Window(QtWidgets.QWidget):
def __init__(self):
super(Window, self).__init__()
layout = QtWidgets.QGridLayout(self)
menubar = QtWidgets.QMenuBar()
filemenu = menubar.addMenu('MENU')
filemenu.triggered.connect(self.actionClicked)
filemenu.addAction('Option #1')
filemenu.addAction('Option #2')
layout.addWidget(menubar)
#QtCore.pyqtSlot(QtWidgets.QAction)
def actionClicked(self, action):
print('Action: ', action.text())
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.setGeometry(600, 100, 300, 100)
window.show()
sys.exit(app.exec_())
Update:
The QAction triggered signal is different from the QMenu triggered signal, in the case of the first one it sends a Boolean value that indicates if the QAction is checked (by default a QAction is not checkable, if you want to activate it you must use setCheckable(True)) and in the second case it sends the QAction that was pressed that belongs to the QMenu. That's why you always get False, if you do not want to have that problem you have to use my first method using sender(). On the other hand in Qt very rarely you will have to use try-except, in reality you should never use it in the world of Qt since that has an additional cost that Qt does not want, and on the other hand it hides the cause of the errors , as a recommendation use try-except when there is no other solution, and in the case of Qt there will almost always be another option.
import sys
from PyQt5.QtWidgets import *
class Window(QWidget):
def __init__(self):
super(Window, self).__init__()
layout = QGridLayout(self)
self.menu_bar = QMenuBar()
self.menu = QMenu('MENU', self)
self.menu_action = QAction('Option #1', self)
self.menu_action.setData('option1')
self.menu_action.triggered.connect(self.actionClicked)
self.menu.addAction(self.menu_action)
self.menu_bar.addMenu(self.menu)
layout.addWidget(self.menu_bar)
def actionClicked(self, checked):
action = self.sender()
print(action.text())
print(action.data())
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window()
window.setGeometry(600, 100, 300, 100)
window.show()
sys.exit(app.exec_())
I will make 2 examples to explain it
I created a Button that when i click it, it shows a message in the TextEdit Widget (in this case it shows "A string").
If i do that in the same class of the GUI, i have no problems:
from PyQt4.QtGui import *
import sys
class Aplicacion(QWidget):
def __init__(self, parent=None):
super(Aplicacion, self).__init__()
vbox = QVBoxLayout(self)
self.textedit = QTextEdit('')
self.button = QPushButton("Do anything")
#Layouts
vbox.addWidget(self.textedit)
vbox.addWidget(self.button)
#Connections
self.button.clicked.connect(self.aFunction)
def aFunction(self):
self.textedit.append("A string")
app = QApplication(sys.argv)
test = Aplicacion()
test.show()
app.exec_()
It works fine: http://puu.sh/kpEHC.png
But when i am trying to do the same in another class or function i get this error:
from PyQt4.QtGui import *
import sys
def appendAnything(self):
Aplicacion().textedit.append("test") # HERE IS THE ERROR
class Aplicacion(QWidget):
def __init__(self, parent=None):
super(Aplicacion, self).__init__()
vbox = QVBoxLayout(self)
self.textedit = QTextEdit('')
self.button = QPushButton("Do anything")
#Layouts
vbox.addWidget(self.textedit)
vbox.addWidget(self.button)
#Connections
self.button.clicked.connect(appendAnything)
def aFunction(self):
self.textedit.append("A string")
app = QApplication(sys.argv)
test = Aplicacion()
test.show()
app.exec_()
Error:
Aplicacion().textedit.append("test") # HERE IS THE ERROR
RuntimeError: wrapped C/C++ object of type QTextEdit has been deleted
Image: http://puu.sh/kpETO.png
Thanks and sorry for my english
This happens because you are creating a new Aplicacion instances inside the function scope and it gets destroyed as soon as your code goes back to the main loop since you don't keep any reference of this new instance. Also I don't understand why you are creating a new Aplicacion instance: I think you should probably pass a reference of your main widget to the function. An easy (non thread safe) way to do that is to use partial:
from PyQt4.QtGui import *
from functools import partial
import sys
def appendAnything(self, app):
app.textedit.append("test")
class Aplicacion(QWidget):
def __init__(self, parent=None):
super(Aplicacion, self).__init__()
vbox = QVBoxLayout(self)
self.textedit = QTextEdit('')
self.button = QPushButton("Do anything")
#Layouts
vbox.addWidget(self.textedit)
vbox.addWidget(self.button)
#Connections
self.button.clicked.connect(partial(appendAnything, app=self))
def aFunction(self):
self.textedit.append("A string")
if __name__ == '__main__':
app = QApplication(sys.argv)
test = Aplicacion()
test.show()
app.exec_()
These 2 programs work the same, but there is a small difference in the lines marked with #HERE.
Can someone explain the differences? I do not fully understand what these lines do.
Program 1:
import sys
from PyQt4 import QtGui, QtCore
class myform(QtGui.QDialog):
def __init__(self, parent=None):
super(myform, self).__init__(parent)
form = QtGui.QFormLayout()
form.setHorizontalSpacing(0)
myedit = QtGui.QLineEdit()
form.addWidget(myedit)
self.setLayout(form)
self.setGeometry(300, 300, 400, 0)
self.setWindowTitle('test')
myedit.textChanged.connect(self.editchange) # new style signal slot connections
self.show() # HERE
def editchange(self,data):
print "editchange:", data
if __name__ == "__main__":
app = QtGui.QApplication([])
ex = myform()
#ex.exec_() # HERE
#sys.exit(app.closeAllWindows()) # HERE
sys.exit(app.exec_()) # HERE
Program #2:
import sys
from PyQt4 import QtGui, QtCore
class myform(QtGui.QDialog):
def __init__(self, parent=None):
super(myform, self).__init__(parent)
form = QtGui.QFormLayout()
form.setHorizontalSpacing(0)
myedit = QtGui.QLineEdit()
form.addWidget(myedit)
self.setLayout(form)
self.setGeometry(300, 300, 400, 0)
self.setWindowTitle('test')
myedit.textChanged.connect(self.editchange) # new style signal slot connections
#self.show() # HERE
def editchange(self,data):
print "editchange:", data
if __name__ == "__main__":
app = QtGui.QApplication([])
ex = myform()
ex.exec_() # HERE
sys.exit(app.closeAllWindows()) # HERE
#sys.exit(app.exec_()) # HERE
Program #1 calls exec_ from QApplication (http://pyqt.sourceforge.net/Docs/PyQt4/qapplication.html#exec).
Program #2 calls exec_ from QDialog (http://pyqt.sourceforge.net/Docs/PyQt4/qdialog.html#exec): the resulting dialog is a modal one.
The final behavior is the same because you use a QDialog.
In this case:
app = QtGui.QApplication([])
ex = myform()
ex.show()
app.exec_()
is the same as:
app = QtGui.QApplication([])
ex = myform()
ex.exec_()