PyQt: RuntimeError: wrapped C/C++ object has been deleted - garbage-collection

If I run this code:
#!/usr/local/bin/ python3
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.button1 = QPushButton("1")
self.button2 = QPushButton("2")
self.setCentralWidget(self.button1)
self.button1.clicked.connect(lambda: self.setCentralWidget(self.button2))
self.button2.clicked.connect(lambda: self.setCentralWidget(self.button1))
self.show()
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = Window()
sys.exit(app.exec_())
...I get this output:
Traceback (most recent call last):
File "test.py", line 16, in <lambda>
self.button2.clicked.connect(lambda: self.setCentralWidget(self.button1))
RuntimeError: wrapped C/C++ object of type QPushButton has been deleted
I do not understand why the object is being deleted. Window should maintain a reference to it.
I have thoroughly investigated these posts:
Understanding the “underlying C/C++ object has been deleted” error
Can a PyQt4 QObject be queried to determine if the underlying C++ instance has been destroyed?
Why is the button being deleted?

This answer to this question is as found here:
Python PySide (Internal c++ Object Already Deleted)
Apparently, assigning one widget to QMainWindow using setCentralWidget and then assigning another widget with setCentralWidget will cause the underlying c++ QWidget to be deleted, even though I have an object that maintains reference to it.
Note: QMainWindow takes ownership of the widget pointer and deletes it at the appropriate time.

Brain's answer explains the problem perfectly. This Link explain things in more detail.
My solution to this problem was to set the widgets as attributes of the object (e.g. simply using self.label = ... instead of label = ... in your class methods). You might want to do the same for any layouts attached to the widget.
This way you create a copy of the widget so that when C++ memory cleanup occurs, you still have a reference to the widget.
Hope this helps.

This problem also occurs in PyQT5 when you run a continuous thread and close the mainwindow/dialog without closing thread first but in background the thread is processing. When you again open the window the second thread generated and delete the pre-existed widgets. You need to exit the thread first before re-run it. You can exit the thread by put the check on visibility of any widget in the mainwindow/dialog
**if self.widget.isVisible() == False:
break**

In another case, the solution was to add all child objects to a detached layout first, and adding the layout to the parent layout as the last step.
That is:
l = QGridLayout()
l.addWidget(QLabel("child1"), 0, 0)
l.addWidget(QLabel("child2"), 0, 1)
...
parentLayout.addLayout(l)

wxPython & PyPubSub:
For those who had the same error with wxPython and PyPubSub, look for the solution at:
Defining a wx.Panel destructor in wxpython
and the nonzero() method.

Related

Can you have two instances of tkinter in Python?

Is it possible to have two instances of tkinter ?
import tkinter as tk
import tkinter as sk
root = tk.Tk()
root2 = sk.Tk()
....some window with tk
....some window with sk
root.mainloop()
root2.mainloop()
then have a Toplevel() in both instances.
You can, but the way it works likely won't be like what you expect. Importing it twice isn't the problem (but neither is it the solution). No matter how you import it or how often you import it, creating more than one instance of Tk is the problem. Each instance is backed by a separate internal interpreter. Images and variables and widgets created in one won't exist in the other.
If you need more than one window, it's usually best if second and subsequent windows are instances of Toplevel.
By creating 2 instances or tkinter I can close one instance and all top Levels of that instance. Leaving the second Instance open and running with it's Toplevel instances. This is useful in the sense where I use it for a User preference file. Where it checks for a userfile database for keeping track of variables. I use a csv file to save user variable using pandas. This in turn keeps all the form information in my app safe from being erased after closing the application or accidentally closing the window. Adding AtExit saves the info closing the addinfo window and continues to run the main application. That was my reasoning for having asked the question. I have since found that using multiple Toplevel(s) is a better choice as it will also produce the same result. So my menu items all have a separate instance definition and can be closed in the same manner ,making error checks with each closed window.
from tkinter import Tk,Label,Entry,Button,Toplevel
root=Tk()
def about():
ab=Toplevel()
# About stuff for this window
ab.mainloop()
def info():
inf=Toplevel()
# Information Stuff for this window.
inf.mainloop()
def getUserInfo():
# User info using pandas
getUserInfo.mainloop()
root.mainloop()

Reload UI, Rather Than Recreating

