Lift and raise a Canvas over a Canvas in tkinter - python

I'm creating a game, and I am using tkinter to build the GUI.
In this game, I want the user to start with a window, filled by a Canvas, with an image as background, and some buttons in some sort of item windows. And this Canvas is the home Canvas of the game.
The problem is that I want the user to click on a Button to land on other Canvas, who will host other things like the map or others buttons for others actions. Actually, I would like to superimpose several canvas (as in the Z-index method), and put a canvas on the top of the list, when I want it (if I click on a button for example).
I already searched and I had to change my mind several times, and now I really don't know how to do it.
I find the following code here on Stack Overflow, but it is coded for Python 2 (I think), and I'm starting coding in Python 3, so I am not able to translate it to Python 3 and solve my problem.
import Tkinter as tk
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.frame = tk.Frame(self)
self.frame.pack(side="top", fill="both", expand=True)
self.label = tk.Label(self, text="Hello, world")
button1 = tk.Button(self, text="Click to hide label",
command=self.hide_label)
button2 = tk.Button(self, text="Click to show label",
command=self.show_label)
self.label.pack(in_=self.frame)
button1.pack(in_=self.frame)
button2.pack(in_=self.frame)
def show_label(self, event=None):
self.label.lift(self.frame)
def hide_label(self, event=None):
self.label.lower(self.frame)
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
My code using grid :
from tkinter import *
fenetre = Tk()
fenetre.title(my game)
# acceuil
# variable acceuil
largeur = 700
hauteur = 430
BG_acceuil = PhotoImage(file="BG_acceuil.gif")
acceuil = Canvas(fenetre, width=largeur, height=hauteur)
acceuil.create_image(0, 0, anchor=NW, image=BG_acceuil)
acceuil.grid(row=0)
acceuil.pack()
# fond
fond = PhotoImage(file="BG_acceuil.gif")
acceuil2 = Canvas(fenetre, width=largeur, height=hauteur)
acceuil2.create_image(0, 0, anchor=NW, image=fond)
acceuil2.pack()
# variable bt_jouer
x0 = 80
y0 = 230
class hide_me():
def hide_me(event, widget, pos):
widget.grid_forget()
def show_me(event, widget, pos):
widget.grid(row=pos)
# Boutton jouer
BT_jouer = Button(acceuil, text="Jouer", command=hide_me())
BT_jouer.configure(width=10, activebackground="#33B5E5", relief=GROOVE)
BT_jouer_window = acceuil.create_window(x0, y0, window=BT_jouer,)
BT_jouer.bind('<Button-1>', lambda event: hide_me(event, BT_jouer, 1))
BT_jouer.grid(row=1)
# Bouton règle
BT_regle = Button(acceuil2, text="Règles", command=fenetre.destroy)
BT_regle.configure(width=10, activebackground="#33B5E5", relief=FLAT, bd=0)
BT_regle_window = acceuil2.create_window(x0, y0 + 50, window=BT_regle)
# Boutton quitter
BT_quit = Button(acceuil, text="Quitter", command=fenetre.destroy)
BT_quit.configure(width=10, activebackground="#33B5E5", relief=FLAT)
BT_quit_window = acceuil.create_window(x0, y0 + 100, window=BT_quit)
fenetre.mainloop()

