Disable click events from queuing on a widget while another function runs - python

I have a function (A) that is bound to a click event on a Canvas. At some point, function A calls another function (B). The canvas contents may change in function B, so I want it to ignore canvas click events while it's running. I've tried setting a bool variable that is checked when function A is called to False before the function B is called and then resetting it to True when the function B is done, but the event still queues with bad mouse coordinates for the new contents. I've tried re-binding canvas click events to a function (C) that just returns "break" then calling the function B then re-binding canvas click events back to function A. Still queues with bad mouse coordinates for the new contents. I've tried setting the canvas state to "disabled" calling function B then setting the state to "normal". I've tried returning "break" from function A, function B, and from both. I get the same result it still queues with bad mouse coordinates for the new contents. Any suggestions?
the code I'm using for testing options in its current state:
import time
from tkinter import *
class test_form(object):
def __init__(self, master):
self.master = master
self._CreateGUI()
def _CreateGUI(self):
geom = "200x175+0+0"
self.master.geometry(geom)
self.xx=Canvas(bg='#ff0000',width=100,height=75)
self.xx.bind('<ButtonRelease>',self.test_button)
self.xx.pack()
def waste_time(self):
print('time wasted')
time.sleep(10)
return "break"
def test_button(self,event=None):
self.waste_time()
return "break"
def start_gui():
root = Tk()
form_load = test_form(root)
root.mainloop()
if __name__ == "__main__":
start_gui()

The following approach seems to fix your problem. It's likely similar to one you've already tried but with an after() thrown in:
class test_form(object):
def __init__(self, master):
self.master = master
self._CreateGUI()
def _CreateGUI(self):
geom = "200x175+0+0"
self.master.geometry(geom)
self.xx = Canvas(bg='#ff0000', width=100, height=75)
self.xx.pack()
self.notice()
def waste_time(self):
print('time wasted')
time.sleep(10)
def notice(self):
self.xx.bind('<ButtonRelease>', self.test_button)
def ignore(self):
self.xx.bind('<ButtonRelease>', lambda event: "break")
def test_button(self, event):
self.ignore()
self.waste_time()
self.master.after(500, self.notice)

Related

python tkinter tooltip on modal window

