PyQt widget seems to "forget" its parent - python

I have a strange issue when adding a widget to a PyQt5 application.
The following is the actual code, stripped off of everything that doesn't seem related (like translateUI):
class OllRoot(preferences.Group):
"""Basic openLilyLib installation"""
def __init__(self, page):
super(OllRoot, self).__init__(page)
self.setParent(page)
self.changedRoot()
layout = QGridLayout()
self.setLayout(layout)
self.directory = widgets.urlrequester.UrlRequester()
self.directory.changed.connect(self.changedRoot)
layout.addWidget(self.directory, 0, 1)
def changedRoot(self):
print("Self:", self)
print("Parent:", self.parent())
self.parent().changed.emit()
# TODO: Check for proper openLilyLib installation
When the constructor is called, parent() is correctly set to the object that has been passed in as page, so the two proper objects are printed.
Self: <preferences.openlilylib.OllRoot object at 0x7f855a1de288>
Parent: <preferences.openlilylib.OpenLilyLibPrefs object at 0x7f855a1bcb88>
However, when I make a change in the self.directory widget changedRoot is called again (as I've connected it), but now the parent seems to have disappeared:
Self: <preferences.openlilylib.OllRoot object at 0x7f855a1de288>
Parent: <PyQt5.QtWidgets.QWidget object at 0x7f855a1dbc18>
Question:
Am I doing anything wrong with the setParent?
Am I doing anything wrong with the connect?
Does the object somehow "forget" its parent?
PS: A comparable file which served as a model can be found here: https://github.com/wbsoft/frescobaldi/blob/master/frescobaldi_app/preferences/general.py#L56.

Whenever a widget is added to a layout, Qt will automatically re-parent it so that it becomes a child of the widget the layout is set on. Calling setParent (with a different widget) in __init__ will have no lasting effect.
See: Tips for Using Layouts in the Layout Management Overview.

Related

Can QFrame be triggered by its childs elements

When an item (spinBox, LineEdit etc) changes its value in GUI (via designer) I set a certain button's enable status. For example:
self.ui.lineEdit_1.textChanged.connect(self.pushButton_status)
self.ui.checkBox_1.stateChanged.connect(self.pushButton_status)
self.ui.spinBox_1.valueChanged.connect(self.pushButton_status)
self.ui.spinBox_2.valueChanged.connect(self.pushButton_status)
self.ui.spinBox_3.valueChanged.connect(self.pushButton_status)
self.ui.spinBox_4.valueChanged.connect(self.pushButton_status)
This works fine. Though there are lots of lines here (and even more in the actual code). I have all of these items inside a frame (QFrame). So I was wondering if it is possible to do something like:
self.ui.frame_1.childValueChanged.connect(self.pushButton_status)
which could perhaps stand for all the items inside of it. Is there any way within this logic that could do what I am looking for? If so.. how?
There is no direct way to do what you want, but there is a maintainable way to do it, in this case you just have to filter the type of widget and indicate which signal you will use by adding more options to the function, in your case:
def connectToChildrens(parentWidget, slot):
# get all the children that are widget
for children in parentWidget.findChildren(QtWidgets.QWidget):
# filter if the class that belongs to the object is QLineEdit
if isinstance(children, QtWidgets.QLineEdit):
# Connect the signal with the default slot.
children.textChanged.connect(slot)
elif isinstance(children, QtWidgets.QCheckBox):
children.stateChanged.connect(slot)
elif isinstance(children, QtWidgets.QSpinBox):
children.valueChanged.connect(slot)
And then you use it in the following way:
class MyDialog(QDialog):
def __init__(self, parent=None):
super(MyDialog, self).__init__(parent)
self.ui = Ui_MyDialog()
self.ui.setupUi(self)
connectToChildrens(self.ui.frame_1, self.pushButton_status)

Why is my python class inheritance incorrect?

I'm learning to use PyQt5 and have run across a problem. My code is attempting to just draw a simple black box in the QMainWindow object by writing a second class PaintWidget which inherits from QWidget. I've posted my code first, and the correct one below it.
class PaintWidget(QWidget):
def __init__(self):
super().__init__()
self.qp = QPainter()
self.initUI()
def initUI(self):
self.qp.fillRect(1,1,100,100, Qt.black)
Correct:
class PaintWidget(QWidget):
def paintEvent(self, event):
qp = QPainter(self)
qp.fillRect(1, 1, 100, 100, Qt.black)
This is what confuses me. In order to create this class, we need to inherint from the super class QWidget, inorder to do so we use the function super().__init__() under __init__(self). We then set up the QPaint object which we will use in our method initUI() which actually does the work. Now this doesn't work when I run it.
The second, correct class, doesn't even seem to inherent, since it has no super().__init__(), even worse, it is setting up a method that is never even called (paintevent(self, event)), which takes an argument that seemingly comes from nowhere. Can someone point out why I'm wrong?
There is absolutely no difference to inheritance between the two cases. In both cases you say class PaintWidget(QWidget), so you are inheriting the QWidget.
The difference is in where you draw. In constructor (__init__), the widget is not yet mapped to the screen, so if you try to draw there, it won't have effect.
When the widget is actually displayed on screen, the system will invoke the paintEvent, which is a virtual method of the QWidget, and that is where you must draw the content. You only define that method in the second example.
Note that you need fresh QPainter in each invocation of the paintEvent. Creating one in the constructor and then using it in paintEvent would not work.
Also, most windowing systems don't remember the content of the widget when it is not actually visible on screen and rely on being able to call the paintEvent whenever the widget becomes visible again. So the method will likely be called many times. In contrast, the constructor, __init__, is only called once when creating the object.

Using QT Designer and PyQt, how do I fill an empty widget defined in one .ui file with a widget defined in another .ui file?

Let's say I have a MainWindow.ui that defines the layout of MainWindow.py, where MainWindow.py looks a little like this:
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.ui = uic.loadUi('MainWindow.ui')
MainWindow.ui holds two (actually three) widgets. A simple QLabel text_lbl for argument's sake, and an empty QWidget sub_widget. These two widgets are held in central_widget.
We also have a SubWidget.ui. SubWidget.ui can be anything, really, but let's say it holds a lot of labels and spinboxes. SubWidget.py looks a lot like MainWindow.py, except it holds a lot of signals and slots:
class SubWidget(QtGui.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.ui = uic.loadUi('SubWidget.ui')
# A lot of interesting stuff here.
What I want to do, is put an instance of SubWidget in MainWindow's sub_widget. The obvious thing for me to do would be to add the following lines of code to MainWindow.py's __init__:
from SubWidget import SubWidget # Though really this shouldn't be in
# __init__, but you get the idea.
self.ui.sub_widget = SubWidget()
Which simply doesn't do anything. I eventually achieved rendering SubWidget over the main window's contents and complaining about MainWindow already having a layout, but I lost that code in the midst of all fiddling.
How do I achieve this?
edit: I forgot to mention. self.ui.central_layout.addWidget(SubWidget()) visually achieves what I'm trying to do, but if I ever decide to add UI elements beneath that in the .ui file, that simply won't work.
If you can't, or for whatever reason just don't want to, use widget promotion, then the most obvious solution is to make the SubWidget a child of the place-holder widget.
Since the place-holder widget doesn't yet have a child, you can't give it layout in Qt Designer, so you will have to do it all programmatically:
layout = QtGui.QVBoxLayout(self.ui.empty_widget)
layout.setContentsMargins(0, 0, 0, 0)
self.ui.sub_widget = SubWidget()
layout.addWidget(self.ui.sub_widget)

Python PySide (Internal c++ Object Already Deleted)

I recently decided to write my first app with Python and PySide. But I have a problem and hope you guys can help.
Python keeps raising exceptions that the "Internal C++ Object" is deleted. From my limited experience with Python I figure that my object is going out of scope and being deleted by Python's Garbage Collector.
So how would I go about designing a multi-page application in Python with PySide. And being able to keep my QWidgets so I can show the page again.
Thanks for your time.
Update (Code)
instancing = None
def instance():
global instancing
if instancing == None:
instancing = WPZKernel()
return instancing
class WPZKernel:
win = None
mainscreen = None
def mainwindow(self):
if self.win == None:
self.win = GMKMainWindow(self)
return self.win
def main_panel(self):
if self.mainscreen == None:
self.mainscreen = GMKMainScreen(self.mainwindow())
return self.mainscreen
I would then normally access the mainpanel by calling:
import kernel
kernel.instance().main_panel()
So am I going about this the wrong way?
After some searching and hair pulling, I found the solution. I was showing all the pages by setting them as the central widget, and when reading the QMainWindow documentation I found that my widget basically gets deleted by qt as stated:
Note: QMainWindow takes ownership of
the widget pointer and deletes it at
the appropriate time.
So to develop a Multi-Page application rather take a look at QStackedWidget.
See here: PySide Pitfalls.
If a QObject falls out of scope in
Python, it will get deleted. You have
to take care of keeping a reference to
the object:
Store it as an attribute of an object you keep around, e.g.
self.window = QMainWindow()
Pass a parent QObject to the object’s constructor, so it gets owned
by the parent

replacing widget at runtime

Here is the situation. I have a class which is derived from a QListView that adds some convenience functions, a custom widget if you like. I do not want to wrestle with the Designer to use my widget. I simply want to use a plain QlistView in the Designer (as a placeholder) and compile it with pyuic4. At runtime I want to replace that normal QListView with my own version.
How can you do this?
I was hoping something like this in the init would do the trick:
self.lstView1 = MyListView
but it doesn't...
The problem is that you are actually simply replacing the object pointed by lstView1 but not adding it to the widget. That is, when you create you object you simply tell python to point to your new object using lstView1 but the actual UI is using the old pointer since it as already added.
I'm going to assume that you have use py4uci to convert the ui files to python and you set up the gui as:
class ExambleUI(QtGUi.QDialog, UI_Example):
def __init__(self, parent):
QtGui.QDiialog.__init__(self, parent)
self.setupUI(self)
self.lstView1 = MyListView
Because setupUi is executing before you change the value of lstView, you are not getting the new widget. You just have to swap the lines:
class ExambleUI(QtGUi.QDialog, UI_Example):
def __init__(self, parent):
QtGui.QDiialog.__init__(self, parent)
self.lstView1 = MyListView
self.setupUI(self)
On the other hand I recommend you to follow this tutorial and create and use you widget in the designer, it is easy and faster.
Use QLayout replace function:
ui->main_layout->replace(oldWidget, newWidget);

Categories