I have been struggling to learn PyQt5 (and object oriented programming). In my current script I need to create a tabbed interface but can't seem to manage it. I suspect the problem is related to OOP (I am a novice). "self" seems to be the problem, and I kind of know what that means but not enough to be able to fix it. Below is my latest attempt. It seems like I am using the "wrong self", from elsewhere in the script. I want very much to understand object oriented programming - thanks in advance to anyone kind enough to help!
Some of the code/errors are:
code:
tabbar = QTabBar()
tab1 = QTabWidget()
tabbar.addTab(tab1, 'tab1')
error:
TypeError: arguments did not match any overloaded call:
addTab(self, str): argument 1 has unexpected type 'QTabWidget'
addTab(self, QIcon, str): argument 1 has unexpected type 'QTabWidget'
And here's the code:
class App(QMainWindow):
def launch(self, filepath):
subprocess.run(filepath)
def newLauncher(self, matrix):
pass # cut for brevity
def __init__(self):
super(App, self).__init__()
tabbar = QTabBar()
tab1 = QTabWidget()
index = tabbar.addTab(tab1, 'tab1')
self.initUI()
def initUI(self):
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
It is good that you want to learn about OOP, but that is not the main problem in this case, but it seems that you do not read the documentation. If we check that it is a QTabBar it would see that it refers to the top part of the QTabWidget, it is that part with buttons.
You do not have to use QTabBar but QTabWidget, QTabWidget has the addTab method that requires as a first parameter the widget that will be displayed on a page, and as a second parameter a title that will appear on the buttons.
Another mistake that I see in your code is that you create the widget but not setting it as part of another widget are just local variables that we know are deleted when the function is finished.
Since you are using QMainWindow you must set QTabWidget as part of a central widget, for this we can use the layouts.
import sys
from PyQt5.QtWidgets import *
class App(QMainWindow):
def __init__(self):
super(App, self).__init__()
centralWidget = QWidget()
lay = QVBoxLayout(centralWidget)
tab = QTabWidget()
lay.addWidget(tab)
for i in range(5):
page = QWidget()
tab.addTab(page, 'tab{}'.format(i))
self.setCentralWidget(centralWidget)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
ex.show()
sys.exit(app.exec_())
Related
I am trying to make a calculator app using PyQt6 and I wanted a QPushButton with fixed size to go into a grid layout. I was going to be using this button a lot so I created my own SquarePushButton class inheriting from QPushButton with an additional setMinimumSize and setMaximumSize already in the init function.
I simplified the code as much as possible while replicating the error which is why I am using a QGridLayout for just two widgets.
When I run it, the code works perfectly fine and as expected.
import sys
from PyQt6.QtCore import QSize
from PyQt6.QtWidgets import QWidget, QApplication, QPushButton, QLabel, QGridLayout
# class to have a button of fixed size
class SquarePushButton(QPushButton):
def __init__(self, text):
super().__init__()
self.setText(text)
self.setMinimumSize(QSize(50, 50))
self.setMaximumSize(QSize(50, 50))
# main window class
class MyWindow(QWidget):
def __init__(self):
super().__init__()
# create a text label
self.label = QLabel(self)
self.label.setText("Label")
# create a button
btn = SquarePushButton("Button")
btn.clicked.connect(self.buttonPressed)
# error: "Cannot find reference 'connect' in 'function'"
# add text label into layout
layout = QGridLayout()
layout.addWidget(self.label, 0, 0)
layout.addWidget(btn, 0, 1)
# sets the layout
self.setLayout(layout)
def buttonPressed(self):
self.label.setText("Pressed")
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MyWindow()
window.show()
sys.exit(app.exec())
Here it gives me an error saying "Cannot find reference 'connect' in 'function'"
However if I change how the QWidgets are imported I get a new error at a completely different location, this time the error occurs during layout.addWidget, where it says "unexpected argument" for the last two arguments.
Keep in mind everything still works, its just PyCharm IDE getting mad at me.
import sys
from PyQt6.QtCore import QSize
from PyQt6.QtWidgets import *
# class to have a button of fixed size
class SquarePushButton(QPushButton):
def __init__(self, text):
super().__init__()
self.setText(text)
self.setMinimumSize(QSize(50, 50))
self.setMaximumSize(QSize(50, 50))
# main window class
class MyWindow(QWidget):
def __init__(self):
super().__init__()
# create a text label
self.label = QLabel(self)
self.label.setText("Label")
# create a button
btn = SquarePushButton("Button")
btn.clicked.connect(self.buttonPressed)
# add text label into layout
layout = QGridLayout()
layout.addWidget(self.label, 0, 0)
layout.addWidget(btn, 0, 1)
# error: "unexpected argument"
# sets the layout
self.setLayout(layout)
def buttonPressed(self):
self.label.setText("Pressed")
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MyWindow()
window.show()
sys.exit(app.exec())
Note: It would be easiest to spot if you just copy and pasted the code into your PyCharm IDE because it highlights the supposed error.
I do not understand why there is an error there, everything seems to be working fine so is this a PyCharm bug, PyQt6 bug or am I just using inheritance incorrectly? I am quite new to Python still so sorry if this is really stupid.
I understand that question is simple, but I'm stuck anyway.
Is there any method to get parent layout widget name from inherited widget class?
I have a small piece of code here. So basically I need to get printed self.super_main_layout in the label field where "Push the button" printed now.
I have a function def print_foo which should do that. But I don't know how to get parent() name from inherited class. I need to get exactly this self.super_main_layout printed in label field.
I've tried to use self.parent() method but it doesn't work
import sys
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtWidgets import QApplication
class ButtonTest(QtWidgets.QWidget):
def __init__(self):
super(ButtonTest, self).__init__()
self.setFixedSize(300, 100)
self.layout = QtWidgets.QHBoxLayout()
self.setLayout(self.layout)
self.label = QtWidgets.QLabel("Push the Button")
self.button = QtWidgets.QPushButton("Button")
self.button.clicked.connect(self.print_foo)
self.layout.addWidget(self.label)
self.layout.addWidget(self.button)
def print_foo(self):
### ???????????
self.label.setText(str(self.parent().parent())) # ????
print(self.parent()) # ????
class MyApp(QtWidgets.QWidget):
def __init__(self):
super(MyApp, self).__init__()
self.W = ButtonTest()
self.setFixedSize(300,300)
self.super_main_layout = QtWidgets.QVBoxLayout()
self.setLayout(self.super_main_layout)
self.super_main_layout.addWidget(self.W)
if __name__ == "__main__":
app = QApplication(sys.argv)
w = MyApp()
w.show()
sys.exit(app.exec_())
So now it prints None in label field when you push Button.
But I want layout widget self.super_main_layout to be printed.
Is there any way to do that? I'm working on a bigger project right now. And I'm programming only inherited class with button in which I have to get different parent names when button is pressed.
Thank you very much.
ps. I'm quite new to this site and programming at all, so sorry for any mistakes. Thanks in advance!
The name of a variable is impossible in itself to obtain because the names of the variables are relative, for example if the code were the following:
# ...
self.setFixedSize(300,300)
self.super_main_layout = QtWidgets.QVBoxLayout()
foo_obj = self.super_main_layout
self.setLayout(foo_obj)
self.super_main_layout.addWidget(self.W)
What is the name of the layout: self.super_main_layout or foo_obj? As both variables refer to the same object.
What you can get is the object itself using the parent and then its layout:
def print_foo(self):
pw = self.parentWidget()
if pw is not None:
print(pw.layout())
I am working on a Python project that uses Qt Designer to build interface. when working on building a plugin capability, I was able to allow dynamic loading of user plugins and create a new QMenu item to add to the main menubar. The problem is that there seems to be no way of removing that top level QMenu once it is added to the main menubar. I researched/searched quite a bit on this topic and it seems that every solution related to this topic is for removing sub-menu items from a QMenu via removing its actions, not for removing that dynamically-added QMenu itself. I hope someone would point out this to be a simple thing, and provide a code snippet to demo how this is done.
Achayan's solution above crashes on python2 qt4 (Windows) for the deletion
Better way for it is to to use the clear function.
Adding to the solution above,
def removeMenu():
self.main_menu.clear()
Hope this will give you idea for what you upto. And I took some part from another post, which is same qmenu thing
import sys
# This is bad, but Iam lazy
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class MyWindow(QMainWindow):
def __init__(self):
super(MyWindow, self).__init__()
self.main_menu = self.menuBar()
widget = QWidget()
self.menuList = []
layout2 = QVBoxLayout(widget)
self.menuButton = QPushButton("Add Menu")
self.menuRmButton = QPushButton("Remove Menu")
layout2.addWidget(self.menuButton)
layout2.addWidget(self.menuRmButton)
self.menuButton.clicked.connect(self.create_menu)
self.menuRmButton.clicked.connect(self.removeMenu)
self.setCentralWidget(widget)
def create_menu(self):
menu2 = self.main_menu.addMenu('Menu 1')
self.menuList.append(menu2)
Action1=QAction('Menu 1 0',self)
Action1.triggered.connect(self.action_1)
menu2.addAction(Action1)
Action2=QAction('Menu 1 1',self)
Action2.triggered.connect(self.action_2)
menu2.addAction(Action2)
def removeMenu(self):
if self.menuList:
for eachMenu in self.menuList:
menuAct = eachMenu.menuAction()
self.main_menu.removeAction(menuAct)
# just for safe side
menuAct.deleteLater()
eachMenu.deleteLater()
def action_1(self):
print('Menu 1 0')
def action_2(self):
print('Menu 1 1')
if __name__ == '__main__':
app=QApplication(sys.argv)
new=MyWindow()
new.show()
app.exec_()
I'm trying to remove a QGraphicsItem from a QGraphicsItemGroup. When calling removeFromGroup, the item is removed (of course). However, it's then no longer visible in the Scene. I have to call Scene.addItem(item) in order for it to appear again. This is apparently something you shouldn't do (I'm given a warning for doing that). But I can't seem to find another workaround.
Here's a minimal example:
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.view = QGraphicsView()
self.scene = QGraphicsScene()
self.view.setScene(self.scene)
self.setCentralWidget(self.view)
def add_group(scene):
group = QGraphicsItemGroup()
text = QGraphicsTextItem()
text.setPlainText("I'm visible")
group.addToGroup(text)
scene.addItem(group)
# After this, text is no longer in group. However, it is no longer visible.
group.removeFromGroup(text)
assert not text in group.childItems()
# But text is still in scene.
assert text.scene() == scene
# this works (i.e. text becomes visible again). However, it also produces a
# warning: QGraphicsScene::addItem: item has already been added to this scene.
# The docs also advice against it.
scene.addItem(text)
# According to the docs, I thought this might work, but it gives me a TypeError.
# text.setParentItem(0)
if __name__ == '__main__':
app = QApplication(sys.argv)
main = MainWindow()
add_group(main.scene)
main.show()
sys.exit(app.exec_())
Tips and hints are very welcome.
The QGraphicsTextItem can never be parented to a scene, because it's parent must be a QGraphicsItem (which QGraphicsScene does not inherit).
When the QGraphicsTextItem is created, it's parent is None. Its parent is set to group when it is added to it (QGraphicsItemGroup is a subclass of QGraphicsItem), then set back to None when it's removed from group.
Calling scene.addItem() is actually a NO-OP. Qt checks whether scene is the same as text.scene(), and if it is, it prints the warning and returns without doing anything.
The fact that it seems to "work" in some circumstances, is just an artifact of python's garbage-collecting mechanism.
If your test is re-cast in a more realistic way, the QGraphicsTextItem remains visible after removal from the group:
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.view = QGraphicsView(self)
self.scene = QGraphicsScene(self.view)
self.view.setScene(self.scene)
self.setCentralWidget(self.view)
self.group = QGraphicsItemGroup()
self.text = QGraphicsTextItem()
self.text.setPlainText("I'm visible")
self.group.addToGroup(self.text)
self.scene.addItem(self.group)
self.group.removeFromGroup(self.text)
if __name__ == '__main__':
app = QApplication(sys.argv)
main = MainWindow()
main.show()
sys.exit(app.exec_())
The problem is that text is deleted since you don't have any reference to it after you remove it from the group, try this:
...
text = QGraphicsTextItem()
scene.text = text #just to keep the reference, ideally should be self.text = text
...
Now you don need scene.addItem(text)
Why doesn't the following example work?
from PyQt4 import QtGui
import sys
class TestView(QtGui.QWidget):
def __init__(self):
super(TestView, self).__init__()
self.initUI()
def initUI(self):
self.btn = QtGui.QPushButton('Button', self)
self.btn.resize(self.btn.sizeHint())
self.btn.move(50, 50)
class TestViewController():
def __init__(self, view):
view.btn.clicked.connect(self.buttonClicked)
view.show()
def buttonClicked(self):
print 'clicked'
def main():
app = QtGui.QApplication(sys.argv)
view = TestView()
TestViewController(view)
app.exec_()
if __name__ == '__main__':
main()
The example is supposed to represent an MVC structure (like the one in Figure 4 -- without the Model) where the controller (TestViewController) receives a reference to the view (TestView) and connects the clicked signal from the view's button view.btn to its function self.buttonClicked.
I'm sure the line view.btn.clicked.connect(self.buttonClicked) is executed but, apparently, it has no effect. Does anyone knows how to solve that?
Update (awful solution):
In the example, if I replace the line
view.btn.clicked.connect(self.buttonClicked)
with
view.clicked = self.clicked
view.btn.clicked.connect(view.clicked)
it works. I'm still not happy with that.
The reason it is not working is because the controller class is being garbage collected before you can ever click anything for it.
When you set view.clicked = self.clicked, what you're actually doing is making one of the objects from the controller persist on the view object so it never gets cleaned up - which isn't really the solution.
If you store your controller to a variable, it will protect it from collection.
So if you change your code above to read:
ctrl = TestViewController(view)
You'll be all set.
That being said - what exactly you are trying to do here, I am not sure...it seems you're trying to setup an MVC system for Qt - but Qt already has a pretty good system for that using the Qt Designer to separate the interface components into UI (view/template) files from controller logic (QWidget subclasses). Again, I don't know what you are trying to do and this may be a dumb down version of it, but I'd recommend making it all one class like so:
from PyQt4 import QtGui
import sys
class TestView(QtGui.QWidget):
def __init__(self):
super(TestView, self).__init__()
self.initUI()
def initUI(self):
self.btn = QtGui.QPushButton('Button', self)
self.btn.resize(self.btn.sizeHint())
self.btn.move(50, 50)
self.btn.clicked.connect(self.buttonClicked)
def buttonClicked(self):
print 'clicked'
def main():
app = QtGui.QApplication(sys.argv)
view = TestView()
view.show()
app.exec_()
if __name__ == '__main__':
main()
Edit: Clarifying the MVC of Qt
So this above example doesn't actually load the ui dynamically and create a controller/view separation. Its a bit hard to show on here. Best to work through some Qt/Designer based examples/tutorials - I have one here http://bitesofcode.blogspot.com/2011/10/introduction-to-designer.html but many can be found online.
The short answer is, your loadUi method can be replace with a PyQt4.uic dynamic load (and there are a number of different ways to set that up) such that your code ultimately reads something like this:
from PyQt4 import QtGui
import PyQt4.uic
import sys
class TestController(QtGui.QWidget):
def __init__(self):
super(TestController, self).__init__()
# load view
uifile = '/path/to/some/widget.ui'
PyQt4.uic.loadUi(uifile, self)
# create connections (assuming there is a widget called 'btn' that is loaded)
self.btn.clicked.connect(self.buttonClicked)
def buttonClicked(self):
print 'clicked'
def main():
app = QtGui.QApplication(sys.argv)
view = TestController()
view.show()
app.exec_()
if __name__ == '__main__':
main()
Edit 2: Storing UI references
If it is easier to visualize this concept, you Can also store a reference to the generated UI object:
from PyQt4 import QtGui
import PyQt4.uic
import sys
class TestController(QtGui.QWidget):
def __init__(self):
super(TestController, self).__init__()
# load a view from an external template
uifile = '/path/to/some/widget.ui'
self.ui = PyQt4.uic.loadUi(uifile, self)
# create connections (assuming there is a widget called 'btn' that is loaded)
self.ui.btn.clicked.connect(self.buttonClicked)
def buttonClicked(self):
print 'clicked'
def main():
app = QtGui.QApplication(sys.argv)
view = TestController()
view.show()
app.exec_()
if __name__ == '__main__':
main()