PyQT - list QWidgets/Windows - python

I have a
class Main(QtGui.QMainWindow):
That is able to click>spawn a x number of windows that are:
class dataWindow(QtGui.QWidget)
Is there a way in PyQt to now find all spawned dataWindow's and get their objectName?
each window has unique objectName.
I tried going via :
a= self.findChild(QWidget, self.newDataWids[0]["window_name"]) - as I have all names stored in dict upon creation
but it only returns None. I think its because the dataWindow are not parented to Main window class I believe... so I either have to parent them - not sure how. Or somehow find them out in the "wild"...
Any ideas would be great.
Regards, Dariusz
Edit_1: A glitch in my code bugged out my current attempt. After relooking I managed to get it to work. I simply stored the window in temporary dictionary and then used that to retrieve access to window.

You parent objects by passing in the parent to their constructor. You'll have to check the documentation for each widget to get the correct argument position.
widget = QtGui.QWidget(self)
btn = QtGui.QPushButton('Button Text', self)
But really, you shouldn't have to do a search for children to get the child windows. Your main window should be keeping handles to them.
def __init__(...)
...
self._windows = []
def createSubWindow(self):
window = WindowClass(self)
self._windows.append(window)

Related

PyQt5 Controller cannot access children names

for a python program I created a gui with QtDesigner. Inside my program the gui is initiated and calls the .ui-file to implement content.
The gui class object is than given to a Controller, and here it gets tricky:
Instead of one Controller, there are a main controller and some sub-controller for different parts of the gui. The main Controller does one or two general things and than hands over different parts of the gui to different sub controller.
See the following example for better understanding:
import Controller # import the folder with the controller
def __init__(self, window):
self.window = window # self.window is the handed over gui
self.sub_controls()
def sub_controls(self):
Controller.Sub_Controller_1(window = self.window.part_1)
Controller.Sub_Controller_2(window = self.window.part_2)
Controller.Sub_Controller_3(window = self.window.part_3)
...
The sub-Controller is set up like this:
def __init__(self, window):
self.window = window
... # do stuff:
-----------------------------------------
by trying to call a widget in this Sub-Controller (lets say its a label called timmy), i get an error:
self.window.timmy.setVisible(False)
AttributeError: 'QWidget' object has no attribute 'timmy'
but by using the children()-Method, which returns a list of all children in the gui, I may access this label:
self.window.children()[1].setVisible(False)
This works well and hides timmy.
By trying to do this in the main Controller, it works fine as usual:
self.window.timmy.setVisible(False) # works fine here
I also tried to save the sub-controller object like this:
def sub_controls(self):
self.save_part_1 = Controller.Sub_Controller_1(window = self.window.part_1)
but this doesn't work.
Does any one have a suggestion, how I could solve this Problem?
Sure, I couldt access just all widgets with the children()-method-list, but this is laborious because of the great amount of widgets. Same thing applies to reassigning every child.
PS:
Unfortunately I cannot show you the original Code due to company guidelines.
Ok so I figured out, that the tree Structure of in each other nested widgets has nothing to do with the naming of the widgets. For example:
MainWindow
->centralWidget
->otherWidget
->Button
In this you can address "Button" with self.Button, because (how I believe) the name is saved in MainWindow-Level. If you cut out "otherWidget", the QPushButton in it still exists, but cannot addressed by name.
So it's not possible to just hand over a part of your Gui to a Handler, at least if it's build with QtDesigner.
My Solution for me to the original Problem is to hand over the complete Gui to every sub handler:
def __init__(self, window):
self.window = window # self.window is the handed over gui
self.sub_controls()
def sub_controls(self):
Controller.Sub_Controller_1(window = self.window)
Controller.Sub_Controller_2(window = self.window)
# Controller.Sub_Controller_3(window = self.window.part_3) # before

How to attach a gtk4 PopoverMenu to a parent widget?

