Stuck in infinite loop after destroying parent window (python, Tkinter) - python-3.x

The problem I am encountering is that I appear to be stuck in an infinite loop, (If I am not, please correct me). I am using tkinter for python 3.6 (64 bit) on windows 10.
In the module I am having an issue with I have 3 entry widgets and 2 buttons. Both buttons call the "destroy()" function in order to kill the parent window.
Below is a heavily abstracted version of my module, the purpose of the module is to take inputs from the entry widget and write them to a file.
def Create():
parent = Tk()
parent.MakeItlookNice
entry1 = Entry(parent)
entry1.insert(INSERT, "Please enter your desired username here")
entry2 = Entry(parent)
entry2.insert(INSERT, "Please enter your desired password here")
entry3 = Entry(parent)
entry3.insert(INSERT, "What is your mother's maiden name")
Submit = tk.Button(parent,
text ="Click here to submit your stuff",
command = lambda: [parent.destroy(),
submit.function()])
Cancel = tk.Button(parent,
text ="Click here to cancel your request",
command = lambda: parent.destroy())
parent.mainloop()
This function is contained within the module "RegisterNewUser". The "Menu" module is the module that called this function. As far as I am aware once parent.destroy() is called there is no more code to execute since it is all contained within parent.mainloop(), therefore the function is finished and the "Menu" module should continue executing.
What should happen:
I want the Submit button to destroy the window, execute the function and then return to the "Menu" module.
I want the cancel button to destroy the window and return to the "Menu" module.
What actually happens:
The window closes, like it is supposed to
But the code inside the "Menu" module does not start executing again
When I go to close the python shell, it warns me that the program is still running
Ultimately my question is, what code is still running and why hasn't it stopped?
Thank you for reading this and if you require more detail please let me know.
EDIT: I have done research on this topic before posting this question. I have read the documentation on both the tk.destroy() function and the tk.mainloop() function, I have also opened up the Tkinter module in IDLE to try and understand what happens at a deeper level but after all this, I was still unable to figure out a solution. This is my first question on stack overflow, please forgive me if I have done anything wrong.

Hmmm, so you say multiple windows? an easier way to achieve a complex UI as such is using a concept called frames. Tkinter allows you to completely change you screen and layout if you switch to a new frames. This might require you to reprogram alot of code. for an example see Switch between two frames in tkinter
Also, Some guy built a really nice Bitcoin monitoring app using tkinter and frames on youtube

I think you would probably benefit from using Toplevel() here.
I have taken the code you provided and added it to a class used to create the main window and to manage the pop up window.
I noticed a few things with you code.
Its obvoious you are importing tkinter twice like this:
import tkinter as tk
from tkinter import *
I can tell from how you have written your entry fields vs your buttons. Do not do this. Instead just used one or the other. I recommend just using import tkinter as tk.
You are using a function to create a new tkinter instance and judging by your question you all ready have a tkinter instance created for your menu. Instead of creating new Tk() instances you can use Toplevel() instead to open a new window that can inherit everything from the main window and should be easier to manage.
You do not really need to use lambda in this situation. I have also removed the lambda function and replaced with a simple command that will work here.
Take a look at the below code and let me know if you have any questions.
import tkinter as tk
class MyApp(tk.Frame):
def __init__(self, master, *args, **kwargs):
tk.Frame.__init__(self, master, *args, **kwargs)
self.master = master
self.master.title("Main MENU")
tk.Button(self.master, text="Open top level", command = self.create).pack()
def create(self):
self.top = tk.Toplevel(self.master)
entry1 = tk.Entry(self.top, width = 35)
entry1.pack()
entry1.insert(0, "Please enter your desired username here")
entry2 = tk.Entry(self.top, width = 35)
entry2.pack()
entry2.insert(0, "Please enter your desired password here")
entry3 = tk.Entry(self.top, width = 35)
entry3.pack()
entry3.insert(0, "What is your mother's maiden name")
tk.Button(self.top, text ="Click here to submit your stuff",
command = self.Submit).pack()
tk.Button(self.top, text ="Click here to cancel your request",
command = self.top.destroy).pack()
def Submit(self):
print("Do something here to submit data")
self.top.destroy()
if __name__ == "__main__":
root = tk.Tk()
app1 = MyApp(root)
tk.mainloop()

You can use toplevel() and its library function wait_window() just prior to (or instead of) your mainloop() and your problem will be solved

wait_window() mentioned above worked for me in the code below replacing popup.mainloop() with it, the mainloop() kept my code in an infinite loop upon the popup even after okay button was hit

Related

Can You Implement OSX-Native Tab+Space Navigation with ttk.Button()?

