Should I use tkinter Toplevel for Text widget or not? - python

I am trying to use a tkinter Toplevel window with a Text box to post to a Text box in the main window, but I keep getting the following error:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\someone\ouch\lib\tkinter\__init__.py", line 1705, in __call__
return self.func(*args)
File "<ipython-input-14-df46ff30dac4>", line 21, in GetFromDIALOG
X = littletext.get("1.0","end")
NameError: name 'littletext' is not defined
In my script below, I call the Text widget in the Toplevel "littletext" and the Text widget in the main window "BIGTEXT". I also tried setting the Text boxes as StringVars, as most tutorials and scripts seem to suggest is right, but StringVars seem to work more on Entry (not Text) boxes.
I am wondering if I should use a custom class instead of a Toplevel, although I am not sure how I would handle that would work either.
My code is below.
import tkinter as tk
def openDIALOG(window):
DIALOG = tk.Toplevel(window)
DIALOG.title("DIALOG")
DIALOG.geometry("400x300+100+100")
littletext = tk.Text(DIALOG, height=15)
littletext.pack()
btn1 = tk.Button(DIALOG, text ="POST TO MAIN WINDOW", command=GetFromDIALOG)
btn1.pack(side="left")
btn2 = tk.Button(DIALOG, text ="EXIT", command = DIALOG.destroy)
btn2.pack(side="left")
def GetFromDIALOG ():
X = littletext.get("1.0","end")
print(X)
window = tk.Tk()
window.title("main")
menubar = tk.Menu(window)
filemenu = tk.Menu(menubar, tearoff=0)
filemenu.add_command(label="RUN A DIALOG", command=lambda:openDIALOG(window))
filemenu.add_command(label="Exit", command=window.destroy)
menubar.add_cascade(label="FILE", menu=filemenu)
window.config(menu=menubar)
BIGTEXT = tk.Text(window)
BIGTEXT.pack()
BUTTON = tk.Button(window, text="Post", command=openDIALOG)
BUTTON.pack()
window.mainloop()

One way to fix the NameError, which is due it it being a local variable in the openDIALOG() function in your code, is to make it an attribute of instances of the class. This is better than using a global variable, which is another option, but global variables are bad.
Stackoverflow is not intended to replace existing tutorials or documentation, so I'll describe only very briefly what the demo code below it doing. This topic would be covered much more thoroughly in most of the Python tutorials that are available online, if you took time to for them.
So, here's how that might be done based on the code in your question. Notice how littletext is now self.littletext. This is because it has been turned into a class attribute of the class MY_DIALOG that's been defined, so it can be access through the self argument which will automatically be passed as the first argument of all the class' functions (which are know as class "methods").
import tkinter as tk
from tkinter.constants import *
class MyDialog:
def __init__(self, window):
DIALOG = tk.Toplevel(window)
DIALOG.title("DIALOG")
DIALOG.geometry("400x300+100+100")
self.littletext = tk.Text(DIALOG, height=15)
self.littletext.pack()
btn1 = tk.Button(DIALOG, text="POST TO MAIN WINDOW", command=self.GetFromDIALOG)
btn1.pack(side="left")
btn2 = tk.Button(DIALOG, text="EXIT", command=DIALOG.destroy)
btn2.pack(side="left")
def GetFromDIALOG(self):
X = self.littletext.get("1.0", END)
print(X)
if __name__ == '__main__':
window = tk.Tk()
window.title("main")
menubar = tk.Menu(window)
filemenu = tk.Menu(menubar, tearoff=0)
filemenu.add_command(label="RUN A DIALOG", command=lambda: MyDialog(window))
filemenu.add_command(label="Exit", command=window.destroy)
menubar.add_cascade(label="FILE", menu=filemenu)
window.config(menu=menubar)
BIGTEXT = tk.Text(window)
BIGTEXT.pack()
BUTTON = tk.Button(window, text="Post", command=lambda: MyDialog(window))
BUTTON.pack()
window.mainloop()