The answer is very easy: To convert to Python3, change Tkinter to tkinter, and it works!
import tkinter as tk
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.frame = tk.Frame(self)
self.frame.pack(side="top", fill="both", expand=True)
self.label = tk.Label(self, text="Hello, world")
button1 = tk.Button(self, text="Click to hide label",
command=self.hide_label)
button2 = tk.Button(self, text="Click to show label",
command=self.show_label)
self.label.pack(in_=self.frame)
button1.pack(in_=self.frame)
button2.pack(in_=self.frame)
def show_label(self, event=None):
self.label.lift(self.frame)
def hide_label(self, event=None):
self.label.lower(self.frame)
def main():
app = SampleApp()
app.mainloop()
return 0
if __name__ == '__main__':
main()
Note: You are not really hiding the label - it still occupies space on the canvas. The following code, from this entry, really removes the item. It can then be recalled with a pack() call:
from Tkinter import *
def hide_me(event):
event.widget.pack_forget()
root = Tk()
btn=Button(root, text="Click")
btn.bind('<Button-1>', hide_me)
btn.pack()
btn2=Button(root, text="Click too")
btn2.bind('<Button-1>', hide_me)
btn2.pack()
root.mainloop()
I did some testing, and made an equivalent program to yours... The only problem is that the unhidden widget is always packed at the end:
from tkinter import *
def hide_me(event, widget):
widget.pack_forget()
def show_me(event, widget):
widget.pack()
root = Tk()
lbl = Label(root, text="Victim")
btn = Button(root, text="Hide the victim")
btn.bind('<Button-1>', lambda event: hide_me(event, lbl))
btn.pack()
btn2 = Button(root, text="Show the victim")
btn2.bind('<Button-1>', lambda event: show_me(event, lbl))
btn2.pack()
lbl.pack()
root.mainloop()
A better version uses the grid() packer. Here you can actually restore the 'forgotten' widget to its original position. Only slightly more complicated :)
from tkinter import *
def hide_me(event, widget, pos):
widget.grid_forget()
def show_me(event, widget, pos):
widget.grid(row = pos)
root = Tk()
lbl = Label(root, text="Victim")
lbl.grid(row = 0)
btn = Button(root, text="Hide the victim")
btn.bind('<Button-1>', lambda event: hide_me(event, lbl, 0))
btn.grid(row = 1)
btn2 = Button(root, text="Show the victim")
btn2.bind('<Button-1>', lambda event: show_me(event, lbl, 0))
btn2.grid(row = 2)
root.mainloop()
EDIT: Another observation from the comments: Bryan Oakley commented that if you use .grid_remove() instead of .grid_forget(), then the coordinates will not be lost, and a simple .grid() will restore the widget at its location.

Related

Tkinter: resizable text box with scrollable

When the following code is run, a text box appear with scrollable. However, I need to make the textbox resizable with the cursor. How can I it?
import tkinter as tk
import sys
class Redirect():
def __init__(self, widget, autoscroll=True):
self.widget = widget
self.autoscroll = autoscroll
self.output = widget
def write(self, text):
self.widget.insert('end', text)
if self.autoscroll:
self.widget.see("end")
self.output.update_idletasks()
root = tk.Tk()
root.geometry("1200x620+1+1")
frame = tk.Frame(root)
frame.place(x=650, y=310, anchor="c", width=850, height=400 )
text=tk.Text(frame,width=25, height=8, wrap='word')
text.pack(side='left', fill='both', expand=True )
scrollbar = tk.Scrollbar(frame)
scrollbar.pack(side='right', fill='y')
text['yscrollcommand'] = scrollbar.set
scrollbar['command'] = text.yview
old_stdout = sys.stdout
sys.stdout = Redirect(text)
root.mainloop()
sys.stdout = old_stdout
Tkinter doesn't directly support this. However, it includes all of the basic building blocks to accomplish this.
All you need to do is add a resize grip that the user can click on, and then bindings to resize the widget when you click and drag that grip.
Here's a simple example. It uses the ttk Sizegrip widget. That widget has built-in bindings to resize the window, but we can override them with custom bindings to resize the widget instead. This example requires that you use place, since that's what your original example uses.
import tkinter as tk
from tkinter import ttk
class ResizableText(tk.Frame):
def __init__(self, parent, **kwargs):
super().__init__(parent)
self.text = tk.Text(self, **kwargs)
self.scrollbar = tk.Scrollbar(self, command=self.text.yview)
self.scrollbar.pack(side="right", fill="y")
self.text.pack(side="left", fill="both", expand=True)
sizegrip = ttk.Sizegrip(self.text)
sizegrip.place(relx=1.0, rely=1.0, anchor="se")
sizegrip.configure(cursor="sizing")
sizegrip.bind("<1>", self._resize_start)
sizegrip.bind("<B1-Motion>", self._resize)
def _resize_start(self, event):
self._x = event.x
self._y = event.y
return "break"
def _resize(self, event):
delta_x = event.x - self._x
delta_y = event.y - self._y
self.place_configure(
width = self.winfo_width() + delta_x,
height = self.winfo_height() + delta_y
)
return "break"
root = tk.Tk()
root.geometry("1200x620+1+1")
rt = ResizableText(root, width=25, height=8, wrap="word")
rt.place(x=650, y=310, anchor="c", width=850, height=400 )
root.mainloop()