I searched for ways of implementing tooltips for an application and I found in some comment or answer in this site some while ago a link to this page.
I've been using this class since then and I've been happy with the result.
But recently I noticed that the tooltips came up behind modal windows, when they refer to widgets on that modal window.
Below in the code downloaded from that GitHub link, where I just made the changes of replacing from tkinter import * with import tkinter as tk, and using the prefix tk throughout the code accordingly.
"""Tools for displaying tool-tips.
This includes:
* an abstract base-class for different kinds of tooltips
* a simple text-only Tooltip class
"""
import tkinter as tk
class TooltipBase:
"""abstract base class for tooltips"""
def __init__(self, anchor_widget):
"""Create a tooltip.
anchor_widget: the widget next to which the tooltip will be shown
Note that a widget will only be shown when showtip() is called.
"""
self.anchor_widget = anchor_widget
self.tipwindow = None
def __del__(self):
self.hidetip()
def showtip(self):
"""display the tooltip"""
if self.tipwindow:
return
self.tipwindow = tw = tk.Toplevel(self.anchor_widget)
# show no border on the top level window
tw.wm_overrideredirect(1)
try:
# This command is only needed and available on Tk >= 8.4.0 for OSX.
# Without it, call tips intrude on the typing process by grabbing
# the focus.
tw.tk.call("::tk::unsupported::MacWindowStyle", "style", tw._w,
"help", "noActivates")
except tk.TclError:
pass
self.position_window()
self.showcontents()
self.tipwindow.update_idletasks() # Needed on MacOS -- see #34275.
self.tipwindow.lift() # work around bug in Tk 8.5.18+ (issue #24570)
def position_window(self):
"""(re)-set the tooltip's screen position"""
x, y = self.get_position()
root_x = self.anchor_widget.winfo_rootx() + x
root_y = self.anchor_widget.winfo_rooty() + y
self.tipwindow.wm_geometry("+%d+%d" % (root_x, root_y))
def get_position(self):
"""choose a screen position for the tooltip"""
# The tip window must be completely outside the anchor widget;
# otherwise when the mouse enters the tip window we get
# a leave event and it disappears, and then we get an enter
# event and it reappears, and so on forever :-(
#
# Note: This is a simplistic implementation; sub-classes will likely
# want to override this.
return 20, self.anchor_widget.winfo_height() + 1
def showcontents(self):
"""content display hook for sub-classes"""
# See ToolTip for an example
raise NotImplementedError
def hidetip(self):
"""hide the tooltip"""
# Note: This is called by __del__, so careful when overriding/extending
tw = self.tipwindow
self.tipwindow = None
if tw:
try:
tw.destroy()
except tk.TclError: # pragma: no cover
pass
class OnHoverTooltipBase(TooltipBase):
"""abstract base class for tooltips, with delayed on-hover display"""
def __init__(self, anchor_widget, hover_delay=1000):
"""Create a tooltip with a mouse hover delay.
anchor_widget: the widget next to which the tooltip will be shown
hover_delay: time to delay before showing the tooltip, in milliseconds
Note that a widget will only be shown when showtip() is called,
e.g. after hovering over the anchor widget with the mouse for enough
time.
"""
super(OnHoverTooltipBase, self).__init__(anchor_widget)
self.hover_delay = hover_delay
self._after_id = None
self._id1 = self.anchor_widget.bind("<Enter>", self._show_event)
self._id2 = self.anchor_widget.bind("<Leave>", self._hide_event)
self._id3 = self.anchor_widget.bind("<Button>", self._hide_event)
def __del__(self):
try:
self.anchor_widget.unbind("<Enter>", self._id1)
self.anchor_widget.unbind("<Leave>", self._id2) # pragma: no cover
self.anchor_widget.unbind("<Button>", self._id3) # pragma: no cover
except tk.TclError: # pragma: no cover
pass
super(OnHoverTooltipBase, self).__del__()
def _show_event(self, event=None):
"""event handler to display the tooltip"""
if self.hover_delay:
self.schedule()
else:
self.showtip()
def _hide_event(self, event=None):
"""event handler to hide the tooltip"""
self.hidetip()
def schedule(self):
"""schedule the future display of the tooltip"""
self.unschedule()
self._after_id = self.anchor_widget.after(self.hover_delay,
self.showtip)
def unschedule(self):
"""cancel the future display of the tooltip"""
after_id = self._after_id
self._after_id = None
if after_id:
self.anchor_widget.after_cancel(after_id)
def hidetip(self):
"""hide the tooltip"""
try:
self.unschedule()
except tk.TclError: # pragma: no cover
pass
super(OnHoverTooltipBase, self).hidetip()
def showcontents(self):
"""content display hook for sub-classes"""
# See ToolTip for an example
raise NotImplementedError
class Hovertip(OnHoverTooltipBase):
"""A tooltip that pops up when a mouse hovers over an anchor widget."""
def __init__(self, anchor_widget, text, hover_delay=1000):
"""Create a text tooltip with a mouse hover delay.
anchor_widget: the widget next to which the tooltip will be shown
hover_delay: time to delay before showing the tooltip, in milliseconds
Note that a widget will only be shown when showtip() is called,
e.g. after hovering over the anchor widget with the mouse for enough
time.
"""
super(Hovertip, self).__init__(anchor_widget, hover_delay=hover_delay)
self.text = text
def showcontents(self):
label = tk.Label(self.tipwindow, text=self.text, justify=tk.LEFT,
background="#ffffe0", relief=tk.SOLID, borderwidth=1)
label.pack()
Now some code illustrating the problem I'm having:
class PopupWindow:
def __init__(self, parent):
self.parent = parent
self.gui = tk.Toplevel(self.parent)
self.gui.geometry("100x30")
self.gui.wait_visibility()
self.ok_button = tk.Button(self.gui, text="OK", command=self.on_ok_button)
self.ok_button.pack()
Hovertip(self.ok_button, text="OK button", hover_delay=500)
def on_ok_button(self):
self.gui.destroy()
def show(self):
self.gui.grab_set()
# Hovertip(self.ok_button, text="OK button", hover_delay=500)
self.gui.wait_window()
return 0
class App(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
button = tk.Button(parent, text="Button -- no hover delay", command=self.button)
button.pack()
Hovertip(button, "This is tooltip\ntext for button.", hover_delay=0)
def button(self):
window = PopupWindow(self.parent)
window.show()
if __name__ == '__main__':
root = tk.Tk()
App(root)
root.mainloop()
You'll notice that the tooltip for the OK button in the modal window appears behind the window (I'm changing the geometry of the window for otherwise it would be so small that we wouldn't actually notice this).
Of course this becomes a problem in a real window with several widgets the tips for some of them will not be seen at all.
Apparently there are two ways around the problem: one is to delete the line self.gui.wait_visibility() in the __init__ of the PopupWindow class;
the other is to delete the self.gui.grab_set() in the show() method.
With any of these the window is no longer modal (if I get the meaning right: I mean I want the window to stay on top and prevent changes in the parent window while it exists).
The commented line in the show method was my tentative of working around it by defining the tooltip after the grab_set so that it might come on top, but it doesn't work either.
I suppose there must be an way of doing this properly using this class for tooltips.
How can I do it?
Thanks.
I found a solution.
Apparently, someone had the same problem, although with a different class for tooltips.
I refer to this question and answer.
The solution is to add the line tw.wm_attributes("-topmost", 1) somewhere in the showtip method of the TooltipBase class.
I did it as a last line, but I'm not sure it doesn't work some other place; I know it doesn't if immediately after tw.wm_overrideredirect(1).

How can I show/hide toolbar depending on mouse movements and mouse position inside window?

Hi Iam using Python and GTK+. In my GUI I have 2 toolbars I want show first toolbar only if user moves mouse than hide it again after few seconds as for second toolbar I want to show it when user is on particular x,y coordinates.How can I achieve it ?
EDIT:
Iam creating some kind of media player so I want toolbars to disapear while user is not using mouse in case of playerMenu toolbar or if user doesn't move it to specific location in case of ribbonBar toolbar .Iam using GTK+ here is my code for toolbars:
class Player(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self)
def build_UI(self):
container=Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
ribbonBar=Gtk.Toolbar()
playerMenu=Gtk.Toolbar()
def mouse_moved(self):
#TO-DO here I should check cordinates for example I want to see if mouse.y=window.height-50px and I would like to show ribbonaBar
#after that Gdk.threads_add_timeout(1000,4000,ribbonBar.hide)
#TO-DO here I show playerMenu toolbar if mouse is moved
# smt like playerMenu.show()
#after that I would call Gdk.threads_add_timeout(1000,4000,playerMenu.hide)
# to hide it again after 4 seconds
I should connect my window to some mouse event but I don't know the event name and how can I get mouse.x and mouse.y?
Why do you want to do this? Trying to use widgets that disappear when you're not moving the mouse is rather annoying, IMHO.
But anyway...
To toggle the visibility of a widget use the show() and hide() methods, or map() and unmap() if you don't want the other widgets in your window to move around. To handle timing, use gobject.timeout_add(), and you'll need to connect() your window to "motion_notify_event" and set the appropriate event masks: gtk.gdk.POINTER_MOTION_MASK and probably gtk.gdk.POINTER_MOTION_HINT_MASK. The Event object that your motion_notify callback receives will contain x,y mouse coordinates.
At least, that's how I'd do it in GTK2; I don't know GTK3.
If you want more specific help you need to post some code.
I see that you've posted some code, but it doesn't have a lot of detail... But I understand that GTK can be a bit overwhelming. I haven't used it much in the last 5 years, so I'm a bit rusty, but I just started getting into it again a couple of months ago and thought your question would give me some good practice. :)
I won't claim that the code below is the best way to do this, but it works. And hopefully someone who is a GTK expert will come along with some improvements.
This program builds a simple Toolbar with a few buttons. It puts the Toolbar into a Frame to make it look nicer, and it puts the Frame into an Eventbox so we can receive events for everything in the Frame, i.e., the Toolbar and its ToolItems. The Toolbar only appears when the mouse pointer isn't moving and disappears after a few seconds, unless the pointer is hovering over the Toolbar.
This code also shows you how to get and process mouse x,y coordinates.
#!/usr/bin/env python
''' A framed toolbar that disappears when the pointer isn't moving
or hovering in the toolbar.
A response to the question at
http://stackoverflow.com/questions/26272684/how-can-i-show-hide-toolbar-depending-on-mouse-movements-and-mouse-position-insi
Written by PM 2Ring 2014.10.09
'''
import pygtk
pygtk.require('2.0')
import gtk
import gobject
if gtk.pygtk_version < (2, 4, 0):
print 'pygtk 2.4 or better required, aborting.'
exit(1)
class ToolbarDemo(object):
def button_cb(self, widget, data=None):
#print "Button '%s' %s clicked" % (data, widget)
print "Button '%s' clicked" % data
return True
def show_toolbar(self, show):
if show:
#self.frame.show()
self.frame.map()
else:
#self.frame.hide()
self.frame.unmap()
def timeout_cb(self):
self.show_toolbar(self.in_toolbar)
if not self.in_toolbar:
self.timer = False
return self.in_toolbar
def start_timer(self, interval):
self.timer = True
#Timer will restart if callback returns True
gobject.timeout_add(interval, self.timeout_cb)
def motion_notify_cb(self, widget, event):
if not self.timer:
#print (event.x, event.y)
self.show_toolbar(True)
self.start_timer(self.time_interval)
return True
def eventbox_cb(self, widget, event):
in_toolbar = event.type == gtk.gdk.ENTER_NOTIFY
#print event, in_toolbar
self.in_toolbar = in_toolbar
#### self.show_toolbar(in_toolbar) does BAD things :)
if in_toolbar:
self.show_toolbar(True)
return True
def quit(self, widget): gtk.main_quit()
def __init__(self):
#Is pointer over the toolbar Event box?
self.in_toolbar = False
#Is pointer motion timer running?
self.timer = False
#Time in milliseconds after point stops before toolbar is hidden
self.time_interval = 3000
self.window = win = gtk.Window(gtk.WINDOW_TOPLEVEL)
width = gtk.gdk.screen_width() // 2
height = gtk.gdk.screen_height() // 5
win.set_size_request(width, height)
win.set_title("Magic Toolbar demo")
win.set_border_width(10)
win.connect("destroy", self.quit)
#self.motion_handler = win.connect("motion_notify_event", self.motion_notify_cb)
win.connect("motion_notify_event", self.motion_notify_cb)
win.add_events(gtk.gdk.POINTER_MOTION_MASK |
gtk.gdk.POINTER_MOTION_HINT_MASK)
box = gtk.VBox()
box.show()
win.add(box)
#An EventBox to capture events inside Frame,
# i.e., for the Toolbar and its child widgets.
ebox = gtk.EventBox()
ebox.show()
ebox.set_above_child(True)
ebox.connect("enter_notify_event", self.eventbox_cb)
ebox.connect("leave_notify_event", self.eventbox_cb)
box.pack_start(ebox, expand=False)
self.frame = frame = gtk.Frame()
frame.show()
ebox.add(frame)
toolbar = gtk.Toolbar()
#toolbar.set_border_width(5)
toolbar.show()
frame.add(toolbar)
def make_toolbutton(text):
button = gtk.ToolButton(None, label=text)
#button.set_expand(True)
button.connect('clicked', self.button_cb, text)
button.show()
return button
def make_toolsep():
sep = gtk.SeparatorToolItem()
sep.set_expand(True)
#sep.set_draw(False)
sep.show()
return sep
for i in xrange(5):
button = make_toolbutton('ToolButton%s' % (chr(65+i)))
toolbar.insert(button, -1)
#toolbar.insert(make_toolsep(), -1)
for i in xrange(1, 9, 2):
toolbar.insert(make_toolsep(), i)
button = gtk.Button('_Quit')
button.show()
box.pack_end(button, False)
button.connect("clicked", self.quit)
win.show()
frame.unmap()
def main():
ToolbarDemo()
gtk.main()
if __name__ == "__main__":
main()

