Python tkinter - calling a button command in a class - python

I'm working on functions within a class, and one of the issues I'm running into is adding a button that terminates the program. Here is the current code:
class ClassName():
def __init__(self, root):
self.root = root
def close_root(self, root):
root.destroy()
root.quit()
def addExitButton(self, root):
Button(root, text = 'Exit', width = 10, command = self.close_root).grid(row = 5,
column = 0)
Within the button arguments, I have tried command = self.close_root(root) But this gives me an error because you can't call a function if you want the button to do something (I forget the reason as to why this is). I have also tried
def close_root(self):
self.destroy()
self.quit()
def addExitButton(self, root):
Button(..., command = self.close_root,...)
And this does not work either as the class doesn't have the attribute destroy. I'm not sure how to approach this after trying a few different ways.

You need to actually access the root's functions. So using self.root.destory() or self.root.quit() will work because your root object has those methods but your class does not.
You should also only use one of them, in this case, destroy is the best option I think. And you can probably just use that when creating the button. So replace the button callback (command) with self.root.destory.
More here on how to close a Tkinter window here.

Related

I get the error _tkinter.TclError: bad window path name ".!button" when I destroy the button

from tkinter import *
master=Tk()
class check:
def __init__(self,root):
self.root=root
self.b1=Button(root,text="Click me",command=self.undo)
self.b2=Button(root,text="Again",command=self.click)
def click(self):
self.b1.place(relx=0.5,rely=0.5)
def undo(self):
self.b1.destroy()
self.b2.place(relx=0.2,rely=0.2)
c=check(master)
c.click()
master.mainloop()
This is my code. I get _tkinter.TclError: bad window path name ".!button" error only when I use destroy method. But I want to delete previous button when another button appears.What should I do?
What are you doing? When you click the "Click me" button (and call the self.undo method, where the self.b1 button is destroyed) and then click the "Again" button (and call the self.click method, which tries to place already destroyed self.b1 button), you get the error, that the button does not exist. Of course, it doesn't because you have destroyed it.
It looks like you meant to hide the button. If you intended to do this, then you could just use .place_forget() method (there are also .pack_forget() and .grid_forget() methods for pack and grid window managers, respectively), that hides the widget, but not destroys it, and hence you would be able to restore it again when you need.
Here is your fixed code:
from tkinter import *
master = Tk()
class check:
def __init__(self, root):
self.root = root
self.b1 = Button(root, text="Click me", command=self.undo)
self.b2 = Button(root, text="Again", command=self.click)
def click(self):
self.b2.place_forget()
self.b1.place(relx=0.5, rely=0.5)
def undo(self):
self.b1.place_forget()
self.b2.place(relx=0.2, rely=0.2)
c = check(master)
c.click()
master.mainloop()
I can also give you a piece of advice about the implementation:
1) You should write the code according to the PEP8 style; classes should be named in the CamelCase.
2) You should inherit your Tkinter app class(es) either from Tk (usage is shown below) Toplevel(the same as Tk, but use ONLY for child windows), Frame class (almost the same as for Tk, but you need to pack/grid/place that Frame in a window).
3) It's better to create the widgets in a separate function (it helps while developing complex and big apps).
4) It's recommended to write if __name__ == "__main__": condition before creating the window (if you do like this, you will be able to import this code from other modules, and the window won't open in that case).
Here is an example:
from tkinter import *
class Check(Tk):
def __init__(self):
super().__init__()
self.create_widgets()
self.click()
def create_widgets(self):
self.b1 = Button(self, text="Click me", command=self.undo)
self.b2 = Button(self, text="Again", command=self.click)
def click(self):
self.b2.place_forget()
self.b1.place(relx=0.5, rely=0.5)
def undo(self):
self.b1.place_forget()
self.b2.place(relx=0.2, rely=0.2)
if __name__ == "__main__":
Check().mainloop()
After you destroyed button b1 in the undo(self) function tkinter cannot access it anymore and will be confused when you try to place is somewhere in the click(self) function.
To make button b1 only disappear visually you could place it outside of the window instead of destroying it. To do so replace
self.b1.destroy()
with
self.b1.place(relx=-5, rely=0)
This will move the button b1 far to the left, where it cannot be seen.
When calling the click(self) function, the button will reappear, because it will be moved inside the window again.

Programmatically press the `X` button on the toobar?

