PyQt technique differences in 2 source code examples - python

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

Related

How to destroy a QApplication and then run a new one without exiting the python script?

I want to create a QApplication which is then exited using a keyboard shortcut. Then the python script should call another QApplication.
My issues currently is that I get this error when the second QApplication is about to run:
app2 = QApplication()
RuntimeError: Please destroy the QApplication singleton before creating a new QApplication instance.
I have the following structure:
| main.py
| Q1.py
| Q2.py
This is main.py:
import Q1 as record
import Q2 as display
def main():
record.main()
display.main()
if __name__ == "__main__":
main()
This is Q1 which creates the problem:
import sys
from PySide2 import QtWidgets as qtw
from PySide2 import QtGui as qtg
from PySide2 import QtCore as qtc
from PySide2 import QtMultimedia as qtmm
class MainWindow(qtw.QMainWindow):
def __init__(self):
super().__init__()
#Create Window layout with a sound widget
soundboard = qtw.QWidget()
soundboard.setLayout(qtw.QGridLayout())
self.setCentralWidget(soundboard)
sw = SoundWidget()
soundboard.layout().addWidget(sw)
#Window Dimensions
self.setSizePolicy(qtw.QSizePolicy.Expanding, qtw.QSizePolicy.MinimumExpanding)
# Code ends here
self.show()
class SendOrderButton(qtw.QPushButton):
button_stylesheet = 'background-color: blue; color: white;'
def __init__(self):
super().__init__('Send Order')
self.setSizePolicy(qtw.QSizePolicy.Expanding, qtw.QSizePolicy.Expanding)
self.setStyleSheet(self.button_stylesheet)
#self.clicked.connect(qtc.QCoreApplication.instance().quit)
def press_button(self):
if self.isEnabled():
self.setEnabled(False)
self.setText('Send Order')
else:
self.setEnabled(True)
self.setText('Sent')
class SoundWidget(qtw.QWidget):
def __init__(self):
super().__init__()
self.setLayout(qtw.QGridLayout())
#Send Order Button
self.sendorder_button = SendOrderButton()
self.sendorder_button.setShortcut(qtg.QKeySequence('Tab'))
self.layout().addWidget(self.sendorder_button, 5, 0, 1, 2)
self.sendorder_button.clicked.connect(qtc.QCoreApplication.instance().quit)
def main():
app = qtw.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
if __name__ == '__main__':
app = qtw.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
This is Q2.py which has the second QApplication:
import sys
from PySide2.QtCore import (QAbstractTableModel, Slot)
from PySide2.QtWidgets import (QAction, QApplication, QMainWindow,QWidget)
class MainWindow(QMainWindow):
def __init__(self, widget):
QMainWindow.__init__(self)
# Exit QAction
exit_action = QAction("Exit", self)
exit_action.setShortcut("Ctrl+Q")
exit_action.triggered.connect(self.exit_app)
#Slot()
def exit_app(self, checked):
sys.exit()
class CustomTableModel(QAbstractTableModel):
def __init__(self, data=None):
QAbstractTableModel.__init__(self)
class Widget(QWidget):
def __init__(self):
QWidget.__init__(self)
# Getting the Model
self.model = CustomTableModel()
def main():
app2 = QApplication()
widget = Widget()
window2 = MainWindow(widget)
window2.show()
sys.exit(app2.exec_())
if __name__ == "__main__":
app = QApplication()
widget = Widget()
window = MainWindow(widget)
window.show()
sys.exit(app.exec_())
As noted in the comments a Qt application can only and should have a QApplication (you might not follow this rule but nothing guarantees that it works correctly) so you will have to restructure your code.
Assuming that you want the Q1 window to be first and when that window is closed then the Q2 window opens that does not imply at any time that you have to use several QApplication. The idea is to know when a window is closed and to be notified of it, to know when a window is closed then you must override the closeEvent method of the window and to make the notification you must send a signal.
Considering the above, the solution is:
├── main.py
├── Q1.py
└── Q2.py
main.py
import sys
from PySide2 import QtWidgets as qtw
import Q1 as record
import Q2 as display
def main():
app = qtw.QApplication(sys.argv)
w1 = record.get_mainwindow()
w2 = display.get_mainwindow()
w1.closed.connect(w2.show)
w1.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Q1.py
from PySide2 import QtWidgets as qtw
from PySide2 import QtGui as qtg
from PySide2 import QtCore as qtc
class MainWindow(qtw.QMainWindow):
closed = qtc.Signal()
def __init__(self):
super().__init__()
# Create Window layout with a sound widget
soundboard = qtw.QWidget()
soundboard.setLayout(qtw.QGridLayout())
self.setCentralWidget(soundboard)
sw = SoundWidget()
soundboard.layout().addWidget(sw)
# Window Dimensions
self.setSizePolicy(qtw.QSizePolicy.Expanding, qtw.QSizePolicy.MinimumExpanding)
sw.sendorder_button.clicked.connect(self.close)
def closeEvent(self, event):
self.closed.emit()
super().closeEvent(event)
class SendOrderButton(qtw.QPushButton):
button_stylesheet = "background-color: blue; color: white;"
def __init__(self):
super().__init__("Send Order")
self.setSizePolicy(qtw.QSizePolicy.Expanding, qtw.QSizePolicy.Expanding)
self.setStyleSheet(self.button_stylesheet)
def press_button(self):
if self.isEnabled():
self.setEnabled(False)
self.setText("Send Order")
else:
self.setEnabled(True)
self.setText("Sent")
class SoundWidget(qtw.QWidget):
def __init__(self):
super().__init__()
self.setLayout(qtw.QGridLayout())
# Send Order Button
self.sendorder_button = SendOrderButton()
self.sendorder_button.setShortcut(qtg.QKeySequence("Tab"))
self.layout().addWidget(self.sendorder_button, 5, 0, 1, 2)
def get_mainwindow():
window = MainWindow()
return window
if __name__ == "__main__":
import sys
app = qtw.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
Q2.py
from PySide2 import QtCore as qtc
from PySide2 import QtWidgets as qtw
class MainWindow(qtw.QMainWindow):
def __init__(self, widget):
super().__init__()
file_menu = self.menuBar().addMenu("&File")
# Exit QAction
exit_action = qtw.QAction("Exit", self)
exit_action.setShortcut("Ctrl+Q")
exit_action.triggered.connect(self.close)
file_menu.addAction(exit_action)
class CustomTableModel(qtc.QAbstractTableModel):
pass
class Widget(qtw.QWidget):
def __init__(self):
super().__init__()
# Getting the Model
self.model = CustomTableModel()
def get_mainwindow():
widget = Widget()
window2 = MainWindow(widget)
return window2
if __name__ == "__main__":
import sys
app = qtw.QApplication()
widget = Widget()
window = MainWindow(widget)
window.show()
sys.exit(app.exec_())

