Tkinter newly created button does not execute command - python

A script should open an application with two buttons visible. When Hello button is pressed a new button is gridded into the row number 1 and Hello button to be deactivated. When this new button is pressed it should delete itself off the grid and reactivate hello button but it does not do it.
Please check the video to see it in action.
Code edited to comment suggestion
from tkinter import *
class Application(Frame):
def __init__(self, master=None):
self.master = master
self.master.geometry('300x100+10+10')
Frame.__init__(self, master)
self.pack()
self.createWidgets()
def new_button(self):
print("enable_b")
self.hi_there.config(state=ACTIVE)
self.new_button.grid_remove()
def say_hi(self):
print("hi there, everyone!")
self.new_button = Button(self)
self.new_button.config(text = "New BTN", command=self.new_button)
self.new_button.grid(row=1,column=0)
self.hi_there.config(state=DISABLED)
def createWidgets(self):
self.QUIT = Button(self)
self.QUIT.config(text="QUIT",fg="red",command=self.quit)
self.QUIT.grid(row=0,column=1)
self.hi_there = Button(self)
self.hi_there["text"] = "Hello",
self.hi_there["command"] = self.say_hi
self.hi_there.grid(row=0,column=0)
def quit(self):
self.master.destroy()
def testit():
root = Tk()
app = Application(master=root)
app.mainloop()
if __name__ == '__main__':
testit()

Initially, self.new_button refers to a method. Then, you do this:
self.new_button = Button(self)
That effecting removes the method and replaces it with the button widget itself.
Also, you never assign a command to the new button, so clicking it doesn't cause anything to be called.

Where your program will technically work just fine with the 2 correction mentioned in Bryan's answer I am not sure why you are taking all the extra effort configuring the widgets for each individual field. All your configs can be done when you create the widget.
That said you can also change a few things for a cleaner code and 1 change I think that really needs to be made is how you are removing the new_button from the grid. When you do grid_remove() this only takes the widget off the screen but does not get rid of the widget. Then next time you press the say_hi button you will end up creating a new button and the old button will still exist. Instead I think I would use destroy() on the button and then let say_hi recreate it.
See this revised version of your code. You will see what I mean about configuring everything when creating the widget and also you do not need to write your own function for quit you can simply do self.master.destroy in the quit button command.
import tkinter as tk
class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.master = master
self.master.geometry('300x100+10+10')
self.create_widgets()
def new_btn(self):
print("enable_b")
self.hi_there.config(state="active")
self.new_button.destroy()
def say_hi(self):
print("hi there, everyone!")
self.new_button = tk.Button(self, text="New BTN", command=self.new_btn)
self.new_button.grid(row=1, column=0)
self.hi_there.config(state="disabled")
def create_widgets(self):
tk.Button(self, text="QUIT", fg="red", command=self.master.destroy).grid(row=0,column=1)
self.hi_there = tk.Button(self, text="Hello", command=self.say_hi)
self.hi_there.grid(row=0, column=0)
if __name__ == '__main__':
root = tk.Tk()
app = Application(master=root).pack()
root.mainloop()

Related

Unresponsive tkinter SimpleDialog box

Below is an outline of a tkinter GUI in which I want the same dialog box to be opened in various ways. The response selected by the user from choices in the dialog then needs to be returned to the mainloop.
The SimpleDialog class looks to be ideal for this and here I have just used the example provided in the dialog code. It is accessed by both the button and popup menu in the View class, along with their bindings in the Controller class.
It works just fine when called from the button, but when called from the popup menu (from a right click) the dialog appears and then freezes the entire app.
from tkinter import simpledialog as s
import tkinter as tk
class View(tk.Frame):
def __init__(self, root):
tk.Frame.__init__(self)
self.grid(row=0, column=0, sticky='nsew')
self.configure(bg = 'blue')
self.popup = tk.Menu(self, tearoff=0)
self.bind("<Button-2>", self.make_popup) #right-click to show popup
self.button = tk.Button(self, text='Test')
self.button.grid()
def make_popup(self, event):
try:
self.popup.tk_popup(event.x_root + 15, event.y_root, 0)
finally:
self.popup.grab_release()
class Controller():
def __init__(self, view):
view.popup.add_command(label ='do test', command = lambda : self.do_test(None, view))
view.popup.add_command(label ='dummy test', command = print('This one works OK'))
view.button.bind("<Button-1>", lambda e, : self.do_test(e, view))
def do_test(self, event, view):
d = s.SimpleDialog(view,
text="This is a test dialog. "
"Would this have been an actual dialog, "
"the buttons below would have been glowing "
"in soft pink light.\n"
"Do you believe this?",
buttons=["Yes", "No", "Cancel"],
default=0,
cancel=2,
title="Test Dialog")
print(d.go())
class App(tk.Tk):
def __init__(self):
super().__init__()
self.geometry('200x100')
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
view = View(self)
controller = Controller(view)
if __name__ == "__main__":
app = App()
app.mainloop()
It seems to me that the dialog should either work or not work, and not care how it is called. So I would be very grateful for an explanation as to why it responds in one case but not the other, and of course equally grateful for a fix.
The problem appears to lie in the print statement in the do_test callback, as splitting this into two lines fixes it
#print(d.go())
answer = d.go()
print(answer)
As reported in a comment this may be only an issue for MacOS (I am using MacOS 11.1 and Python 3.10.8 ).

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.

