How to open and close my SubWindow In QMdiArea in PyQt5? - python

In QMidArea how to open a SubWindow? My Programs as follows. My intention to open/attach my second program in SubWindow. But Nothing Will happen. visible Only blank Window. How to resolve it?
How to attach my file in QMdi Sub-window ? and after my work, how to close the sub-window properly?
Main Programme
import sys,os
from PyQt5.QtWidgets import *
from sample_countrypage import Countrypage
class MainPage(QMainWindow):
def __init__(self):
super().__init__()
self.mdi = QMdiArea()
self.setWindowTitle(" Sample Programme")
self.setGeometry(100,100,1600,600)
self.Ui()
self.show()
def Ui(self):
self.btn1=QPushButton("Country")
self.btn1.setFixedSize(100, 30)
self.btn1.clicked.connect(self.countrypage)
self.left_layout = QVBoxLayout()
self.main_layout = QHBoxLayout()
self.left_layout.setContentsMargins(3,5,5,3)
self.left_layout.addWidget(self.btn1)
self.left_layout.addStretch()
self.main_layout.setSpacing(5)
self.main_layout.setContentsMargins(0,0,0,0)
self.main_layout.addLayout(self.left_layout)
self.main_layout.addStretch()
self.setLayout(self.main_layout)
widget = QWidget()
widget.setLayout(self.main_layout)
self.setCentralWidget(widget)
def countrypage(self):
print("country page")
self.countrywindow = Countrypage()
subwindow = QMdiSubWindow()
subwindow.setWidget(self.countrywindow)
self.mdi.addSubWindow(subwindow)
# subwindow.setFixedSize(500,500)
subwindow.show()
if __name__ == "__main__":
app = QApplication(sys.argv)
mainwindow = MainPage()
app.setStyle("fusion")
mainwindow.show()
sys.exit(app.exec_())
Second Program
import sys,os
from PyQt5.QtWidgets import *
class Countrypage(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Country Page")
self.btn1 = QPushButton("Accept")
self.btn1.clicked.connect(self.result)
self.btn2 = QPushButton("Re Enter")
self.form_layout = QFormLayout()
self.form_layout.addRow("Country",QLineEdit())
self.form_layout.addRow("continent",QLineEdit())
self.layout_btn = QHBoxLayout()
self.layout_btn.addStretch()
self.layout_btn.addWidget(self.btn1)
self.layout_btn.addWidget(self.btn2)
self.layout_country = QVBoxLayout()
self.layout_country.addLayout(self.form_layout)
self.layout_country.addLayout(self.layout_btn)
self.layout_country.addStretch()
self.setLayout(self.layout_country)
def result(self):
print("bye")
exec .close()
if __name__=="__main__":
app = QApplication(sys.argv)
countrywin = Countrypage()
countrywin.show()
sys.exit(app.exec_())

First of all, there are two main issues with your code:
You never added the mdi area to the main layout (and you also tried to set the layout for the QMainWindow, which is forbidden);
exec is a python builtin, and has no close attribute; if you want to close the widget, you have to call self.close();
Then, the setWidget() method of QMdiSubWindow reparents the widget:
QMdiSubWindow takes temporary ownership of widget;
This means that if you want to close the sub window that contains the widget from that widget, you have to check the parent and eventually close it, as soon as you verify that it's an instance of QMdiSubWindow.
class Countrypage(QWidget):
# ...
def result(self):
print("bye")
# ensure that the parent is actually a subwindow
if isinstance(self.parent(), QMdiSubWindow):
self.parent().close()
else:
self.close()
Alternatively, you can use a custom signal and connect that when creating the subwindow.
class Countrypage(QWidget):
closeRequested = pyqtSignal()
# ...
def result(self):
print("bye")
self.closeRequested.emit()
class MainPage(QMainWindow):
# ...
def countrypage(self):
print("country page")
self.countrywindow = Countrypage()
subwindow = QMdiSubWindow()
subwindow.setWidget(self.countrywindow)
self.mdi.addSubWindow(subwindow)
subwindow.show()
self.countrywindow.closerequested.connect(subwindow.close)
If you want to close the active subwindow from the mdi area (or outside of it) and no matter what that sub window is, just call self.mdi.closeActiveSubWindow().
Note that if you're going to create multiple Countrypage instances, there's no point in creating an instance attribute (self.countrywindow) as it will always be overwritten as soon as another instance will be created. Adding the widget to the subwindow and that subwindow to the mdi area will automatically create a persistent reference (due to the parenting); if you need a python reference to existing pages, then create a list as an instance member in the __init__ (eg. self.pages = []) and add the new instances to that list.

Related

How to show My Labels and activate respective Shortcut in Pyqt5?

Here is my code, How to show My Labels and activate respective QShortcut?. Want to show my labels(both instances) and assign respective shortcut keys to labels and activate it.
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
class Create_workingDict(QWidget):
def __init__(self,lblname,lblscut):
super(). __init__()
self.lblname = lblname
self.lblscut = lblscut
lbl_1 = QLabel()
lbl_1.setText(self.lblname)
lbl_2 = QLabel()
lbl_2.setText(self.lblscut)
vbox = QVBoxLayout()
vbox.addWidget(lbl_1)
vbox.addWidget(lbl_2)
self.setLayout(vbox)
self.msgSc = QShortcut(QKeySequence(f'{self.lblscut}'), self)
self.msgSc.activated.connect(lambda: QMessageBox.information(self,'Message', 'Ctrl + M initiated'))
class Mainclass(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Sample Window")
x = Create_workingDict("Accounts","Alt+A")
y = Create_workingDict("Inventory", "Ctrl+B")
def main():
app = QApplication(sys.argv)
ex = Mainclass()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Shortcuts work based on the context (and visibility) of the parent widget: as long as the parent is visible and the context is compatible, they will be triggered, otherwise they will not even be considered.
Be aware that the visibility is of the parent widgets (note the plural) is mandatory, no matter of the context: Qt has no API for a "global" shortcut, not only external to the application, but also within it: there is no shortcut that can automatically work anywhere in the app if (any of) its parent(s) is hidden. The context only ensures that the shortcut can only be activated if the active widget is part of the application (ApplicationShortcut), if the current active window is an ancestor of the shortcut parent (WindowShortcut), if any of the grand[...]parent widgets has focus (WidgetWithChildrenShortcut) or the current parent has it (WidgetShortcut).
Long story short: if the shortcut's parent is not visible (at any level), it will not be triggered.
Not only. In your code, both x and y are potentially garbage collected (they are not due to the fact that the lambda scope avoids destruction, but that's just "sheer dumb luck"), so that code would be actually prone to fail anyway if the activated signal would have been connected to an instance method.
If you want them to be available to the visible window, you must add their parent widgets to that window, even if they're not shown. Otherwise, just add the shortcuts to that window.
For instance:
class Mainclass(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Sample Window")
x = Create_workingDict("Accounts","Alt+A")
y = Create_workingDict("Inventory", "Ctrl+B")
layout = QVBoxLayout(self)
layout.addWidget(x)
layout.addWidget(y)
The only and automatic way to access a global application shortcut from any window of the application is to create a QKeySequence that is checked within an event filter installed on the application.
This is a possible, but crude implementation, so, take it as it is and consider its consequences:
class ShortCutFilter(QObject):
triggered = pyqtSignal(QKeySequence)
def __init__(self, shortcuts=None):
super().__init__()
self.shortcuts = {}
def addShortcut(self, shortcut, slot=None):
if isinstance(shortcut, str):
shortcut = QKeySequence(shortcut)
slots = self.shortcuts.get(shortcut)
if not slots:
self.shortcuts[shortcut] = slots = []
if slot is not None:
slots.append(slot)
return shortcut
def eventFilter(self, obj, event):
if event.type() == event.KeyPress:
keyCode = event.key()
mods = event.modifiers()
if mods & Qt.ShiftModifier:
keyCode += Qt.SHIFT
if mods & Qt.MetaModifier:
keyCode += Qt.META
if mods & Qt.ControlModifier:
keyCode += Qt.CTRL
if mods & Qt.ALT:
keyCode += Qt.ALT
for sc, slots in self.shortcuts.items():
if sc == QKeySequence(keyCode):
self.triggered.emit(sc)
for slot in slots:
try:
slot()
except Exception as e:
print(type(e), e)
return True
return super().eventFilter(obj, event)
def main():
app = QApplication(sys.argv)
shortcutFilter = ShortCutFilter()
app.installEventFilter(shortcutFilter)
shortcutFilter.addShortcut('alt+b', lambda:
QMessageBox.information(None, 'Hello', 'How are you'))
shortcutFilter.triggered.connect(lambda sc:
print('triggered', sc.toString())
ex = Mainclass()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
This, obviously, means that any key press event will pass through the known python bottleneck. A better solution would be to create global QActions and addAction() to any possible top level window that could accept it.
While this approach might seem more complex, it has its benefits; for instance, you have more control on the context of the shortcut: in the case above, you could trigger Alt+B from any window, including the one shown after previously triggering it, which is clearly not a good thing.
Add the layout in main widget.
Then pass the layout to the function where you are creating labels and add them to layout.
Below is the modified code.
Find the details as comments in below code.
Your main window class
class Mainclass(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Sample Window")
#CREATE THE LAYOUT HERE IN MAIN WINDOW
vbox = QVBoxLayout()
x = Create_workingDict("Accounts","Alt+A",vbox) #PASS THE LAYOUT OBJECT (vbox) AS AN ARGUMENT
y = Create_workingDict("Inventory", "Ctrl+B",vbox) #PASS THE LAYOUT OBJECT (vbox) AS AN ARGUMENT
#SET THE LAYOUT TO MAIN WINDOW
self.setLayout(vbox)
Your function where labels created.
Observe that the functions vbox = QVBoxLayout() and self.setLayout(vbox) are moved out of this function to main window.
class Create_workingDict(QWidget):
def __init__(self,lblname,lblscut,vbox): #RECEIVE LAYOUT (vbox) ARGUMENT
super(). __init__()
self.lblname = lblname
self.lblscut = lblscut
lbl_1 = QLabel()
lbl_1.setText(self.lblname)
lbl_2 = QLabel()
lbl_2.setText(self.lblscut)
vbox.addWidget(lbl_1)
vbox.addWidget(lbl_2)
self.msgSc = QShortcut(QKeySequence(f'{self.lblscut}'), self)
self.msgSc.activated.connect(lambda: QMessageBox.information(self,'Message', 'Ctrl + M initiated'))

revert rejected dialogue/form to show last accepted values on reopen

I'm setting up an "options" dialog in a program, where I can change some values and close the dialog with Ok/Cancel to accept of reject my changes. After closing the dialog with cancel and reopening it, i would like the last accepted values to be displayed, however I am know sure how to implement this.
Below is a very simplified version of my code. I chose to instanciate the dialog only once (as opposed to creating a new instance each time I call the dialog), mainly to avoid having to call the __init__ and import data from save files each time I open the dialog.
from PyQt5.QtWidgets import QMainWindow, QPushButton,\
QApplication, QTextEdit, QDialog, QDialogButtonBox
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
btn = QPushButton('open text 1', self)
btn.move(10, 10)
btn.clicked.connect(self.open_dlg)
self.txtdlg = TextDialog()
def open_dlg(self):
if self.txtdlg.exec_() == QDialog.Accepted:
print(self.txtdlg.preferences)
class TextDialog(QDialog):
def __init__(self):
super().__init__()
self.preferences = "text here"
self.resize(200, 150)
self.textedit = QTextEdit(self)
self.textedit.resize(200, 100)
self.textedit.setText(self.preferences)
btns = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel, self)
btns.move(20, 100)
btns.accepted.connect(self.save_and_close)
btns.rejected.connect(self.reject)
def save_and_close(self):
self.preferences = self.textedit.toPlainText()
self.accept()
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
MW = MainWindow()
MW.show()
sys.exit(app.exec_())
As it is, after clicking Cancel the dialog keeps any unsaved changes to its widgets if I reopen it. My fist idea was to connect the cancel button to a close_without_saving method that updates the dialog to the last saved values before closing, but the displayed values will not be up to date if preferences is changed for some reason while the dialog is invisible. Can I run some code when i call exec_ ? Or is the logic behind my implementation wrong somehow?
You have to implement a method that sets the values of the dialog to the default values:
# ...
class MainWindow(QMainWindow):
# ...
def open_dlg(self):
self.txtdlg.reset()
if self.txtdlg.exec_() == QDialog.Accepted:
print(self.txtdlg.preferences)
class TextDialog(QDialog):
# ...
def reset(self):
self.preferences = "text here"
self.textedit.setText(self.preferences)
def save_and_close(self):
self.preferences = self.textedit.toPlainText()
self.accept()
# ...

PyQt Make parent GUI wait till child GUI is closed

I am new to pyqt.I am trying to invoke a child GUI when a button is clicked in the parent GUI. In this process, parent GUI has to wait for the child GUI to be closed by the user after selecting some inputs. But this is not happening, Parent GUI does execute the next lines after which the child GUI has been invoked. Below is the code where I am passing an argument to child GUI from parent GUI. The child GUI will return value based on OK/Cancel button click
Code:
import sys
from PyQt4 import QtGui,QtCore,Qt
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class Child(QtGui.QWidget):
def __init__(self,switches=None):
super(Child,self).__init__()
self.swwidget = QtGui.QWidget()
self.swlayout = QtGui.QGridLayout()
switches = ['abc1','def1']
switches.sort()
self.switches = switches
def switchesUI(self):
self.swwidget.setWindowModality(QtCore.Qt.ApplicationModal)
self.swl = len(self.switches)
self.sw = {}
self.addsw = []
print ("I am in switchesUI")
#Add the switches to layout dynamically
for i in range(self.swl):
self.sw[i] = QtGui.QCheckBox(self.switches[i])
self.swlayout.addWidget(self.sw[i],i,0)
self.swbuttonbox = QtGui.QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel);
self.swbuttonbox.setOrientation(QtCore.Qt.Horizontal)
self.swlayout.addWidget(self.swbuttonbox)
self.swwidget.setWindowTitle('Switches')
self.swwidget.setLayout(self.swlayout)
self.swwidget.show()
self.connect(self.swbuttonbox,QtCore.SIGNAL("accepted()"),self.swaccept)
self.connect(self.swbuttonbox,QtCore.SIGNAL("rejected()"),self.swreject)
def swaccept(self):
for i in range(self.swl):
if self.sw[i].isChecked():
self.addsw.append(self.switches[i])
self.swwidget.close()
return self.addsw
def swreject(self):
self.swwidget.close()
return None
class Parent(QtGui.QWidget):
def __init__(self):
super(Parent,self).__init__()
QtGui.QWidget.__init__(self)
self.button = QtGui.QPushButton('Test', self)
self.layout = QtGui.QVBoxLayout(self)
self.layout.addWidget(self.button)
self.assw = ['Test1','Test2']
self.CH = Child(self.assw)
self.connect(self.button,SIGNAL("clicked()"),self.popup)
print ("Child GUI closed")
def popup(self):
self.CH.switchesUI()
def main():
app = QtGui.QApplication(sys.argv)
form = Parent()
form.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
After the button "Test" is clicked, a child GUI will pop-up. I don't want the statement "Child GUI Closed" to be printed till the child GUI is closed.
Can someone suggest me how to achieve this functionality ?
You have to handle closeEvent to perform operations when a window wants to close, also since your Child class inherits from QWidget which means it's a QWidget itself you dont need to create another one with self.swwidget
import sys
from PyQt4 import QtGui,QtCore,Qt
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class Child(QtGui.QWidget):
def __init__(self,switches=None):
super(Child,self).__init__()
# self.swwidget = QtGui.QWidget() # you don't need to do this you can add all the properties to self
self.swlayout = QtGui.QGridLayout()
switches = ['abc1','def1']
switches.sort()
self.switches = switches
def switchesUI(self):
self.setWindowModality(QtCore.Qt.ApplicationModal)
self.swl = len(self.switches)
self.sw = {}
self.addsw = []
print ("I am in switchesUI")
#Add the switches to layout dynamically
for i in range(self.swl):
self.sw[i] = QtGui.QCheckBox(self.switches[i])
self.swlayout.addWidget(self.sw[i],i,0)
self.swbuttonbox = QtGui.QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel);
self.swbuttonbox.setOrientation(QtCore.Qt.Horizontal)
self.swlayout.addWidget(self.swbuttonbox)
self.setWindowTitle('Switches')
self.setLayout(self.swlayout)
self.show()
self.connect(self.swbuttonbox,QtCore.SIGNAL("accepted()"),self.swaccept)
self.connect(self.swbuttonbox,QtCore.SIGNAL("rejected()"),self.swreject)
def swaccept(self):
for i in range(self.swl):
if self.sw[i].isChecked():
self.addsw.append(self.switches[i])
self.close()
return self.addsw
def swreject(self):
self.close()
return None
def closeEvent(self, event):
print ("Child GUI closed")
class Parent(QtGui.QWidget):
def __init__(self):
super(Parent,self).__init__()
QtGui.QWidget.__init__(self)
self.button = QtGui.QPushButton('Test', self)
self.layout = QtGui.QVBoxLayout(self)
self.layout.addWidget(self.button)
self.assw = ['Test1','Test2']
self.CH = Child(self.assw)
self.connect(self.button,SIGNAL("clicked()"),self.popup)
def popup(self):
self.CH.switchesUI()
def main():
app = QtGui.QApplication(sys.argv)
form = Parent()
form.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

Call a class to add tabs in a QTabWidget

I am trying to create tabs each time that i press a button that i added to a toolbar. For this i create a windows in Pyqt, and call the class NewTab, which has the QWidget that i want to add in the QTabWidget.
This is my code:
class Window(QMainWindow):
def __init__(self):
self.tabWidget = QTabWidget()
self.tabWidget.setTabsClosable(True)
self.setCentraWidget(self.tabWidget)
self.setLayout(NewTab().newTab.tab_layout)
class NewTab(QWidget):
list_1 = [] #These lists are for another method that i will use later
list_2 = []
def __init__(self):
QWidget.__init__(self)
self.newTab()
def newTab(self):
self.new_tab = QWidget()
Window.tabWidget.addTab(self.new_tab)
plot = MatPlotLibFigure() #This is another class that i add
#in the splitter as a widget
self.splitter = QSplitter(Qt.Vertical)
self.splitter.addWidget(plot)
self.splitter2 = QSplitter(Qt.Horizontal)
self.splitter2.addWidget(self.splitter)
self.tab_layout = QHBoxLayout(Window.tabWidget)
self.tab_layout.addWidget(self.splitter2)
print "New Tab created"
def Close(self):
pass
app = QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec_())
I do not know what happens. It does not work. What am i doing wrong? Hope you can help me

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