Unreal Pyside6 widgets wont get removed by garbage collector - python

I am unable to remove PySide6 widgets in Unreal 5 python. Will describe the code first:
I have my ArtToolsUI class inheriting from QMainWindow. In this class I set some basic stuff like groupboxes, layouts etc.:
class ArtToolsUI(QMainWindow):
def __init__(self):
super().__init__()
self.setAttribute(Qt.WA_DeleteOnClose, True)
self._app = self._get_qt_app()
self.resize(600,400)
self.editor_tools_buttons = [] #for showcase this has no widgets in it
self._main_widget = self._set_layouts()
def _get_qt_app(self):
qt_app = QApplication.instance()
qt_app.setQuitOnLastWindowClosed(True)
return qt_app
def get_v_layout(widgets):
widgets = make_list(widgets)
layout = QVBoxLayout()
layout.setObjectName("vert_layout")
for widget in widgets:
layout.addWidget(widget)
return layout
def _set_layouts(self):
default_grpbox_names = {"tools":self.editor_tools_buttons}
all_grp_boxes = []
for key in default_grpbox_names:
layout = get_v_layout(default_grpbox_names[key]) # right now this just returns empty QVBoxLayout
new_grp_box = QGroupBox(title=key)
new_grp_box.setAttribute(Qt.WA_DeleteOnClose, True)
new_grp_box.setLayout(layout)
all_grp_boxes.append(new_grp_box)
main_layout = get_v_layout(all_grp_boxes)
main_widget = QWidget()
main_widget.setObjectName("tat_main_widget")
main_widget.setLayout(main_layout)
self.setCentralWidget(main_widget)
return main_widget
I also implemented function for removing all widgets in this unreal python session. This method is called from different module.
def _remove_widgets():
qt_app = QApplication.instance()
if qt_app != None:
all_qt_widgets = qt_app.allWidgets()
for widget in all_qt_widgets:
widget.setParent(None)
widget.close()
widget.deleteLater()
This _remove_widgets() method goes through all existing widgets, which should be destroyed, but for some reason only my ArtToolsUI main window is getting destroyed and all other widgets are still hanging around in the memory.
On the other side when I manually click on the X button to close the window it closes everything.
Does anyone know what might be the problem?

Ok I think I figured it out.
When running PySide6 on unreal you actualy cannot start your own QT event loop ( if you do, Unreal Editor will freeze and wait till you end your window). QT somehow finds unreal GUI event loop, which is responsible for everything .. except answering deleteLater().
Only two ways ( I found) for deleting UI are:
When user clicks on X button to end the Window -> This automatically ends and removes all widgets in that window.
When calling QApplication.closeAllWindows() -> This calls all windows, but definitelly better than if they stay in memory.
Both ways seem to bypass deleteLater() functionality, as documentation for deleteLater() says:
The object will be deleted when control returns to the event loop.
At the end I ended up with this functionality:
I am not deleting dialogs, I just hide them on close().
Then when creating them I ask if there are dialogs with my specific object name (object name is class property at every window I have)
This will prevent spawning multiple widgets ( 10k widgets is aprox. 1.2GB RAM)
ADD: There is also possibility to use self.setAttribute(Qt.WA_DeleteOnClose, True) on closing. This will delete dialog from memory as well.

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

In PyQT why somes widgets needs the "self" parameter before calling them, while others don't