I know I can intercept pressing the X button with protocol("WM_DELETE_WINDOW", do_something) however I am having a hard time figuring out how to activate this button or at least the protocol that triggers when this button is pressed.
Here is the situation. I have 2 classes. My main Tk class and my Menu class. When I am setting up the command to close the program with an exit button from the menu I want this button to do exactly the same thing as the X button on the Tk class.
Now I know I could simply call the controller that was passed to the menu class and then call the method I built to handle the close event however I am trying to build this menu class in such a way that I do not need to do this from the menu class. This will allow me to use the menu class on any app I build with little to no editing.
I have not been able to find a post or some documentation that tells me how I can programmatically activate the "WM_DELETE_WINDOW" protocol.
Here is an image if it is unclear what I want. Simply I want the exit button to do exactly what the X button does.
Main class:
import tkinter as tk
import PIP_MENU
class PIP(tk.Tk):
def __init__(self):
super().__init__()
PIP_MENU.start(self)
self.protocol("WM_DELETE_WINDOW", self.handle_close)
def handle_close(self):
print("Closing")
self.quit()
if __name__ == '__main__':
PIP().mainloop()
Menu class on separate .py file:
import tkinter as tk
class Menu(tk.Menu):
def __init__(self, controller):
super().__init__()
self.controller = controller
controller.config(menu=self)
file_menu = tk.Menu(self, tearoff=0)
self.add_cascade(label="File", menu=file_menu)
file_menu.add_command(label="Exit", command=self.handle_exit)
def handle_exit(self):
# What can I do here that will be handled by
# protocol "WM_DELETE_WINDOW" of the main class?
# All I can find is destroy() and quit()
# But both of these do not get handled by "WM_DELETE_WINDOW".
def start(controller):
Menu(controller)
I have not been able to find a post or some documentation that tells me how I can programmatically active the "WM_DELETE_WINDOW" protocol.
You can't. By definition, the WM_DELETE_WINDOW protocol comes from the window manager.
Catching the protocol handler is designed to give you an opportunity to override its behavior. It is not designed to be a way to trigger some code no matter how the application is destroyed. If you want to run some code when the window is destroyed, whether that is by the user clicking the control on the window frame or through some other way, the correct way to do that is to bind to the <Destroy> event on the root window.
You have to be careful, in that any binding on the root window will be triggered for every widget. Therefore, your binding should only run when event.widget is the same as the root window.
The following example illustrates the technique. There is a method handle_close which is called whenever the window is destroyed. Whether you close the window by clicking on the control on the window frame, or whether you click on the "Close me!" button, the code still runs.
import tkinter as tk
class Example(tk.Tk):
def __init__(self):
super().__init__()
self.bind("<Destroy>", self.handle_close)
button = tk.Button(self, text="Close me!", command=self.destroy)
button.pack()
def handle_close(self, event):
if event.widget == self:
print("Closing")
self.quit()
example = Example()
example.mainloop()
I don't believe there's a method that invokes a specific protocol, since protocol seems to be a specific event watch. Here's a snippet from the module's class Tk:
class Tk(Misc, Wm):
"""Toplevel widget of Tk which represents mostly the main window
of an application. It has an associated Tcl interpreter."""
def _loadtk(self):
...
self.protocol("WM_DELETE_WINDOW", self.destroy)
As you can see, by default the module itself sets the protocol to destroy(). The protocol() method only seeks to replace the default function (at the absence of a function, it just removes the function):
def wm_protocol(self, name=None, func=None):
"""Bind function FUNC to command NAME for this widget.
Return the function bound to NAME if None is given. NAME could be
e.g. "WM_SAVE_YOURSELF" or "WM_DELETE_WINDOW"."""
if callable(func):
command = self._register(func)
else:
command = func
return self.tk.call(
'wm', 'protocol', self._w, name, command)
protocol = wm_protocol
but to achieve what you want you should be able to reference back to the same handling method with this:
def handle_exit(self):
self.controller.handle_close()
Of course, this is not as versatile since you must explicitly know the handler in your main window.
Thought I have accepted Bryan's answer I did manage to come to a workaround I think is fine here.
If I pass the method that is being used to deal with window closing to my menu class and then check if something has been passed I can then decide on weather or not to use the exit method I made or self.controller.destroy() with an if statement.
Here is my solution.
Main file:
import tkinter as tk
from tkinter import messagebox
import PIP_MENU
class PIP(tk.Tk):
def __init__(self):
super().__init__()
PIP_MENU.start(self, self.handle_close)
self.protocol("WM_DELETE_WINDOW", self.handle_close)
def handle_close(self):
x = messagebox.askquestion("DERP", "Do you want to close without saving?")
if x == "yes":
self.destroy()
if __name__ == '__main__':
PIP().mainloop()
Menu file:
import tkinter as tk
class Menu(tk.Menu):
def __init__(self, controller, exit_handler=None):
super().__init__()
self.controller = controller
self.exit_handler = exit_handler
controller.config(menu=self)
file_menu = tk.Menu(self, tearoff=0)
self.add_cascade(label="File", menu=file_menu)
file_menu.add_command(label="Exit", command=self.handle_exit)
def handle_exit(self):
if self.exit_handler != None:
self.exit_handler()
else:
self.controller.quit()
def start(controller, exit_handler=None):
return Menu(controller, exit_handler)

