tkinter label and button grid placing - python

i have created window, in that window i put frame. Then i want to create two labels after them button widget.
But, button widget appears upper than second label widget even though i put btn in row=2, and label2 in row=1. It 's hard for me to get why?
from tkinter import *
window=Tk()
window.geometry('620x540+33+33')
var_pg=StringVar()
var_pg.set('Page 001 placeholder')
class lbl_custom(Label):
def __init__(self,frame_window):
super().__init__()
self['width']=33
self['font']='Segoe 12'
frame_submit=Frame(window)
lbl_page_num01=Label(frame_submit,width=33,textvariable=var_pg,font='Segoe 12')
lbl_page_num02=lbl_custom(frame_submit)
lbl_page_num02['text']='Page 002 placeholder'
btn_submit=Button(frame_submit,width=33,relief=RAISED,text='submit')
frame_submit.grid(row=0,column=0)
lbl_page_num01.grid(row=0,column=0)
lbl_page_num02.grid(row=1,column=0)
btn_submit.grid(row=2,column=0)
window.mainloop()
Now, it is for sure that. Every label widget which is created by class inheritance is going down than button nonetheless grid placeholder. But if I create it as Label() class it will be upper than button. Why it is so?

You must pass the parent object when calling super, otherwise your custom object will always be a child of the root window.
class lbl_custom(Label):
def __init__(self,frame_window):
super().__init__(frame_window)
# ^^^^^^^^^^^^
You can pass other options as well, which will save you a couple lines of code:
class lbl_custom(Label):
def __init__(self,frame_window):
super().__init__(frame_window, width=33, font=("Segoe 12",))

Related

How do I efficiently change all the buttons in my program?

I have coded multiple buttons in my Tkinter program, and I want to change the text colour of all of them to blue. I know I could type 'fg="blue"'every time I create a new button, but I'm looking for a way to select all the buttons in my program, and change the background colour of all of them at the same time.
So far I've tried
for AllButtons in (Button1, Button, ect.)
But it still takes a long time and I'll have to add to the list every time I make a new button. What's the most efficient way of changing the text colour of all the buttons in my program?
You can use ttk widgets and their style to change the appearance of all of the widgets of a specific class at once.
Consider the following example, clicking on the change style button will change the text color from red to blue.
import tkinter as tk
from tkinter import ttk
class App(tk.Tk):
def __init__(self):
super().__init__()
self.geometry('300x110')
self.resizable(0, 0)
self.style = ttk.Style(self)
self.style.configure('W.TButton', foreground = 'red')
# login button
login_button = ttk.Button(self, text="Login",style="W.TButton")
login_button.grid(column=1, row=1, sticky=tk.E)
change_button = ttk.Button(self, text="Change Style",style="W.TButton", command=self.changeStyle)
change_button.grid(column=1,row=2,sticky=tk.E)
def changeStyle(self):
print("Change")
self.style.configure('W.TButton', foreground='blue')
if __name__ == "__main__":
app = App()
app.mainloop()
All of the buttons are mapped to a "W.TButton" style so when we change that style, all of the widgets associated with that style will change their appearance.

Python tkinter When Toplevel object is destroyed, With toplevel binding for <Destroy> the bound command executes multiple times

