I use a disabled text widget filled with labels, to make a scrollable frame.
This works just fine, except for one thing: when the mouse is over a label, then scrolling the mouse wheel no longer gets registered.
I have below a simple demonstration script, where you will see that you can scroll when the mouse is over an empty part of the text widget, but cannot scroll anymore when you are hovering over a button (in the example I just used buttons, same effect as labels).
So very concretely my question: how can I make the text widget scrollable even when the mouse hovers over a widget?
from tkinter import Tk, Button, Text,Scrollbar
class test:
def __init__(self):
self.win = Tk()
text = Text(self.win, width=40, height=10, wrap = "none")
ys = Scrollbar(self.win, orient = 'vertical', command = text.yview)
text['yscrollcommand'] = ys.set
text.grid(column = 0, row = 0, sticky = 'nwes')
ys.grid(column = 1, row = 0, sticky = 'ns')
for x in range(1,100):
b = Button(text, text='Push Me')
text.window_create("end", window=b)
text.insert("end",'\n')
self.win.mainloop()
test = test()
Ok, I just found an answer. The problem lies, partly, in that there was a difference between working with Linux and Windows when binding Mousewheel to a widget.
So in addition to the above I made this code, to bind the mousewheel to the buttons. Problem solved, albeit, in my opionion, apparently unnecesarily complex. It would be nicer if the Text Widget would still register scrolling despite the presence of other widgets inside of it. :
b = Button(self.text, text="Push Me")
b.bind("<Button-4>", self.on_mousewheel)
b.bind("<Button-5>", self.on_mousewheel)
def on_mousewheel(self,event):
if event.num==4:
self.text.yview("scroll",-1,"units")
else:
self.text.yview("scroll",1,"units")
Update:
self.text.bind_all("<Button-4>", self.on_mousewheel)
self.text.bind_all("<Button-5>", self.on_mousewheel)
works also, so that not every individual Button or Label needs its own binding.
Related
I am placing labels on a Tab in Tkinter with a for loop. How can I identify in the event handler which label was clicked (or its loop index)? I guess it is functionally similar to a ListBox but without the formatting restrictions. I might want to put the labels in a circle or place them diagonally. I tried finding the coordinates of the label but these are available only if the tab is the first one visible or the tab is redrawn when made active. Also the x, y passed in the event handler is the x, y within the label which does not help to identify the label.
I could copy the label code about 10 times and and have about 10 event handlers. This would work but this is no longer 1970!
Perhaps I could bind a handler to the tab canvas and identify the label from its coordinates. The label would need to be on the first tab or the tab drawn when active.
Perhaps I could create a different event handler for each label by holding the event handlers in an array. I would need an event handler for each label. The code would need to change if the number of labels changed.
I am currently trying a label with ''. Would using buttons with command be easier?
What simple part of Python am I missing? I cannot be the first person to need this! Any help or advice would be appreciated.
You can save a reference to the label text for each label widget in a dict.
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
root.geometry('+800+50')
notebook = ttk.Notebook(root, width=300, height=200, padding=[10,10,10,10])
notebook.pack()
tab_one = tk.Frame(notebook, bg='wheat')
notebook.add(tab_one, text='Cheese', padding=[10,10,10,10])
tab_two = tk.Frame(notebook, bg='mint cream')
notebook.add(tab_two, text='Misc.', padding=[10,10,10,10])
def clicked(event):
print('Clicked:', name_dict[event.widget])
# Populating Cheese tab with clickable Labels
name_list = ['Cheddar', 'Ilchester', 'Limburger']
name_dict = {}
for index, name in enumerate(name_list):
a = tk.Label(tab_one, width=10, text=name, bg='peru')
a.grid(row=index, column=0, padx=5, pady=5)
name_dict[a] = name # Save references in a dict
a.bind('<Button-1>', clicked)
tk.Label(tab_two, text='Just some text...', bg='powder blue').pack(anchor='nw')
root.mainloop()
Is this what you had in mind?
When you bind events, the function receives an object that includes a reference to the widget that received the event. In the following example, notice how it uses event.widget to refer to the widget that was clicked on.
import tkinter as tk
def update_label(event):
event.widget.configure(text="You clicked me")
root = tk.Tk()
for i in range(10):
l = tk.Label(root, text="Click me", width=20)
l.pack()
l.bind("<1>", update_label)
root.mainloop()
Good days all, I am new here, and I have stuck the problem a few days a problem currently with Tkinter, I have done some research about how to close Tkinter window when the mouse has clicked away from it but there are not much information to do so.
So, my problem is how to close the Tkinter window when the mouse clicked outside the Tkinter? I have tried the method of FocusOut to my Tkinter. However, I have tried to bind with root, it will close the window even though I clicked inside the frame widget. Then, I bind with the frame, the Tkinter will close when I clicked outside the Tkinter. Therefore, I have proved that the idea to close the Tkinter is works so far.
Then a new problem has happened, when I clicked the Combobox widget in the window, the window will close also. Is there any better solution to prove this concept?
Here is the code to indicate my problem.
import tkinter as tk
from tkinter import StringVar, ttk,messagebox
root = tk.Tk()
root.title("Sample Window")
root.minsize(300,350)
info_frame = tk.LabelFrame(root, text = "Information")
info_frame.pack(padx = 5, pady = 5 , fill = "both",expand=True)
tabControl = ttk.Notebook(info_frame)
person1tab = ttk.Frame(tabControl)
tabControl.add(person1tab,text = "Person1")
tabControl.pack(expand=1,fill="both")
person2tab = ttk.Frame(tabControl)
tabControl.add(person2tab,text = "Person2")
tabControl.pack(expand=1,fill="both")
fname_var = tk.StringVar()
lname_var = tk.StringVar()
gender_var = tk.StringVar()
age_var = tk.IntVar()
fname_label = tk.Label(person1tab, text = "First name:").pack(padx=5,pady=3)
fname_entry = tk.Entry(person1tab, textvariable=fname_var).pack(padx=5,pady=3)
lname_label = tk.Label(person1tab, text = "Last name:").pack(padx=5,pady=3)
lname_entry = tk.Entry(person1tab, textvariable=lname_var).pack(padx=5,pady=3)
gender_label = tk.Label(person1tab, text = "Gender:").pack(padx=5,pady=3)
gender_combo = ttk.Combobox(person1tab, textvariable=gender_var,state='readonly')
gender_combo['values'] = ('Male','Female')
gender_combo.current(0)
gender_combo.pack(padx=5,pady=3)
age_label = tk.Label(person1tab, text = "Age:").pack(padx=5,pady=3)
age_label = tk.Entry(person1tab, textvariable=age_var).pack(padx=5,pady=3)
page2label = tk.Label(person2tab,text = "This is tab 2.").pack(padx=5,pady=3)
def lossfocus(event):
root.quit()
pass
tabControl.bind('<FocusOut>', lossfocus)
root.mainloop()
You can still bind <FocusOut> on root window, but you need to check:
whether the widget that trigger this event is root window
no other widget in this root window getting the focus:
def lossfocus(event):
if event.widget is root:
# check which widget getting the focus
w = root.tk.call('focus')
if not w:
# not widget in this window
root.destroy()
Before you read:
I am a complete novice at programming let alone Python. I don't expect solutions. I will appreciate it even if I just get a pointer in the right direction. If you feel the way I've requested help is unrefined, please let me know so next time I need help, I'll do a better job at asking.
Goal:
I'm making a program to test how to get a program to respond depending on which widget has to focus on. Currently, I'm just having it respond by displaying 'what' has focus.
Eventually, once I've figured this out I can implement what I've learnt into another program I'm working on was focusing on an entry field will result in the field clearing of all input. So naturally, I will want this program to not only change the label but the entry widget too. That will be for later.
Progress:
I've managed to have the program print which widget has a focus in both the terminal and in a label widget.
Problem:
My issue is that the message is ugly, and I want it to look neater.
Example of the problem:
Instead of saying "Entry has focus", it says ".!frame.!entry {has focus}"
I have tried:
Checking if other's have made similar programs. The only one I found was how I made this much progress but it didn't make the text nicer. I've also done research on various sites on how Python handles certain commands I've used in this program.
I am a novice with programming so I admit I'm not completely sure what the best way to run this program is and so trying to research it is difficult for me.
Code:
# Call tkinter tools
from tkinter import *
from tkinter import ttk
"""
TODO:
Add different widgets that can 'gain' focus.
Print in the terminal which widget has focus.
Change entry and label text depending on which widget has focus.
"""
# Develop the window and frame
root = Tk()
root.title("Focus Test")
root.columnconfigure(0, weight = 1)
root.rowconfigure(0, weight = 1)
frame = ttk.Frame(root, padding = "1 1 1 1")
frame.grid(column = 0, row = 0, sticky = (N, S, E, W))
# Resizes columns in frame
for col in range(1, 3):
frame.columnconfigure(col, weight = 1)
# Resizes rows in frame
for row in range(1, 3):
frame.rowconfigure(row, weight = 1)
# Add response label
foc_labvar = StringVar()
foc_labvar.set("No focus")
foc_lab = ttk.Label(frame, width = 7, textvariable = foc_labvar)
foc_lab.grid(column = 2, row = 2, sticky = (W, E))
# Add entry box
foc_entvar = StringVar()
foc_entvar.set("Entry widget")
foc_ent = ttk.Entry(frame, width = 7, textvariable = foc_entvar)
foc_ent.grid(column = 1, row = 1, sticky = (W, E))
# Add button
foc_butvar = StringVar()
foc_butvar.set("Button widget")
foc_but = ttk.Button(frame, width = 7, textvariable = foc_butvar)
foc_but.grid(column = 2, row = 1, sticky = (W, E))
# Focus commands
def focus(event):
focused_widget = frame.focus_get()
foc_labvar.set((focused_widget, "has focus"))
print(focused_widget, "has focus")
# Bind mouse click to run focus command
root.bind("<Button-1>", focus)
# Resize widgets inside frame
for child in frame.winfo_children():
child.grid_configure(padx = 5, pady = 5)
root.mainloop()
You can just easily do this having event handle the widget identification part, like:
def focus(event):
focused_widget = event.widget.winfo_class()
foc_labvar.set((focused_widget, "has focus")) # Or f'{focused_widget} has focus'
print(focused_widget, "has focus")
Here event will provide the widget with widget method and then you can use any widget methods on it, like focus() or insert()(if its an entry widget) and so on. Here winfo_class should work, because it is a universal method and works with all the widgets.
More info on winfo_class, it will provide the class of the widget, like TButton for buttons, because that is how tk refers to them. If you adamantly still want to get rid of the T then just go for event.widget.winfo_class()[1:] to trim the 'T' off.
You can give widgets a name of your choosing. You can then use winfo_name to get the name of the widget. About the only required for a name is that it cannot have a period in it.
Also, you should bind to <FocusIn> and <FocusOut> so that your code works even if the focus changes via the keyboard.
foc_ent = ttk.Entry(..., name="foc entry")
foc_but = ttk.Button(..., name="foc button")
...
def focus(event):
focused_widget = frame.focus_get()
if focused_widget:
foc_labvar.set(f"{focused_widget.winfo_name()} as focus")
else:
foc_labvar.set("nothing has focus")
root.bind("<FocusIn>", focus)
root.bind("<FocusOut>", focus)
You can just use an if statement like this:
def focus(event):
focused_widget = frame.focus_get() # You can also use `event.widget` like what #CoolCloud did.
if focused_widget == foc_ent:
foc_labvar.set("Entry has focus")
if focused_widget == foc_but:
foc_labvar.set("Button has focus")
Combining the advice provided. Here is what I've ended up with which is working the way I had imagined in my head plus a little extra from everyone's help:
# Focus commands
def focus(event):
focused_widget = event.widget.winfo_class()[1:]
foc_labvar.set(focused_widget + " has focus")
foc_entvar.set(focused_widget + " has focus")
print(focused_widget, "has focus")
# Bind mouse and keyboard to run focus command
root.bind("<Button-1>", focus)
root.bind("<FocusIn>", focus)
root.bind("<FocusOut>", focus)
When I try to scroll down the Text object the scroll bar sort of bounces back instead of actually scrolling
memo_content = Text(edit_window,height = 4, width = 40)
memo_content.grid(row = 1, column = 0, sticky = E+W)
scroll_memo_content=Scrollbar(edit_window)
scroll_memo_content.grid(row = 1, column = 2, sticky = N+S)
memo_content.configure(yscrollcommand=scroll_memo_content.set)
The scroll bar has no control over the text field and when I drag it it snaps back to where it was meaning that the only way to scroll is by holding the up/down arrow and the cursor moving
from tkinter import *
root = Tk()
text = Text(root, height=25, width=50)
sb = Scrollbar(root)
text.configure(yscrollcommand=sb.set)
sb.configure(command=text.yview) # <- You missed this
sb.pack(side=RIGHT, fill=Y)
text.pack()
You missed configuring the actual scrollbar.
Configuring a scrollbar requires two steps:
You must configure the scrollbar to know which widget it is controlling. This is typically done by setting the command attribute of the scrollbar to be the yview command of a scrollable widget.
You must configure the window to be scrolled to know scrollbar needs to be updated when its position changes. This is typically done by setting the yscrollcommand or xscrollcomand of the widget to the set method of an appropriate scrollbar.
You are forgetting to do the first step.
Make the following change:
scroll_memo_content=Scrollbar(edit_window, command=memo_content.yview)
Something like this, would make the widget appear normally:
Label(self, text = 'hello', visible ='yes')
While something like this, would make the widget not appear at all:
Label(self, text = 'hello', visible ='no')
You may be interested by the pack_forget and grid_forget methods of a widget. In the following example, the button disappear when clicked
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()
One option, as explained in another answer, is to use pack_forget or grid_forget. Another option is to use lift and lower. This changes the stacking order of widgets. The net effect is that you can hide widgets behind sibling widgets (or descendants of siblings). When you want them to be visible you lift them, and when you want them to be invisible you lower them.
The advantage (or disadvantage...) is that they still take up space in their master. If you "forget" a widget, the other widgets might readjust their size or orientation, but if you raise or lower them they will not.
Here is a simple example:
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()
I know this is a couple of years late, but this is the 3rd Google response now for "Tkinter hide Label" as of 10/27/13... So if anyone like myself a few weeks ago is building a simple GUI and just wants some text to appear without swapping it out for another widget via "lower" or "lift" methods, I'd like to offer a workaround I use (Python2.7,Windows):
from Tkinter import *
class Top(Toplevel):
def __init__(self, parent, title = "How to Cheat and Hide Text"):
Toplevel.__init__(self,parent)
parent.geometry("250x250+100+150")
if title:
self.title(title)
parent.withdraw()
self.parent = parent
self.result = None
dialog = Frame(self)
self.initial_focus = self.dialog(dialog)
dialog.pack()
def dialog(self,parent):
self.parent = parent
self.L1 = Label(parent,text = "Hello, World!",state = DISABLED, disabledforeground = parent.cget('bg'))
self.L1.pack()
self.B1 = Button(parent, text = "Are You Alive???", command = self.hello)
self.B1.pack()
def hello(self):
self.L1['state']="normal"
if __name__ == '__main__':
root=Tk()
ds = Top(root)
root.mainloop()
The idea here is that you can set the color of the DISABLED text to the background ('bg') of the parent using ".cget('bg')" http://effbot.org/tkinterbook/widget.htm rendering it "invisible". The button callback resets the Label to the default foreground color and the text is once again visible.
Downsides here are that you still have to allocate the space for the text even though you can't read it, and at least on my computer, the text doesn't perfectly blend to the background. Maybe with some tweaking the color thing could be better and for compact GUIs, blank space allocation shouldn't be too much of a hassle for a short blurb.
See Default window colour Tkinter and hex colour codes for the info about how I found out about the color stuff.
I'm also extremely late to the party, but I'll leave my version of the answer here for others who may have gotten here, like I did, searching for how to hide something that was placed on the screen with the .place() function, and not .pack() neither .grid().
In short, you can hide a widget by setting the width and height to zero, like this:
widget.place(anchor="nw", x=0, y=0, width=0, height=0)
To give a bit of context so you can see what my requirement was and how I got here.
In my program, I have a window that needs to display several things that I've organized into 2 frames, something like this:
[WINDOW - app]
[FRAME 1 - hMainWndFrame]
[Buttons and other controls (widgets)]
[FRAME 2 - hJTensWndFrame]
[other Buttons and controls (widgets)]
Only one frame needs to be visible at a time, so on application initialisation, i have something like this:
hMainWndFrame = Frame(app, bg="#aababd")
hMainWndFrame.place(anchor="nw", x=0, y=0, width=480, height=320)
...
hJTensWndFrame = Frame(app, bg="#aababd")
I'm using .place() instead of .pack() or .grid() because i specifically want to set precise coordinates on the window for each widget. So, when i want to hide the main frame and display the other one (along with all the other controls), all i have to do is call the .place() function again, on each frame, but specifying zero for width and height for the one i want to hide and the necessary width and height for the one i want to show, such as:
hMainWndFrame.place(anchor="nw", x=0, y=0, width=0, height=0)
hJTensWndFrame.place(anchor="nw", x=0, y=0, width=480, height=320)
Now it's true, I only tested this on Frames, not on other widgets, but I guess it should work on everything.
For hiding a widget you can use function pack_forget() and to again show it you can use pack() function and implement them both in separate functions.
from Tkinter import *
root = Tk()
label=Label(root,text="I was Hidden")
def labelactive():
label.pack()
def labeldeactive():
label.pack_forget()
Button(root,text="Show",command=labelactive).pack()
Button(root,text="Hide",command=labeldeactive).pack()
root.mainloop()
I was not using grid or pack.
I used just place for my widgets as their size and positioning was fixed.
I wanted to implement hide/show functionality on frame.
Here is demo
from tkinter import *
window=Tk()
window.geometry("1366x768+1+1")
def toggle_graph_visibility():
graph_state_chosen=show_graph_checkbox_value.get()
if graph_state_chosen==0:
frame.place_forget()
else:
frame.place(x=1025,y=165)
score_pixel = PhotoImage(width=300, height=430)
show_graph_checkbox_value = IntVar(value=1)
frame=Frame(window,width=300,height=430)
graph_canvas = Canvas(frame, width = 300, height = 430,scrollregion=(0,0,300,300))
my_canvas=graph_canvas.create_image(20, 20, anchor=NW, image=score_pixel)
vbar=Scrollbar(frame,orient=VERTICAL)
vbar.config(command=graph_canvas.yview)
vbar.pack(side=RIGHT,fill=Y)
graph_canvas.config(yscrollcommand=vbar.set)
graph_canvas.pack(side=LEFT,expand=True,fill=BOTH)
frame.place(x=1025,y=165)
Checkbutton(window, text="show graph",variable=show_graph_checkbox_value,command=toggle_graph_visibility).place(x=900,y=165)
window.mainloop()
Note that in above example when 'show graph' is ticked then there is vertical scrollbar.
Graph disappears when checkbox is unselected.
I was fitting some bar graph in that area which I have not shown to keep example simple.
Most important thing to learn from above is the use of frame.place_forget() to hide and frame.place(x=x_pos,y=y_pos) to show back the content.
For someone who hate OOP like me (This is based on Bryan Oakley's answer)
import tkinter as tk
def show_label():
label1.lift()
def hide_label():
label1.lower()
root = tk.Tk()
frame1 = tk.Frame(root)
frame1.pack()
label1 = tk.Label(root, text="Hello, world")
label1.pack(in_=frame1)
button1 = tk.Button(root, text="Click to hide label",command=hide_label)
button2 = tk.Button(root, text="Click to show label", command=show_label)
button1.pack(in_=frame1)
button2.pack(in_=frame1)
root.mainloop()
import tkinter as tk
...
x = tk.Label(text='Hello', visible=True)
def visiblelabel(lb, visible):
lb.config(visible=visible)
visiblelabel(x, False) # Hide
visiblelabel(x, True) # Show
P.S. config can change any attribute:
x.config(text='Hello') # Text: Hello
x.config(text='Bye', font=('Arial', 20, 'bold')) # Text: Bye, Font: Arial Bold 20
x.config(bg='red', fg='white') # Background: red, Foreground: white
It's a bypass of StringVar, IntVar etc.