Python; tkinter; Canvas objects and events

I have a class with some mouse events I made :
class graphic_object(object):
def mouse_click(self,event):
#do something
def mouse_move(self,event):
#do something
def mouse_unpressed(self,event):
#do something
Instances of this class aren't literally graphic objects on the screen, but they have their graphic representation, which is circle-shaped, and as I said, they listen to the mouse events. Both, graphic representation and event handling are managed by tkinter.Canvas object, which is their visual container.
When I make one istance of this class:
graphic1 = graphic_object(a,b,c,d) # init method takes coordinates of the circle as arguments; a,b,c,d - numbers
Everything works as it should, object responds on the mouse events in desired way. But when I make two instances:
graphic1 = graphic_object(a,b,c,d)
graphic2 = graphic_object(e,f,g,h)
only the last created object responds on the mouse events.
This is the condition where I check if the mouse is over the circle:
if d < self.radius:
where d is distance between mouse position, and the center of the circle, and radius is radius of the circle.
In the debugger I see that self.center is always the center of the last created object, so condition is always on
the second circle. So, how can I make that both objects respond to the mouse events?
Events handling:
C = Canvas()
C.bind("<Button-1>" ,self.mouse_click)
C.bind("<B1-Motion>",self.mouse_move)
C.bind("<ButtonRelease-1>",self.mouse_unpressed)
It appears that in your mouse binding you are relying on a pre-computed global variable (d). This is not how you should implement such bindings. The first thing you should do in the binding is get the current mouse coordinates, and then calculate d.
Your other choice is to put the binding on each canvas object using the tag_bind method of the canvas. See this question for an example: How do I attach event bindings to items on a canvas using Tkinter?
You wrote in a comment to this answer that you are only sometimes getting mouse clicks. There is not enough detail in your code to know what you're doing, but I can assure you that the canvas doesn't normally fail in such a manner.
I can't debug your code since you are only showing bits and pieces, but here's a working example that tries to illustrate the use of tag_bind. I took some liberties with your code. For example, I added a name parameter so I can print out which circle you clicked on. When I test this, every click seems to register on the proper circle.
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.canvas = tk.Canvas(self, width=400, height=400,
background="bisque")
self.canvas.pack(fill="both", expand=True)
graphic1 = GraphicObject(10,10,100,100, name="graphic1")
graphic2 = GraphicObject(110,110,200,200, name="graphic2")
graphic1.draw(self.canvas)
graphic2.draw(self.canvas)
class GraphicObject(object):
def __init__(self, x0,y0,x1,y1, name=None):
self.coords = (x0,y0,x1,y1)
self.name = name
def draw(self, canvas, outline="black", fill="white"):
item = canvas.create_oval(self.coords, outline=outline, fill=fill)
canvas.tag_bind(item, "<1>", self.mouse_click)
def mouse_click(self, event):
print "I got a mouse click (%s)" % self.name
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()