Make QToolBar Items have ContextMenu

How can I add a context menu to the items in my QToolBar. The reason for the context menu, is i want to give each Action a right-click > Delete action. Similar to the Chrome browsers ability to remove bookmarks from the bookmark toolbar. How do i achieve this in Pyside?
import os, sys
from PySide import QtGui, QtCore
class Example(QtGui.QMainWindow):
def __init__(self):
super(Example, self).__init__()
self.resize(300,200)
self.createToolBars()
self.createActions()
def createToolBars(self):
self.toolBar = self.addToolBar('Presets')
self.toolBar.setIconSize(QtCore.QSize(16,16))
self.toolBar.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon)
def createActions(self):
self.toolBar.clear()
presets = ['Abby','Kevin','Bob','Mary']
for x in presets:
act = QtGui.QAction(x, self)
act.setData(x)
self.toolBar.addAction(act)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
ex = Example()
ex.show()
sys.exit(app.exec_())
You have to implement a custom QToolBar and implement the contextMenuEvent method:
import os
import sys
from PySide import QtCore, QtGui
class ToolBar(QtGui.QToolBar):
def contextMenuEvent(self, event):
current_action = self.actionAt(event.pos())
if current_action is None:
return
menu = QtGui.QMenu()
delete_action = menu.addAction("delete")
action = menu.exec_(event.globalPos())
if action == delete_action:
self.removeAction(current_action)
class Example(QtGui.QMainWindow):
def __init__(self, parent=None):
super(Example, self).__init__(parent)
self.resize(300,200)
self.createToolBars()
self.createActions()
def createToolBars(self):
self.tool_bar = ToolBar('Presets', self)
self.addToolBar(self.tool_bar)
self.tool_bar.setIconSize(QtCore.QSize(16,16))
self.tool_bar.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon)
def createActions(self):
self.tool_bar.clear()
presets = ['Abby','Kevin','Bob','Mary']
for x in presets:
act = QtGui.QAction(x, self)
act.setData(x)
self.tool_bar.addAction(act)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
ex = Example()
ex.show()
sys.exit(app.exec_())

How to get the name of a QMenu Item when clicked?

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

Add different text to a window

I have a button and a text label. Each time the button is pressed, i would like text placed from a line edit to be placed onto the window. So far I can only get one text to draw onto the window, even if I create another textlabel. Ive tried seeting a click count determining how many times a user has clicked a button but this doesnt work either. Heres what I have so far, any suggestions?
import sys
import os
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.QtGui import QApplication
class Window(QMainWindow):
def __init__(self, *args):
QMainWindow.__init__(self, *args)
self.centralWidget = QWidget(self)
self.setCentralWidget(self.centralWidget)
self.setGeometry(450,100,350,680)
self.btn1 = QPushButton("Enter", self.centralWidget)
self.btn1.setGeometry(10,50,150, 20)
self.btn1.clicked.connect(self.enter)
self.edit = QtGui.QLineEdit(self)
self.edit.setGeometry(10, 10, 150, 20)
self.label = QtGui.QLabel(self)
self.label.setGeometry(240, 170,150, 20)
def enter(self):
self.label.setText(self.edit.text())
def main(args):
global app
app = App(args)
app.exec_()
class App(QApplication):
def __init__(self, *args):
QApplication.__init__(self, *args)
self.main = Window()
self.connect(self, SIGNAL("lastWindowClosed()"), self.byebye )
self.main.show()
def byebye( self ):
self.exit(0)
if __name__ == "__main__":
main(sys.argv)
There are a few problems with your example code, the main one being that you are trying to manually arrange the widgets, rather than using a layout.
It's hard to tell from your question exactly what you expect the output to be, so I've assumed you want the text from line-edit to be appended to the label, so that you end up with a series of lines.
Here's a simplified version of your example that hopefully does what you want:
from PyQt4 import QtCore, QtGui
class Window(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.btn1 = QtGui.QPushButton("Enter", self)
self.btn1.clicked.connect(self.enter)
self.edit = QtGui.QLineEdit(self)
self.label = QtGui.QLabel(self)
self.label.setAlignment(
QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft)
widget = QtGui.QWidget(self)
layout = QtGui.QVBoxLayout(widget)
layout.addWidget(self.edit)
layout.addWidget(self.btn1)
layout.addWidget(self.label)
self.setCentralWidget(widget)
def enter(self):
text = self.edit.text()
if text:
self.label.setText('%s\n%s' % (self.label.text(), text))
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(450, 100, 350, 680)
window.show()
sys.exit(app.exec_())

how to use a custom signal with QStateMachine addtransition

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

Categories

Resources