Accessing parent attribute from inside child window - python

My main window has a variable self.x setup in the INIT. Then I need to access this value from the popup password box that is created. This is just a test script to get my point accross. I'm assuming it's something with the inheritance stuff, all that is still a little foreign to me.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
from PyQt5 import QtWidgets
# Created by MyWindow
class LoginDlg(QtWidgets.QDialog):
def __init__(self):
super(LoginDlg, self).__init__()
self.password = QtWidgets.QLineEdit()
# THIS IS THE LINE I NEED IT TO PULL X FROM PARENT
self.password.setText(self.x)
self.button_box = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)
self.button_box.accepted.connect(self.accept)
self.button_box.rejected.connect(self.reject)
layout = QtWidgets.QFormLayout()
layout.setFieldGrowthPolicy(QtWidgets.QFormLayout.AllNonFixedFieldsGrow)
layout.addRow('Password', self.password)
layout.addWidget(self.button_box)
self.setLayout(layout)
self.setWindowTitle("Login")
self.setMinimumWidth(350)
# Main parent window
class MyWindow(QtWidgets.QWidget):
def __init__(self):
super(MyWindow, self).__init__()
self.edit = QtWidgets.QLineEdit()
button = QtWidgets.QPushButton("Get input from dialog")
button.clicked.connect(self.get_login)
layout = QtWidgets.QHBoxLayout()
layout.addWidget(self.edit)
layout.addWidget(button)
self.setLayout(layout)
self.x = "PASS THIS STRING"
def get_login(self):
login = LoginDlg()
if login.exec_():
self.edit.setText(login.password.text())
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = MyWindow()
window.show()
sys.exit(app.exec_())
Normally I would just pass this data through the constructor, but let's say I have a lot of data that I don't want to pass back and forth, is there a way to access parent attributes another way?

Don't get too complicated and just pass the value of that variable through the constructor:
class LoginDlg(QtWidgets.QDialog):
def __init__(self, x):
super(LoginDlg, self).__init__()
self.password = QtWidgets.QLineEdit()
self.password.setText(x)
# ...
class MyWindow(QtWidgets.QWidget):
# ...
def get_login(self):
login = LoginDlg(self.x)
if login.exec_():
self.edit.setText(login.password.text())
Another similar option is to access "password" in get_login:
class LoginDlg(QtWidgets.QDialog):
def __init__(self):
super(LoginDlg, self).__init__()
self.password = QtWidgets.QLineEdit()
# self.password.setText(self.x)
self.button_box = QtWidgets.QDialogButtonBox(
QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel
)
# ...
class MyWindow(QtWidgets.QWidget):
# ...
def get_login(self):
login = LoginDlg()
login.password.setText(self.x)
if login.exec_():
self.edit.setText(login.password.text())
Note: my answer does not try to implement what the OP asks is to "access a class from another" (there is no relationship so using parent-child is incorrect) since that complicates the modularization because if a object could affect the other, in general. I think the OP has an XY problem as it asks how to implement a possible solution instead of the underlying problem which is "how to share information between classes"

Related

QtCore.signal not doing anything

