Force set tkinter window to always have focus - python

Is there a way to tell Tkinter that I want some widget to always remain focused? I've created a minimal example that can be run to show my issue , here's an example window with small toplevel windows also overlayed:
Now if I click the upper title tk, the main window comes into focus and suddenly the small windows are behind the main window
I want to treat these smaller windows as if they are always in focus until the user specifically closes them. Of course this is a minimal example that is an idea behind a small subsection of my large application , is there any easy setting I can use for the toplevel that guarantees it will always remain in focus regardless of other windows? Here's the actual code that can be run to replicate this:
from Tkinter import *
class PropertyDialog(Toplevel):
def __init__(self, root, string):
Toplevel.__init__(self)
self.wm_overrideredirect(1)
self.root = root
self.\
geometry('+%d+%d' %
(root.winfo_pointerx(),
root.winfo_pointery()))
try:
self.tk.call('::Tk::unsupported::MacWindowStyle',
'style', self._w,
'help', 'noActivates')
except TclError:
pass
window_frame = Frame(self)
window_frame.pack(side=TOP, fill=BOTH, expand=True)
exit_frame = Frame(window_frame, background='#ffffe0')
exit_frame.pack(side=TOP, fill=X, expand=True)
button = Button(exit_frame, text='x', width=3, command=self.free,
background='#ffffe0', highlightthickness=0, relief=FLAT)
button.pack(side=RIGHT)
text_frame = Frame(window_frame)
text_frame.pack(side=TOP, fill=BOTH, expand=True)
label = Label(text_frame, text=string, justify=LEFT,
background='#ffffe0',
font=('tahoma', '8', 'normal'))
label.pack(ipadx=1)
def free(self):
self.destroy() # first we destroy this one
for val,widget in enumerate(dialogs): # go through the dialogs list
if widget is self: # when we find this widget
dialogs.pop(val) # pop it out
break # and stop searching
if dialogs: # if there are any dialogs left:
for widget in dialogs: # go through each widget
widget.lift(aboveThis=self.root) # and lift it above the root
def bind():
"""
toggle property window creation mode
"""
root.bind('<ButtonPress-1>', create)
def create(event):
"""
Create actual window upon mouse click
"""
dialogs.append(PropertyDialog(root, 'help me'))
root = Tk()
dialogs = []
root.geometry('%dx%d' % (300,400))
Button(root, text='create', command=bind).pack()
root.mainloop()

change this:
if dialogs: # if there are any dialogs left:
for widget in dialogs: # go through each widget
widget.lift(aboveThis=self.root) # and lift it above the root
to this:
if dialogs: # if there are any dialogs left:
for widget in dialogs: # go through each widget
widget.lift() # and lift it above the root
the widgets will stay above the main window.
EDIT:
Sorry that only half worked... the widows will stay above sometimes with that code
:-X
It was keeping the widgets on top until you closed one of them.... this code does keep the widgets on top
it uses the self.attributes("-topmost", True) when you spawn the windows.
Sorry again.
from Tkinter import *
class PropertyDialog(Toplevel):
def __init__(self, root, string):
Toplevel.__init__(self)
self.wm_overrideredirect(1)
self.root = root
self.\
geometry('+%d+%d' %
(root.winfo_pointerx(),
root.winfo_pointery()))
try:
self.tk.call('::Tk::unsupported::MacWindowStyle',
'style', self._w,
'help', 'noActivates')
except TclError:
pass
window_frame = Frame(self)
window_frame.pack(side=TOP, fill=BOTH, expand=True)
exit_frame = Frame(window_frame, background='#ffffe0')
exit_frame.pack(side=TOP, fill=X, expand=True)
button = Button(exit_frame, text='x', width=3, command=self.free,
background='#ffffe0', highlightthickness=0, relief=FLAT)
button.pack(side=RIGHT)
text_frame = Frame(window_frame)
text_frame.pack(side=TOP, fill=BOTH, expand=True)
label = Label(text_frame, text=string, justify=LEFT,
background='#ffffe0',
font=('tahoma', '8', 'normal'))
label.pack(ipadx=1)
self.attributes("-topmost", True)
def free(self):
self.destroy() # first we destroy this one
def bind():
"""
toggle property window creation mode
"""
root.bind('<ButtonPress-1>', create)
def create(event):
"""
Create actual window upon mouse click
"""
dialogs.append(PropertyDialog(root, 'help me'))
root = Tk()
dialogs = []
root.geometry('%dx%d' % (300,400))
Button(root, text='create', command=bind).pack()
root.mainloop()

