Pop up window no longer closes when using lambda function - python

Using the answer to this question:
Python: PyQt Popup Window
I was able to produce a gui with a button that has a popup window.
What I would like to do now is press a button in the popup and pass a command to a function in the MyPopup class. This is easily accomplished using the lambda function, however, when you press the button in the mainwindow the popup window no longer closes and a new instance of the popup is created, resulting in two popup screens. From my understanding this is due to a signal being produced by the lambda function. Is there a way to clear this lambda function such that when the main button is pressed the old instance is closed and a new instance of the popup is loaded?
If this is not possible using lambda, is there another way to pass variables to the function to obtain the results I am looking for?
Here are some example screenshots to better illustrate my issue:
Running Script without lambda in popup
Running Script with lambda in popup
Here is the modified popup code from the previous question:
import sys
from PyQt4.Qt import *
class MyPopup(QWidget):
def __init__(self):
QWidget.__init__(self)
self.btn_popup = QPushButton("broken", self)
self.btn_popup.clicked.connect(lambda state, x='lambda prevents refresh': self.function(x))
def function(self, word):
print('Now I dont close',word)
def paintEvent(self, e):
dc = QPainter(self)
dc.drawLine(0, 0, 100, 100)
dc.drawLine(100, 0, 0, 100)
class MainWindow(QMainWindow):
def __init__(self, *args):
QMainWindow.__init__(self, *args)
self.cw = QWidget(self)
self.setCentralWidget(self.cw)
self.btn1 = QPushButton("Click me", self.cw)
self.btn1.setGeometry(QRect(0, 0, 100, 30))
self.connect(self.btn1, SIGNAL("clicked()"), self.doit)
self.w = None
def doit(self):
print ("Opening a new popup window...")
self.w = MyPopup()
self.w.setGeometry(QRect(100, 100, 400, 200))
self.w.show()
class App(QApplication):
def __init__(self, *args):
QApplication.__init__(self, *args)
self.main = MainWindow()
self.main.show()
def main(args):
global app
app = App(args)
app.exec_()
if __name__ == "__main__":
main(sys.argv)

It seems that if the lambda function does not exist the popup is destroyed, verify this by adding the following:
class MyPopup(QWidget):
def __init__(self, i):
[..]
self.destroyed.connect(lambda: print("destroyed"))
in the case where there was no lambda, the message was printed, while in the other case it was not. So the solution is to destroy it manually using the deleteLater() method:
def doit(self):
print ("Opening a new popup window...")
if self.w:
self.w.deleteLater()
self.w = MyPopup()
self.w.setGeometry(QRect(100, 100, 400, 200))
self.w.show()

Related

QMenu (contextMenuEvent) not closing on click on action nor parent QWidget