Thanks martineau! :)
There was a few small problems I cleaned up.
The dialog you coded didnt post back to BIGTEXT in the main window, so I completed the callback aspect of it.
The BUTTON in the footer of the main window was actually a mistake on my part. I accidentally forgot to delete it in the original post, so I deleted it here and let the menu work the call.
Final code is below.
import tkinter as tk
from tkinter.constants import *
class MY_DIALOG:
def __init__(self, window):
DIALOG = tk.Toplevel(window)
DIALOG.title("DIALOG")
DIALOG.geometry("400x300+100+100")
self.littletext = tk.Text(DIALOG, height=15)
self.littletext.pack()
btn1 = tk.Button(DIALOG, text="POST TO MAIN WINDOW", command=self.GetFromDIALOG)
btn1.pack(side="left")
btn2 = tk.Button(DIALOG, text="EXIT", command=DIALOG.destroy)
btn2.pack(side="left")
def GetFromDIALOG(self):
X = self.littletext.get("1.0","end")
BIGTEXT.insert("end", X)
self.littletext.delete("1.0","end")
if __name__ == '__main__':
window = tk.Tk()
window.title("main")
menubar = tk.Menu(window)
filemenu = tk.Menu(menubar, tearoff=0)
filemenu.add_command(label="RUN A DIALOG", command=lambda: MY_DIALOG(window))
filemenu.add_command(label="Exit", command=window.destroy)
menubar.add_cascade(label="FILE", menu=filemenu)
window.config(menu=menubar)
BIGTEXT = tk.Text(window)
BIGTEXT.pack()
window.mainloop()

Related

Can I make the checkbutton already checked when I open the window in tkinter menubar in Python?

I was trying to make a application by tkinter, and I wanted to make a checkbutton in menubar. However, I don't know how to make the checkbutton already checked when I run the code.
Here's my code
import tkinter as tk
def func():
#some code here
var = tk.BooleanVar
win = tk.Tk()
menubar = tk.Menu(win)
optmenu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label='Options', menu=optmenu)
optmenu.add_checkbutton(label='xyz', variable=var, onvalue=1, offvalue=0, command=func)
win.config(menu=menubar)
win.mainloop()
use this code below:
import tkinter as tk
def func():
pass
#some code here
win = tk.Tk()
var = tk.StringVar(win,'on')
menubar = tk.Menu(win)
optmenu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label='Options', menu=optmenu)
optmenu.add_checkbutton(label='xyz', variable=var, onvalue='on', offvalue='off', command=func)
win.config(menu=menubar)
win.mainloop()
have fun :)

Python: Tkinter open custom widget in new window