Run an infinite loop in the backgroung in Tkinter

I would like the code to run in the background and to update my GUI periodically. How can I accomplish this?
For example, suppose I want to execute something like this in the background of the GUI code you can see below:
x = 0
while True:
print(x)
x = x + 1
time.sleep(1)
This is the GUI code:
class GUIFramework(Frame):
def __init__(self,master=None):
Frame.__init__(self,master)
self.master.title("Volume Monitor")
self.grid(padx=10, pady=10,sticky=N+S+E+W)
self.CreateWidgets()
def CreateWidgets(self):
textOne = Entry(self, width=2)
textOne.grid(row=1, column=0)
listbox = Listbox(self,relief=SUNKEN)
listbox.grid(row=5,rowspan=2,column=0,columnspan=4,sticky=N+W+S+E,pady=5)
listbox.insert(END,"This is an alert message.")
if __name__ == "__main__":
guiFrame = GUIFramework()
guiFrame.mainloop()
It is a little unclear what your code at the top is supposed to do, however, if you just want to call a function every second (or every the amount of seconds you want), you can use the after method.
So, if you just want to do something with textOne, you'd probably do something like:
...
textOne = Entry(self, width=2)
textOne.x = 0
def increment_textOne():
textOne.x += 1
# register "increment_textOne" to be called every 1 sec
self.after(1000, increment_textOne)
You could make this function a method of your class (in this case I called it callback), and your code would look like this:
class Foo(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.x = 0
self.id = self.after(1000, self.callback)
def callback(self):
self.x += 1
print(self.x)
#You can cancel the call by doing "self.after_cancel(self.id)"
self.id = self.after(1000, self.callback)
gui = Foo()
gui.mainloop()
If you truly want to run a distinct infinite loop you have no choice but to use a separate thread, and communicate via a thread safe queue. However, except under fairly unusual circumstances you should never need to run an infinite loop. Afte all, you already have an infinite loop running: the event loop. So, when you say you want an infinite loop you are really asking how to do an infinite loop inside an infinite loop.
#mgilson has given a good example on how to do that using after, which you should consider trying before trying to use threads. Threading makes what you want possible, but it also makes your code considerably more complex.

Get rid of a label inside a thread?

so i have this code:
import thread
from Tkinter import *
import random
import time
Admin=Tk()
def moveit(number):
songas=Label(Admin,text=number,bg='red')
def ji():
plad=0.0
recount=0
times=0
while 1:
plad-=0.1
recount+=1
times+=1
time.sleep(0.5)
pls=0.0
pls+=plad
if recount==4:
pls=0
plad=0.0
recount=0
songas.place(relx=pls,rely=0.7)
thread.start_new_thread(ji,())
za=random.random()
button=Button(Admin,text='Press',command=lambda:moveit(str(za)))
button.place(relx=0.2)
Admin.mainloop()
And it starts to move to the left but if you press the 'press' button again it puts some more numbers on top of the old ones.
does any one know how to erase the old numbers to make it so there are only the knew ones?
Tkinter isn't thread safe -- you can't manipulate widgets in any thread except the main one or you'll get undefined results.
You don't need threads for this. Your code adds an infinite loop, but the application already has an infinite loop (the event loop) that you can take advantage of.
If you want to move some item create a function that does two things. First, it does whatever it is you want, such as move the item. Second, it uses the standard after method to call itself again in a short amount of time (for example, half a second or 500ms). This way you let your event loop drive the animation, you don't need threads, and your UI stays responsive.
Here's an example. I doubt it does exactly what you want because I'm not certain of exactly what you want.
import Tkinter as tk
import random
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
self._running = False
self._relx = None
tk.Tk.__init__(self, *args, **kwargs)
self.pack_propagate(False)
self.configure(width=400, height=400)
self.label = tk.Label(self, text="hello, world", background="red")
self.button = tk.Button(self, text="Start", command=self.toggle)
self.button.pack(side="top")
def toggle(self):
'''toggle animation on or off'''
self._running = not self._running
if self._running:
self.button.configure(text="Stop")
self.moveit()
else:
self.button.configure(text="Start")
def moveit(self):
'''Animate the label'''
if not self._running:
# animation has been stopped
# hide the label from view.
self.label.place_forget()
if self._running:
if not self.label.winfo_viewable():
# not visible; establish future locations
self._relx = [.5, .4, .3, .2, .1, 0]
relx = self._relx.pop(0)
self._relx.append(relx)
self.label.place(relx=relx, rely=0.7)
self.after(1000, self.moveit)
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
You must signal the old thread to exit somehow. It is probably easiest to perform with locks - you create a lock when creating a new thread and acquire it. And you release it when the thread is no longer needed. The thread then only needs to check in the main loop whether its lock is still locked - if it isn't it will remove the label and exit. Here the modified version of your code (replace "Remove label here" comment by suitable code):
import thread
from Tkinter import *
import random
import time
Admin=Tk()
lock = None
def moveit(number):
global lock
songas=Label(Admin,text=number,bg='red')
def ji(lock):
plad=0.0
recount=0
times=0
while 1:
plad-=0.1
recount+=1
times+=1
time.sleep(0.5)
pls=0.0
pls+=plad
if recount==4:
pls=0
plad=0.0
recount=0
songas.place(relx=pls,rely=0.7)
if not lock.locked():
# Remove label here
break
if lock:
# Signal old thread to exit
lock.release()
lock = thread.allocate_lock()
lock.acquire()
thread.start_new_thread(ji,(lock,))
za=random.random()
button=Button(Admin,text='Press',command=lambda:moveit(str(za)))
button.place(relx=0.2)
Admin.mainloop()

Categories