This should be a no-brainer but I just can't seem to figure it out. If I build a Gtk.PopoverMenu:
menu = Gio.Menu.new()
test = Gio.MenuItem.new(label='test', detailed_action='win.test')
menu.append_item(test)
popup = Gtk.PopoverMenu.new_from_model(menu)
I don't know how to attach it to the parent widget (in my case a ListBox item). The API docs say "Popovers are attached to a parent widget" but there seems to be no relevant method for attaching it to the parent. Trying to popup.popup() results in a warning "Calling gtk_widget_realize() on a widget that isn't inside a toplevel window is not going to work very well. Widgets must be inside a toplevel container before realizing them", and a subsequent segfault.
To set the parent of the PopoverMenu, just use its set_parent() method to set the parent to any widget:
...
popup = Gtk.PopoverMenu.new_from_model(menu)
popup.set_parent(parent_widget)

Getting parent of wrapInstance causes window to break

I've been using a modified version of this code to wrap a Qt window inside Maya's own workspaceControl. To get the outside level of the widget to query geometry, size, move, etc, I actually need to do self.parent().parent().parent().parent().parent(), since it's wrapped within quite a lot of widgets.
I've been working around one or two various issues caused by doing that, but today I hit something a bit more vital and decided to find exactly what the cause is.
Through testing I've narrowed down the code as much as possible, and I've found it's when attempting to get the parent of shiboken2.wrapInstance. Attempting to create the window after that comes up with an error saying RuntimeError: Internal C++ object (PySide2.QtWidgets.QWidget) already deleted..
import maya.OpenMayaUI as omUI
import pymel.core as pm
import shiboken2
from PySide2 import QtWidgets
win_id = 'test'
#Delete existing window
if pm.workspaceControl(win_id, exists=True):
pm.deleteUI(win_id)
#Create window and setup wrapper
pm.workspaceControl(win_id)
c_pointer = omUI.MQtUtil.findControl(win_id)
parent_wrap = shiboken2.wrapInstance(int(c_pointer), QtWidgets.QWidget)
print parent_wrap.parent()
#Create actual window
#This will error if parent_wrap.parent() was called
win = QtWidgets.QMainWindow(parent_object)
How could I get the parent of the wrap instance without causing issues? I assume it's something to do with unreferencing things from memory prematurely, but I'm not sure how I could go about fixing it.
I found a fix that is kinda dirty but seems to work reliably so far.
So to start with I found if you create two instances of the above parent_wrap, and get the parent before you create the second one, it's possible to pass into the window separately without causing issues. It works up until the point the window is docked/detached, but that deletes the old parents and provides new ones, so the old pointer is no longer valid.
It seemed that providing this parent before the window being initialised allowed me to then regerate it with self.parent().parent().... without breaking the code.
It needs to be updated if the state has changed (floating/docked), and whenever a RuntimeError occurs, but as it's not going to be a huge hit in performance, for simplicity I'm just regenerating it each time it is needed.
To override a function, like move for example, this would be how to use it:
def move(self, x, y):
if self.dockable:
return self._parent_override().move(x, y)
return QtWidgets.QMainWindow.move(self, x, y)
And this is the override class to solve the issue:
def _parent_override(self, create=True):
#Determine if it's a new window, we need to get the C++ pointer again
if not hasattr(self, '__temp_parent'):
base = qt_get_window(self.ID)
else:
base = self.parent()
#Get the correct parent level
if pm.workspaceControl(self.ID, query=True, floating=True):
parent = base.parent().parent().parent().parent()
else:
parent = base.parent().parent()
#Even though this attribute is never used,
#PySide2 is dumb and deletes the parent if it's not set
self.__temp_parent = parent
return parent

QLayout Additem - prevent transfer of ownership