My aim is to generate a window with a button "NewWindow" on it. If I press the button, the program should open a new window. This window I stored in a class "NewWindow" to quickly reproduce it.
In this "NewWindow" I have another button. If I press it the label of basic window should be updated and the window "NewWindow" should be closed automatically.
Here is my code:
from tkinter import *
class NewWindow(Toplevel):
def __init__(self, master = None):
super().__init__(master = master)
self.title('NewWindow')
self.lb = Label(self, text='Hello')
self.lb.grid(column=0, row=0, columnspan=1)
self.bt1 = Button(self, text="apply Hello", command= self.bt_press)
self.bt1.grid(column=0, row=1)
def bt_press(self):
window.basic_lb.text = "Hello"
window = Tk()
def new_Editor():
a = NewWindow(window)
window.title("BasicWindow")
window.basic_lb = Label(window, text='None')
window.basic_lb.grid(column=0, row=0, columnspan=1)
window.basic_bt = Button(window, text="NewWindow", command=new_Editor)
window.basic_bt.grid(column=0, row=1)
window.mainloop()
Problems:
At start both windows NewWindow and BasicWindow are displayd. I only want to open BasicWindow and NewWindow should be opened after button basic_bt is clicked. How can I solve it? (already solved by commed below)
Why the label text in basic_lb did not get some update after pressing self.bt1?
How is it possible to close NewWindow with use of bt_press method?
You have a few typos/errors in your code that are casuing some of your problems. As #Tim said, when you pass a function to a command like command=function(), it will be called on runtime, not when the button is pressed. You need to pass the function handle to the command, command=function. You got around this by using a lambda function in your button command, but it is easier to just have command=self.bt_press
Answering your second question, window.basic_lb.text = "Hello" is not how you change the text in a tkinter Label, use <Label>.config(text="Hello"). You also should use self.master and define self.master = master in __init__ instead of just using window, because while you can access window due to it not being defined in local scope, it's better to explicitly define it.
You can close a window using window.destroy().
Your working code is now:
from tkinter import *
class NewWindow(Toplevel):
def __init__(self, master = None):
super().__init__(master = master)
self.title('NewWindow')
self.master = master
self.lb = Label(self, text='Hello')
self.lb.grid(column=0, row=0, columnspan=1)
self.bt1 = Button(self, text="apply Hello", command=self.bt_press)
self.bt1.grid(column=0, row=1)
def bt_press(self):
self.master.basic_lb.config(text="Hello")
self.destroy()
window = Tk()
def new_Editor():
a = NewWindow(window)
window.title("BasicWindow")
window.basic_lb = Label(window, text='None')
window.basic_lb.grid(column=0, row=0, columnspan=1)
window.basic_bt = Button(window, text="NewWindow", command=new_Editor)
window.basic_bt.grid(column=0, row=1)
window.mainloop()

python, tkinter, how can i display console result to tkinter textbox

if i click noticeall in menubar, i wanna it to be displayed on textarea at mainframe.py
how can i write code in def noticeall.. plz
main.py file code is
import mainframe
mainframe.show()
mainframe.py file code is
def show ():
frame = tkinter.Tk()
frame.title('test')
frame.geometry('800x600')
frame.resizable(False, False)
sf = ScrolledFrame(frame, width=800, height=600)
sf.pack(side="top", expand=1, fill="both")
sf.bind_arrow_keys(frame)
sf.bind_scroll_wheel(frame)
menu_bar = menubar.make_menubar(frame,)
frame.config(menu=menu_bar)
inner_frame = sf.display_widget(Frame)
entry = Entry(inner_frame, width=750)
entry.pack()
entry.focus_set()
textarea = Text(inner_frame, width=750, height=200)
textarea.pack()
frame.mainloop()
menubar.py file code is
def make_menubar(frame):
menubar = Menu(frame)
file_menu = Menu(menubar, tearoff=0)
file_menu.add_command(label="noticeall",command=noticeall)
def noticeall():
notice_list = ncontroller.select_all()
print_list(notice_list)'
plz recommend next line code
print_list.insert(textarea)??? - i cant, textarea is local variable in mainframe.py
def make_menubar(frame, textarea):
menubar = Menu(frame)
file_menu = Menu(menubar, tearoff=0)
file_menu.add_command(label="noticeall", command=lambda: noticeall(textarea))
return menubar
def noticeall(textarea):
notice_list = ncontroller.select_all()
textarea.insert(notice_list)
In fact, the best way is to create a class. Pass parameters through self.

Tkinter - RuntimeError: maximum recursion depth exceeded