Script continues running after destroy() tkinter

I'm using tkinter for the first time and I've created a window that has a listbox with values that the user can select. These values are passed into a function, and any console output is shown in a text box in the window.
Here's my code:
class Display(tk.Frame):
def __init__(self,parent=0):
tk.Frame.__init__(self,parent)
# code here that creates a bunch of widgets
sys.stdout = self
# code here that packs widgets into frame
def onSubmit(self):
self.years = list()
self.selection = self.lstbox.curselection()
for i in self.selection:
entry = self.lstbox.get(i)
self.years.append(int(entry))
batch_process(self.years)
def write(self, txt):
self.output.insert(tk.END,str(txt))
self.output.see(tk.END)
self.update_idletasks()
class MainW(tk.Tk):
def __init__(self, parent):
tk.Tk.__init__(self ,parent)
self.parent = parent
self.display = Display(self)
self.display.pack()
self.title('Test')
self.protocol('WM_DELETE_WINDOW', self.close_window)
def close_window(self):
if messagebox.askokcancel('Exit', 'Are you sure you want to close?'):
self.destroy()
if __name__=='__main__':
window = MainW(None)
window.mainloop()
The window works fine - listbox successfully passes values into the function and output is shown in the textbox.
However, when I click "X" and close the window, the console shows that the script is still running for some reason. I then have to completely close Spyder in order to run the script again. What's the issue? My understanding is that destroy() would terminate the mainloop.
EDIT: Had someone else run the script and got the following error:
Exception ignored in: <__main__.Display object .!display>
AttributeError: 'Display' object has no attribute 'flush'
I got it... even though I don't fully understand it. I had to add a "flush" function to my Display class.
def flush(self):
pass

tkinter 'Tk().after()' method causing gui to not start

