I have an application in which the images(created using Label(root,image='my_image)) change whenever some event occurs. Buttons are not used. One of my image has a label having text above an image. So it occurs where i want it. But when i move to next image after that, it is still there and i don't want it. What can i do? I have tried to destroy() the text label but it says that the variable is used before assignment.
Here is the part where i insert a text label. The panel2 variable doesn't work outside if block so i am not able to destroy it:
if common.dynamic_data:
to_be_displayed = common.dynamic_data
panel2 = tk.Label(root, text = to_be_displayed, font=("Arial 70 bold"), fg="white", bg="#9A70D4")
panel2.place(x=520,y=220)
You can do it on the canvas. Place label on the canvas and use bind functions for Enter and Leave events:
# imports
import tkinter as tk
# creating master
master = tk.Tk()
# hover functions
def motion_enter(event):
my_label.configure(fg='green')
print('mouse entered the canvas')
def motion_leave(event):
my_label.configure(fg='grey')
print('mouse left the canvas')
# create canvas, on which if you hover something happens
canvas = tk.Canvas(master, width=100, height=100, background='grey')
canvas.pack(expand=1, fill=tk.BOTH)
# create label
my_label = tk.Label(canvas, text='Toggle text is here!', fg='grey')
my_label.pack()
# binding enter and leave functions
master.bind('<Enter>', motion_enter)
master.bind('<Leave>', motion_leave)
# set window size
master.geometry('400x200')
# start main loop
master.mainloop()
You can change what configuration any objects have when you hovering canvas or anything else in created functions. Play with objects and code to do whatever you want to.
Also as it was mentioned you can store your labels or other objets in the list or dictionary to change separate objects, for exaple:
# imports
import tkinter as tk
# creating master
master = tk.Tk()
d = {}
# hover functions
def motion_enter(event):
d['first'].configure(fg='green')
print('mouse entered the canvas')
def motion_leave(event):
d['first'].configure(fg='grey')
print('mouse left the canvas')
# create canvas, on which if you hover something happens
canvas = tk.Canvas(master, width=100, height=100, background='grey')
canvas.pack(expand=1, fill=tk.BOTH)
# create label
my_label = tk.Label(canvas, text='Toggle text is here!', fg='grey')
my_label.pack()
d['first'] = my_label
my_label = tk.Label(canvas, text='Toggle text is here!', fg='grey')
my_label.pack()
d['second'] = my_label
# binding enter and leave functions
master.bind('<Enter>', motion_enter)
master.bind('<Leave>', motion_leave)
# set window size
master.geometry('400x200')
# start main loop
master.mainloop()
EDIT 1
If you want to remove label when the mouse left the canvas you can write such a functions:
def motion_enter(event):
d['first'].pack()
d['second'].pack()
print('mouse entered the canvas')
def motion_leave(event):
d['first'].pack_forget()
d['second'].pack_forget()
print('mouse left the canvas')
or just add 2 rows in the previous to combine them.
Related
I am working on a project where I need to display information when a button is clicked, and hide information when that button is clicked again. The information can be several rows and extend beyond the window, so I am trying to add a scroll bar to get around this issue. The problem is that when the information is displayed, the scroll bar does not show. Scrolling is still possible but the actual bar is not there. Resizing the window fixes this issue for some reason. Also when the button is clicked again to hide the information, the size of the canvas and the scrollbar remain the same, so you can scroll far beyond the button into empty space. My theory is that the scroll region is not updating when the widgets in the canvas change size - but I'm not sure how to go about fixing that.
I realize this explanation may be a bit confusing, so I have provided a simplified example below. This example has a single button that when clicked reveals several lines of "other info". The scrollbar and/or canvas does not resize properly upon interacting with this button.
My strategy right now was to have a method called add_scrollbar that removes the current scrollbar and creates a new one every time the widgets change in hopes that the new one would be the right size; however this still is not working.
from tkinter import *
from tkinter import ttk
def add_scrollbar(outer_frame, canvas):
if len(outer_frame.winfo_children()) == 2:
# canvas is at index 0 of the outer frame, if a scrollbar has been added it will be at index 1
outer_frame.winfo_children()[1].destroy()
# my strategy here was to destroy the existing scroll bar
# and create a new one each time the widget changes size
scrollbar = ttk.Scrollbar(outer_frame, orient=VERTICAL, command=canvas.yview)
scrollbar.pack(side=RIGHT, fill=Y)
canvas.configure(yscrollcommand=scrollbar.set)
canvas.bind('<Configure>', lambda e: canvas.configure(scrollregion=canvas.bbox("all")))
return
def create_example():
root = Tk()
root.geometry("1200x1200")
my_outer_frame = Frame(root)
my_outer_frame.pack(fill=BOTH, expand=1)
my_canvas = Canvas(my_outer_frame)
my_canvas.pack(side=LEFT, fill=BOTH, expand=1)
inner_frame = Frame(my_canvas)
my_canvas.create_window((0, 0), window=inner_frame)
# ^^^ Sets up the ability to have a scroll_bar
changing_frame = Frame(inner_frame, borderwidth=4) # this is the frame that will be changing its contents
changing_frame.pack(side=LEFT, anchor="n")
display_frame(changing_frame, my_outer_frame, my_canvas)
# this method re-displays the changing frame depending on the specified size ('big' or 'small'
root.mainloop()
return
def display_frame(frame, outer_frame, canvas, size='small'):
for widget in frame.winfo_children():
widget.destroy()
if size == 'small':
Button(frame, height=5, width=5, text="Show",
command=lambda this_frame=frame: display_frame(this_frame, outer_frame, canvas, size='big')).grid(row=0,
column=0)
elif size == 'big':
Button(frame, height=5, width=5, text="Hide",
command=lambda this_frame=frame: display_frame(this_frame, outer_frame, canvas, size='small')).grid(
row=0, column=0)
for n in range(1, 100):
Label(frame, text="Other Stuff!").grid(row=n, column=0)
frame.pack(side=LEFT)
add_scrollbar(outer_frame, canvas) # this method is supposed to destroy the existing scrollbar and make a new one
return
if __name__ == '__main__':
create_example()
I have a frame that holds buttons and it's packed in a LabelFrame with grid geometry manager.
When I remove this frame with grid_forget, the LabelFrame still has the same size.
With other words
it doesn't shrink.
Here is the code, when you press the button all the buttons are removed
but the size remains.
I expected that the grid geometry manager deals automatically with resizing when widgets are removed.
import tkinter as tk
class Collapsible():
def __init__(self, master):
self.master = master
self.dynamic_widgets()
self.fill_lb()
def dynamic_widgets(self):
"""create widgets"""
#frame that holds labelwidgets
self.fr_collapse = tk.Frame(self.master, bg="orange")
#title for label frame----------------------------------------------------------------
self.bt_title = tk.Button(self.fr_collapse, text="o",
highlightthickness = 0, bd = 0,
relief="flat", bg="orange", fg="red")
self.bt_title.grid(row=0, column=0)
self.label_title = tk.Label(self.fr_collapse, text="Name", bg="orange")
self.label_title.grid(row=0, column=1)
#-------------------------------------------------------------------------------------
self.label_frame = tk.LabelFrame(self.master,
bg="orange", labelwidget=self.fr_collapse)
self.label_frame.grid(sticky="wesn", ipady=(10))
#frame for buttons
self.frame_forget = tk.Frame(self.label_frame, bg="orange")
self.frame_forget.grid()
#set command
self.bt_title.configure(command=lambda x=self.frame_forget, y=self.bt_title: self.hide(x, y))
def fill_lb(self):
"fill label frame with dumb buttons"""
b = tk.Button(self.frame_forget, text="Example button 1", bg="orange", relief="flat")
b.grid()
b2 = tk.Button(self.frame_forget, text="Example button 2", bg="orange", relief="flat")
b2.grid()
def hide(self, frame, button):
"""switch value: hide frame based on text configuration"""
bt_text = button.configure("text")
if bt_text[-1] == "o":
frame.grid_remove()
button.configure(text="-")
else:
frame.grid()
button.configure(text="o")
if __name__ == "__main__":
root = tk.Tk()
col = Collapsible(root)
root.configure(bg="orange")
root.mainloop()
What I tried so far:
I thought that maybe I need to grid frame that holds buttons after deleting them. Does'n work because this will
grid my hidden buttons again which is logically.
I thought that maybe I need to grid the LableFrame again. No changes in size either
I thought that maybe I should put a dumb frame like a placeholder with minimal width and height values.
and grid it as child in my frame_forget frame with the hope that it will shrink. But still nothing.
None of those thoughts brought me a solution and the question remains
When I run my script it looks like this:
Then when I press flat button in the left corner 'o', I get this:
I wish it would collapse like this one:
I created a main root with two frames.
-One frame is for program toolbar.
-Other frame is for canvas where data will be displayed and a scrollbar widget.
-Inside of the canvas is a third smaller frame which will be used for scrolling trough data.
However, when I try to define new widgets and place them on that third smaller frame, nothing happens. I'm defining new widgets inside of a function call of a button command. I have also tried declaring everything as global variables but without luck.
Hint: I tried placing the code from the function to the top level of the code and it works. Also, if I try to mount these widgets on the toolbar frame it also works. It seems that the only thing I can't do is to mount these new widgets on the small frame that is inside the canvas.
I used a simple for loop to create labels just for testing.
Could anyone tell what I am doing wrong?
from tkinter import *
from tkinter import ttk
#Creating main window
root = Tk()
root.resizable(width=False, height=False)
#Defining Background
toolbar = Frame(root, width=613, height=114)
toolbar.grid(row=0, column=0)
background_frame = Frame(root, width=615, height=560)
background_frame.grid(row=1, column=0)
background = Canvas(background_frame, width=615, height=560)
background.pack(side=LEFT, fill=BOTH, expand=1)
scroll_bar = ttk.Scrollbar(background_frame, orient=VERTICAL, command=background.yview)
scroll_bar.pack(side=RIGHT, fill=Y)
background.configure(yscrollcommand=scroll_bar.set)
background.bind('<Configure>', lambda e:background.configure(scrollregion = background.bbox('all')))
second_frame = Frame(background)
background.create_window(150,100, window=second_frame, anchor='nw')
def confirm1():
for x in range(100):
Label(second_frame, text = x ).grid(row=x, column=1)
show_labels = Button(toolbar, text= "Show labels", fg="black", command=confirm1)
show_labels.grid(row=0, column=2)
root.mainloop()
Picture of the app so far
I surely can't reproduce the issue with your current code, but looking at the previous edit it is pretty clear what your problem is.
(taken from your previous edit)
def confirm1():
global background_image1
background.delete('all') # <--- this line of code
for x in range(100):
Label(second_frame, text = x ).grid(row=x, column=1)
Here you delete all your items from your canvas:
background.delete('all')
hence no item appears.
You should instead delete only those items that you want to remove by passing the id or tags to delete method. You can delete multiple items together at once by giving the same tags.
Another option would be to recreate the frame item again on canvas using create_window (Do note: your frame is not deleted/destroyed, it's only removed from canvas)
I would like a text box to ask for input in a tkinter window, then use that input as a parameter to call a function that draws a Sierpinski triangle. My buttons work but my input box does not. I keep trying to fix my code but it is not working, any help would be appreciated.
import tkinter as tk
from tkinter import *
root = tk.Tk()
frame = tk.Frame(root)
frame.pack()
root.title('Fractals') #titles the button box
top_frame = tk.Frame()
mid_frame = tk.Frame()
prompt_label = tk.Label(top_frame, \
text='Enter a number of iterations (more is better):')
iterations = tk.Entry(root,bd=1)
itr=iterations.get()
itr=int(itr)
button = tk.Button(frame,
text="QUIT",
fg="red",
command=quit)
button.pack(side=tk.LEFT)
sTriangle = tk.Button(frame,
text="Triangle",
command=lambda: sierpinski(fred, (-500,-500), (500,-500),
(0,500),itr))
sTriangle.pack(side=tk.LEFT)
fsquare = tk.Button(frame,
text="Square",
command=fractalsquare(fred,(-500,-500),(500,-500),
(500,500),(-500,500),itr))
fsquare.pack(side=tk.LEFT)
root.mainloop()
There are several issues:
1) Choose one way to import tkinter or confusion will result
2) You should provide a master for your Frames and then pack them. Pay attention on where the frames appear and what they contain.
3) It's usual to assign a textvariable to the Entry which will contain what you enter into it. The textvariable should be a tk.StringVar.
4) If a Button has a callback function, it must be defined before you create the button.
5) The variable fred is not defined.
Example of how you can write it:
import tkinter as tk
root = tk.Tk()
root.title('Fractals') #titles the button box
# Create the Label at the top
top_frame = tk.Frame(root) # Top Frame for
top_frame.pack()
prompt_label = tk.Label(top_frame,
text='Enter a number of iterations (more is better):')
prompt_label.pack()
# Create the Entry in the middle
mid_frame = tk.Frame(root)
mid_frame.pack()
itr_string = tk.StringVar()
iterations = tk.Entry(mid_frame,textvariable=itr_string)
iterations.pack()
fred=None # Was not defined...
# Create Buttons at the bottom
bot_frame = tk.Frame(root)
bot_frame.pack()
button = tk.Button(bot_frame, text="QUIT", fg="red", command=quit)
button.pack(side=tk.LEFT)
def sierpinski(*args): # sTriangle button callback function
itr = int(itr_string.get()) # How to get text from Entry
# if Entry does not contain an integer this will throw an exception
sTriangle = tk.Button(bot_frame, text="Triangle",
command=lambda: sierpinski(fred, (-500,-500), (500,-500),(0,500),itr_string))
sTriangle.pack(side=tk.LEFT)
def fractalsquare(*args): pass # fsquare button callback function
fsquare = tk.Button(bot_frame, text="Square", command=fractalsquare(fred,
(-500,-500),(500,-500),(500,500),(-500,500),itr_string))
fsquare.pack(side=tk.LEFT)
root.mainloop()
You should seriously study a basic tkinter tutorial. Try this one: An Introduction To Tkinter
I am using Tix to automatically create a scroll bar as the content changes. I want to keep a button or two in the user's view while they scroll through the contents of the application.
I haven't seen this question for Tkinter/Tix yet so I thought I'd ask.
The following code will create a sample of the problem where the button is at a fixed point in the window, and is subject to being scrolled.
from Tkinter import *
import Tix
class some_GUI:
def __init__(self, root):
sw= Tix.ScrolledWindow(root, scrollbar=Tix.AUTO)
sw.pack(fill=Tix.BOTH, expand=1)
frame1 = Frame(sw.window)
frame1.grid(row = 0, column = 1)
frame2 = Frame(sw.window)
frame2.grid(row = 0, column = 2)
def quit():
root.quit()
for i in range(0,300):
label1 = Label(frame1, text = "foo")
label1.grid(row = i, column = 0)
button = Button(frame2, text = "Quit", command = quit)
button.pack()
root = Tix.Tk()
display = some_GUI(root)
root.mainloop()
I want the button(s) to be in "frame2" and centered vertically relative to the application's window. I tried using winfo_height/winfo_width to find the frame's height/ width to work with update, but these values didn't change with the addition of the labels and button.
Attempted/possible solutions:
I put frame2 in sw.subwidgets_all[1] by doing the following:
frame1.pack(side = LEFT)
frame2 = Frame(sw.subwidgets_all()[1])
frame2.pack(side = RIGHT)
button = Button(frame2, text = "Quit", command = quit)
button.pack(side = RIGHT)
This allows the fixed position relative to the application, but the window resizes relative to the button's parent instead of frame1. Another drawback is that the horizontal scrollbar is only relative to frame1.
Find the midpoint of the scrollbar and update the position of the buttons relative to those coordinates using place(maybe?) not sure how to accomplish this, and seeing SO solutions in general I think this might be an inefficient way of doing this.
EDIT: Although this isn't exactly what I had in mind, the following code works as per falsetru's suggestion in the comments:
from Tkinter import *
import Tix
class some_GUI:
def __init__(self, root):
def quit():
root.quit()
frame2 = Frame(root)
frame2.pack(side = RIGHT)
button = Button(frame2, text = "Quit", command = quit)
button.pack()
frame1 = Frame(root)
frame1.pack(side = LEFT)
sw= Tix.ScrolledWindow(frame1, scrollbar=Tix.AUTO)
sw.pack(fill=Tix.BOTH, expand=1)
for widget in sw.subwidgets_all():
print widget
for i in range(0,300):
label1 = Label(sw.window, text = "foo")
label1.grid(row = i, column = i)
print root.winfo_toplevel()
for widget in sw.subwidgets_all():
print widget
root = Tix.Tk()
display = some_GUI(root)
root.mainloop()
You can put the button out of ScrollWindows:
import Tix
from Tkinter import *
def build_ui(root):
sw = Tix.ScrolledWindow(root, scrollbar=Tix.AUTO)
sw.pack(side=LEFT, fill=Tix.BOTH, expand=1)
for i in range(300):
label1 = Label(sw.window, text="foo")
label1.grid(row=i, column=0)
button = Button(root, text="Quit", command=root.quit)
button.pack(side=RIGHT)
root = Tix.Tk()
build_ui(root)
root.mainloop()
The second option you mentioned could be the one that satisfies your situation, however that is computationally expensive as you will need to delete the button(s) and redraw them over and over relatively to the scrollbar up/down motion. Not only this is ugly by design but it can be an obstacle for any further scalability of your application or even lead to unexpected bugs if your application runs some serious operations.
The only realistic solution I see for your problem is to fix the button(s) on (for example the bottom of) the upper canvas (or whatever region you want to set) and outside the scrollable region as #falsetru commented you.