I've created a custom widget in pyqt4 that I've worked on and tested and now am ready to load it into my main window. Since it doesn't show up in designer, I need to manually add it to my main window manually.
My widget uses uic to load the ui file instead of converting it to a py file (it's been quicker less hassle so far) so it looks something like this:
class widgetWindow(QtGui.QWidget):
def __init__(self, parent = None):
super(widgetWindow, self).__init__(parent)
self.ui = uic.loadUi("widget.ui")
#everything else
now in my main class (example for brevity) I create the layout, add the widget to the layout and then add it to the main widget
class main(QtGui.QMainWindow):
def __init__(self, parent = None):
super(main, self).__init__(parent)
self.ui = uic.loadUi("testWindow.ui")
mainlayout = QtGui.QVBoxLayout()
window = widgetWindow(self)
mainlayout.addWidget(window)
centerWidget = QtGui.QWidget()
centerWidget.setLayout(mainlayout)
self.ui.setCentralWidget(centerWidget)
There are no errors thrown, and it will make space for the widget, but it simply won't show anything.
adding in the line window.ui.show() will just pop open a new window overtop the space that it should be occupying on the main window. What am I missing?
Doing some more research into the uic loader, there are two ways to load a ui file. The way I'm using it in the question is one way, the other way is with the uic.loadUiType(). This creates both the base class and the form class to be inherited by the class object instead of just the QtGui.QWidget class object.
widgetForm, baseClass= uic.loadUiType("addTilesWidget.ui")
class windowTest(baseClass, widgetForm):
def __init__(self, parent = None):
super(windowTest, self).__init__(parent)
self.setupUi(self)
This way, the widget can be loaded into another form as expected. As for exactly why, I haven't found that answer yet.
Some more info on the different setup types: http://bitesofcode.blogspot.com/2011/10/comparison-of-loading-techniques.html
Try to add the parent argument into the loadUi statements:
self.ui = uic.loadUi("widget.ui",parent)
self.ui = uic.loadUi("testWindow.ui",self)
And try the following line at the end of your main class.
self.setCentralWidget(centerWidget)
You need to specify that 'centerWidget' is the central widget of the main window.
i.e your code for class main should be have a line like:
self.setCentralWidget(centerWidget)
class main(QMainWindow):
def __init__(self, parent = None):
super(main, self).__init__(parent)
....
self.setCentralWidget(centerWidget)
Related
I have a window that contains two main widgets, both of which are containers of other widgets, which in turn may contain further widgets.
Each Widget is a separate class ( to maintain code readability ), and child widgets are instantiated directly inside this class, which is similar to something like React components structure.
I would like to call some methods from any widget to a top level one, or send signals from a deeply nested widget to one near top level without having to use something akin to "self.parent().parent().parent().doStuff(args)", it works but if hierarchy changes this will raise bugs, it gets harder to maintain the more complex my GUI gets.
Edit : Here is a simplified example of what I'm trying to achieve :
from PySide2.QtWidgets import *
import sys
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.central = QWidget()
self.central.setObjectName("Central_Widget")
self.setCentralWidget(self.central)
mainLayout = QHBoxLayout(self.central)
self.central.setStyleSheet("""
#Central_Widget{
background-color: #2489FF;
}""")
#init mainView widget
self.leftSide = leftWindow(self.central)
self.rightSide = rightWindow(self.central)
mainLayout.addWidget(self.leftSide)
mainLayout.addWidget(self.rightSide)
class leftWindow(QWidget):
def __init__(self, parent):
super().__init__(parent)
self.setObjectName("leftWindow")
layout = QVBoxLayout()
self.setLayout(layout)
self.label = QLabel(" Left ")
self.leftButton = customBtn(" Left Button ", "someSVGHere", self)
layout.addWidget(self.label)
layout.addWidget(self.leftButton)
self.setStyleSheet("border : 2px solid white;")
class rightWindow(QWidget):
def __init__(self, parent):
super().__init__(parent)
self.setObjectName("rightWindow")
layout = QVBoxLayout()
self.setLayout(layout)
self.label = QLabel(" Right ")
self.rightButton = customBtn(" Right Button ", "someSVGHere", self)
layout.addWidget(self.label)
layout.addWidget(self.rightButton)
self.setStyleSheet("border : 2px solid black;")
class customBtn(QWidget):
def __init__(self, text, svgIcon, parent):
super().__init__(parent)
layout = QVBoxLayout()
self.setLayout(layout)
self.btnText = QLabel(text)
self.Icon = svgIcon
layout.addWidget(self.btnText)
# setup the icon and the stylesheet etc ....
def mouseReleaseEvent(self, event):
# if it's the right button getting clicked, change the stylesheet of the central widget in main window
# How do I do that here ???
return super().mouseReleaseEvent(event)
# Launcher
if __name__ == '__main__':
app = QApplication(sys.argv)
win = MainWindow()
win.setWindowTitle("nesting test")
win.show()
sys.exit(app.exec_())
In the above example, I want to change the background of the central widget in the MainWindow class using the right button inside the rightWindow widget, in this example it's only 2 layers of nesting so it's not really deep, but in my App I would have up to 5 or 6 layers.
ideally, each class would be in a separate file but that doesn't change this example.
I unfortunately cannot use QT designer because I'm building a modern GUI app, so every single widget will be a custom one, and I would like to keep my code fragmented instead of instantiating everything in a single class like what QT designer generates.
Generally speaking, when dealing with object hierarchy, child objects should not attempt to directly interact their parents. In fact, they should always be able to "work" on their own, no matter of their parent, and even when they have no parent at all.
Considering this, instead of connecting a signal to a slot of a [great-][grand]parent, it should be that parent that connects the [great-][grand]child signal to its own slot. This perspective is important, because it's also what allows us to connect sibling objects that are (nor could) be aware of each other.
class customBtn(QWidget):
customSignal = pyqtSignal(Qt.MouseButton)
def mouseReleaseEvent(self, event):
self.customSignal.emit(event.button())
class MainWindow(QMainWindow):
def __init__(self):
# ...
self.leftSide = leftWindow(self.central)
self.leftSide.leftButton.customSignal.connect(self.something)
def something(self):
# whatever
If the structure has many levels, it's also good practice to create custom signals for the intermediate classes, since signals can also be chained as long as they have a compatible signature (the target signal must have the same argument types or fewer arguments with same types).
class leftWindow(QWidget):
customSignal = pyqtSignal()
def __init__(self, parent):
# ...
self.leftButton = customBtn(" Left Button ", "someSVGHere", self)
self.leftButton.customSignal.connect(self.customSignal)
class MainWindow(QMainWindow):
def __init__(self):
# ...
self.leftSide = leftWindow(self.central)
self.leftSide.customSignal.connect(self.something)
In the case above, the signal of leftWindow doesn't have any arguments, so it's compatible with the button signal, as those arguments will be automatically discarded.
I am working on developing a GUI with PyQt5. This is my first step into OOP, and I'm trying to teach myself as I go. I'm struggling with understanding when classes inherit methods/attributes etc and what methods they have available -- I guess it is a scope-related question? I have produced a MWE to my GUI below. In total, there will be many more pages and signals/slots.
What I want:
The stack should initialize with the "MainMenu" widget/object showing (left image below). Clicking on "Next Page" button should switch the stack order to put the "OtherPage" widget/object on top (right image below). I am creating each page as a class, thinking this would be a good way to organize my project. Is this good or bad practice?
What happens now:
The GUI works (initializes) if the line nextPg.clicked.connect(self.drawOtherPage()) is commented out, but of course then clicking on the button does nothing. I can switch the initial stack order so that "other" widget is on top of the stack and it shows up fine, so I think that class is also working. When the above line is included in the code, the following error is thrown:
in __init__
nextPg.clicked.connect(self.drawOtherPage())
AttributeError: 'MainMenu' object has no attribute 'drawOtherPage'
What I've tried
I thought that the call to super() was supposed to allow the child class (in this case MainMenu) to inherit the methods from the parent class (RootInit). Therefore, I would think this should make the drawOtherPage method available to the button connect signal. Obviously, the error isa result of the method not being available.
What am I doing wrong? Should I be creating these "page" widgets in methods instead? Do they need to be under the RootInit class or can they live in the top level of the .py file? I'm trying to follow best practices as the project will become pretty large in the end. Fortunately, most of it should be pages with variations based on what buttons were clicked to get there -- I therefore thought classes would be helpful. Please be harsh on the code and my python/PyQt vernacular, trying to learn -- thanks!
import sys, os
from PyQt5.QtWidgets import *
from PyQt5 import QtGui, QtCore
class RootInit(QMainWindow):
# root window
def __init__(self, parent=None):
QMainWindow.__init__(self)
self.root = QWidget()
self.stack = QStackedWidget()
rootLayout = QVBoxLayout()
rootLayout.addWidget(self.stack)
self.root.setLayout(rootLayout)
self.setCentralWidget(self.root)
self.initializeGUI()
def initializeGUI(self):
self.main = MainMenu(self) # build MainMenu (class)
self.other = OtherPage(self) # build OtherPage (class)
self.stack.addWidget(self.main)
self.stack.addWidget(self.other)
def drawMain(self):
self.stack.setCurrentIndex(0)
def drawOtherPage(self):
self.stack.setCurrentIndex(1)
class MainMenu(QWidget):
# class for main menu
def __init__(self, parent=None):
QWidget.__init__(self, parent)
super().__init__()
mainLayout = QGridLayout() # layout for entire main menu
quitBtn = QPushButton("Quit")
quitBtn.clicked.connect(QtCore.QCoreApplication.instance().quit)
nextPg = QPushButton("Next page")
nextPg.clicked.connect(self.drawOtherPage())
mainLayout.addWidget(quitBtn, 0, 0)
mainLayout.addWidget(nextPg, 0, 1)
self.setLayout(mainLayout)
class OtherPage(QWidget):
# class for another menu
def __init__(self, parent=None):
QWidget.__init__(self, parent)
label = QLabel("test label")
layout = QGridLayout() #
layout.addWidget(label, 0, 0)
self.setLayout(layout)
if __name__ == '__main__':
app = QApplication(sys.argv)
root = RootInit()
root.setWindowTitle("Title")
root.show()
sys.exit(app.exec_())
Your code has the following errors:
The variable self refers to the same instance of the class, in your case self refers to an instance of MainMenu, and if we observe MainMenu it does not have any drawOtherPage() method.
Another mistake in your case is to call the parent's constructor twice:
class MainMenu(QWidget):
# class for main menu
def __init__(self, parent=None):
QWidget.__init__(self, parent)
super().__init__()
In the first constructor you are assigning a parent, and in the second, you are not. To clarify in python there are several ways to call the parent's constructor:
QWidget.__init__(self, parent)
super(MainMenu, self).__init__(parent)
super().__init__(parent)
so you should only use one of them.
Another error is that a signal is connected through the name of a function, the function must not be evaluated using parentheses
and for the last use of functions or methods that involve several objects should be done in a place where both objects can access, in your case you can take advantage of what you are going to RootInit as parent of MainMenu: self.main = MainMenu(self), and access the connection to that element through the method parent().
All of the above entails modifying the MainMenu class to the following:
class MainMenu(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
mainLayout = QGridLayout() # layout for entire main menu
quitBtn = QPushButton("Quit")
quitBtn.clicked.connect(QtCore.QCoreApplication.instance().quit)
nextPg = QPushButton("Next page")
nextPg.clicked.connect(self.parent().drawOtherPage)
mainLayout.addWidget(quitBtn, 0, 0)
mainLayout.addWidget(nextPg, 0, 1)
self.setLayout(mainLayout)
I'm creating an application which loads a couple of .ui files. The first one is of type QMainWindow and the others are of type QWidget.
I can't figure out how to load the second UI (module.ui) into self, making widgets accessible through self.<widget_name>.
How can this be achieved?
from PyQt4 import QtGui
from PyQt4 import uic
class TestApp(QtGui.QMainWindow):
def __init__(self):
super(TestApp, self).__init__()
# Load main window and the module
uic.loadUi('main_window.ui', self) # QMainWindow, contains testLayout, loads into self
ui_module = uic.loadUi('module.ui') # QWidget
# Attach module to main window
self.testLayout.addWidget(ui_module) # this works fine
# Edit widget in UI module
self.label.setText('Hello') # does not work (since self.label doesn't exist)
I could do this:
self.label = ui_module.label
self.label.setText('Hello')
...but I'd like to instead load the UI into self from the start.
If I try to load the UI into self, I get an error:
uic.loadUi('module.ui', self)
>>> QLayout: Attempting to add QLayout "" to TestApp "Form", which already has a layout
You need to create a widget to load the ui file onto
self.widget = QWidget(self)
uic.loadUi('module.ui', self.widget)
self.widget.label.setText('Hello')
That being said, it would probably be better if you created a separate class for the other widget.
class MyWidget(QWidget):
def __init__(self, **args, **kwargs):
super(MyWidget, self).__init__(*args, **kwargs)
uic.loadUi('module.ui', self)
self.label.setText('Hello')
class TestApp(QtGui.QMainWindow):
def __init__(self):
...
self.widget = MyWidget(self)
I am developping an application with PyQt based on a QWidget. I put my whole app inside a class (named "Example"). Inside this app, I have a button which calls to another QWidget window (named "Report Widget") I designed on QtDesigner and opens it.
The problem is that I don't know how to do this. Here is the method I use (which comes from something I found on internet).
On my main class "Example", a method defines the Button calling for the "Report Widget" :
ReportBtn = QtGui.QPushButton("Generate report")
ReportBtn.clicked.connect(self.ShowReportWidget)
The second line calls the ShowReportWidget method of the Example (main) class :
def ShowReportWidget(self):
self.f = QtGui.QWidget()
self.ReportWidget = ReportWidget(self.f)
self.ReportWidget.show()
This method calls the class ReportWidget, which is a class from the Example (main) class :
class ReportWidget(QtGui.QWidget):
def __init__(self, parent = None):
QtGui.QWidget.__init__(self)
self.ui = rw.Ui_ReportWidget()
self.ui.setupUi(parent)
and finally, this class refers to an outer class (Ui_ReportWidget), contained in another separate Python file (the one generated from the QtDesigner .ui file), which I import as rw in the beginning of my script.
The problem is that with this method, when I click on the Report Widget button, a new window pops but is empty. The content of the Ui_ReportWidget class is not loaded.
I hope my question is clear enough.
Thanks
SetupUi takes an Qwidget as input , while the init() function should take the calling QObject as the parent.
def ShowReportWidget(self):
self.f = QtGui.QWidget()
self.ReportWidget = ReportWidget(self)
self.ReportWidget.setupUi(self.f)
self.f.exec_()
class ReportWidget(QtGui.QWidget):
def __init__(self, parent = None):
QtGui.QWidget.__init__(self,parent)
def setupUi(self,Widget):
self.ui = rw.Ui_ReportWidget()
self.ui.setupUi(Widget)
I'm trying to make a class that extends qwidget, that pops up a new window, I must be missing something fundamental,
class NewQuery(QtGui.QWidget):
def __init__(self, parent):
QtGui.QMainWindow.__init__(self,parent)
self.setWindowTitle('Add New Query')
grid = QtGui.QGridLayout()
label = QtGui.QLabel('blah')
grid.addWidget(label,0,0)
self.setLayout(grid)
self.resize(300,200)
when a new instance of this is made in main window's class, and show() called, the content is overlaid on the main window, how can I make it display in a new window?
follow the advice that #ChristopheD gave you and try this instead
from PyQt4 import QtGui
class NewQuery(QtGui.QWidget):
def __init__(self, parent=None):
super(NewQuery, self).__init__(parent)
self.setWindowTitle('Add New Query')
grid = QtGui.QGridLayout()
label = QtGui.QLabel('blah')
grid.addWidget(label,0,0)
self.setLayout(grid)
self.resize(300,200)
app = QtGui.QApplication([])
mainform = NewQuery()
mainform.show()
newchildform = NewQuery()
newchildform.show()
app.exec_()
Your superclass initialiser is wrong, you probably meant:
class NewQuery(QtGui.QWidget):
def __init__(self, parent):
QtGui.QWidget.__init__(self, parent)
(a reason to use super):
class NewQuery(QtGui.QWidget):
def __init__(self, parent):
super(NewQuery, self).__init__(parent)
But maybe you want inherit from QtGui.QDialog instead (that could be appropriate - hard to tell with the current context).
Also note that the indentation in your code example is wrong (a single space will work but 4 spaces or a single tab are considered nicer).