I am trying to text in a textbox every seconds. I have found several explanations on how to do this using the Tk().after() method. like this example
root = Tk()
def foo():
print(repeat)
root.after(5000, foo())
foo()
root.mainloop()
However, when trying this solution the main window never appears. It also does not exit with an exception. The only thing I can think of is that I am entering an infinite loop before reaching the mainloop call.
This is a condensed version of my code
def vp_start_gui():
global val, w, root
root = Tk()
top = MainWindow (root)
init(root, top)
root.mainloop()
class MainWindow():
def __init__():
self.widgets
def init(top, gui, *args, **kwargs):
global w, top_level, root
w = gui
top_level = top
root = top
root.after(15000,updateLoans(w, root))
def updateLoans(w, rt):
w.LoanOfferView.insert(END, viewModel.loanOffers())
w.LoanDemandView.insert(END, viewModel.loanDemands())
rt.after(15000,updateLoans(rt))
vp_start_gui()
The viewModel is a third module that pulls a very small amount of data from an API. `LoanDemands is a scrolledText widget.
Does anyone have any idea what might be going wrong?
python3.4
using 'page' gui designer to develop tkinter UI
You need something for the program to wait for before entering infinite loop. Try this:
Button(root, text='start', command=foo).pack()
Too replace your line:
foo()
Also, you should not put parentheses when passing functions to other functions:
root.after(5000, foo)
So I found the answer to this problem myself.
code example that was not working
root = Tk()
def foo():
print(repeat)
root.after(5000, foo())
foo()
root.mainloop()
code that does work for me
root = Tk()
def foo():
print(repeat)
root.after(5000, foo)
foo()
root.mainloop()
After looking closer at this SO answer, this rasperri pi forum answer and tkinter documentation on effbot that I quoted at the bottom. I realized that the name of the function was being referenced but the function itself is not actually called inside of the after() method
Registers an alarm callback that is called after a given time.
This method registers a callback function that will be called after a
given number of milliseconds. Tkinter only guarantees that the
callback will not be called earlier than that; if the system is busy,
the actual delay may be much longer.
The callback is only called once for each call to this method. To keep
calling the callback, you need to reregister the callback inside
itself:
class App:
def init(self, master):
self.master = master
self.poll() # start polling
def poll(self):
... do something ...
self.master.after(100, self.poll)

Tkinter Toplevel in OOP script: how?

Goal of the script:
(3) different windows, each in its own class, with its own widgets and layout, are created via Toplevel and callbacks.
When a new (Toplevel) window is created, the previous one is destroyed. Thus, only one window is visible and active at a time.
Problem?
Basically, I've tried many things and failed, so I must understand too little of ["parent", "master", "root", "app", "..."] :(
Note on raising windows:
I have implemented a successful example of loading all frames on top of each other, and controlling their visibility via the .raise method.
For this problem, however, I don't want to load all the frames at once.
This is an abstracted version of a quiz program that will require quite a lot of frames with images, which makes me reluctant to load everything at once.
Script (not working; bugged):
#!/usr/bin/env python
from Tkinter import *
import tkMessageBox, tkFont, random, ttk
class First_Window(Frame):
"""The option menu which is shown at startup"""
def __init__(self, master):
Frame.__init__(self, master)
self.gotosecond = Button(text = "Start", command = self.goto_Second)
self.gotosecond.grid(row = 2, column = 3, sticky = W+E)
def goto_Second(self):
self.master.withdraw()
self.master.update_idletasks()
Second_Window = Toplevel(self)
class Second_Window(Toplevel):
"""The gamewindow with questions, timer and entrywidget"""
def __init__(self, *args):
Toplevel.__init__(self)
self.focus_set()
self.gotothird = Button(text = "gameover", command = self.goto_Third)
self.gotothird.grid(row = 2, column = 3, sticky = W+E)
def goto_Third(self):
Third_Window = Toplevel(self)
self.destroy()
class Third_Window(Toplevel):
"""Highscores are shown with buttons to Startmenu"""
def __init__(self, *args):
Toplevel.__init__(self)
self.focus_set()
self.master = First_Window
self.gotofirst = Button(text = "startover", command = self.goto_First)
self.gotofirst.grid(row = 2, column = 3, sticky = W+E)
def goto_First(self):
self.master.update()
self.master.deiconify()
self.destroy()
def main():
root = Tk()
root.title("Algebra game by PJK")
app = First_Window(root)
root.resizable(FALSE,FALSE)
app.mainloop()
main()
The problem is not really a Tkinter problem, but a basic problem with classes vs. instances. Actually, two similar but separate problems. You probably need to read through a tutorial on classes, like the one in the official Python tutorial.
First:
self.master = First_Window
First_Window is a class. You have an instance of that class (in the global variable named app), which represents the first window on the screen. You can call update and deiconify and so forth on that instance, because it represents that window. But First_Window itself isn't representing any particular window, it's just a class, a factory for creating instances that represent particular windows. So you can't call update or deiconify on the class.
What you probably want to do is pass the first window down through the chain of windows. (You could, alternatively, access the global, or do various other things, but this seems cleanest.) You're already trying to pass it to Second_Window, you just need to stash it and pass it again in the Second_Window (instead of passing self instance, which is useless—it's just a destroyed window object), and then stash it and use it in the Third_Window.
Second:
Second_Window = Toplevel(self)
Instead of creating an instance of the Second_Window class, you're just creating an instance of the generic Toplevel class, and giving it the local name Second_Window (which temporarily hides the class name… but since you never use that class, that doesn't really matter).
And you have the same problem when you try to create the third window.
So:
class First_Window(Frame):
# ...
def goto_Second(self):
# ...
second = Second_Window(self)
class Second_Window(Toplevel):
def __init__(self, first, *args):
Toplevel.__init__(self)
self.first = first
# ...
def goto_Third(self):
third = Third_Window(self.first)
self.destroy()
class Third_Window(Toplevel):
"""Highscores are shown with buttons to Startmenu"""
def __init__(self, first, *args):
Toplevel.__init__(self)
self.first = first
# ...
def goto_First(self):
self.first.update()
self.first.deiconify()
self.destroy()

Categories

Resources