In the following code, When the toplevel window is destroyed, the command in the
bind statement is executed multiple times. Probably once for each child widget within the Top Level. When I change the toplevel to a Frame, the bind command only executes once.
In the example, quit() or raise SystemExit are deferred until the command finishes its looping. Why is this happening?
import tkinter as tk
from tkinter import ttk
from tkinter.messagebox import showinfo
class PlainFrame(tk.Frame):
def __init__(self,parent):
super().__init__(parent)
self.butts = []
for i in range(20) :
# button
self.butts.append(ttk.Button(self, text=f'Click Me {i}'))
self.butts[i]['command'] = self.button_clicked
self.butts[i].grid(column=0,row=i)
self.pack()
def button_clicked(self):
showinfo(title='Information',
message='Hello, Tkinter!')
class MainFrame(tk.Toplevel):
#class MainFrame(tk.Frame):
def __init__(self, container,*args,**kwargs):
super().__init__(container,kwargs)
options = {'padx': 5, 'pady': 5}
# label
self.label = ttk.Label(self, text='Hello, Tkinter!')
self.label.pack(**options)
self.quit_button = ttk.Button(self,text='Quit',
command = self._quit)
self.quit_button.pack()
self.frame = PlainFrame(self)
# add when frame
#self.pack()
def button_clicked(self):
showinfo(title='Information',
message='Hello, Tkinter!')
def _quit(self):
self.destroy()
class App(tk.Tk):
def __init__(self):
super().__init__()
# configure the root window
self.title('My Awesome App')
self.geometry('600x100')
def quitting(self,event):
print ('just passing through')
quit()
raise SystemExit
if __name__ == "__main__":
app = App()
frame = MainFrame(app)
# app.withdraw()
frame.bind('<Destroy>',app.quitting)
app.mainloop()
With toplevel binding for the bound command executes multiple times
Yes, this is how tkinter is designed to work.
When you bind to something, you don't bind to a widget. Rather, you bind to a binding tag. Every widget has a set of binding tags. When a widget receives an event, tkinter will check each of its binding tags to see if there's a function bound to it for the given event.
So, what are the widget binding tags? Every widget gets the binding tag "all". Each widget also gets a binding tag named after the widget itself. It gets a third binding tag that is the name of the widget class (eg: "Button", "Label", etc). The fourth tag -- the one causing you trouble -- is the name of the window that contains the widget. The order goes from most to least specific: widget, widget class, window, "all".
You can see this by printing out the binding tags for a widget. Consider the following code:
import tkinter as tk
root = tk.Tk()
toplevel = tk.Toplevel(root)
label = tk.Label(toplevel)
print(f"binding tags for label: {label.bindtags()}")
When run, the above code produces this output:
binding tags for label: ('.!toplevel.!label', 'Label', '.!toplevel', 'all')
The first string, .!toplevel.!label is the internal name of the label widget. Label is the widget class, .!toplevel is the name of the toplevel widget, and then there's the string all.
What happens when you click on the label? First, tkinter will check to see if there is a binding for the button click on the tag .!toplevel.!label. It will have one if you bound to the widget itself. Next, it will check to see if there is a binding on the widget class. Widgets like scrollbars and buttons will have a binding on the widget class, but label won't. Next, tkinter will see if there's a binding on the window itself for the event. And finally, it will see if there is a binding on the special tag all.
You can alter the bindtags for a widget by passing the list of binding tags to the bindtags command. For example, if you want every widget to have a bind tag for the frame, you could set the bindtags to include the frame, and then every widget will respond to an event that is bound to the frame.
You can use the same technique to remove bindings as well. For example, if you wanted to remove all default bindings from a Text widget, you could remove Text from its list of bind tags. Once you do that, the widget will not respond to any key presses or key releases.
The canonical description of how binding tags works is in the bindtags man page for tcl/tk.

In tkinter, is there a way to change the draw stack order of overlapping canvases?

The Problem
Using Python's tkinter, I'm trying to create custom buttons and other widgets by extending the Canvas widget. How can I change which custom canvas widgets get drawn on top as the user interacts with them?
lift() works for regular tkinter Buttons and other widgets, but raises an error when I try to use it to lift a Canvas, because Canvas has its own lift() method. Canvas's lift() is deprecated for Canvas in favor of tag_raise(). However, tag_raise() documentation says it "doesn’t work with window items", which fits my experience, and directs me to use lift() instead. My brain chased this seemingly circular documentation until it raised its own kind of StackOverflow exception, which brings me to ask you.
Code Illustration
Here's some basic code that runs and illustrates my problem. I've included button3, a regular button that can lift() as expected. If I click on custom_button1, however, the click_handler raises an exception.
from tkinter import Button, Canvas, Frame, Tk
from tkinter.constants import NW
class Example(Frame):
def __init__(self, root):
Frame.__init__(self, root)
self.canvas = Canvas(self, width=200, height=200, background="black")
self.canvas.grid(row=0, column=0, sticky="nsew")
self.button3 = Button(self.canvas, text="button3")
self.custom_button1 = MyCustomButton(self.canvas)
self.custom_button2 = MyCustomButton(self.canvas)
self.canvas.create_window(20, 20, anchor=NW, window=self.button3)
self.canvas.create_window(40, 40, anchor=NW, window=self.custom_button1)
self.canvas.create_window(34, 34, anchor=NW, window=self.custom_button2)
self.button3.bind("<Button-1>", self.click_handler)
self.custom_button1.bind("<Button-1>", self.click_handler)
self.custom_button2.bind("<Button-1>", self.click_handler)
def click_handler(self,event):
event.widget.lift() #raises exception if event.widget is a MyCustomButton
#note that Canvas.lift() is deprecated, but documentation
#says Canvas.tag_raise() doesn't work with window items
class MyCustomButton(Canvas):
def __init__(self, master):
super().__init__(master, width=40, height=25, background='blue')
if __name__ == "__main__":
root = Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
This works for as desired for button3, but for custom_button1, the exception that is raised is:
_tkinter.TclError: wrong # args: should be ".!example.!canvas.!mycustombutton2 raise tagOrId ?aboveThis?"
That exception makes sense in the context that Canvas.lift() and Canvas.tag_raise() are normally used to affect an item on the canvas by tag or id, not the canvas itself. I just don't know what to do about changing the stack order of the canvas itself so I can use it as a custom widget.
A Work-Around I've Considered
I could manage a bunch of custom widgets on a canvas by only having one canvas that handles all drawing and all the mouse events for all the widgets. I could still have classes for the widgets, but instead of inheriting from Canvas, they'd accept Canvas parameters. So adding would look something like the code below, and I'd have to write similar code for lifting, moving, determining if a click event applied to this button, changing active state, and so forth.
def add_to_canvas(self, canvas, offset_x=0, offset_y=0):
self.button_border = canvas.create_rectangle(
offset_x + 0, offset_y + 0,
offset_x + 40, offset_y + 25
)
#create additional button features
This work-around seems to go against established coding paradigms in tkinter, though. Furthermore, I believe this approach would prevent me from drawing these custom buttons above other window objects. (According to the create_window() documentation "You cannot draw other canvas items on top of a widget." In this work-around, all the custom buttons would be canvas items, and so if I'm reading this correctly, couldn't be drawn on top of other widgets.) Not to mention the extra code it would take to implement. That said, I don't currently have a better idea of how to implement this.
Thank you in advance for your help!
Unfortunately you've stumbled on a bug in the tkinter implementation. You can work around this in a couple of ways. You can create a method that does what the tkinter lift method does, or you can directly call the method in the tkinter Misc class.
Since you are creating your own class, you can override the lift method to use either of these methods.
Here's how you do it using the existing function. Be sure to import Misc from tkinter:
from tkinter import Misc
...
class MyCustomButton(Canvas):
...
def lift(self, aboveThis=None):
Misc.tkraise(self)
Here's how you directly call the underlying tk interpreter:
class MyCustomButton(Canvas):
...
def lift(self, aboveThis=None):
self.tk.call('raise', self._w, aboveThis)
With that, you can raise one button over the other by calling the lift method:
def click_handler(self,event):
event.widget.lift()