import sys
import webbrowser
import hou
from PySide2 import QtCore, QtUiTools, QtWidgets, QtGui
# Calling UI File & Some Modification
class someWidget(QtWidgets.QWidget):
def __init__(self):
super(someWidget,self).__init__()
ui_file = 'C:/Users/XY_Ab/Documents/houdini18.5/Folder_CGI/someUI.ui'
self.ui = QtUiTools.QUiLoader().load(ui_file, parentWidget=self)
self.setParent(hou.qt.mainWindow(), QtCore.Qt.Window)
self.setFixedSize(437, 42)
self.setWindowTitle("Requesting For Help")
window_C = someWidget()
window_C.show()
So, I have created this small script that shows the UI, I have connected this to Houdini Menu Bar. Now The Problem is if I click the menu item multiple times it will create another instance of the same UI & the previous one stays back, What I want is something called "If Window Exist Delete It, Crate New One" sort of thing.
Can someone guide me? I am fairly new to python in Houdini and Qt so a little explanation will be hugely helpful. Also, why can't I use from PySide6 import?? Why do I have to use from PySide2?? Because otherwise Houdini is throwing errors.
For the same thing what used to do in maya is
# Check To See If Window Exists
if cmds.window(winID, exists=True):
cmds.deleteUI(winID)
Trying to do the same thing inside Houdini.
I don't have Maya or Houdini, so I can't help you too much.
According to https://www.sidefx.com/docs/houdini/hom/cb/qt.html
It looks like you can access Houdini's main window. The main reason the window is duplicated or deleted is how python retains the reference to window_C. You might be able to retain the reference to just show the same widget over and over again by accessing the main Houdini window.
In the examples below we are using references a different way. You probably do not need your code that has
self.setParent(hou.qt.mainWindow(), QtCore.Qt.Window)
Create the widget once and keep showing the same widget over and over.
import hou
# Create the widget class
class someWidget(QtWidgets.QWidget):
def __init__(self, parent=None, flags=QtCore.Qt.Window): # Note: added parent as an option
super(someWidget,self).__init__(parent, flags)
...
MAIN_WINDOW = hou.ui.mainQtWindow()
try:
MAIN_WINDOW.window_C.show()
except AttributeError:
# Widget has not been created yet!
# Save the widget reference to an object that will always exist and is accessible
# parent shouldn't really matter, because we are saving the reference to an object
# that will exist the life of the application
MAIN_WINDOW.window_C = someWidget(parent=MAIN_WINDOW)
MAIN_WINDOW.window_C.show()
To delete the previous window and create a new window.
import hou
# Create the widget class
class someWidget(QtWidgets.QWidget):
def __init__(self, parent=None, flags=QtCore.Qt.Window): # Note: added parent as an option
super(someWidget,self).__init__(parent, flags)
...
MAIN_WINDOW = hou.ui.mainQtWindow()
# Hide the previous window
try:
MAIN_WINDOW.window_C.close()
MAIN_WINDOW.window_C.deleteLater() # This is needed if you parent the widget
except AttributeError:
pass
# Create the new Widget and override the previous widget's reference
# Python's garbage collection should automatically delete the previous widget.
# You do not need to have a parent!
# If you do have a parent then deleteLater above is needed!
MAIN_WINDOW.window_C = someWidget() # Note: We do not parent this widget!
MAIN_WINDOW.window_C.show()
Another resource shows you can access the previous widget from the page level variable. https://echopraxia.co.uk/blog/pyqt-in-houdinimaya-basic This is possible, but seems odd to me. The module should only be imported once, so the page level variable "my_window" should never exist. However, it sounds like the Houdini plugin system either reloads the python script or re-runs the import. If that is the case every time you show a new window from the import of the script, you are creating a new window. If the previous window is not closed and deleted properly, Houdini could have an ever growing memory issue.
try:
my_window.close()
except (NameError, Exception):
pass # Normal python would always throw a NameError, because my_window is never defined
my_window = MyWindow()
#This is optional you can resize the window if youd like.
my_window.resize(471,577)
my_window.show()
PySide6
https://www.sidefx.com/docs/houdini/hom/cb/qt.html
The bottom of the page shows how to use PyQt5. The same would apply for PySide6. Houdini just happens to come with PySide2.

tkinter askstring deleted before its visibility changed