I recommend moving away from Toplevel widgets, since those are separate windows and you're suppressing their window-like behavior. This version makes PropertyDialog inherit from Frame instead of Toplevel, using the place() geometry manager. When you click the main window, it first checks whether the widget clicked was the main window or a popup window to prevent a new popup from appearing when you close an existing one. Changed areas are marked with #CHANGED#.
from Tkinter import *
class PropertyDialog(Frame): #CHANGED#
def __init__(self, root, string, event): #CHANGED#
Frame.__init__(self) #CHANGED#
self.root = root
try:
self.tk.call('::Tk::unsupported::MacWindowStyle',
'style', self._w,
'help', 'noActivates')
except TclError:
pass
exit_frame = Frame(self, background='#ffffe0') #CHANGED#
exit_frame.pack(side=TOP, fill=X, expand=True)
button = Button(exit_frame, text='x', width=3, command=self.free,
background='#ffffe0', highlightthickness=0, relief=FLAT)
button.pack(side=RIGHT)
text_frame = Frame(self) #CHANGED#
text_frame.pack(side=TOP, fill=BOTH, expand=True)
label = Label(text_frame, text=string, justify=LEFT,
background='#ffffe0',
font=('tahoma', '8', 'normal'))
label.pack(ipadx=1)
self.place(x=event.x, y=event.y, anchor=NW) #CHANGED#
def free(self):
self.destroy()
# other things you want to do - if there's nothing else,
# just bind the close button to self.destroy
def bind():
"""
toggle property window creation mode
"""
root.bind('<ButtonPress-1>', create)
def create(event):
"""
Create actual window upon mouse click
"""
if event.widget is root: #CHANGED#
dialogs.append(PropertyDialog(root, 'help me', event))
root = Tk()
dialogs = []
root.geometry('%dx%d' % (300,400))
Button(root, text='create', command=bind).pack()
root.mainloop()

Related

Copying from Messagebox with Tkinter

I've written a password generator with Tkinter and have set a messagebox that pops-up when the data for the website is already in the database.
Is there an option that allows me to copy the text from the pop-up? Because these passwords are really long. Or do I need to go into the file where it is saved to copy it?
messagebox.showinfo(title=website, message=f" Email: {email}\nPassword: {password}")
Try something like this:
import tkinter as tk
class Popup:
def __init__(self, title:str="Popup", message:str="", master=None):
if master is None:
# If the caller didn't give us a master, use the default one instead
master = tk._get_default_root()
# Create a toplevel widget
self.root = tk.Toplevel(master)
# A min size so the window doesn't start to look too bad
self.root.minsize(200, 40)
# Stop the user from resizing the window
self.root.resizable(False, False)
# If the user presses the `X` in the titlebar of the window call
# self.destroy()
self.root.protocol("WM_DELETE_WINDOW", self.destroy)
# Set the title of the popup window
self.root.title(title)
# Calculate the needed width/height
width = max(map(len, message.split("\n")))
height = message.count("\n") + 1
# Create the text widget
self.text = tk.Text(self.root, bg="#f0f0ed", height=height,
width=width, highlightthickness=0, bd=0,
selectbackground="orange")
# Add the text to the widget
self.text.insert("end", message)
# Make sure the user can't edit the message
self.text.config(state="disabled")
self.text.pack()
# Create the "Ok" button
self.button = tk.Button(self.root, text="Ok", command=self.destroy)
self.button.pack()
# Please note that you can add an icon/image here. I don't want to
# download an image right now.
...
# Make sure the user isn't able to spawn new popups while this is
# still alive
self.root.grab_set()
# Stop code execution in the function that called us
self.root.mainloop()
def destroy(self) -> None:
# Stop the `.mainloop()` that's inside this class
self.root.quit()
# Destroy the window
self.root.destroy()
def show_popup():
print("Starting popup")
Popup(title="title", message="Message on 1 line", master=root)
print("Ended popup")
print("Starting popup")
Popup(title="title", message="Message\nOn 2 lines", master=root)
print("Ended popup")
root = tk.Tk()
root.geometry("300x300")
button = tk.Button(root, text="Click me", command=show_popup)
button.pack()
root.mainloop()
It's just a simple class that behaves a lot like messagebox.showinfo. You can add an icon if you want. Please note that some of the functionality is missing but it should work with your code.
For more info on the functions that I used please read the docs. Here are the unofficial ones.