I am a beginner with PyQt5 and I am having trouble to use QtCore.signal
I'd like to send a signal when I press my buttons and switch the current widget displayed.
I don't have any errors when I run my code but when I press the buttons nothing happen and I guess it is because I am doing something wrong with the QtCore.Signal
Here is my code :
from PySide2 import QtCore, QtWidgets
from ui_Page_accueil import Ui_MainWindow
from ui_NouvelleVerif import Ui_Dialog as Ui_NouvelleVerif
from ui_NouvelleVerifEssieux import Ui_Dialog as Ui_NouvelleVerifEssieux
import sys
class MainWindowUi(Ui_MainWindow):
to_NouvelleVerif = QtCore.Signal()
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
self.setupUi(self)
#self.pushButton.clicked.connect(self.pushbutton_handler1)
self.pushButton_2.clicked.connect(self.pushbutton_handler2)
#def pushbutton_handler1(self):
# self.to_MainWindow.emit()
def pushbutton_handler2(self):
self.to_NouvelleVerif.emit()
class NouvelleVerifUi(QtWidgets.QWidget, Ui_NouvelleVerif):
to_MainWindow = QtCore.Signal()
to_NouvelleVerifEssieux = QtCore.Signal()
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.setupUi(self)
self.pushButton.clicked.connect(self.pushbutton_handler1)
#self.pushButton_2.clicked.connect(self.pushbutton_handler2)
self.pushButton_3.clicked.connect(self.pushbutton_handler3)
def pushbutton_handler1(self):
self.to_MainWindow.emit()
#def pushbutton_handler2(self):
# self.switch_window.emit()
def pushbutton_handler3(self):
self.to_NouvelleVerifEssieux.emit()
class NouvelleVerifEssieuxUi(QtWidgets.QWidget, Ui_NouvelleVerifEssieux):
to_NouvelleVerif = QtCore.Signal()
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.setupUi(self)
self.pushButton.clicked.connect(self.pushbutton_handler1)
def pushbutton_handler1(self):
self.to_NouvelleVerif.emit()
class Controller :
app = QtWidgets.QApplication(sys.argv)
widget = QtWidgets.QStackedWidget()
MainWindow = MainWindowUi()
NouvelleVerif = NouvelleVerifUi()
NouvelleVerifEssieux = NouvelleVerifEssieuxUi()
def __init__(self):
self.widget.addWidget(self.MainWindow) # create an instance of the first page class and add it to stackedwidget
self.widget.addWidget(self.NouvelleVerif) # adding second page
self.widget.addWidget(self.NouvelleVerifEssieux)
self.widget.setCurrentWidget(self.MainWindow) # setting the page that you want to load when application starts up. you can also use setCurrentIndex(int)
def show_MainWindow(self):
self.NouvelleVerif = NouvelleVerifUi()
self.NouvelleVerif.to_MainWindow.connect(self.show_MainWindow)
self.widget.setCurrentWidget(self.MainWindow)
def show_NouvelleVerif(self):
self.MainWindow = MainWindowUi()
self.NouvelleVerifEssieux = NouvelleVerifEssieuxUi()
self.MainWindow.to_NouvelleVerif.connect(self.show_NouvelleVerif)
self.NouvelleVerifEssieux.to_NouvelleVerif.connect(self.show_NouvelleVerif)
self.widget.setCurrentWidget(self.NouvelleVerif)
def show_NouvelleVerifEssieux(self):
self.NouvelleVerif = NouvelleVerifUi()
self.NouvelleVerif.to_NouvelleVerifEssieux.connect(self.show_NouvelleVerifEssieux)
self.widget.setCurrentWidget(self.NouvelleVerifEssieux)
def main():
controller = Controller()
controller.widget.show()
sys.exit(controller.app.exec_())
if __name__ == '__main__':
main()
#musicamante I don't know why I though the QtCore.Signal could be call even if he was in a function which has not been called. I did what you said and I realise I made another mistake with the widgets.
In the controller class they are create and add to the QStackedWidget in the __init__, but I was creating new ones and trying to set them as CurrentWidget without adding them to the QStackedWidget.
#alexpdev I wanted to navigate through my three differents with pushButton UI this way :
Start with MainWindowUI
MainWindowUI pushButton_2.clicked --> set the current widget display to NouvelleVerifUI
NouvelleVerifUI pushButton_1.clicked --> set the current widget display back to MainWindowUI
NouvelleVerifUI pushButton_3.clicked --> set the current widget display to NouvelleVerifEssieuxUI
NouvelleVerifEssieuxUI pushButton_1.clicked --> set the current widget display back to NouvelleVerifUI
Now everything work I did what you said #musicamante and I also use the UI created at first.
class Controller :
app = QtWidgets.QApplication(sys.argv)
widget = QtWidgets.QStackedWidget()
MainWindow = MainWindowUi()
NouvelleVerif = NouvelleVerifUi()
NouvelleVerifEssieux = NouvelleVerifEssieuxUi()
def __init__(self):
self.widget.addWidget(self.MainWindow) # create an instance of the first page class and add it to stackedwidget
self.widget.addWidget(self.NouvelleVerif) # adding second page
self.widget.addWidget(self.NouvelleVerifEssieux)
self.widget.setCurrentWidget(self.MainWindow) # setting the page that you want to load when application starts up. you can also use setCurrentIndex(int)
self.NouvelleVerif.to_MainWindow.connect(self.show_MainWindow)
self.MainWindow.to_NouvelleVerif.connect(self.show_NouvelleVerif)
self.NouvelleVerifEssieux.to_NouvelleVerif.connect(self.show_NouvelleVerif)
self.NouvelleVerif.to_NouvelleVerifEssieux.connect(self.show_NouvelleVerifEssieux)
def show_MainWindow(self):
self.widget.setCurrentWidget(self.MainWindow)
def show_NouvelleVerif(self):
self.widget.setCurrentWidget(self.NouvelleVerif)
def show_NouvelleVerifEssieux(self):
self.widget.setCurrentWidget(self.NouvelleVerifEssieux)
Thank you all for your time