I am trying to make a popup window where someone can fill in a string in an Entry box. I have gone through many examples, but it doesn't work.
I am trying to this:
var_entry = simpledialog.askstring("Test", "Test")
I get this error message:
_tkinter.TclError: window ".!_querystring" was deleted before its visibility changed
Thanks in advance!
edit: posted wrong error message
I know this is an old thread, but I'm having the same problem and so far haven't found the root cause.
However, this workaround works for me in case anyone else needs it:
#Create a new temporary "parent"
newWin = Tk()
#But make it invisible
newWin.withdraw()
#Now this works without throwing an exception:
retVal = simpledialog.askstring("Enter Value","Please enter a value",parent=newWin)
#Destroy the temporary "parent"
newWin.destroy()
I was also able to work around the problem by using the above workaround suggested by John D.
I did some research on this, and it seems that this exception is raised when all of the following conditions are met.
The thread that called the simpledialog.askstring method is not the main thread.
The Tk window specified in the parent or the Tk window specified in the default_root variable is different from the thread that called the simpledialog.askstring method.
However, I could not come up with a process to deal with this problem. I hope this helps to solve the problem.
This issue is caused by askstring is not called in the main thread, which is the main thread.
Make sure to call this method in the main thread. For example, this code works well.
from tkinter import Tk
from tkinter.simpledialog import askstring
a = Tk()
askstring('1', '2')
a.mainloop()
And this code will throw your exception when you close the dialog window.
from tkinter import Tk
from tkinter.simpledialog import askstring
from threading import Thread
a = Tk()
Thread(target=askstring, args=('1', '2')).start()
a.mainloop()
I'm not sure without your code example but I figured that this error message means that the variable where you are putting the 'askstring' return value or initialvalue is going out of scope before the dialog window is finished.
When I had this error message I placed a declaration of the variables outside the inner scope. Forgive me if I am talking here using C concepts (which most Python users would rather ignore).
answer = "dummy"
query_str = "dummy" # without these lines query_str and answer
# can be cleaned up by Python
# before the 'askstring' is done with them
while (1):
query_str = "0"
answer = simpledialog.askstring("Get Number",
"Enter NextNumber",
initialvalue = query_str)
print(answer)
print(query_str)
time.sleep(1)
Adding the declarations outside the loop scope worked for me.

Tkinter objects being garbage collected from the wrong thread

