How to programmatically quit mainloop via a tkinter canvas Button - python-3.x

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()

Related

How to remove focus from Text block by clicking outside it

I want to create a program using tkinter. The goal is to remove the focus from Text widget by clicking somewhere outside it. I've tried this:
import tkinter as tk
root = tk.Tk()
txt = tk.Text(root)
txt.pack()
root.bind('<Button-1>', lambda event: root.focus_set())
#txt.bind('<Button-1>', lambda event: txt.focus_set()) <- it doesn't work with this too
root.mainloop()
But this just sets focus to root every time i press B1 (in both variants). Help, please!
You could detect the current widget under the mouse coordinates and use this to determine if you should focus on root or not:
def click_event(event):
x,y = root.winfo_pointerxy() # get the mouse position on screen
widget = root.winfo_containing(x,y) # identify the widget at this location
if (widget == ".text_widget") == False: # if the mouse is not over the text widget
root.focus() # focus on root
text_widget = tk.Text(root, name="text_widget")
text_widget.pack()
root.bind("<Button-1>", click_event)

popup in tkinter with pygame that doest go away unless closed/pressed ok

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.

Is it possible to make Tkinter scrollbars move independently of each other in different Toplevel windows?

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

Tkinter display different frames

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.

free line drawing on tkinter canvas

I was trying to understand the following piece of Tkinter code that allows a user to freely draw on the canvas using the computer's mouse. I was however unable to understand what the following line of code is actually doing.
prev = move_event
here is the complete code ...
from tkinter import *
master = Tk()
canvas = Canvas(master, width=600, height=300, bg='white')
canvas.pack(padx=20, pady=20)
def click(click_event):
global prev
prev = click_event
def move(move_event):
global prev
canvas.create_line(prev.x, prev.y, move_event.x, move_event.y, width=2)
prev = move_event # what does this do ?
canvas.bind('<Button-1>', click)
canvas.bind('<B1-Motion>', move)
mainloop()
In the click function, a global variable is used to store the initial mouse click. The same variable is used in the move function as a reference to the starting point of the line being drawn. As the user moves the mouse the move function gets called repeatedly and the drawing continues from the last previous point (prev = move_event). When the user releases the mouse click and then re-clicks, the process begins again with the click function storing the initial point.

Resources