Tkinter bind callback to activating a window

I'm writing a Tkinter application in Python 3 and I've created a custom Title Bar (the bar at the top of every Windows application with the application name, close button etc.). The way I achieved this was to create an invisible window (we'll call this window test) that controls the main application window's behavior (which we'll call Main App). Below is a piece of test code that illustrates this:
from tkinter import Tk, Toplevel
from tkinter.ttk import Button, Label, Frame, Style
class NewRoot(Tk):
def __init__(self):
Tk.__init__(self)
self.attributes('-alpha', 1.0) # This is normally set to 0.0
class MyMain(Toplevel):
def __init__(self, master):
Toplevel.__init__(self, master)
self.overrideredirect(1)
self.geometry('750x650+400+600')
self.style = Style()
self.style.configure('TTitleBar.Label',
width=8,
relief='flat',
foreground='red',
background='red',
anchor='center',
borderwidth=-1)
self.style.configure('TMainWindow.Label',
width=8,
relief='flat',
background='blue',
anchor='center',
borderwidth=-1)
self.tk_setPalette(background='green')
self.x_win = None
self.y_win = None
self.start_x = None
self.start_y = None
# make a frame for the title bar
title_bar = Frame(self, style='TTitleBar.Label')
title_bar.grid(row=0, column=0, sticky='wn')
label = Label(title_bar, text='Main App', style='TMainWindow.Label')
label.grid(row=0, column=0, padx=(4, 2), pady=(4, 0), sticky='nw')
minimize_button = Button(title_bar, text='MIN', command=self.minimize_window,
style='TMainWindow.Label', takefocus=False)
minimize_button.grid(row=0, column=2, padx=(563.5, 0), sticky='nw')
maximise_button = Button(title_bar, text='MAX', command=self.maximize_window,
style='TMainWindow.Label', takefocus=False)
maximise_button.grid(row=0, column=3, pady=(1.4, 0), sticky='nw')
close_button = Button(title_bar, text='CLOSE', command=self.close_window,
style='TMainWindow.Label', takefocus=False)
close_button.grid(row=0, column=4, sticky='nw')
window = Frame(self)
window.grid(row=1, column=0, sticky='ne')
# bind title bar motion to the move window function
title_bar.bind('<B1-Motion>', self.move_window)
title_bar.bind('<Button-1>', self.get_pos)
self.master.bind("<Map>", self.on_root_deiconify)
self.master.bind("<Unmap>", self.on_root_iconify)
self.mainloop()
def minimize_window(self):
self.master.iconify()
def maximize_window(self):
pass
def close_window(self):
self.master.destroy()
def on_root_iconify(self, event):
# print('unmap')
self.withdraw()
def on_root_deiconify(self, event):
# print('map')
self.deiconify()
def get_pos(self, event):
self.x_win = self.winfo_x()
self.y_win = self.winfo_y()
self.start_x = event.x_root
self.start_y = event.y_root
self.y_win = self.y_win - self.start_y
self.x_win = self.x_win - self.start_x
def move_window(self, event):
# print('+{0}+{1}'.format(event.x_root, event.y_root))
self.geometry('+{0}+{1}'.format(event.x_root + self.x_win, event.y_root + self.y_win))
self.start_x = event.x_root
self.start_y = event.y_root
if __name__ == '__main__':
root = NewRoot()
root.title('test')
app = MyMain(root)
In the code above, whenever the test window is minimized, the Main App window is also minimized, which works as intended.
The problem is that whenever the test window is made active, the Main App window doesn't become active also. For example, if another app covers Main App but test is not minimized, I need to click on the test icon in the Windows Task Bar three times for it to appear.
I was wondering if there is a way to fix this using something like:
self.master.bind(<some_command>, self.some_other_command)
However, I can't find a comprehensive list of bind commands anywhere.
Is this a good way of going about this, or is there something else I should be doing?
Also, I noticed that using self.overrideredirect(1) causes the shadows made by the windows to disappear, which causes overlapping windows in my application to 'merge together', since the background colors are the same. Is there a way to add the shadows back?
Thank you in advance.
I found a solution to this for anyone else with a similar problem. You can create a 'dummy' button in the invisible window that will become active when the window is in focus. You then have that call a function that places the main application window in focus.
class NewRoot(Tk):
def __init__(self):
Tk.__init__(self)
self.attributes('-alpha', 0.0)
entry = Button()
entry.pack()
entry.focus_set()
entry.pack_forget()
Then add this to __init__ in the MyMain class:
self.master.bind('<FocusIn>', self.on_root_deiconify)

Python-Tkinter Place button on left of frame

How do I place the QUIT button in below code to the extreme right of the Frame?
I tried several things like:
padx
and
self.pack(side="top", anchor="e")
but after trying some 15 times both buttons are coming close to each other. Maybe Some help from anyone would be really appreciated. I need one button on extreme right and other on extreme left
import tkinter as tk
from tkinter.ttk import *
class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.pack()
self.createWidgets()
self.master.title("Log Parser")
def createWidgets(self):
self.Run_Main = tk.Button(self)
self.Run_Main["text"] = "Browse.."
# self.Run_Main["fg"] = "blue"
self.Run_Main["command"] = self.Sayhello
self.Run_Main.pack(side='left')
self.Label = tk.Label(self)
self.Label["text"] = 'Processing...'
self.progressbar = Progressbar(mode="indeterminate", maximum=20)
self.QUIT = tk.Button(self)
self.QUIT["text"] = "Quit!"
self.QUIT["command"] = self.quit
self.QUIT.pack(anchor='e')
self.pack(side="top", anchor="w")
def Sayhello(self):
print("Hello")
# scroll text inside application frame
class scrollTxtArea:
def __init__(self, root):
frame = tk.Frame(root)
frame.pack()
self.textPad(frame)
return
def textPad(self, frame):
# add a frame and put a text area into it
textPad = tk.Frame(frame)
self.text = tk.Text(textPad, height=18, width=60)
self.text.config()
# add a vertical scroll bar to the text area
scroll = tk.Scrollbar(textPad)
self.text.configure(yscrollcommand=scroll.set,background="black", foreground="green")
# pack everything
self.text.pack(side=tk.LEFT, pady=2)
scroll.pack(side=tk.RIGHT, fill=tk.Y)
textPad.pack(side=tk.TOP)
return
root = tk.Tk()
root.resizable(width=False, height=False)
root.option_add('*font', ('verdana', 9, 'bold'))
app = Application(master=root)
scrollFrame = scrollTxtArea(root)
app.mainloop()
You have several problems here.
First, you're using the wrong geometry manager. The pack geometry manager, as the name implies, packs the widgets as close together as possible. That's not what you want. The grid geometry manager lets you put the widgets into a table-like layout with rows and columns. If you put the Browse button into the first column and the Quit button into the last column, you'll be a step closer.
Second, your Application window contains three child widgets and you're only putting two of them into a geometry manager. How that is going to mess you up I don't even want to think about. So I put the label into column 1, the Quit button into column 2, and the Browse button into column 0. The Quit button I gave a "sticky" value of "e" so it will be attached to the east (right) side of its allocated space.
Third, all the geometry managers try to compact the widgets as much as possible unless you specifically tell it to do otherwise. I told the grid manager to expand column 2 so that the extra space gets assigned to the cell that holds the Quit button.
Fourth, you need to tell the pack manager to expand the top widget so that it spans the entire window. The directive for that is fill="x".
Fifth, you have a redundant call to the pack manager at the end of your createWidgets function.
import tkinter as tk
from tkinter.ttk import *
class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.pack(fill="x")
self.createWidgets()
self.master.title("Log Parser")
def createWidgets(self):
self.Run_Main = tk.Button(self)
self.Run_Main["text"] = "Browse.."
# self.Run_Main["fg"] = "blue"
self.Run_Main["command"] = self.Sayhello
self.Label = tk.Label(self)
self.Label["text"] = 'Processing...'
self.progressbar = Progressbar(mode="indeterminate", maximum=20)
self.QUIT = tk.Button(self)
self.QUIT["text"] = "Quit!"
self.QUIT["command"] = self.quit
self.Label.grid(row=0, column=1)
self.Run_Main.grid(row=0, column=0, sticky="w")
self.QUIT.grid(row=0, column=2, sticky="e")
self.columnconfigure(2, weight=1)
def Sayhello(self):
print("Hello")
# scroll text inside application frame
class scrollTxtArea:
def __init__(self, root):
frame = tk.Frame(root)
frame.pack()
self.textPad(frame)
return
def textPad(self, frame):
# add a frame and put a text area into it
textPad = tk.Frame(frame)
self.text = tk.Text(textPad, height=18, width=60)
self.text.config()
# add a vertical scroll bar to the text area
scroll = tk.Scrollbar(textPad)
self.text.configure(yscrollcommand=scroll.set,background="black", foreground="green")
# pack everything
self.text.pack(side=tk.LEFT, pady=2)
scroll.pack(side=tk.RIGHT, fill=tk.Y)
textPad.pack(side=tk.TOP)
return
root = tk.Tk()
root.resizable(width=False, height=False)
root.option_add('*font', ('verdana', 9, 'bold'))
app = Application(master=root)
scrollFrame = scrollTxtArea(root)
app.mainloop()
These link, link helped. The other option would be to use tkinter's grid manager, it will be more intuitive and keep you more organized in the future.
import tkinter as tk
from tkinter.ttk import *
class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.pack()
self.createWidgets()
self.master.title("Log Parser")
def createWidgets(self):
self.Run_Main = tk.Button(self)
self.Run_Main["text"] = "Browse.."
# self.Run_Main["fg"] = "blue"
self.Run_Main["command"] = self.Sayhello
self.Run_Main.pack(side='left')
self.Label = tk.Label(self)
self.Label["text"] = 'Processing...'
self.Label.pack(side='left')
self.progressbar = Progressbar(mode="indeterminate", maximum=20)
self.QUIT = tk.Button(self)
self.QUIT["text"] = "Quit!"
self.QUIT["command"] = self.quit
self.QUIT.pack(side='right')
self.pack(side="top", fill=tk.BOTH) # changes here
def Sayhello(self):
print("Hello")
# scroll text inside application frame
class scrollTxtArea:
def __init__(self, root):
frame = tk.Frame(root)
frame.pack()
self.textPad(frame)
return
def textPad(self, frame):
# add a frame and put a text area into it
textPad = tk.Frame(frame)
self.text = tk.Text(textPad, height=18, width=60)
self.text.config()
# add a vertical scroll bar to the text area
scroll = tk.Scrollbar(textPad)
self.text.configure(yscrollcommand=scroll.set,background="black", foreground="green")
# pack everything
self.text.pack(side=tk.LEFT, pady=2)
scroll.pack(side=tk.RIGHT, fill=tk.Y)
textPad.pack(side=tk.TOP)
return
root = tk.Tk()
root.resizable(width=False, height=False)
root.option_add('*font', ('verdana', 9, 'bold'))
app = Application(master=root)
scrollFrame = scrollTxtArea(root)
app.mainloop()
There are two simple fixes you can make in order to get the behavior you want.
First, you need to pack Application so that it fills the window:
class Application(...):
def __init__(...):
...
self.pack(fill="x")
Next, simply pack the quick button on the right side of the window:
self.QUIT.pack(side="right", anchor='e')
Even though the above is all you need to do in this specific example, there are additional things you can do to make your job much easier.
I would recommend creating a frame specifically for the buttons. You can pack it at the top. Then, put the buttons inside this frame, and pack them either on the left or right. You'll get the same results, but you'll find it easier to add additional buttons later.
I also find that it makes the code much easier to read, write, maintain, and visualize when you separate widget creation from widget layout.
class Application(...):
...
def createWidgets(self):
toolbar = tk.Frame(self)
toolbar.pack(side="top", fill="x")
self.Run_Main = tk.Button(toolbar)
self.Label = tk.Label(toolbar)
self.QUIT = tk.Button(toolbar)
...
self.Run_Main.pack(side="left")
self.Label.pack(side="left", fill="x")
self.QUIT.pack(side="right")
...

tkinter: scrollbar autohide without window resize

Using the folowing sample code I wrote I am having issues with some behavior.
I want to add/remove the scrollbar as needed. But when I do it shifts all other elements in the window as the window resizes. This is just a sample to demonstrate the issue, you will see the window resize when the scrollbar is added and removed. In the real application there are more widgets on the window.
Am I trying to do this the right way or if not how can I resolve the issue? I also plan to have a second widget with scrollbars as well in another separate frame.
from tkinter import *
from tkinter import ttk
class TopFrame(ttk.Frame):
def __init__(self, parent, col=0, row=0):
ttk.Frame.__init__(self, parent)
self.innerframe = ttk.Frame(parent)
self.list_scroll = ttk.Scrollbar(self.innerframe)
self.list_scroll.grid(column=1, row=0, sticky=NS)
self.list_scroll.grid_remove()
self.list = Listbox(self.innerframe, width=64, height=8,
yscrollcommand=self.list_scroll.set)
self.list_scroll.config(command=self.list.yview)
self.list.grid(column=0, row=0, sticky=NSEW)
self.innerframe.grid(column=col, row=row)
self.addbtn = ttk.Button(parent, text='add item',
command=self.additem)
self.addbtn.grid(column=col, row=row+1, padx=10, pady=2)
self.delbtn = ttk.Button(parent, text='del item',
command=self.delitem)
self.delbtn.grid(column=col, row=row+2, padx=10, pady=2)
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
def additem(self):
count = str(len(self.list.get(0, END)))
self.list.insert(END, 'demo' + count)
if len(self.list.get(0, END)) > 8:
self.list_scroll.grid()
def delitem(self):
self.list.delete(END)
if len(self.list.get(0, END)) <= 8:
self.list_scroll.grid_remove()
class MasterFrame(Tk):
def __init__(self):
Tk.__init__(self)
topframe = TopFrame(self)
if __name__ == '__main__':
MasterFrame().mainloop()
Once the window has been displayed for the first time you can get the window size, and then use that to call the geometry method on the root window. When you set the size of the window with the geometry command it will stop resizing based on changes to its internal widgets.
The simplest thing is to write a function to do that, and schedule it to run with after_idle, which should fire after the window is first displayed.

How to put widgets inside a ttk.Button

Is there a proper way to nest widgets inside of a ttk.Button? It supports specifying a label (a str) and image (a PhotoImage) which I assume is implemented using children widgets.
Here's an example where I'm adding a left-aligned and a right-aligned label to a button.
import tkinter as tk
import tkinter.ttk as ttk
root = tk.Tk()
normal_button = ttk.Button(root, text="Normal Button")
normal_button.pack(fill=tk.X)
custom_button = ttk.Button(root)
custom_button.pack(fill=tk.X)
left_label = ttk.Label(custom_button, text="Left")
left_label.pack(side=tk.LEFT, padx=16, pady=4)
right_label = ttk.Label(custom_button, text="Right")
right_label.pack(side=tk.RIGHT, padx=16, pady=4)
root.mainloop()
This sort of works, but there are some quirks:
When hovering over the button, the button's background is highlighted but the nested labels keep their unhighlighted backgrounds.
If I click within either nested label, the button will press down, but will not become unpressed.
When the button is pressed, the nested labels will not shift giving the illusion of a button being pressed.
Is there a proper way to pack widgets inside of a button?
As I said in a comment you can create your own widget.
Here is a simple example with tk.Frame and tk.Label (ttk.Label needs more work with ttk.Style).
I bind events <Enter> and <Leave> to change frame and labels backgrounds.
For more widgets you could keep them in a list and use a for loop to change the background.
import tkinter as tk
import tkinter.ttk as ttk
class MyButton(tk.Frame):
def __init__(self, master, bg_hover='red', bg_normal=None, **options):
tk.Frame.__init__(self, master, **options)
self.bg_normal = bg_normal
self.bg_hover = bg_hover
# use default color if bg_normal is `None`
if not self.bg_normal:
self.bg_normal = self['bg']
# add first label
self.left_label = tk.Label(self, text="Left")
self.left_label.pack(side=tk.LEFT, padx=16, pady=4)
# add second label
self.right_label = tk.Label(self, text="Right")
self.right_label.pack(side=tk.RIGHT, padx=16, pady=4)
# bind events
self.bind('<Enter>', self.on_enter)
self.bind('<Leave>', self.on_leave)
def on_enter(self, event=None):
# change all backgrounds on mouse enter
self['bg'] = self.bg_hover
self.left_label['bg'] = self.bg_hover
self.right_label['bg'] = self.bg_hover
def on_leave(self, event=None):
# change all backgrounds on mouse leave
self['bg'] = self.bg_normal
self.left_label['bg'] = self.bg_normal
self.right_label['bg'] = self.bg_normal
root = tk.Tk()
normal_button = ttk.Button(root, text="Normal Button")
normal_button.pack(fill=tk.X)
my_button = MyButton(root)
my_button.pack()
root.mainloop()
There is no proper way to pack widgets inside a button. Buttons aren't designed for that feature. As you've seen, you can indeed use pack or grid to put widgets inside of buttons. However, you'll have to add custom bindings to make it appear as if it's all one button widget.

Categories