tkinter wait_variable doesn't return

i've a tkinter toplevel widget were you can type some text, and then press ok. it is created in a function: if you call that funtion the program should wait for a click on the button and than the function should return that text, so that my function have the same use a the input function in python 3. now my problem is: how can i pause my script until the user clicked on a button, so that all other files in my project pause (there happens nothing if the user clicks on a button in another tkinter window) are paused? i tried to use button.wait_variable(chosen) and when the user clicks on the button the variable "chosen" will change.
def choosefile():
root = tk.Tk()
root.geometry("275x50")
chosen = tk.BooleanVar()
chosen.set(False)
label = tk.Label(root, text="enter name of file (without extention)")
label.pack(fill="both")
entry = tk.Entry(root)
entry.pack(side="left", expand=True, fill="x")
button = tk.Button(root, text="Ok", command=lambda: [chosen.set(True), output(chosen.get())])
button.pack(side="right")
output("waiting")
button.wait_variable(chosen)
output("not waiting")
result = entry.get() + ".py"
root.quit()
return result
this works ok if you only run this file, but when i this run with main.py(below) the window don't destroy and also not return.
main.py
from tkinter import Tk, Button, Entry, StringVar
import tkinter.font as tkfont
from customtext import CustomText
from execute import Execute
from coloring import color
from files import create, openfile, choosefile
from alert import alert
import keyboard
class App(Tk):
def __init__(self):
super(App, self).__init__()
self.width = self.winfo_screenwidth() - 1
self.height = self.winfo_screenheight() - 1
self.attributes('-fullscreen', True)
self.state = StringVar()
self.state.set("saved")
self.input = CustomText(self)
self.input.place(x=10, y=10, width=self.width - 20, height=self.height - 20)
self.input.bind("<<TextModified>>", lambda a: [color(self.input), self.state.set("unsaved")])
self.run = Button(self, text="run", command=lambda: [create(self.name.get("1.0", "end-1c"), ".py", self.input.get("1.0", "end-1c")), Execute(self.name.get("1.0", "end-1c")), self.state.set("saved")])
self.run.place(x=self.width - 70, y=20, width=50, height=30)
self.exit = Button(self, text="exit", command=lambda: self.destroy())
self.exit.place(x=self.width - 130, y=20, width=50, height=30)
self.save = Button(self, text="save", command=lambda: [create(self.name.get("1.0", "end-1c"), ".py", self.input.get("1.0", "end-1c")), self.state.set("saved")])
self.save.place(x=self.width - 190, y=20, width=50, height=30)
self.open = Button(self, text="open", command=lambda: self.openfile())
self.open.place(x=self.width - 250, y=20, width=50, height=30)
self.name = CustomText(self, border=3, font=('Arial', 13))
self.name.place(x=self.width - 360, y=21, width=100, height=30)
def openfile(self):
if self.state.get() == "unsaved":
choise = alert("your code is unsaved, save now?")
if choise == "Ok":
create(self.name.get("1.0", "end-1c"), ".py", self.input.get("1.0", "end-1c"))
self.state.set("saved")
self.input.delete("1.0", "end")
self.input.insert("end-1c", openfile(choosefile())) # here choosefile is called.
if __name__ == "__main__":
app = App()
keyboard.add_hotkey("ctrl+enter", lambda: [keyboard.write("\b"), Execute(app.input.get("1.0", "end"))])
app.mainloop()
can someone help? thanks in advance!
EDIT:
i tried to implemant what acw1668 commented and it works! what i've done: in all my functions, i added a parameter master, so that i at any place in my project can use tk.Toplevel(master). now, since i only one instance of tk.Tk() have, it is working correctly.

How to change focus of simpledialog in Tkinter?