I am trying to create a context menu (QMenu) for a right-click inside of PatternWidget(QWidget) with PySide6.
For this purpose I have overwritten contextMenuEvent as seen below.
For whatever reason, the context menu will NOT exit, when any of the actions in it or any space around it within PatternWidget is clicked, whether by left nor by right click.
The only way I can make the QMenu close is to click inside another widget or outside of the whole Application window.
I spent a long time on Google already and it seems that people usually have the opposite problem (keeping it open when clicking somewhere). I therefore think that something must be wrong in my code that prevents the default behavior to close the QMenu rather than me having to connect to some Signal and close it manually
I was also wondering if the Mouse Click event is not bubbling up until the QMenu but,the signals of the actions in the QMenu (Copy Row Up and Down) are firing as expected when clicked.
EDIT:
Removed QScrollArea
Added Code for PatternOverlay
class MainWindow(QMainWindow):
...
def __init_layout(self):
self.layout = QHBoxLayout()
self.layout.setContentsMargins(10, 10, 10, 10)
self.layout.setSpacing(1)
self.mainWidget = QWidget()
self.mainWidget.setLayout(self.layout)
self._scene = QtWidgets.QGraphicsScene(self)
self._view = QtWidgets.QGraphicsView(self._scene)
self._view.setStyleSheet("border: 0px; background-color: Gainsboro")
self.pattern_widget = PatternWidget()
self._scene.addWidget(self.pattern_widget)
self.layout.addWidget(self._view)
self.setCentralWidget(self.mainWidget)
class PatternOverlay(QWidget):
def __init__(self, parent):
super().__init__(parent=parent)
self.setAttribute(Qt.WA_NoSystemBackground)
self.setAttribute(Qt.WA_TransparentForMouseEvents)
self.rects = []
def paintEvent(self, _):
painter = QPainter(self)
painter.setPen(QPen(Qt.blue, 2, Qt.SolidLine))
for rect in self.rects:
painter.drawRect(rect)
painter.end()
self.rects.clear()
def addRect(self, rect):
self.rects.append(rect)
class PatternWidget(QWidget):
def __init__(self, cell_width, cell_height, stitches, rows):
super().__init__()
self.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
self.setMouseTracking(True)
# initialize overlay
self.__overlay = PatternOverlay(self)
self.__overlay.resize(self.size())
self.__overlay.show()
...
def contextMenuEvent(self, e):
copy_row_up_action = QAction("Copy Row Up", self)
copy_row_up_action.setStatusTip("Copy Row Up")
copy_row_up_action.triggered.connect(self.__handle_copy_row_up)
copy_row_down_action = QAction("Copy Row Down", self)
copy_row_down_action.setStatusTip("Copy Row Down")
copy_row_down_action.triggered.connect(self.__handle_copy_row_down)
context = QMenu(self)
context.addActions([copy_row_up_action, copy_row_down_action])
context.exec_(e.globalPos())

How to get a PyQt5 QPushButton to do different commands on different button clicks

I wish to have the QPushButton do different things on different clicks. One the first click it should execute one command and on the next click, it should execute the other command. I've tried to make a program to do it but it only executes one command, not the other
my code I:
import PyQt5.QtWidgets as pyqt
import sys
ongoing = False
class Stuff(pyqt.QWidget):
def __init__(self):
super().__init__()
self.windows()
def windows(self):
w = pyqt.QWidget()
layout = pyqt.QGridLayout()
self.setLayout(layout)
button = pyqt.QPushButton('click me', w)
layout.addWidget(button)
if not ongoing:
button.clicked.connect(click_one)
else:
button.clicked.connect(click_two)
self.show()
w.show()
def click_one():
global ongoing
print('one')
ongoing = not ongoing
def click_two():
global ongoing
print('two')
ongoing = not ongoing
if __name__ == '__main__':
app = pyqt.QApplication(sys.argv)
x = Stuff()
app.exec_()
What should I do to fix this?
Since the value of ongoing is False when the class is initialized, the button's clicked signal gets connected to click_one(). Connect the button to an initial slot and then call the desired function based on the value of ongoing.
class Stuff(pyqt.QWidget):
def __init__(self):
super().__init__()
self.windows()
def windows(self):
w = pyqt.QWidget()
layout = pyqt.QGridLayout()
self.setLayout(layout)
button = pyqt.QPushButton('click me', w)
layout.addWidget(button)
button.clicked.connect(on_click)
self.show()
w.show()
def on_click():
global ongoing
if not ongoing:
click_one()
else:
click_two()
I suggest rewriting the code with the functions and ongoing variable belonging to the class. The QWidget assigned to variable w seems redundant because the QPushButton is then added to the layout of the class, so its parent gets changed anyways.
class Stuff(pyqt.QWidget):
def __init__(self):
super().__init__()
self.ongoing = False
self.windows()
def windows(self):
layout = pyqt.QGridLayout(self)
button = pyqt.QPushButton('click me')
layout.addWidget(button)
button.clicked.connect(self.on_click)
self.show()
def on_click(self):
self.click_one() if not self.ongoing else self.click_two()
self.ongoing = not self.ongoing
def click_one(self):
print('one')
def click_two(self):
print('two')
Also you might be interested in using a checkable button.

