PyQT4 - Replacing widgets on main window - python

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)

Related

Adding a Image Button Class

I have a PyQT layout built with QWidget. I have a few labels, buttons, etc and now I want to add a image that changes color on click. For this, I have built a class as opposed to using a default widget. For the default widgets I am easily able to add them back into the main window but I am not sure how to do this with the custom class.
This is how my code is laid out:
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.setWindowTitle("Project Starfish Prototype GUI")
MainWindow.resize(1920, 1080)
MainWindow.setStyleSheet("background-color: rgb(9, 40, 122);")
self.cameraViewer = QtWidgets.QWidget(MainWindow)
self.cameraViewer.setObjectName("cameraViewer")
self.logo = QtWidgets.QLabel(self.cameraViewer)
self.logo.setGeometry(QtCore.QRect(825, 20, 270, 42))
pixmap = QPixmap('teleflex-logo.png')
pixmap = pixmap.scaled(270, 42)
self.logo.setPixmap(pixmap)
self.cameraFeed = QtWidgets.QLabel(self.cameraViewer)
self.cameraFeed.setGeometry(QtCore.QRect(320, 110, 1280, 960))
self.cameraFeed.setObjectName("cameraFeed")
self.startBTN = QtWidgets.QPushButton(self.cameraViewer)
self.startBTN.setGeometry(QtCore.QRect(110, 610, 100, 100))
self.startBTN.setObjectName("startBTN")
self.startBTN.clicked.connect(self.StartFeed)
There is some more code but this is the jist. I add the default QButton or QLabel to the main QWidget.
Now I am trying to add my Class to the main window but I am not sure how to do this.
Here is my code for adding the Class (inside setupUI) to the main window and then my class:
self.imageButton = PicButton(QPixmap("image.png"))
self.imageButton = self.imageButton(self.cameraViewer)
self.imageButton.setGeometry(QtCore.QRect(110, 110, 100, 100))
self.imageButton.setObjectName("imageButton")
class PicButton(QAbstractButton):
def __init__(self, pixmap, parent=None):
super(PicButton, self).__init__(parent)
self.pixmap = pixmap
def hover(self):
def click(self):
I do understand that my code does not make sense, specifically this line self.imageButton = self.imageButton(self.cameraViewer) but I am not sure what the solution is.

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

Pop up window no longer closes when using lambda function

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

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.

Wrong widget order using vbox layout PyQt

I am trying to put a QLabel widget on top of (ie before) a QLineEdit widget edit.
But it keeps appearing after the QLineEdit widget. My code,
class CentralWidget(QtGui.QWidget):
def __init__(self, parent=None):
super(CentralWidget, self).__init__(parent)
# set layouts
self.layout = QtGui.QVBoxLayout(self)
# Flags
self.randFlag = False
self.sphereFlag = False
self.waterFlag = False
# Poly names
self.pNames = QtGui.QLabel("Import file name", self) # label concerned
self.polyNameInput = QtGui.QLineEdit(self) # line edit concerned
# Polytype selection
self.polyTypeName = QtGui.QLabel("Particle type", self)
polyType = QtGui.QComboBox(self)
polyType.addItem("")
polyType.addItem("Random polyhedra")
polyType.addItem("Spheres")
polyType.addItem("Waterman polyhedra")
polyType.activated[str].connect(self.onActivated)
self.layout.addWidget(self.pNames)
self.layout.addWidget(self.polyNameInput)
self.layout.addWidget(self.pNames)
self.layout.addWidget(self.polyTypeName)
self.layout.addWidget(polyType)
self.layout.addStretch()
def onActivated(self, text):
# Do loads of clever stuff that I'm not at liberty to share with you
class Polyhedra(QtGui.QMainWindow):
def __init__(self):
super(Polyhedra, self).__init__()
self.central_widget = CentralWidget(self)
self.setCentralWidget(self.central_widget)
# Set up window
self.setGeometry(500, 500, 300, 300)
self.setWindowTitle('Pyticle')
self.show()
# Combo box
def onActivated(self, text):
self.central_widget.onActivated(text)
def main():
app = QtGui.QApplication(sys.argv)
poly = Polyhedra()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
The window I get is below.
What am I missing? I thought QVbox allowed to stack things vertically in the order that you add the items to the main widget. (Are these sub-widget objects called widgets?)
The problem is because you are adding self.pNames label to layout twice.
#portion of your code
...
self.layout.addWidget(self.pNames) # here
self.layout.addWidget(self.polyNameInput)
self.layout.addWidget(self.pNames) # and here
self.layout.addWidget(self.polyTypeName)
self.layout.addWidget(polyType)
self.layout.addStretch()
...
The first time you add the QLabel, it gets added before the LineEdit and when you add it second time, it just moves to the bottom of LineEdit. This happens because there is only one object of QLabel which is self.pNames. It can be added to only one location. If you want to use two labels, consider creating two separate objects of QLabel

Categories