populate python qt widget with items from class

I've written a script here in python which consists of a class I made called 'Profile'. Each profile has a 'Name' and list of 'Plugin Names'
I need help getting the list to populate the Ui. When the ui is initiated I want the dropdownlist to be populated with the 'Names' of each profile. Then as the 'Profile' is selected, the listbox be populate with the appropriate plugin names. I've commented out the profiles as I wasn't sure how to properly get them working.
Hope that is clear explaining.
import sys, os
from PyQt4 import QtCore, QtGui
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
class Profile(object):
def __init__(self, name, plugins):
self.name = name
self.plugins = plugins
def initUI(self):
# UI CONTORLS
uiProfiles = QtGui.QComboBox(self)
uiPluginList = QtGui.QListWidget(self)
uiLaunch = QtGui.QPushButton("Launch")
# STYLING
uiLaunch.setToolTip('This is a <b>QPushButton</b> widget')
uiLaunch.resize(uiLaunch.sizeHint())
uiLaunch.setMinimumHeight(30)
# UI LAYOUT
grid = QtGui.QGridLayout()
grid.setSpacing(10)
grid.addWidget(uiProfiles, 1, 0)
grid.addWidget(uiPluginList, 2, 0)
grid.addWidget(uiLaunch, 3, 0)
self.setLayout(grid)
self.setGeometry(300, 500, 600, 200)
self.setWindowTitle('3ds Max Launcher')
self.resize(400,150)
self.show()
# profiles = [
# Profile(name="3ds Max Workstation", plugins=["FumeFX", "Afterworks", "Multiscatter"]),
# Profile(name="3ds Max All Plugins", plugins=["FumeFX"]),
# Profile(name="3ds Max Lite", plugins=["default 3ds max"]),
# ]
# for p in profiles:
# uiProfiles.addItem(p.name)
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
You had a few problems. Your MainWindow never got shown. You were defining the Profile class inside your Example class (instead of on it's own). You also had no event function that did something when the user changed the profile list.
I made put the profile names into a QStringListModel. This means that any changes to the names in the model will automatically update the widget. You don't have to do it this way, but it's easier in larger projects and not really any harder to do.
I also connected a function to the event that occurs when the value of the combo box is changed. You will need to make another event function and connect it to a launch button event as well.
import sys, os
from PyQt4 import QtCore, QtGui
class Profile(object):
def __init__(self, name, plugins):
self.name = name
self.plugins = plugins
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.profiles = [Profile(name="3ds Max Workstation", plugins=["FumeFX", "Afterworks", "Multiscatter"]),
Profile(name="3ds Max All Plugins", plugins=["FumeFX"]),
Profile(name="3ds Max Lite", plugins=["default 3ds max"])]
profile_names = [p.name for p in self.profiles]
# make a model to store the profiles data in
# changes to data will automatically appear in the widget
self.uiProfilesModel = QtGui.QStringListModel()
self.uiProfilesModel.setStringList(profile_names)
# UI CONTORLS
self.uiProfiles = QtGui.QComboBox(self)
self.uiPluginList = QtGui.QListWidget(self)
self.uiLaunch = QtGui.QPushButton("Launch")
# associate the model to the widget
self.uiProfiles.setModel(self.uiProfilesModel)
# connect signals
self.uiProfiles.currentIndexChanged.connect(self.on_select_profile)
# STYLING
self.uiLaunch.setToolTip('This is a <b>QPushButton</b> widget')
self.uiLaunch.resize(self.uiLaunch.sizeHint())
self.uiLaunch.setMinimumHeight(30)
# UI LAYOUT
grid = QtGui.QGridLayout()
grid.setSpacing(10)
grid.addWidget(self.uiProfiles, 1, 0)
grid.addWidget(self.uiPluginList, 2, 0)
grid.addWidget(self.uiLaunch, 3, 0)
self.setLayout(grid)
self.setGeometry(300, 500, 600, 200)
self.setWindowTitle('3ds Max Launcher')
self.resize(400,150)
self.show()
# run once to fill in list
self.on_select_profile(0)
def on_select_profile(self, index):
# clear list
self.uiPluginList.clear()
# populate list
for plugin in self.profiles[index].plugins:
self.uiPluginList.addItem(plugin)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
ex = Example()
ex.show()
sys.exit(app.exec_())

PySide multi-window, get QStackWidget to work

I need to create multi-window GUI, first I tried it with QWidgets, but finally I discover QStackWidget tool I need to use. So Im trying to, but Ive got some problems. Thanks for Your time.
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow,self).__init__()
self.mainWidget = MainWidget()
self.searchWidget = SearchWidget()
self.sWidget = QStackedWidget()
self.sWidget.addWidget(self.mainWidget)
self.sWidget.addWidget(self.searchWidget)
self.initUI()
and calling setCurrentWidget from the sub_widget class:
class MainWidget(QWidget):
def __init__(self, parent=MainWindow):
super(MainWidget,self).__init__()
self.initUI()
def initUI(self):
searchButton = QPushButton('searchButton',self)
optionButton = QPushButton('optionButton',self)
quitButton = QPushButton('quitButton',self)
listButton = QPushButton('listButton',self)
searchButton.clicked.connect(self.goSearch)
hbox = QHBoxLayout()
hbox.addWidget(listButton)
hbox.addWidget(quitButton)
vbox = QVBoxLayout()
vbox.addStretch(1)
vbox.addWidget(searchButton)
vbox.addWidget(optionButton)
vbox.addLayout(hbox)
self.setLayout(vbox)
def goSearch(self):
self.parent().sWidget.setCurrentWidget(self.parent().searchWidget)
Ive got this message from IDE:
self.parent().sWidget.setCurrentWidget(self.parent().searchWidget)
AttributeError: 'PySide.QtGui.QStackedWidget' object has no attribute 'sWidget'
What is the thing Im doing wrong?
I'm going to comment on the code you posted here: http://pastebin.com/fBfS1X5m
An important thing to know is that you can put widgets within widgets and so on. For example:
class Widget(QWidget):
def __init__(self, parent=None):
layout = QVBoxLayout(self)
childWidget = QWidget(parent=self)
layout.addWidget(childWidget)
Just a quick note: You don't need setLayout if you pass self to the main layout constructor - via the docs.
Anyways, what I'm trying to illustrate here is that the QStackedWidget and the SearchWidget really shouldn't be a part of the MainWindow, but should live inside their own relevant widget that will handle switching between the QStackedWidget pages.
For example the MainWindow.__init__ would only look like this:
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.mainWidget = MainWidget(self)
self.setCentralWidget(self.mainWidget)
self.initUI()
Your MainWidget would then look something like:
class MainWidget(QtGui.QWidget):
...
def initUI(self):
...
self.stack = QtGui.QStackedWidget(parent=self)
self.searchWidget = SearchWidget(parent=self)
self.searchWidget.searchButton.clicked.connect(self.goSearch)
self.backWidget = BackWidget(parent=self)
self.backWidget.backButton.clicked.connect(self.goBack)
...
def goSearch(self):
self.stack.setCurrentWidget(self.backWidget)
def goBack(self):
self.stack.setCurrentWidget(self.searchWidget)
I've renamed some of the class names to make a little more sense (To me at least). SearchWidget was your old MainWidget. BackWidget was your old SearchWidget. Following those changes SearchWidget would look the same as your old MainWidget with one exception - we save a reference to the search button so we can access it in the MainWidget class as seen above (when we connect their signals to our slots). We do the same for the button in BackWidget.
The two renamed "child" widgets:
class SearchWidget(QtGui.QWidget):
...
def initUI(self):
self.searchButton = QtGui.QPushButton('searchButton', parent=self)
optionButton = QtGui.QPushButton('optionButton', parent=self)
quitButton = QtGui.QPushButton('quitButton', parent=self)
listButton = QtGui.QPushButton('listButton', parent=self)
vbox = QtGui.QVBoxLayout(self)
vbox.addStretch(1)
vbox.addWidget(self.searchButton)
vbox.addWidget(optionButton)
hbox = QtGui.QHBoxLayout()
hbox.addWidget(listButton)
hbox.addWidget(quitButton)
vbox.addLayout(hbox)
class BackWidget(QtGui.QWidget):
...
def initUI(self):
self.backButton = QtGui.QPushButton('GoBack', parent=self)
So now we have something like:
MainWindow
|---MainWidget
|---QStackedWidget
|---SearchWidget
|---BackWidget
You can find the full working code here.
This line:
def __init__(self, parent=MainWindow):
sets the MainWindow class as a default argument, when you actually need an instance. But even if it was an instance, in the next line, you also fail to pass it on to the base-class:
super(MainWidget,self).__init__()
What you need to do instead is something like this:
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow,self).__init__()
# pass an instance of MainWindow here
self.mainWidget = MainWidget(self)
...
class MainWidget(QWidget):
def __init__(self, parent):
# pass the parent to the base-class
super(MainWidget, self).__init__(parent)
...
UPDATE:
The stack-widget will re-parent any widgets added to it, so that it becomes the parent. There are ways of working around this, but I think the real problem with your code is that you have the structure backwards. The buttons that set the current widget should be controlled by the main-window, and the widgets in the stack should work completely independantly of that.