I have several simpledialog popup windows. The first one that shows is in focus and after it closes then every single one after that is not in focus. The simpledialog code is this:
from tkinter import *
from tkinter import messagebox
import os
import tkinter as tk
from tkinter import simpledialog
def doing_stocks():
name = myaskstring("Input data", "Enter box number:", parent = root)
name2 = myaskstring("Input data2", "Enter box number2:", parent = root)
name3 = myaskstring("Input data3", "Enter box number3:", parent = root)
class My_QueryString(tk.simpledialog._QueryString):
def body(self, master):
self.bind('<KP_Enter>', self.ok)
self.bind('<Return>', self.ok)
w = Label(master, text=self.prompt, justify=LEFT)
w.grid(row=0, padx=5, sticky=W)
self.entry = Entry(master, name="entry")
self.entry.grid(row=1, padx=5, sticky=W+E)
if self.initialvalue is not None:
self.entry.insert(0, self.initialvalue)
self.entry.select_range(0, END)
root.update_idletasks()
self.entry.focus_force()
return self.entry
def myaskstring(title, prompt, **kw):
d = My_QueryString(title, prompt, **kw)
root.update_idletasks()
answer = d.result
d.destroy()
return answer
root = Tk()
root.geometry("700x761")
label1 = Label(root, text="")
label1.place(x = 0, y = 0)
button2 = Button(root, text = "Doing Stocks", command=doing_stocks).place(x = 300, y = 340)
root.mainloop()
This is a simplified version of the code. I call my simpledialog popups like this:
myaskstring("Title", "Prompt", parent = root)
In the doing_stocks() method the first time I call myaskstring the window and the entry field will be in focus then all times after that it won't be. How can I make every simpledialog be in focus when it appears on the screen?
I agree is seems odd that the focus isn't set to each My_QueryString instance when it's initialized. Regardless of the reason why, a workaround for it not happening is to bind a <Map> event handler function to the dialog's Entry widget to shift keyboard focus to itself whenever it's made visible.
The code below shows an implementation doing that. It's based on your code with the important changes indicated with # ALL CAP comments to make them stand out.
import tkinter as tk
from tkinter import simpledialog
from tkinter.constants import *
def doing_stocks():
name = myaskstring("Input data", "Enter box number:", parent=root)
name2 = myaskstring("Input data2", "Enter box number2:", parent=root)
name3 = myaskstring("Input data3", "Enter box number3:", parent=root)
class My_QueryString(tk.simpledialog._QueryString):
def body(self, master):
self.bind('<KP_Enter>', self.ok)
self.bind('<Return>', self.ok)
w = tk.Label(master, text=self.prompt, justify=LEFT)
w.grid(row=0, padx=5, sticky=W)
self.entry = tk.Entry(master, name="entry")
self.entry.grid(row=1, padx=5, sticky=W+E)
self.entry.bind('<Map>', self.on_map) # <--- ADDED.
if self.initialvalue is not None:
self.entry.insert(0, self.initialvalue)
self.entry.select_range(0, END)
root.update_idletasks()
# self.entry.focus_force() # <--- NOT NEEDED.
return self.entry
# ADDED METHOD.
def on_map(self, event):
self.entry.focus_force()
def myaskstring(title, prompt, **kw):
d = My_QueryString(title, prompt, **kw)
root.update_idletasks()
answer = d.result
# d.destroy() # <--- NOT NEEDED.
return answer
root = tk.Tk()
root.geometry("700x761")
label1 = tk.Label(root, text="")
label1.place(x = 0, y = 0)
button2 = tk.Button(root, text = "Doing Stocks", command=doing_stocks)
button2.place(x = 300, y = 340)
root.mainloop()

Display message when the cursor is hovering a canvas in Python (tkinter)