I seem to be breaking tkinter on linux by using some multi-threading. As far as I can see, I am managing to trigger a garbage collection on a thread which is not the main GUI thread. This is causing __del__ to be run on a tk.StringVar instance, which tries to call the tcl stack from the wrong thread, causing chaos on linux.
The code below is the minimal example I've been able to come up with. Notice that I'm not doing any real work with matplotlib, but I can't trigger the problem otherwise. The __del__ method on Widget verifies that the Widget instance is being deleted from the other thread. Typical output is:
Running off thread on 140653207140096
Being deleted... <__main__.Widget object .!widget2> 140653210118576
Thread is 140653207140096
... (omitted stack from from `matplotlib`
File "/nfs/see-fs-02_users/matmdpd/anaconda3/lib/python3.6/site-packages/matplotlib/text.py", line 218, in __init__
elif is_string_like(fontproperties):
File "/nfs/see-fs-02_users/matmdpd/anaconda3/lib/python3.6/site-packages/matplotlib/cbook.py", line 693, in is_string_like
obj + ''
File "tk_threading.py", line 27, in __del__
traceback.print_stack()
...
Exception ignored in: <bound method Variable.__del__ of <tkinter.StringVar object at 0x7fec60a02ac8>>
Traceback (most recent call last):
File "/nfs/see-fs-02_users/matmdpd/anaconda3/lib/python3.6/tkinter/__init__.py", line 335, in __del__
if self._tk.getboolean(self._tk.call("info", "exists", self._name)):
_tkinter.TclError: out of stack space (infinite loop?)
By modifying the tkinter library code, I can verify that __del__ is being called from the same place as Widget.__del__.
Is my conclusion here correct? How can I stop this happening??
I really, really want to call matplotlib code from a separate thread, because I need to produce some complex plots which are slow to render, so making them off-thread, generating an image, and then displaying the image in a tk.Canvas widget seemed like an elegant solution.
Minimal example:
import tkinter as tk
import traceback
import threading
import matplotlib
matplotlib.use('Agg')
import matplotlib.figure as figure
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
class Widget(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
self.var = tk.StringVar()
#tk.Entry(self, textvariable=self.var).grid()
self._thing = tk.Frame(self)
def task():
print("Running off thread on", threading.get_ident())
fig = figure.Figure(figsize=(5,5))
FigureCanvas(fig)
fig.add_subplot(1,1,1)
print("All done off thread...")
#import gc
#gc.collect()
threading.Thread(target=task).start()
def __del__(self):
print("Being deleted...", self.__repr__(), id(self))
print("Thread is", threading.get_ident())
traceback.print_stack()
root = tk.Tk()
frame = Widget(root)
frame.grid(row=1, column=0)
def click():
global frame
frame.destroy()
frame = Widget(root)
frame.grid(row=1, column=0)
tk.Button(root, text="Click me", command=click).grid(row=0, column=0)
root.mainloop()
Notice that in the example, I don't need the tk.Entry widget. However if I comment out the line self._thing = tk.Frame(self) then I cannot recreate the problem! I don't understand this...
If I uncomment then gc lines, then also the problem goes away (which fits with my conclusion...)
Update: This seem to work the same way on Windows. tkinter on Windows seems more tolerant of being called on the "wrong" thread, so I don't get the _tkinter.TclError exception. But I can see the __del__ destructor being called on the non-main thread.
I had exactly the same problem
It was a nightmare to find the cause of the issue. I exaustivelly verified that no tkinter object was being called from any thread. I made a mechanism based in queues to handle tkinter objects in threads.
There are many examples on the web on how to do that, or... search for a module 'mttkinter', a thread safe wrapper for Tkinter)
In a effort to force garbage collection, I used the "gc" method in the exit function of every TopLevel window of my App.
#garbage collector
import gc
...
gc.collect()
but for some reason, closing a toplevel window continued to reproduce the problem. Anyway... it was precisely using some prints in the aforementioned "mttkinter" module that I detected that, in spite the widgets are being created in the main thread, they could be garbage collected when garbage collector is triggered inside another thread. It looks like that the garbage collector gathers all the garbage without any distinction of its provenience (mainthread or other threads?). Someone please correct me if I'm wrong.
My solution was to call the garbage collector explicitly using the queue as well.
PutInQueue(gc.collect)
Where "PutInQueue" belongs to a module created by me to handle tkinter object and other kind of objects with thread safety.
Hope this report can be of great usefullness to someone or, it it is the case, to expose any eventual bug in garbage collector.
Tkinter is not thread safe. Calling Tkinter objects in a thread may cause things such as "The del method on Widget verifies that the Widget instance is being deleted from the other thread."
You can use locking and queues to make it done properly.
Check this example:
Tkinter: How to use threads to preventing main event loop from "freezing"
and this example (there are many many other examples you can find):
Mutli-threading python with Tkinter
Hope this will put you in the right direction.

PyQt5 GUI freeze caused by Windows focus-follows-mouse

When Windows focus-follows-mouse-without-raising-the-window is enabled by either of the two methods linked to below, I consistently get PyQt5 GUI 'freezes' where you have to type any character in the terminal that you ran python from in order to unfreeze the GUI; complete description and test case (Windows 10, Python 3.6.1, PyQt5) is here: pyqt5 click in terminal causes GUI freeze
To enable the focus-follows-mouse-without-raise behavior, try either of these - they both work in Windows 10:
downloadable program ('X-Mouse' though that name is used by other programs):
https://joelpurra.com/projects/X-Mouse_Controls/
registry hack description:
https://sinewalker.wordpress.com/2010/03/10/ms-windows-focus-follows-mouse-registry-hacks/
So - a few questions:
can anyone reproduce the issue? It seems 100% reproducible for me, but it would be great to hear the same from someone else.
is there a way to change the python code to detect-and-circumvent focus-follows-mouse, or just to be immune to it, i.e. maybe by ensuring the GUI application always takes focus back again when you - for example - click in a dialog or qmessagebox owned by the main GUI window, or by some other means? (Is the object hierarchy set up optimally, and if not, maybe this could all be resolved by correcting the ownership structure?)
The brute-force solution seems to work, though I'd like to leave this question open to see if someone knows of a more optimal solution; it took a fair amount of searching to figure out the right way; mainly by taking a look a the open-source code for X-Mouse. Basically, this method takes effect immediately, whereas the registry hack doesn't take effect until reboot.
New version of pyqt_freeze_testcase.py (the file from the referenced stackoverflow question); the changes are only additions, noted between lines of hash marks:
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import sys
####################### added begin:
import win32gui
import win32con
####################### added end
# import the UI file created with pyuic5
from minimal_ui import Ui_Dialog
class MyWindow(QDialog,Ui_Dialog):
def __init__(self,parent):
QDialog.__init__(self)
self.parent=parent
self.ui=Ui_Dialog()
self.ui.setupUi(self)
################################# added begin:
self.initialWindowTracking=False
try:
self.initialWindowTracking=win32gui.SystemParametersInfo(win32con.SPI_GETACTIVEWINDOWTRACKING)
except:
pass
if self.initialWindowTracking:
print("Window Tracking was initially enabled. Disabling it for now; will re-enable on exit.")
win32gui.SystemParametersInfo(win32con.SPI_SETACTIVEWINDOWTRACKING,False)
################################# added end
def showMsg(self):
self.really1=QMessageBox(QMessageBox.Warning,"Really?","Really do stuff?",
QMessageBox.Yes|QMessageBox.No,self,Qt.WindowTitleHint|Qt.WindowCloseButtonHint|Qt.Dialog|Qt.MSWindowsFixedSizeDialogHint|Qt.WindowStaysOnTopHint)
self.really1.show()
self.really1.raise_()
if self.really1.exec_()==QMessageBox.No:
print("nope")
return
print("yep")
################################## added begin:
def closeEvent(self,event):
if self.initialWindowTracking:
print("restoring initial window tracking behavior ("+str(self.initialWindowTracking)+")")
win32gui.SystemParametersInfo(win32con.SPI_SETACTIVEWINDOWTRACKING,self.initialWindowTracking)
################################## added end
def main():
app = QApplication(sys.argv)
w = MyWindow(app)
w.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()

Resources