How do i use a While/For loop to create classes?? (Python/PyQt)

I have created the code to create multiple classes with different class names (I.e. Question1, Question2 etc.)
import sys
from PyQt4 import QtCore, QtGui
class StartTest(QtGui.QMainWindow):
def __init__(self, parent=None):
super(StartTest, self).__init__(parent)
self.central_widget = QtGui.QStackedWidget()
self.setCentralWidget(self.central_widget)
question1 = Question1(self)
self.central_widget.addWidget(question1)
self.central_widget.setCurrentWidget(question1)
question1.proceed.clicked.connect(self.question2)
def question2(self):
question2 = Question2(self)
self.central_widget.addWidget(question2)
self.central_widget.setCurrentWidget(question2)
i = 0
while i<2:
class Question+i(QtGui.QWidget):
def __init__(self, parent=None):
super(Question+i, self).__init__(parent)
question = QtGui.QLabel('What is 5+5?')
self.proceed = QtGui.QPushButton("Proceed")
self.Answer = QtGui.QLineEdit(self)
layout = QtGui.QFormLayout()
layout.addRow(question, self.Answer)
layout2 = QtGui.QVBoxLayout()
layout2.addLayout(layout)
layout2.addWidget(self.proceed)
self.setLayout(layout2)
print('Question'+i)
if __name__ == '__main__':
User = ''
app = QtGui.QApplication([])
window = StartTest()
window.showFullScreen()
app.exec_()
However when I write:
i = 0
while i<2:
class Question+i(QtGui.QWidget):
i obtain a syntax error on the Question+i part error, which is undertandable. But how would i overcome this whilst still creating multiple classes with different class names???
See this related question with answers on how to set class names dynamically.
You need to implement a factory method. For you it might look like this:
def factory(num) :
BaseClass = QtGui.QWidget
class NewClass(BaseClass): pass
NewClass.__name__ = "factory_%s%d" % (BaseClass.__name__, num)
return NewClass