Call a function from another class by clicking a button PyQt4

I have a checkbox and a run button. When the checkbox is checked, I want to run some functions by clicking the button. The problem is that the function is in another class outside the button's class. My example codes are as below.
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class Tab1Widget1(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.Tab1Widget1initUI()
def Tab1Widget1initUI(self):
self.setLayout(QGridLayout())
self.T1W1checkBox1 = QCheckBox('a', self)
self.layout().addWidget(self.T1W1checkBox1, 1, 0)
def run(self):
if self.T1W1checkBox1.isChecked() == True:
pass
class Tab1Layout(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.setLayout(QGridLayout())
self.group1 = Tab1Widget1(self)
self.layout().addWidget(self.group1, 0, 0)
btn = QPushButton('Run', self)
self.layout().addWidget(btn, 1, 0)
btn.clicked.connect(Tab1Widget1().run()) ##the problem is in this line.
class Page1(QTabWidget):
def __init__(self, parent=None):
super(Page1, self).__init__(parent)
self.tab1 = Tab1Layout()
self.addTab(self.tab1, "Tab1")
self.tab2 = QWidget()
self.tab3 = QWidget()
self.addTab(self.tab2, "Tab2")
self.addTab(self.tab3, "Tab3")
self.tab2_initUI()
self.tab3_initUI()
def tab2_initUI(self):
grid = QGridLayout()
self.tab2.setLayout(grid)
def tab3_initUI(self):
grid = QGridLayout()
self.tab3.setLayout(grid)
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setGeometry(300, 200, 600, 370)
self.startPage1()
def startPage1(self):
x = Page1(self)
self.setWindowTitle("Auto Benchmark")
self.setCentralWidget(x)
self.show()
def main():
app = QApplication(sys.argv)
main = MainWindow()
main.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
As you can see, I want to run the "run" function in "Tab1Widget1" class. However, the button is in "Tab1Layout" class.
When I run the codes, it returns to me "TypeError: connect() slot argument should be a callable or a signal, not 'NoneType'"
If anyone knows how to solve this, pls let me know. Appreciated!
There is no problem in connecting any callable to a button click regardless of what object it is in. But your code has two specific problems. You write
btn.clicked.connect(Tab1Widget1().run())
The first problem here is that Tab1Widget1() is creating a new Tab1Widget1 but presumably you don't want that. You want to call run on the Tab1Widget1 you have already created and stored in self.group.
The second problem is that when you connect a signal you need to connect it to a callable: the method you want to call. Instead here you are calling the run method at connect time and trying to connect to the result of that call (which is None). So you are trying to connect the signal to None which will of course fail. You need to refer to the method without calling it: just remove the calling brackets.
Putting it together:
btn.clicked.connect(self.group1.run)
That seems to work.

A toggle button which once clicked initiates full screen window and if clicked again goes back to the normal state

My code in pyqt is simple:
QtCore.QObject.connect(self.pushButton, QtCore.SIGNAL("clicked()"), self.add_entry)
def add_entry(self):
if QtCore.Qt.WindowFullScreen:
MainWindow.showNormal()
else :
MainWindow.showMaximized()
The toggle button when clicked however does its job it's showing full screen but on clicking again it's not reverting back to normal screen mode.
You are mixing things together. QtCore.Qt.WindowFullScreen is constant value - therefore your condition is always true. And at second - .showMaximized window method switch window object to the Qt.WindowMaximized state.
Here is how you should change it:
class Example(QtGui.QMainWindow):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
cb = QtGui.QPushButton('Switch', self)
cb.move(20, 20)
cb.clicked.connect(self.add_entry)
self.setGeometry(300, 300, 250, 150)
self.show()
def add_entry(self):
if self.windowState() & QtCore.Qt.WindowFullScreen:
self.showNormal()
else:
self.showFullScreen()

PyQT4 - Replacing widgets on main window

I`m new in PyQT4 and I've faced the problem below.
Some application must collect users' data on one screen
and show next screen on button click.
main.py
app = QtGui.QApplication(sys.argv)
#here I create main window with inherited class
w = SAN_MainWindow()
#and apply some basic geometry
w.SetUI()
#here first screen rendered and button event applyed
w.ApplyWidget( SAN_Intro() )
w.show()
sys.exit(app.exec_())
Rendering of first widget occurs correctly.
SAN_MainWindow.py:
class SAN_MainWindow(QtGui.QMainWindow):
def __init__(self ):
super(SAN_MainWindow, self).__init__()
def SetUI(self):
self.setObjectName(_fromUtf8("MainWindow"))
self.resize(789, 602)
self.setWindowTitle(QtGui.QApplication.translate("MainWindow", "MainWindow", None, QtGui.QApplication.UnicodeUTF8))
self.central_widget = QtGui.QWidget(self)
self.central_widget.setObjectName(_fromUtf8("central_widget"))
self.setCentralWidget(self.central_widget)
def ApplyWidget( self, widget ):
self.active_widget = widget
self.active_widget.Setup(self.central_widget)
self.active_widget.SetControls( self )
def RemoveActiveWidget( self ):
self.active_widget.widget().setParent(None)
def ShowInstruction( self ):
self.RemoveActiveWidget()
self.ApplyWidget( SAN_Instruction() )
#self.centralWidget().update() <- Problem
SAN_Intro.py:
class SAN_Intro(object):
def __init__(self):
super(SAN_Intro, self).__init__()
def Setup(self, widget):
self.gridLayoutWidget = QtGui.QWidget(widget)
self.gridLayoutWidget.setGeometry(QtCore.QRect(80, 30, 638, 451))
self.gridLayoutWidget.setObjectName(_fromUtf8("gridLayoutWidget"))
self.gridLayout = QtGui.QGridLayout(self.gridLayoutWidget)
self.gridLayout.setContentsMargins(-1, 16, 16, -1)
self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
#a lot of form inputs instructions....
def SetControls( self, main_window ):
QtCore.QObject.connect(self.pushButton, QtCore.SIGNAL("clicked()"), main_window.ShowInstruction)
def widget(self):
return self.gridLayoutWidget
SAN_Instruction is almost the same as SAN_Intro, but with different commands in Setup:
class SAN_Instruction(object):
def __init__(self):
super(SAN_Instruction, self).__init__()
def Setup(self, widget):
self.verticalLayoutWidget = QtGui.QWidget(widget)
self.verticalLayoutWidget.setGeometry(QtCore.QRect(40, 20, 591, 521))
self.verticalLayoutWidget.setObjectName(_fromUtf8("verticalLayoutWidget"))
self.verticalLayout = QtGui.QVBoxLayout(self.verticalLayoutWidget)
self.verticalLayout.setMargin(0)
self.verticalLayout.setObjectName(_fromUtf8("verticalLayout"))
#a lot of form inputs instructions....
def SetControls( self, main_window ):
pass
def widget(self):
return self.verticalLayoutWidget
AND HERE IS QUESTION:
How must I show the second (generated on fly with SAN_Instruction) widget?
I tried to update/show/repaint mainwindow/centralwidget/parent.
But all I've got on button click is self.RemoveActiveWidget().
Maybe my concept is incorrect?
Thanx in advance
Well, your code isn't complete but i would advise you to take advantage of the hide() method.
You can define this in your button function, so that all the widgets you do not want to appear get hidden, and on the same function call the widgets you want to appear on their place. I use to do that way because it makes my projects more organized and easy to track and debug. Furthermore, this also allows you to make that widgets visible again if you need, with the show() method.
You can use a "QtGui.QStackedWidget()". You Split your Window to many Frames, add each frame to the QStackedWidget() and use function setCurrentWidget() to choose which frame to display (switch between frames).
self.central_widget.addWidget(self.mainFrame)
self.central_widget.setCurrentWidget(self.mainFrame)

Categories