I am having problems with the object references when using the QLayout to arrange my widgets in on bigger window next to each other.
I have the following situation
class MyClass(QObject):
widgetCollection = []
def spawn(self):
widget = MyQWidget() #containing a QLineWidget called "nameEdit"
self.widgetCollection.append(widget)
self._window = QtGui.QWidget()
layout = QtGui.QHBoxLayout()
listView = QtGui.QListWidget()
for equation in self.wigdetCollection:
equationName = equation.nameEdit.text()
item = QtGui.QListWidgetItem(equationName)
listView.addItem(item)
layout.addWidget(listView)
layout.addWidget(widget)
self._window.setWindowTitle("Equation Editor")
self._window.setLayout(layout)
self._window.show()
def respawn(self):
self.spawn()
Each time I call spawn(), I want to add a new widget to the collection. Addionally, I want to open a new window where there is a ListView with all widget names on the left and the newly created widget on the right.
Now the first call to the spawn()-method works like expected. But the second call throws an exeption:
equationName = widget.nameEdit.text()
RuntimeError: wrapped C/C++ object of type QLineEdit has been deleted
I think it has something to do with the line
layout.addWidget(widget)
I have read somewhere that the layout takes ownership of the widget when added as an item. With that I loose the widget as soon as I am out of the scope of the layout-reference (which is local in this case). So it seems that the widget-item in my collection gets deleted too.
Can someone help? How do I prevent that.
The problem is that self._window is replaced every time spawn() is closed: Python discards the old window widget, and self._window is made to reference a new QWidget instance. Since the old window widget is parent to a list view and qlineedit widget (called -- confusingly -- just "widget" in the code), these will be destroyed in the Qt sense of the term -- i.e. their C++ portion will be destroyed. The list view is not referenced anywhere else in the shown code so the Python portion of the list view will be destroyed too, and this is the way it should be.
HOWEVER, the qlineedit is referenced in the class-wide registry (widgetCollection) and so the Python portion of the qlineedit will NOT be destroyed. This means that every time spawn() is called, the previous instance of QLineEdit (via MyQWidget in class-wide widgetCollection) becomes a zombie: it is dead at the C++ Qt level, but it is still alive at the Python level. A zombie's methods can still be accessed, but in many cases you will see the Qt error about C/C++ having been deleted, and you may get a crash or other undefined behavior.
It is not clear from the code posted what is the intent of the widgetCollection, but since the previous item appended will become a zombie during a spawn(), widgetCollection is useless as shown. The solution to the problem depends on details about how the collection is used, but since one can presume that the collection has a purpose, then the issue is the replacement of the window widget: perhaps the widget collection should be a window collection, then when self._window is replaced, the previous window stays alive so the child widgets stay alive too.
Solved it by myself :-)
My initial call to the spawn()-method was like that:
mc = MyClass()
mc.spawn()
First one okay. Now I wanted to spawn another from within inst1. For that I have used
self.spawn()
in the respawn()-method throwing the above mentioned error.
It has to be the following
def respawn(self):
mc = MyClass()
mc.spawn()
I had to create another instance of MyClass that shares the widgetCollection with all other instances. As desired....

Trying to make a collapsible widget: How to hide/unhide all child widgets?

Im making a simplish widget that can act as a container for other widgets. One of the features of the widget is that you can expand/collapse it by clicking on it. My current method is basically looking up all child widgets of the layout and hiding them. I'm looking for any help on how to handle this properly - my current implementation has at least one serious caveat: that you can't add widgets while it's collapsed (they're added in an 'unhidden' state)
heres the setCollapsed method that is run when the widget is clicked
def collapsed(self):
return self._isCollapsed
def setCollapsed(self, collapseBool):
self._isCollapsed = collapseBool
if self.layout()!=None:
childWidgets = [self.layout().itemAt(i).widget() for i in range(self.layout().count())]
for w in childWidgets:
if isinstance(w,QtGui.QWidget):
w.setHidden(collapseBool)
if collapseBool:
self._cachedMargin = self.layout().margin()
self.layout().setMargin(0)
else:
self.layout().setMargin(self._cachedMargin)
Rather than hiding all child widgets individually, I would just hide a single parent item.

Categories