So i have these lines of code:
from tkinter import *
root = Tk()
root.geometry("400x200")
canvas = Canvas(root, width=150, height=150, bg="black", bd=2, relief="ridge")
canvas.place(x=20, y=20)
A = canvas.create_oval(20,20,30,30, outline='grey', fill="grey")
B = canvas.create_oval(130,130,140,140, outline='grey', fill="grey")
root.mainloop()
I'm trying to find out how I could show a text when im hovering over a canvas. For example, when my mouse is hovering the first circle, the text should say "A" and for the second circle, it should say "B".
I found a code that does what I want to do but it's with text and I don't know how to make it work with a canvas too:
import tkinter as tk
class Example(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
self.l1 = tk.Label(self, text="Hover over me")
self.l2 = tk.Label(self, text="", width=40)
self.l1.pack(side="top")
self.l2.pack(side="top", fill="x")
self.l1.bind("<Enter>", self.on_enter)
self.l1.bind("<Leave>", self.on_leave)
def on_enter(self, event):
self.l2.configure(text="Hello world")
def on_leave(self, enter):
self.l2.configure(text="")
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(side="top", fill="both", expand="true")
root.mainloop()
In addition, let's say i have another .py file in which I want to assign both A and B canvases a different variables from that .py file and have it show in the text config. For example: In the .py file i have the variables
for_A = 123
for_B = 456
How can I make it look such as when I'm hovering the A canvas it shows something like this:
A
123
you need to add tag option in canvas.create_oval(...):
A = canvas.create_oval(20,20,30,30, outline='grey', fill="grey", tag="A")
B = canvas.create_oval(130,130,140,140, outline='grey', fill="grey", tag="B")
create a label to show the circle name:
lbl = Label(root)
lbl.place(x=200, y=20, anchor="nw")
use canvas.tag_bind() to bind <Enter> and <Leave> events on the two circles:
def on_enter(e):
# find the canvas item below mouse cursor
item = canvas.find_withtag("current")
# get the tags for the item
tags = canvas.gettags(item)
# show it using the label
lbl.config(text=tags[0])
def on_leave(e):
# clear the label text
lbl.config(text="")
for item in (A, B):
canvas.tag_bind(item, "<Enter>", on_enter)
canvas.tag_bind(item, "<Leave>", on_leave)

How do I use the The Tkinter Menu Widget?

I don't get how to add the The Tkinter Menu Widget with my code I tried the code on a website but it won't work. It think im just being silly but please help. Just for the record I have looked at other stack overflow solutions but they did't work.
import pyautogui
import time
import tkinter as tk
class Coordinates():
replayBtn = (100,350)
class YourGUI(tk.Tk):
def __init__(self):
# inherit tkinter's window methods
tk.Tk.__init__(self)
#Enter X field and label ⬇
tk.Label(self, text="ENTER X:").grid(row=0, column=3)
self.inputX = tk.Entry(self)
self.inputX.grid(row=0, column=1)
#Enter Y field and label ⬇
tk.Label(self, text="ENTER Y:").grid(row=0, column=0)
self.inputY = tk.Entry(self)
self.inputY.grid(row=0, column=4)
# Start Button ⬇
tk.Button(self, text="start", command=self.do_conversion).grid(row=3, column=0, columnspan=2)
# close button ⬇
tk.Button(self, text="exit!", command=self.EXITME).grid(row=4, column=0, columnspan=2)
def EXITME(self):
exit(0) # crashed prog so it closes
# strtoint("crashmE!")
def do_conversion(self):
y = self.inputY.get()
x = self.inputX.get()
running = True
try:
x = int(x)
y = int(y)
except:
print("Invalid point")
exit(0)
# strtoint("crashmE!")
while running:
pyautogui.click(x, y)
if __name__ == '__main__':
your_gui = YourGUI()
your_gui.title('Macro Clicker') # Set title
your_gui.iconbitmap('favicon.ico') # Set icon
your_gui.resizable(False, False)
your_gui.mainloop()
time.sleep(0)
Sorry to say that but if you aren't lazy u will find over thousend examples about tkinter menu.
Even your code has nothing to do with tkinter.menu!?
Here's an example of mine. The "Info" menu is empty just to show you how create additional menus.
from tkinter import Tk, Frame, Menu
class Gui(Frame):
def __init__(self, master):
self.master = master
Frame.__init__(self, self.master) #main container
self.grid()
self.create_menu()
def create_menu(self):
self.menubar = Menu(self.master)
self.theme = Menu(self.menubar, tearoff=0)
self.menubar.add_cascade(label="View", menu=self.theme)
self.menubar.add_command(label="Info", command=self.passing)
self.views = Menu(self.theme, tearoff=0)
self.theme.add_cascade(label="Themes", menu=self.views)
self.views.add_command(label="Default", command=self.passing)
self.views.add_command(label="Red", command=self.passing)
self.views.add_command(label="Blue", command=self.passing)
self.views.add_command(label="Random", command=self.passing)
self.master.configure(menu=self.menubar)
def passing(self):
print("Use your brain before asking questions on stackoverflow or use google if your brain is slow")
if __name__ == "__main__":
root = Tk()
root.geometry("300x200")
my_gui = Gui(root)
root.mainloop()

Categories