UPDATE : The problem was solved by removing the window.mainloop() in my second function.
I'm trying to make a game in Python 3.7 using tkinter.
The game begins with a menu (button-widgets in a frame). Clicking in the 'Play' button should open another menu using a different frame. This second menu should contain a 'back' button to return to the first menu.
Each menu is defined in a function. So to go from the main menu to the play menu I call the function playMenu(window) in the function used as command by the 'Play' button.
It looks like this :
def clickButtonPlay():
menuFrame.grid_remove()
playMenu(window)
menuFrame.grid()
In the play menu, the function used as 'back button' command put an end to the function by destroying its frame and using return.
So the program should get back to the clickButtonPlay() function and show the frame of the main menu back, but instead I get a tkinter error :
_tkinter.TclError: can't invoke "grid" command: application has been destroyed
But my frame menuFrame hasn't been destroyed, just un-grid!
Can anyone help me understand what's wrong with the code or find an easier way to do the same thing?
Thank you very much!
Here's a sample of how my program works:
mainMenu file :
import tkinter as tk
from PlayMenu import playMenu
window = tk.Tk()
window.grid()
def menu(window):
def clickButtonPlay():
menuFrame.grid_remove()
playMenu(window)
menuFrame.grid()
menuFrame = tk.Frame(window)
menuFrame.grid()
background = tk.Label(menuFrame, image= backgroundImage)
background.grid()
playButton = tk.Button(menuFrame, image= playButtonImage[0], command= clickButtonPlay)
playButton.place(relx= 0.5, rely= 0.15)
window.mainloop()
menu(window)
playMenu file :
class MyError(Exception):
pass
def _playMenu(window):
def clickButtonBack():
playMenuFrame.destroy()
raise MyError
playMenuFrame = tk.Frame(window)
playMenuFrame.grid()
background = tk.Label(playMenuFrame, image= backgroundImage)
background.grid()
backButton = tk.Button(playMenuFrame, image= backButtonImage[0], command= clickButtonBack)
backButton.place(relx=0.375, rely=0.8)
window.mainloop()
def playMenu(window):
try:
return _playMenu(window)
except MyError:
return
The problem (or at least a problem) is that you're calling mainloop more than once. Each time you call it, a new infinite loop is created. The new loop won't exit until the main window is destroyed. Once that happens, the previous loop will likely throw errors since the widgets it's managing no longer exist.
Related
I am trying to get a popup for my pygame screen using tkinter, but i just want a simple messagebox that i pass in the message, and message type (like: "error"). What i don't know how to do is make it so that they can't avoid not answering it, if they click somewhere else it will not let the user do anything till they answer it, not even go to desktop sort of thing.
what i have so far:
def popUp(self, message, messagetype='Error'):
#Tk().wm_withdraw() #to hide the main window
messagebox.showinfo(messagetype, message)
For this to work your games's mainloop must be in a function (let's call it play). Let's say you have very simple code.
When a condition is met you can access the new function popUp. Which can have your tkinter window. When the button is pressed you can have its command as ...command=play) if the player wants to restart. As the popUp function is not inside the mainloop the game will be unresponsive. An example:
def play():
while carryOn:
for event in pygame.event.get():
if event.type==pygame.QUIT:
carryOn=False
#Checking if a key is pressed and then responding e=with function in the sprite class
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
player.moveLeft(5)
if keys[pygame.K_RIGHT]:
player.moveRight(5)
if keys[pygame.K_UP]:
player.moveUp(5)
if keys[pygame.K_DOWN]:
player.moveDown(5)
if keys[pygame.K_a]:
player.moveLeft(5)
if keys[pygame.K_d]:
player.moveRight(5)
if keys[pygame.K_w]:
player.moveUp(5)
if keys[pygame.K_s]:
player.moveDown(5)
#yourcondition
# if ... :
# carryOn = False
# popUp()
#Game Logic
all_sprites_list.update()
#Setting the background
screen.fill(GREEN)
#Now let's draw all the sprites
all_sprites_list.draw(screen)
#Refresh Screen
pygame.display.flip()
#Number of frames per second
clock.tick(60)
This is not full code - missing sprite class and code before that. You could then have your tkinter window (after importing tkinter) like:
global window #so it can be destroyed in the other function
window = Tk()
#message = ...
#message.grid ...
button = Button(window, text="restart", width=5, command=play)
button.grid # where you want it
The game will be frozen until the user presses restart (you may also want a window .destroy() in your code somewhere. You can adapt this to pretty much whatever you want but it is no where close to complete code. I hope this was useful.
Tkinter button only runs a separate script once
Hello all, Im a new to python and raspberry pi and have been searching high and low on how to get a Tkinter button to run a script more than once on my raspberry pi. From research I believe it has something to do with name="main", but I cant figure out what needs to be done and why. My button runs a separate python file (called SendRF.py) in the same directory that generates an RF signal, it works the first time but then the button click does nothing else after. Any advice would be much appreciated :)
from tkinter import *
#create a window
window =Tk()
window.title("Chappers Home Automation project")
#define a function
def test_function ():
import SendRF
#create a button
B = Button(text ="Test Button 1", command=test_function)
B.pack(padx = 100, pady = 50)
window.mainloop()
No error messages appear. The button sends the RF signal when pressed the first time, but nothing happens for further button clicks.
You can't import a module multiple times. Each additional import for the same module is a NOP. You need to functionize whatever is in sendRF, and call that function in test_function.
You can check if the function is working correctly by adding a simple print statement inside your function
from tkinter import *
#create a window
window =Tk()
window.title("Chappers Home Automation project")
#define a function
def test_function ():
import SendRF
print('CHECK')
#create a button
B = Button(text ="Test Button 1", command=test_function)
B.pack(padx = 100, pady = 50)
window.mainloop()
It is working one time, cause you alredy have the SendRf function mported, you need to close it, after to import again
I have made a button within a function and when the button is clicked a command is run to change the button color.
However this does not work as I get an error, but I need to create the button in the function.
It works when the button is defined outside the function and I assume the issue is that the data is forgotten after a function ends.
from tkinter import *
root = Tk()
def ColourChange(Letter):
NameButton.config(bg = "red")
def Change():
Letter = "a"
NameButton=Button(root, text = "This", command = lambda Letter = Letter:
ColourChange(Letter)
NameButton.pack()
Change()
When I click the button I would like the color of the background to change.
The actual error is
NameButton.config(bg="red") NameError: name 'NameButton' is not defined"
Set your global variable so it can be access by other function.Also move NameButton.pack() to new line after NameButton=Button(root,text="This",command=lambda Letter=Letter: ColourChange(Letter)).
from tkinter import *
root=Tk()
def ColourChange(Letter):
NameButton.config(bg="red")
def Change():
global NameButton # global variable
Letter="a"
NameButton=Button(root,text="This",command=lambda Letter=Letter: ColourChange(Letter))
NameButton.pack()
#NameButton.pack()
Change()
Imagine there are two Tkinter.Toplevel() windows, called Window_1 and Window_2, which can be opened by clicking the same button (lets called Button_0).
Button_0 is pressed and Window_1 pops up. In Window_1, I can scroll up and down using a mouse pad (MAC OS). After that, I left Window_1 open.
Button_0 is pressed again and Window_2 pops up, while Window_1 stays open. In Window_2, I can again scroll up and down.
Now, I go back to Window_1 and try to scroll using mouse pad, contents in Window_1 DO NOT MOVE, but contents in Window_2 DO MOVE.
Then I close Window_2, and try to scroll on Window_1, this time I got error messages asking for a canvas on Window_2.
I did bind function,
def on_vertical(canvas,event):
canvas.yview_scroll(-3 * event.delta, 'units')
to a canvas inside each windows. As far as I know about the error, it seems that this function could not be used twice at the same time (both windows are opened).
I would like the way that when both Windows stay open. While on each window, I can scroll up-down while the another one do not move. Is it possible to code that?
This is the code example (please do noted that the Window name is not corrected label.)
from tkinter import *
######################## FUNCTIONS (DEF) ########################
def on_vertical(canvas,event):
canvas.yview_scroll(-3 * event.delta, 'units')
######################## FUNCTIONS (CLASS) ########################
class Window(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.master = master
self.init_window()
#INITIAL WINDOW
def init_window(self):
self.master.title("Main Window")
self.pack(fill=BOTH, expand=1)
Button(self, text="Button_0",command = self.load_and_print).place(x = 7, y = 95)
# creating a button instance
Button(self, text="EXIT PROGRAM", command=self.client_exit).place(x=500, y=250)
#OPEN A NEW WINDOW CONTAINING STOCK LISTS
def load_and_print(self):
new_window = Toplevel(self)
new_window.title("Window")
canvas = Canvas(new_window, width = 800, height = 500, scrollregion = (0, 0, 0, 2500))
frame = Frame(canvas)
vbar = Scrollbar(new_window, orient = VERTICAL, command = canvas.yview)
vbar.pack(side = RIGHT,fill = Y)
canvas.create_window(0,0, window = frame, anchor = NW)
canvas.config(yscrollcommand = vbar.set)
canvas.pack(side = TOP,expand = True,fill = BOTH)
canvas.bind_all('<MouseWheel>', lambda event, canvas=canvas: on_vertical(canvas,event))
#MAKE PROGRAM EXIT
def client_exit(self):
exit()
######################## MAIN PROGRAMME ########################
#call window
root = Tk()
#size of the window
root.geometry("700x300")
app = Window(root)
root.mainloop()
root.update()
The problem is that you are using bind_all instead of bind for the mousewheel event.
Because you're using bind_all, each time you create a new window it replaces the old binding with a new binding. No matter which window gets the event, your function will always only work for the last window to be created. And, of course, when that window is destroyed then the mouse binding will throw an error since the canvas no longer exists.
Using bind
One solution is simple: use bind instead of bind_all.
canvas.bind_all('<MouseWheel>', lambda event, canvas=canvas: on_vertical(canvas,event))
Using bind_all
If you want the benefits of bind_all -- namely, that the scrolling works even if the mouse is over some other widget, you need to modify on_vertical to figure out which canvas to scroll at the time that it is running instead of having the canvas being passed in.
You can do that with a little bit of introspection. For example, the event object knows which widget received the event. From that you can figure out which window the mouse is in, and from that you can figure out which canvas to scroll.
For example, move the binding up to the __init__ and change it like this:
self.bind_all('<MouseWheel>', on_vertical)
Next, change on_vertical to figure out which canvas to scroll. In the following example I assume each toplevel has exactly one canvas and that you always want to scroll that canvas (ie: you lose the ability to scroll text widgets and listboxes)
If that's not the case, you can add whatever logic you want to figure out which widget to scroll.
def on_vertical(event):
top = event.widget.winfo_toplevel()
for child in top.winfo_children():
if child.winfo_class() == "Canvas":
child.yview_scroll(-3 * event.delta, 'units')
break
My program generates several graphs one at a time and each has a quit button.
The program pauses in mainloop until I press the button, then generates the next graph.
I would like a way to programmatically press or invoke the action associated to that button, in this case root.quit()
I have tried calling invoke() on the button but this doesn't work. My feeling is that the event is lost before mainloop is started.
from tkinter import *
pause = False # passed in as an arg
root = Tk()
root.title(name)
canvas = Canvas(root, width=canvas_width, height=canvas_height, bg = 'white')
canvas.pack()
quit = Button(root, text='Quit', command=root.quit)
quit.pack()
# make sure everything is drawn
canvas.update()
if not pause:
# Invoke the button event so we can draw the next graph or exit
quit.invoke()
root.mainloop()
I realised that the problem was with the event being lost and mainloop blocking so I used the pause arg to determine when to run mainloop, i.e. on the last graph.
See Tkinter understanding mainloop
All graphs are displayed and when you press Quit on any window all windows disappear and the program ends.
If there is a better way to do this please let me know, but this works.
root = Tk()
root.title(name) # name passed in as an arg
# Creation of the canvas and elements moved into another function
draw( root, ... )
if not pause:
root.update_idletasks()
root.update()
else:
mainloop()