How do I create different Tkinter frames across multiple files?

I am creating a GUI on Tkinter that needs to open or close windows when buttons are pressed, and I would prefer if each window was in its own file. I tried creating a very simple example of this with the three files below. The first window is supposed to have a button that, when pressed, closes the current window and opens the next one. I'm currently running into a problem where the window is created but the button is not. How do I fix this?
Main.py
from MyTkWindow import *
myWindow = MyTkWindow()
myWindow.start()
MyTkWindow.py
import tkinter as tk
from NextFrame import *
class MyTkWindow(tk.Frame):
def __init__(self, parent=None):
tk.Frame.__init__(self)
nextWin = NextWindow()
NextScreen = tk.Button(self, text="Next", command=lambda:[self.destroy(), nextWin.start()])
NextScreen.pack()
def start(self):
self.mainloop()
NextFrame.py
import tkinter as tk
class NextWindow(tk.Frame):
def __init__(self, parent=None):
tk.Frame.__init__(self)
Leave = tk.Button(self, text="Quit", command=lambda: self.destroy())
Leave.pack()
def start(self):
self.mainloop()
I got this to work with the indicated changes. The primary problem was due to not calling the pack() method of the windows/frames being created.
main.py:
from MyTkWindow import *
myWindow = MyTkWindow()
myWindow.pack() # ADDED
myWindow.start()
MyTkWindow.py:
import tkinter as tk
from NextFrame import *
class MyTkWindow(tk.Frame):
def __init__(self, parent=None):
tk.Frame.__init__(self, parent) # ADDED parent argument.
nextWin = NextWindow()
NextScreen = tk.Button(self, text="Next",
command=lambda: [self.destroy(),
nextWin.pack(), # ADDED
nextWin.start()])
NextScreen.pack()
def start(self):
self.mainloop()
NextFrame.py:
import tkinter as tk
class NextWindow(tk.Frame):
def __init__(self, parent=None):
tk.Frame.__init__(self, parent) # ADDED parent argument.
Leave = tk.Button(self, text="Quit",
command=lambda: self.destroy())
Leave.pack()
def start(self):
self.mainloop()
Suggestion: Read and start following the PEP 8 - Style Guide for Python Code because it will make your code more understanable and maintainable. Specifically, the Naming Conventions section especially with respect to class, variable, and module file names.

Tkinter GUI stuck till end of the task when pressing a button

When I press the "start program" button, it starts a 5 second task and blocks the GUI.
As i understand, I need to use Threading so each button will work independently from the GUI.
I've been stuck for almost a month already, can someone show me how can execute def start_Button(self): function using threading?
from tkinter import *
import time
class Window(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.master = master
self.init_window()
def init_window(self):
self.var = IntVar()
self.master.title("GUI")
self.pack(fill=BOTH, expand=1)
quitButton = Button(self, text="Exit", command=self.client_exit)
startButton = Button(self, text="Start Program", command=self.start_Button)
quitButton.grid(row=0,column=0)
startButton.grid(row=0, column=2)
def client_exit(self):
exit()
def start_Button(self):
print('Program is starting')
for i in range (5):
print(i)
time.sleep(1)
root = Tk()
root.geometry("200x50")
app = Window(root)
root.title("My Program")
root.mainloop()
There are a lot of important questions to ask before you jump into threading for the first time, but by and large the most important question is "how do I want to communicate between my threads?" In your minimal example you don't require any communication at all, however in your real code start_Button may be doing some Work and returning data back to the GUI. If that's the case, you have more work to do. Please clarify that as a comment if that's the case.
For the minimal example, it's actually quite easy.
class Window(tkinter.Frame):
# the rest of your GUI class as written, but change...
def start_Button(self):
def f():
# this is the actual function to run
print('Program is starting')
for i in range (5):
print(i)
time.sleep(1)
t = threading.Thread(target=f)
t.start()

How to use TkInter fill or expand functions with self?

I have a very simple program with TkInter in Python.
How to I use the "fill" or "expand" options with the following code?
from Tkinter import *
class Application(Frame):
def sayhello(self):
print "Hello!"
def createWidgets(self):
self.var = Button(self, text="Hello", command = self.sayhello)
self.var.pack(side=LEFT)
self.QUIT = Button(self, text="QUIT", fg="red", command = self.quit)
self.QUIT.pack(side=LEFT)
def __init__(self, master=None):
Frame.__init__(self, master)
self.pack()
self.createWidgets()
root = Tk()
app = Application(master=root)
app.mainloop()
root.destroy()
Note: I have to use the self.var.pack() format so it can do a "command" when it's pressed...unless anyone has a better way?
To make an object fill it's container in the y axis, you would use the parameter fill="y" (or fill=Y if you import Y from Tkinter).
Note that this only controls how the widget fills its container. In your code, this makes the button fill the inner frame, but because your inner frame doesn't fill the main window in the y axis, you might not get the visual effect you expect.
Also, specifically in the case of buttons on the Macintosh, the button won't grow to fill the space. On OSX, buttons are native widgets which can't grow in height.

Categories

Resources