I started programming in Python on Monday. I have enjoyed learning it. But I am stuck trying to understand how to avoid recursion when switching between tkinter menus! I am sure this is a very basic question, and I appreciate you tolerating my ignorance on this subject, but I have been unable to find an answer elsewhere.
What I am now doing is, eventually, giving me the error: RuntimeError: maximum recursion depth exceeded while calling a Python object
This is the pattern I am currently using. UPDATED: The code below is now a full, isolated copy reproducing the problem I am facing! :D
from tkinter import *
def mainmenu():
global frame, root
frame.destroy()
frame = Frame()
frame.pack()
button1 = Button(frame, text="anothermenulikethis", command = anothermenulikethis)
button2 = Button(frame, text="anothermenulikethis", command = anothermenulikethis)
button3 = Button(frame, text="mainmenu", command = mainmenu)
button1.pack(side=LEFT)
button2.pack(side=LEFT)
button3.pack(side=LEFT)
root.mainloop()
def anothermenulikethis():
global frame, root
frame.destroy()
frame = Frame()
frame.pack()
button1 = Button(frame, text="mainmenu", command = mainmenu)
button2 = Button(frame, text="mainmenu", command = mainmenu)
button3 = Button(frame, text="anothermenulikethis", command = anothermenulikethis)
button1.pack(side=LEFT)
button2.pack(side=LEFT)
button3.pack(side=LEFT)
root.mainloop()
root = Tk()
root.title("Recursive Menu Problem Isolation")
root.geometry("1200x600")
frame = Frame()
mainmenu()
And it all works fine, until its inevitable failure from maximum recursion depth. If anyone can suggest a better way of doing things, or has a link to an example of a better way of doing this, I am eager to learn.
PS: I have looked at and tried increasing the recursion depth, but I feel that is a poor man's solution to what is a fundamental problem with my approach.
Thank you in advance, everyone.
As requested, here is the traceback:
Exception in Tkinter callback
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.2/lib/python3.2/tkinter/__init__.py", line 1399, in __call__
return self.func(*args)
File "/Users/diligentstudent/Desktop/menutest.py", line 11, in mainmenu
button1 = Button(frame, text="anothermenulikethis", command = anothermenulikethis)
File "/Library/Frameworks/Python.framework/Versions/3.2/lib/python3.2/tkinter/__init__.py", line 2028, in __init__
Widget.__init__(self, master, 'button', cnf, kw)
File "/Library/Frameworks/Python.framework/Versions/3.2/lib/python3.2/tkinter/__init__.py", line 1958, in __init__
(widgetName, self._w) + extra + self._options(cnf))
File "/Library/Frameworks/Python.framework/Versions/3.2/lib/python3.2/tkinter/__init__.py", line 1043, in _options
v = self._register(v)
File "/Library/Frameworks/Python.framework/Versions/3.2/lib/python3.2/tkinter/__init__.py", line 1079, in _register
f = CallWrapper(func, subst, self).__call__
RuntimeError: maximum recursion depth exceeded
Only one mainloop() is needed to handle a tkinter GUI.
With that said, I think you just need an example of the class structure:
from tkinter import Tk,Button
class Application(Tk):
def say_hi(self):
print('Hello world?!')
def close_app(self):
self.destroy()
def create_Widgets(self):
self.quitButton = Button(self, width=12, text='Quit', bg='tan',
command=self.close_app)
self.quitButton.grid(row=0, column=0, padx=8, pady=8)
self.helloButton = Button(self, width=12, text='Hello',
command=self.say_hi)
self.helloButton.grid(row=0, column=1, padx=8, pady=8)
def __init__(self):
Tk.__init__(self)
self.title('Hello world!')
self.create_Widgets()
app = Application()
app.mainloop()
To avoid possible conflicts with other modules, some people prefer importing like this
(clearly stating where everything comes from):
import tkinter as tk
class Application(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.title('Hello world!')
self.quitButton = tk.Button(self, width=12, text='Quit', bg='tan',
command=self.close_app)
self.quitButton.grid(row=0, column=0, padx=8, pady=8)
self.helloButton = tk.Button(self, width=12, text='Hello',
command=self.say_hi)
self.helloButton.grid(row=0, column=1, padx=8, pady=8)
def say_hi(self):
print('Hello world?!')
def close_app(self):
self.destroy()
app = Application()
app.mainloop()
And as you can see, creating the widgets can easily happen in the __init__
I decided to make a more practical / educational example based on what I've learned in the past month. While doing so I had a bit of a revelation: not everything requires a self. prefix in a class! This is especially true with a tkinter class, because you aren't manipulating it as an object in the main program. Mostly you need the self. prefix when you are going to use something in a method later. The previous examples display how anything (like the buttons) can receive a self. prefix, even when completely unnecessary.
Some things this example will show:
• pack() and grid() can be used in the same GUI as long as they don't share a master.
• A Text widget can be made to not expand when the font size changes.
• How to toggle a bold tag on and off of selected text.
• How to truly center a GUI on the screen. (more information here)
• How to make a Toplevel window appear in the same location relative to the main window.
• Two ways to prevent a Toplevel window from being destroyed, so it only needs to be created once.
• Make ctrl+a (select all) function properly.
import tkinter as tk
import tkFont
class Application(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.title('T-Pad')
# Menubar
menubar = tk.Menu(self)
filemenu = tk.Menu(menubar, tearoff=0)
filemenu.add_command(label="Exit", command=self.close_app)
menubar.add_cascade(label="File", menu=filemenu)
formatmenu = tk.Menu(menubar, tearoff=0)
formatmenu.add_command(label="Font", command=self.show_sizeWin)
menubar.add_cascade(label="Format", menu=formatmenu)
self.config(menu=menubar)
# Bold Button
boldButton = tk.Button(self, width=12, text='Bold',
command=self.make_bold)
boldButton.pack()
# Text widget, its font and frame
self.defaultFont = tkFont.Font(name="defFont")
textFrame = tk.Frame(self, borderwidth=1, relief="sunken",
width=600, height=600)
textFrame.grid_propagate(False) # ensures a consistent GUI size
textFrame.pack(side="bottom", fill="both", expand=True)
self.mText = tk.Text(textFrame, width=48, height=24, wrap='word',
font="defFont")
self.mText.grid(row=0, column=0, sticky="nsew")
# Scrollbar and config
tScrollbar = tk.Scrollbar(textFrame, command=self.mText.yview)
tScrollbar.grid(row=0, column=1, sticky='nsew', pady=1)
self.mText.config(yscrollcommand=tScrollbar.set)
# Stretchable
textFrame.grid_rowconfigure(0, weight=1)
textFrame.grid_columnconfigure(0, weight=1)
# Bold Tag
self.bold_font = tkFont.Font(self.mText, self.mText.cget("font"))
self.bold_font.configure(weight="bold")
self.mText.tag_configure("bt", font=self.bold_font)
# Center main window
self.update_idletasks()
xp = (self.winfo_screenwidth() / 2) - (self.winfo_width() / 2) - 8
yp = (self.winfo_screenheight() / 2) - (self.winfo_height() / 2) - 30
self.geometry('{0}x{1}+{2}+{3}'.format(self.winfo_width(), self.winfo_height(),
xp, yp))
# Font Size Window (notice that self.sizeWin is given an alias)
sizeWin = self.sizeWin = tk.Toplevel(self, bd=4, relief='ridge')
self.sizeList = tk.Listbox(sizeWin, width=10, height=17, bd=4,
font=("Times", "16"), relief='sunken')
self.sizeList.grid()
doneButton = tk.Button(sizeWin, text='Done', command=sizeWin.withdraw)
doneButton.grid()
for num in range(8,25):
self.sizeList.insert('end', num)
sizeWin.withdraw()
sizeWin.overrideredirect(True) # No outerframe!
# Below is another way to prevent a TopLevel window from being destroyed.
# sizeWin.protocol("WM_DELETE_WINDOW", self.callback)
# Bindings
# Double click a font size in the Listbox
self.sizeList.bind("<Double-Button-1>", self.choose_size)
self.bind_class("Text", "<Control-a>", self.select_all)
## def callback(self):
## self.sizeWin.withdraw()
def select_all(self, event):
self.mText.tag_add("sel","1.0","end-1c")
def choose_size(self, event=None):
size_retrieved = self.sizeList.get('active')
self.defaultFont.configure(size=size_retrieved)
self.bold_font.configure(size=size_retrieved)
def show_sizeWin(self):
self.sizeWin.deiconify()
xpos = self.winfo_rootx() - self.sizeWin.winfo_width() - 8
ypos = self.winfo_rooty()
self.sizeWin.geometry('{0}x{1}+{2}+{3}'.format(self.sizeWin.winfo_width(),
self.sizeWin.winfo_height(), xpos, ypos))
def make_bold(self):
try:
current_tags = self.mText.tag_names("sel.first")
if "bt" in current_tags:
self.mText.tag_remove("bt", "sel.first", "sel.last")
else:
self.mText.tag_add("bt", "sel.first", "sel.last")
except tk.TclError:
pass
def close_app(self):
self.destroy()
app = Application()
app.mainloop()
A note to others with this issue: your command for your button may not be at the proper indentation level! Check that it is inline with your other class methods before digging any further. I ran into this issue myself not long ago, and re-checking my indentations fixed everything.

Tkinter Button does not appear on TopLevel?

This is a piece of code I write for this question: Entry text on a different window?
It is really strange what happened at mySubmitButton, it appears that the button does not want to appear when it is first started, it will, however appear when you click on it. Even if you click on it and release it away from the button, that way it won't be send. I am suspecting if this only happen on a mac, or it only happen to my computer, because it is a very minor problem. Or it is something silly I did with my code.
self.mySubmitButton = tk.Button(top, text='Hello', command=self.send)
self.mySubmitButton.pack()
Am I missing something? I googled and found this question and answer on daniweb. And I do a diff on them, can't figure out what he did "fixed", but I did see the line is changed to command=root.quit. But it is different from mine anyway...
Here is the full source code, and there is no error message, but the button is just missing.
import tkinter as tk
class MyDialog:
def __init__(self, parent):
top = self.top = tk.Toplevel(parent)
self.myLabel = tk.Label(top, text='Enter your username below')
self.myLabel.pack()
self.myEntryBox = tk.Entry(top)
self.myEntryBox.pack()
self.mySubmitButton = tk.Button(top, text='Hello', command=self.send)
self.mySubmitButton.pack()
def send(self):
global username
username = self.myEntryBox.get()
self.top.destroy()
def onClick():
inputDialog = MyDialog(root)
root.wait_window(inputDialog.top)
print('Username: ', username)
username = 'Empty'
root = tk.Tk()
mainLabel = tk.Label(root, text='Example for pop up input box')
mainLabel.pack()
mainButton = tk.Button(root, text='Click me', command=onClick)
mainButton.pack()
root.mainloop()
Adding another button right after this one, the second one actually appear. I thought it might be because I didn't call the same function, but I called the same one and it does the exact same thing it appears...
Adding a empty label between them, doesn't work. The button still isn't being draw.
PS: I am using Mac OS 10.5.8, and Tk 8.4.7.
I see the hello button, but I'm on windows 7.
I did a quick re-write of your example. I'll be curious if it makes any difference for you.
import tkinter as tk
class GUI(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
mainLabel = tk.Label(self, text='Example for pop up input box')
mainLabel.pack()
mainButton = tk.Button(self, text='Click me', command=self.on_click)
mainButton.pack()
top = self.top = tk.Toplevel(self)
myLabel = tk.Label(top, text='Enter your username below')
myLabel.pack()
self.myEntryBox = tk.Entry(top)
self.myEntryBox.pack()
mySubmitButton = tk.Button(top, text='Hello', command=self.send)
mySubmitButton.pack()
top.withdraw()
def send(self):
self.username = self.myEntryBox.get()
self.myEntryBox.delete(0, 'end')
self.top.withdraw()
print(self.username)
def on_click(self):
self.top.deiconify()
gui = GUI()
gui.mainloop()

Categories