PySide (or PyQt) signals and slots basics

Consider a simple example like this which links two sliders using signals and slots:
from PySide.QtCore import *
from PySide.QtGui import *
import sys
class MyMainWindow(QWidget):
def __init__(self):
QWidget.__init__(self, None)
vbox = QVBoxLayout()
sone = QSlider(Qt.Horizontal)
vbox.addWidget(sone)
stwo = QSlider(Qt.Horizontal)
vbox.addWidget(stwo)
sone.valueChanged.connect(stwo.setValue)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyMainWindow()
w.show()
sys.exit(app.exec_())
How would you change this so that the second slider moves in the opposite direction as the first? Slider one would be initialized with these values:
sone.setRange(0,99)
sone.setValue(0)
And slider two would be initialized with these values:
stwo.setRange(0,99)
stwo.setValue(99)
And then the value of stwo would be 99 - sone.sliderPosition.
How would you implement the signal and slot to make this work? I would appreciate a working example that builds on the simple example above.
Your example is a bit broken, because you forgot to set the parent of the layout, and also to save the slider widgets as member attributes to be accessed later... But to answer your question, its really as simple as just pointing your connection to your own function:
class MyMainWindow(QWidget):
def __init__(self):
QWidget.__init__(self, None)
vbox = QVBoxLayout(self)
self.sone = QSlider(Qt.Horizontal)
self.sone.setRange(0,99)
self.sone.setValue(0)
vbox.addWidget(self.sone)
self.stwo = QSlider(Qt.Horizontal)
self.stwo.setRange(0,99)
self.stwo.setValue(99)
vbox.addWidget(self.stwo)
self.sone.valueChanged.connect(self.sliderChanged)
def sliderChanged(self, val):
self.stwo.setValue(self.stwo.maximum() - val)
Note how sliderChanged() has the same signature as the original setValue() slot. Instead of connecting one widget directly to the other, you connect it to a custom method and then transform the value to what you want, and act how you want (setting a custom value on stwo)
You can connect signals to functions that do things. Your code isn't structured to do that easily and required refactoring, so you can do it the easy way:
stwo.setInvertedAppearance(True)
sone.valueChanged.connect(stwo.setValue)
Here's the way I did it. I added this class which reimplements setValue. (I got the idea from http://zetcode.com/tutorials/pyqt4/eventsandsignals/)
class MySlider(QSlider):
def __init__(self):
QSlider.__init__(self, Qt.Horizontal)
def setValue(self, int):
QSlider.setValue(self, 99-int)
Here's the complete code. Is this a good approach?
from PySide.QtCore import *
from PySide.QtGui import *
import sys
class MySlider(QSlider):
def __init__(self):
QSlider.__init__(self, Qt.Horizontal)
def setValue(self, int):
QSlider.setValue(self, 99-int)
class MyMainWindow(QWidget):
def __init__(self):
QWidget.__init__(self, None)
vbox = QVBoxLayout()
sone = QSlider(Qt.Horizontal)
sone.setRange(0,99)
sone.setValue(0)
vbox.addWidget(sone)
stwo = MySlider()
stwo.setRange(0,99)
stwo.setValue(0)
vbox.addWidget(stwo)
sone.valueChanged.connect(stwo.setValue)
self.setLayout(vbox)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyMainWindow()
w.show()
sys.exit(app.exec_())

Categories