How to get the name of a widget Python Tkinter

I am working on a program where I create some widgets in a for loop. So I need to get the name of them dynamically. I have set up is when the mouse enters the frame. Which holds the two text label widgets. I causes a function to run. And I want to change the background color of a widget with the name of noteName. But I seem to have run into a stopping point and I can no figure it out. I have searched online but could not find much. SO does anyone here know how to get the name of a widget?
Code:
def get_children_hover(event):
for widgets in event.widget.winfo_children():
#This is here where I can not seem to figure out how to get the widgets name.
Can someone push me into the right direction.
winfo_children() is the right thing to use but you are using it wrong. It is a method for parent widgets. (i.e. root, frame, canvas etc..)
Also:
If the order doesn’t matter, you can get the same information from the
children widget attribute (it’s a dictionary mapping Tk widget names
to widget instances, so widget.children.values() gives you a list of
instances).
simple example:
import tkinter as tk
def foo():
print ("Frame:", frm.winfo_children())
print ("Root:", root.winfo_children())
print ("children_values:", root.children.values())
root = tk.Tk()
frm = tk.Frame(root)
tk.Label(root,text="foo").pack()
btn = tk.Button(frm,text="FOOO",command=foo)
frm.pack()
btn.pack()
root.mainloop()
about your code:
def get_children_hover(event):
for widgets in root.winfo_children(): #assuming your Tk() instance named root

In Tkinter how do i remove focus from a widget?

I'd like to remove focus from a widget manually.
You can focus to another dummy widget.
Edit
from Tkinter import *
def callback():
print master.focus()
master = Tk()
e = Entry(master)
e.pack()
e.focus()
b = Button(master, text="get", width=10, command=callback)
b.pack()
master.mainloop()
Focusing on a non-'focusable' widget will remove focus from another widget.
Set focus to another widget to remove focus from the target widget is a good idea. There are two methods for this: w.focus_set() and w.focus_force(). However, method w.focus_force() is impolite. It's better to wait for the window manager to give you the focus. Setting focus to parent widget or to the root window removes focus from the target widget.
Some widgets have takefocus option. Set takefocus to 0 to take your widget out of focus traversal (when user hits <Tab> key).
My solution is root.focus() it will remove widget focus.
If the dummy widget is Canvas then c.focus() will not work.
use c.focus_set() or c.tk.call('focus',c) to first focus on the canvas window itself.
That's because
c.focus()
... returns the id for the item that currently has the focus, or an empty string if no item has the focus. Reference
c.focus(id_) will focus on the item having id id_ within the canvas.
c.focus("") will remove the focus from any item in the canvas.
Hence (within some callback)
c.config(highlightthickness = 0) # to remove the highlight border on focus
c.foucs_set()
c.focus("") # just to be sure
The reason c.focus() functions differently is that within Tcl/Tk's Commands there's the "Primary" Command focus
as well as the Canvas-specific Widget Command focus
That's not an issue within the Tcl/Tk syntax but in the tkinter module c.focus() will call the underlying canvas-specific foucs.
From tkinter.py within the Canvas class Line 2549
def focus(self, *args):
"""Set focus to the first item specified in ARGS."""
return self.tk.call((self._w, 'focus') + args)
So the question may be a duplicate here, but the answer from #Bryan Oakley works perfectly for me in Python 3.8
root.focus_set()
Too easy...
If you use ttk widgets you can "remove" the focus ring by removing the color; for example on a button:
style = ttk.Style()
style.configure('TButton', focuscolor='')

Categories