Summarize the Problem
I am looking to use tkinter to implement the actions of tabbing and using the spacebar to cycle and activate UI elements, respectively. I am hoping to do so in a way that mimics the native behavior of OSX, as seen in the attachment below. This works fine on most other widgets in ttk.
This is made up of the following:
Allow components to be "focused" by the user using the tab key
Cause components to become visually highlighted when focused
Component can be triggered using the spacebar while focused
And the tricky part, it uses (close to) the native GUI appearance of the Operating System
Other StackOverflow Questions About This
The closest answer on this site I could find was this question. It may be important to note that the question is asked about Python 2.7.9. This question is interesting, because it suggests that changing a tk.Button() to ttk.Button() alleviates the issue, while when testing the code, I have found the opposite to be true. A tk.Button() will (inelegantly) highlight on OSX, whereas a ttk.Button() provides no visual feedback. Below is the code (with imports modified to run in 2021 on Python 3.X)
import tkinter as tk
from tkinter import ttk
class App(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.master = master
entry1 = tk.Entry(self)
entry1.pack()
entry2 = tk.Entry(self)
entry2.pack()
# CHANGE BELOW LINE TO A ttk.Button() TO TEST
button1 = tk.Button(self, text="Test", command=self.yup)
button1.pack()
def yup(self):
print("yup")
root = tk.Tk()
app = App(root).pack()
root.mainloop()
My Attempted Solutions
Solution One: Implement buttons normally using ttk.Button() with ttk.Style() to indicate focus
Pros:
Uses native OS style
Accepts focus via tab and activation via spacebar
Cons:
Does not visually indicate focus unless forced to with ttk.Style()
To the extent of my knowledge cannot be given a highlighted border by ttk.Style(), so we must settle for colored text
Example Code:
from tkinter import Tk
from tkinter import ttk
root = Tk()
def one():
print("one")
def two():
print("two")
style = ttk.Style()
style.configure('C.TButton')
style.map('C.TButton',
foreground = [('pressed','red'),('focus','blue')],
background = [('pressed','!disabled','red'),('focus','white')],
relief=[('pressed', 'sunken'),
('!pressed', 'raised')])
# Define lower GUI
B1 = ttk.Button(root, text='Button One', command=one, style='C.TButton')
B1.pack(padx=20, pady=(20,0))
B2 = ttk.Button(root, text='Button Two', command=two, style='C.TButton')
B2.pack(padx=20, pady=10)
root.mainloop()
Solution Two: Use tk.Button() instead
Pros:
Accepts focus via tab and activation via spacebar
Natively highlights button using a border
Cons:
Does not look that appealing, border is misaligned and a plain rectangle
I cannot get many parameters to work properly on OSX, particularly activebackground and highlightthickness, limiting aesthetic options.
Example Code:
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
def one():
print("one")
def two():
print("two")
B1 = tk.Button(root, text='Button One', command=one)
B1.pack(padx=20, pady=(20,0))
B2 = tk.Button(root, text='Button Two', command=two)
B2.pack(padx=20, pady=10)
root.mainloop()
Solution Three: Use the tkmacosx library's Button()
Pros:
Custom made for this problem
Highlights on tab-press with OSX-like style
Really, just everything I'm looking for
Cons:
Does not trigger button on spacebar
This last part is interesting, because based on the documentation (takefocus), this should be the expected behavior. On my machine, this is not the case (Catalina 10.15.7)
Example Code:
from tkinter import Tk
from tkmacosx import Button
root = Tk()
def one():
print("one")
def two():
print("two")
B1 = Button(root, text='Button One', command=one)
B1.pack(padx=20, pady=(20,0))
B2 = Button(root, text='Button Two', command=two)
B2.pack(padx=20, pady=10)
root.mainloop()
Concluding
Historically I understand that tkinter and OSX have not always played together perfectly, and that if I want more precise native control, I might switch to QT. I am immensely thankful for the existence of tkinter, and hope I am not asking too much.
I do want to be sure however that I'm not making an error before I try forking a repo or pursuing other solutions.
Regarding tkmacosx, it seems like this solution should be working the way it is described in the documentation, and I was hoping to get confirmation of this problem from another user, to see if it would be appropriate to file an issue on the github page.
Thank you very much for reading this post. Please feel free to ask for any additional info!

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.

Why the assigned action is done before clicking the button in pycharm ?(language- python)

I created a root window by using pycharm. I added a button widget which does not work properly. When I run the program it executes the action (action that is assigned for that button) before clicking on the button.
code:-
from tkinter import messagebox
def buttontapped():
messagebox._show("Message", "Hello World")
root = Tk()
label1 = Label(root, text="Nish")
label1.pack()
Button(root, text="Message", command=buttontapped()).pack()
root.mainloop()```
As you have not shared the code, i can only guess what could be causing the problem.
However, this sounds like a common mistake of including parenthesis in the bind method: button.bind('<event>',function())
This calls the function immediately and binds the return value instead of the function.
If this is the problem, the solution is to remove the parenthesis.

Print Current Time In Tkinter

I have written some code in python for a live time in tkinter.
Whenever I run the code it comes up with some numbers on the tkinter window like 14342816time. Is there a way to fix this?
import tkinter
import datetime
window = tkinter.Tk()
def time():
datetime.datetime.now().time()
datetime.time(17, 3,)
print(datetime.datetime.now().time())
tkinter.Label(window, text = time).pack()
window.mainloop()
After some fixes to your code, I came up with the following, which should at least get you started toward what you want:
import datetime
import tkinter
def get_time():
return datetime.datetime.now().time()
root = tkinter.Tk()
tkinter.Label(root, text = get_time()).pack()
root.mainloop()
The imports are needed so that your program knows about the contents of the datetime and tkinter modules - you may be importing them already, however, I can't tell that for certain from what you posted. You need to create a window into which you put your label, which wasn't happening; following convention, I called that parent (and only) window "root". Then I put the Label into root. I changed the name of your time() function to get_time(), since it's best to avoid confusing fellow programmers (and maybe yourself) with a function that shares its name with another (the time() function in time). I removed two lines in get_time() that don't actually accomplish anything. Finally, I changed the print you had to a return, so that the value can be used by the code calling the function.
There are other improvements possible here. If you're content with the time as it is, you could eliminate the get_time function and just use datetime.datetime.now().time() instead of calling get_time(). However, I suspect you might want to do something to clean up that time before it is displayed, so I left it there. You might want to research the datetime and time modules some more, to see how to clean things up.

Adding items to a Listbox using Entries from another class

I have created two classes, one is in People.py (which will be the parent class) which contains a list box that can be populated by just opening a file and adding content to the list box line by line.
Another class is in Names.py (which I want it to be child class), which contains entries of first name, last name, and a combo box for titles that should (will implement once question/problem is answered) go into the list in the main window where the class is People. I am trying to use an OOP model. Right now, it's not fully OOP, but I will refactor the code later.
I have tried posting this code before but people are having trouble running it due to indentation problems, so I'm providing the links to the classes. In Dropbox Name Class and People Class:
Note: I'm running this in a Linux environment, so you may have to modify the file choosing line in the People class if using Windows (or another OS).
f = os.path.expanduser('~/Desktop')
Actually, you still have a problem of inconsistent use of tabs and spaces, which I solved, but maybe other people cannot solve it.
First of all, you should name your files/modules with lower cases (by convention, and you should follow it!). Then, in Names.py you are doing from Tkinter import * and then from Tkinter import Tk, which does not make any sense: with the first you are already importing Tk.
Your problem is the following:
People.people_list.insert(Tk.END, FirstName.get()) AttributeError:
'module' object has no attribute 'people_list'
In fact, you are trying to access an inexistent attribute of the module People called people_list, which is a local variable to some functions, from what I have been seeing.
If you want to fill a Listbox which is a property of some Toplevel A, with the input from another Toplevel B, you should pass a reference of the Toplevel A to B, maybe during its construction.
Here you have an example:
from tkinter import * # python 3
class Other(Toplevel):
"""Note that the constructor of this class
receives a parent called 'master' and a reference to another Toplevel
called 'other'"""
def __init__(self, master, other):
Toplevel.__init__(self, master)
self.other = other # other Toplevel
self.lab = Label(self, text="Insert your name: ")
self.lab.grid(row=0, column=0)
self.entry = Entry(self)
self.entry.grid(row=0, column=1)
# When you click the button you call 'self.add'
self.adder = Button(self, text='Add to Listbox', command=self.add)
self.adder.grid(row=1, column=1)
def add(self):
"""Using a reference to the other window 'other',
I can access its attribute 'listbox' and insert a new item!"""
self.other.listbox.insert("end", self.entry.get())
class Main(Toplevel):
"""Window with a Listbox"""
def __init__(self, master):
Toplevel.__init__(self, master)
self.people = Label(self, text='People')
self.people.grid(row=0)
self.listbox = Listbox(self)
self.listbox.grid(row=1)
if __name__ == "__main__":
root = Tk()
root.withdraw() # hides Tk window
main = Main(root)
Other(root, main) # passing a reference of 'main'
root.mainloop()
I noticed also that you are using 2 instance of Tk for each of your windows, which is bad. You should use just one instance of Tk for every application. If you want to use multiple windows, just use Toplevels, as I mentioned.
In general, your structure is not so good. You should start by creating simple good applications, and then pass to big ones once you grasp the basic concepts.

Resources