I'm a little bit confused about the use of the "self" parameter with some widgets like (QLineEdit), indeed when learning to use the QLabel widget, I used to call the class without the self paramater, or when using the QLineEdit widget, the widget wouldn't work without the "self" parameter, here's the code I'm working on :
# Import necessary modules
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QLineEdit, QPushButton
from PyQt5.QtCore import Qt
class EntryWindow(QWidget): # Inherits QWidget
def __init__(self): # Constructor
super().__init__() # Initializer which calls constructor for QWidget
self.initializeUI() # Call function used to set up window
def initializeUI(self):
"""
Initialize the window and display its contents to the screen
"""
self.setGeometry(400, 300, 400, 200)
self.setWindowTitle('QLineEdit Widget')
self.displayWidgets()
self.show() # Show everything
def displayWidgets(self):
'''
Setup the QLineEdit and other widgets.
'''
# Create name label and line edit widgets
QLabel("Please enter your name below.", self).move(100, 20)
name_label = QLabel("Name:", self)
name_label.move(55, 70)
self.name_entry = QLineEdit(self)
self.name_entry.move(120, 68)
self.name_entry.resize(200, 25) # Change size of entry field
self.name_entry.setAlignment(Qt.AlignLeft) # The default alignment
text_font = self.name_entry.font() # Get font option from the Qlineedit
text_font.setPointSize(12) # Modify font size
#text_font.setBold(True) # Bold
self.name_entry.setFont(text_font) # Apply font
self.clear_button = QPushButton('Clear text', self)
self.clear_button.clicked.connect(self.clearEntries)
self.clear_button.move(120, 130)
self.exit_button = QPushButton("Exit", self)
self.exit_button.clicked.connect(self.exitApplication)
self.exit_button.move(240, 130)
def clearEntries(self):
sender = self.sender()
if sender.text() == 'Clear text':
self.name_entry.clear()
def exitApplication(self):
sender = self.sender()
if sender.text() == "Exit":
self.close() # Close the window
# Run program
if __name__ == '__main__':
app = QApplication(sys.argv)
window = EntryWindow()
sys.exit(app.exec_())
So here is where I'm confused, when using QLabel, I didn't have to put the "self" parameter before, or when using QLineEdit, I had to put "self" otherwise my code wouldn't work :
QLabel("Please enter your name below.", self).move(100, 20)
self.name_entry = QLineEdit(self)
First of all, the problem or difference has nothing to do with the "self", but what it is for, pre-established rules in Qt design.
In Qt there is a hierarchy tree between the QObjects where it is established that the parent QObject manages the memory (the life cycle of its children) so if the parent deleted the children, they will also be deleted. This allows avoiding memory leaks since many QObjects are generally used in many applications.
On the other hand, that concept of kinship is also passed to QWidgets since they are also QObjects, but there is also another characteristic: a QWidget in general will be drawn on top of its parent. So if you want the QLineEdit and QLabel to be part of the window then they must be children of the window so it is necessary that you pass the window object which is "self" as parent.
So when you go to the window (in this case "self") you avoid 2 problems:
That the object has a longer life cycle (the same as the window).
And you make the widget (either QLabel or QLineEdit) be placed on top of the window.
The self name is a Python convention to indicate the instance of an object. When set as argument in an object constructor, it's normally used to give the new object a reference to the one that created it, and that's normally called a "parent". Note that while self is usually used as parent argument, you can set any QWidget instance as parent.
In Qt terms, not only it indicates the roles in a "parentship" of the object structure, but also results in two important aspects:
In the world of QObjects (from which QWidget inherits) this helps for memory management: when a parent is deleted, all its children objects are deleted as well. It's also useful to find child objects (using findChild() or findChildren()) which are direct children or [great, ...] grandchildren of another.
When creating a widget with a QWidget parent, that widget is also only "drawn inside" it: it can only be visible within the boundaries of the parent, not outside it; note that this is usually optional, as most times you'll add a widget to a layout manager (but you didn't), which automatically reparents the widget to the widget that is managed by that layout: if it was a child of another widget, it's removed from that and added to the new one. Also consider that if a widget has no parent and is not added to a layout, it's considered a "top level widget" (a standalone window), and if show() or setVisible(True) is called it will have its own window.
This means that if the widget has a parent and the parent becomes visible, the child will be automatically visible too (unless hide() or setVisible() was explicitly called).
Note that while a QWidget that has no parent is always a top level window, a widger that has a parent could be a top level window, and that happens if it has the QtCore.Qt.Window flag set (using the f keyword in the constructor, or with setWindowFlags()). This is useful when the widget has to be a top level window, but the parentship must still be preserved, not only for memory management (when the parent window is closed and deleted, any child should be deleted as well) but for interaction purposes: for example, with QDialogs (which are standalone windows) it's important to set the parent, so that they can be modal to that parent, meaning that no keyboard/mouse interaction can happen on the parent until the dialog is closed.
Finally, it's important to remember that creating widgets "on the fly" in python has a (sometimes perceived incoherent) result: if no python reference is given to the widget but the widget has a parent, it won't garbage collected.
Consider the following:
def test1(self):
someDialog = QtWidgets.QDialog()
someDialog.show()
This will probably show a window just for an instant, and then it will disappear instantly.
def test2(self):
someDialog = QtWidgets.QDialog(self)
someDialog.show()
In this case, the dialog will not disappear, that's because it has a parent, PyQt knows it, and it will not try to delete it.
Even this is actually valid (but, no, don't do it):
def test2b(self):
QtWidgets.QDialog(self).show()
Similar result, but only apparently:
def test3(self):
self.someDialog = QtWidgets.QDialog()
self.someDialog.show()
In the case above, the visual result will be probably the same as test2, but if test3 is called again while the dialog is still visible, it will delete the current one and create a new one: that's because the dialog has no parent, and overwriting self.someDialog with a new instance will cause python to release the previous one, which will tell Qt that it can safely erase it, since it has no parent.
Finally, it's not necessary to set the parent to widgets, as long as they are added to a layout (which is the preferred approach, as using fixed geometries is highly discouraged), since the layout will automatically set the parent anyway as soon as it's [going to be] set on the actual parent.
Note that QDialog is normally shown with exec_() in order to correctly display it as modal of the parent and use it as it should.
Note the import statements at the top
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QLineEdit, QPushButton
QLabel and QLineEdit as can be seen in the documentations
https://doc.qt.io/qtforpython/PySide2/QtWidgets/QLabel.html
https://doc.qt.io/qtforpython/PySide2/QtWidgets/QLineEdit.html
both take a QWidget as a parameter. The self passed as the second parameter indicates the current QWidget type. For other methods inside the class, self is used to invoke the instance methods.

Error "QObject::startTimer: QTimer can only be used with threads started with QThread" many times when closing application

I know this has been asked many times before. I read all of those threads, and my case seems different. Everybody else who has this trouble has a few straightforward causes that I think I’ve ruled out, such as:
Starting a timer with no event loop running
Starting/stopping a timer from a thread other than the one that created the timer
Failing to set the parent property of a widget, leading to problems with the order of destruction
Below I have a minimal code sample that demonstrates the problem. Notice that I’ve started no threads or timers. I also have set the parent of every widget. If I remove the graph widgets, the problem goes away, so one is tempted to blame pyQtGraph, however, if I include the plot widgets but exclude all the blank tabs (i.e. every tab except tabCatchaTiger), the problem also goes away, and that seems to vindicate pyQtGraph.
Versions:
Windows 7
Python 2.7.8
Wing IDE 5.0.9-1
PyQt 4.11.1
PyQwt 5.2.1
PyQtGraph 0.9.8
Test case:
from PyQt4 import Qt, QtGui, QtCore
import PyQt4.Qwt5 as Qwt
import pyqtgraph as pg
pg.functions.USE_WEAVE = False # Lets pyqtgraph plot without gcc
pg.setConfigOption('background', 'w')
pg.setConfigOption('foreground', 'k')
# GUI for visualizing data from database
class crashyGUI(QtGui.QWidget) :
def __init__(self) :
# Make the window
QtGui.QWidget.__init__(self)
self.resize(700, QtGui.QDesktopWidget().screenGeometry(self).height()*.85)
self.setWindowTitle('Data Visualization')
# Create tab interface
tabWidget = QtGui.QTabWidget(self)
# define the tab objects
self.tabEeny = QtGui.QWidget(tabWidget)
self.tabMeeny = QtGui.QWidget(tabWidget)
self.tabMiney = QtGui.QWidget(tabWidget)
self.tabMoe = QtGui.QWidget(tabWidget)
self.tabCatchaTiger = QtGui.QWidget(tabWidget)
self.tabByThe = QtGui.QWidget(tabWidget)
self.tabToe = QtGui.QWidget(tabWidget)
# Initialize the tab objects
self.initTabCatchaTiger()
###########################################
############### Main Layout ###############
###########################################
tabWidget.addTab(self.tabEeny, 'Eeny')
tabWidget.addTab(self.tabMeeny, 'Meeny')
tabWidget.addTab(self.tabMiney, 'Miney')
tabWidget.addTab(self.tabMoe, 'Moe')
tabWidget.addTab(self.tabCatchaTiger, 'Catch a Tiger')
tabWidget.addTab(self.tabByThe, 'By The')
tabWidget.addTab(self.tabToe, 'Toe')
self.mainLayout = QtGui.QVBoxLayout(self)
self.mainLayout.addWidget(tabWidget)
self.setLayout(self.mainLayout)
def initTabCatchaTiger(self):
###########################################
############# ADC Capture Tab #############
###########################################
# define tab layout
grid = QtGui.QGridLayout(self.tabCatchaTiger)
# create copy of adc plot and add to row 3 of the grid
self.catchaTigerPlot1 = pg.PlotWidget(name = 'Catch a Tiger 1', parent = self.tabCatchaTiger)
self.catchaTigerPlot1.setTitle('Catch a Tiger 1')
grid.addWidget(self.catchaTigerPlot1, 2, 0, 1, 8)
self.catchaTigerPlot2 = pg.PlotWidget(name = 'Catch a Tiger 2', parent = self.tabCatchaTiger)
self.catchaTigerPlot2.setTitle('Catch a Tiger 2')
grid.addWidget(self.catchaTigerPlot2, 3, 0, 1, 8)
# set layout for tab
self.tabCatchaTiger.setLayout(grid)
def closeEvent(self, event) :
pass
def main() :
# open a QApplication and dialog() GUI
app = QtGui.QApplication([])
windowCrashy = crashyGUI()
windowCrashy.show()
app.exec_()
main()
There seem to be two closely-related issues in the example.
The first one causes Qt to print the QObject::startTimer: QTimer can only be used with threads started with QThread messages on exit.
The second one (which may not affect all users) causes Qt to print QPixmap: Must construct a QApplication before a QPaintDevice, and then dump core on exit.
Both of these issues are caused by python deleting objects in an unpredicable order when it exits.
In the example, the second issue can be fixed by adding the following line to the __init__ of the top-level window:
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
Unless QApplication.setQuitOnLastWindowClosed has been changed to False, this will ensure that the application quits at the right time, and that Qt has a chance to automatically delete all the children of the top-level window before the python garbage-collector gets to work.
However, for this to be completely successful, all the relevant objects must be linked together in a parent-child hierarchy. The example code does this where it can, but there seem to be some critical places in the initialization of the PlotWidget class where it is not done.
In particular, there is nothing to ensure that the central item of the PlotWidget has a parent set when it is created. If the relevant part of the code is changed to this:
class PlotWidget(GraphicsView):
...
def __init__(self, parent=None, background='default', **kargs):
GraphicsView.__init__(self, parent, background=background)
...
self.plotItem = PlotItem(**kargs)
# make sure the item gets a parent
self.plotItem.setParent(self)
self.setCentralItem(self.plotItem)
then the first issue with the QTimer messages also goes away.
Here's a better answer:
You are allowing the QApplication to be collected before python exits. This causes two different issues:
The QTimer error messages are caused by pyqtgraph trying to track its ViewBoxes after the QApplication has been destroyed.
The crash appears to be intrinsic to Qt / PyQt. The following crashes in the same way:
from PyQt4 import Qt, QtGui, QtCore
def main() :
app = QtGui.QApplication([])
x = QtGui.QGraphicsView()
s = QtGui.QGraphicsScene()
x.setScene(s)
x.show()
app.exec_()
main()
You can fix it by adding global app to your main function, or by creating the QApplication at the module level.
Try to write this in block of __init__:
self.setAttribute(Qt.WA_DeleteOnClose)
Personally, I don't put any effort into chasing exit crashes anymore--just use pg.exit() and be done with it.
(but if you do happen to find a bug in pyqtgraph, don't hesitate to open an issue on github)
I had this happen as well and in my case it was caused by a call to deleteLater() on the aboutToQuit-Signal of the application, like so:
def closeEvent(self, *args, **kwargs):
self.deleteLater()
if __name__ == "__main__":
application = QtWidgets.QApplication(sys.argv)
window = testApplication()
# Handle application exit
application.aboutToQuit.connect(window.closeEvent)
# System exit
sys.exit(application.exec_())
Getting rid of the deleteLater on the whole window seemed to solve it.

Carcass of QProgressDialog lingers - sometimes

progress = QtGui.QProgressDialog("Parsing Log", "Stop", 0,numberOfLinesInFile , self)
progress.setWindowModality(QtCore.Qt.WindowModal)
for lineNumber, line in enumerate(file):
# yield a bit to the Qt UI handler
QtGui.QApplication.processEvents()
progress.setValue(lineNumber + 1) # lineNumber is zero-based so need the plus one to match the more literal numberOfLinesInFile
if progress.wasCanceled():
progressWasCancelled = True
break
# ...read and parse lines from file (20mb takes ~10 seconds)
# crank the progress bar through to completion to get rid of it
# this seems to forgo the opportunity to use progress.wasCanceled() subsequently?
progress.setValue(numberOfLinesInFile)
if not progressWasCancelled:
self.updateTable(self.requestRoster)
After this, and regardless of the progress dialogue being cancelled or not, the progress dialogue is hidden (it slides back up into the toolbar). But if I switch application ('command tab' on the Mac) then switch back to my application, a ghost of the QProgressDialog is in front of the main application window! Its progress bar is at 100% and the stop button is blue but not pulsing. It is unresponsive. If I move the application window it disappears.
If I call progress.destroy() after progress.setValue(numberOfLinesInFile) that seems to help. But it seems worrying to copy the example from the docs and get bitten, and I don't know the ramifications of destroy().
I was using PySide, I switched to PyQt and same thing.
Also, sometimes progress.setValue(numberOfLinesInFile) causes subsequent reads of progress.wasCancelled() to return false (but sometimes it returns true!) which is why I set my own progressWasCancelled. Its randomness is disturbing.
I'm on Mac 10.6.8, Qt 4.8.2, Python 2.7. Tried with PySide 1.1.0 and PyQt 4.9.4.
Am I doing this all wrong?
I can't test on a Mac, but I'll try to make a few suggestions which could help solve your issues.
Firstly, if you use a modal progress dialog, there's no need to call processEvents(), as the dialog will handle this itself.
Secondly, this line in your code:
progress.setValue(lineNumber + 1)
is problematic, because to quote the Qt docs:
For the progress dialog to work as expected, you should initially set this property to 0 and finally set it to QProgressDialog::maximum(); you can call setValue() any number of times in-between.
so you should either call progress.setValue(0) before the loop, or just avoid adding the offset altogether. Also, on the final iteration, lineNumber + 1 will equal the maximum, which will reset the dialog at that point (unless autoReset has been set to False). It is for this reason that the Qt example calls setValue(maximum) after the loop has completed.
Finally, there is no problem with calling destroy() or deleteLater() after you've finished with the progress dialog - in fact, it's a good idea. When you pass self to the QProgressDialog constructor, it will become the parent of the dialog and keep a reference to it. So, unless you explicitly delete it, a new child dialog (plus all it's child objects) will be added every time you call the function that uses it (which could potentially waste a lot of memory).
Here's a demo script that may be improvement:
import sys, time
from PyQt4 import QtGui, QtCore
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.button = QtGui.QPushButton('Test', self)
self.button.clicked.connect(self.handleButton)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.button)
def handleButton(self):
file = range(30)
numberOfLinesInFile = len(file)
progressWasCancelled = False
progress = QtGui.QProgressDialog(
"Parsing Log", "Stop", 0, numberOfLinesInFile, self)
progress.setWindowModality(QtCore.Qt.WindowModal)
progress.setMinimumDuration(0)
for lineNumber, line in enumerate(file):
progress.setValue(lineNumber)
if progress.wasCanceled():
progressWasCancelled = True
break
time.sleep(0.05)
progress.setValue(numberOfLinesInFile)
print 'cancelled', progress.wasCanceled(), progressWasCancelled
progress.deleteLater()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())

tk destroy() and grid_forget() don't always work

i'm hoping anyone can help me out here. i'm having an issue with a tkinter gui i built. the issue only happens in windows. My GUI creates a results frame with some labels in it, when it's time to calculate something else, the user clicks on the "newPort" button and that button is supposed to remove the results frame and set to False some instance attributes internal to the calculation. The issue i'm having, which is apparent only in windows is that sometimes the results frame, and its descendant labels don't disappear every time. Sometimes they do, sometimes they don't. The instance variable is correctly set to False but the widgets are still visible on the main GUI. The GUI also contains a couple checkboxes and radiobuttons but they don't impact the creation of the results frame nor its expected destruction. I have not been able to pin point a pattern of actions the user takes before clicking on the newPort button which causes the frame and labels to not get destroyed. This happens when i freeze my app with py2exe, as well as running the app from the python interpreter within the eclipse IDE. I have not tried running the app from the python interpreter directly (i.e. without the IDE) and this problem does not happen on my Mac when i run the app using the eclipse python interpreter. Thanks very much all! My code looks like this:
import Tkinter as TK
class widget(object):
def __init__(self,parent=None):
self.parent = TK.Frame(parent)
self.parent.grid()
self.frame = TK.Frame(self.parent)
self.frame.grid()
newLedger = TK.Button(self.parent,command=self.newPort).grid()
self.calcButton = TK.Button(self.frame,command=self.showResults)
self.calcButton.grid()
self.calcVariable = True
def newPort(self):
self.calcVariable = False
try:
self.second.grid_forget()
self.first.grid_forget()
self.resultsFrame.grid_forget()
self.second.destroy()
self.first.destroy()
self.resultsFrame.destroy()
except:
raise
self.frame.update_idletasks()
def showResults(self):
self.resultsFrame = TK.Frame(self.frame)
self.resultsFrame.grid()
self.first = TK.Label(self.resultsFrame,text='first')
self.first.grid()
self.second = TK.Label(self.resultsFrame,text='second')
self.second.grid()
if __name__ == '__main__':
root = TK.Tk()
obj = widget(root)
root.mainloop()
You don't need to destroy or call grid_forget on the labels, and you don't need to call grid_forget on the resultsFrame; when you destroy the resultsFrame it will cause all off its children to be destroyed, and when these widgets are destroyed they will no longer be managed by grid.
The only way I can get widgets to not be destroyed is if I click on the "calc" button twice in a row without clicking on the "new" button in-between. I'm doing this by running your program from the command line.

Categories