Open DimaTepliakov opened 10 months ago
@DimaTepliakov, This is because the ttkbootstrap
tries to modify the color of the widgets placed in the main window. To efficiently change the color, ttkbootstrap recommends to use Window()
class to initiate new window, and not Tk()
.
See, normal button behavior in Tkinter:
And, Using ttkbootstrap button behavior in Tkinter:
Customtkinter
is built over tkinter
and performs hight DPI and scaling operations by its own for each widget. In the other hand ttkbootstrap uses ttk
(themed tkinter), and creates its own theme manager which is completely unsupported by customtkinter
.
To use this functionality, you need to create a toast box using widgets provided by customtkinter
which auto handles the CTkBaseClass
for scaling, appearance mode, and theming.
Code for a basic toast box using customtkinter
:
from typing import Literal
from customtkinter import (
CTk,
CTkToplevel,
CTkFrame,
CTkImage,
CTkButton,
CTkLabel,
CTkFont,
ThemeManager
)
class ToastNotification():
"""Toast notification functionality for `customtkinter` inspired by Windows' notifications and as an
alternative to the `ttkbootstrap.toast.ToastNotification`.
Methods:
- show() -> Shows the toast box
- hide() -> Hides the toast box
"""
def __init__(self,
master: CTk = None,
title: str = "Toast Notification Title",
message: str = "This is the message that the Toast box contains.",
duration: int | None = 1000, # If None, will be disappear on click
alert_sound: bool = False,
icon: CTkImage | str | None = None,
anchor: Literal["nw", "ne", "sw", "se"] = "se",
size: tuple[int, int] = (400, 150),
fg_color: str | tuple[str, str] | None = None,
):
# Saving indicating variables
self.master = master
self._anchor = anchor
self._size = size
self._fg_color = fg_color
self._duration = duration
self._alert = alert_sound
self._title = title
self._message = message
self._icon = icon # or ⚠
self._opened: bool = False
# Taking a color for toast box if None was provided
if not fg_color: self._fg_color = ThemeManager.theme["CTkFrame"]["fg_color"]
def show(self):
if self._opened: return
self.toplevel = CTkToplevel(self.master,
fg_color=self._fg_color, width=self._size[0], height=self._size[1])
self._opened = True
self.__fill_items()
self._applying_position()
self.toplevel.bind("<ButtonPress>", lambda _: self.hide())
if self._alert:
self.toplevel.bell()
if self._duration is not None:
self.master.after(self._duration, lambda: self.hide())
def __fill_items(self):
self.container = CTkFrame(self.toplevel, fg_color="transparent")
self.container.grid(padx=20, pady=20)
icon_label = CTkLabel(self.container, text="⚠" if not self._icon else self._icon if isinstance(self._icon, str) else "", image=self._icon if not isinstance(self._icon, str) else None, font=CTkFont(size=30, weight="bold"))
icon_label.grid(row=0, column=0, rowspan=2, sticky="w", padx=(0, 10))
title_label = CTkLabel(self.container, text=self._title, font=CTkFont(weight="bold"), wraplength=self._size[0]-20)
title_label.grid(row=0, column=1, sticky="w", padx=(0, 10))
message_label = CTkLabel(self.container, text=self._message, justify="left", wraplength=self._size[0]-20)
message_label.grid(row=1, column=1, padx=(0, 10))
def _applying_position(self):
self.toplevel.update_idletasks()
self.toplevel.wm_overrideredirect(True)
# Define position coordinates
positions = {
"nw": "+0+0",
"ne": f"+{self.toplevel.winfo_screenwidth()-self.toplevel.winfo_reqwidth()//4}+0",
"sw": f"+0+{self.toplevel.winfo_screenheight()-self.toplevel.winfo_reqheight()}",
"se": f"+{self.toplevel.winfo_screenwidth()-self.toplevel.winfo_reqwidth()//3}+{self.toplevel.winfo_screenheight()-self.toplevel.winfo_reqheight()//3}"
}
# Place window at the specified position
self.toplevel.geometry(positions.get(self._anchor, "+0+0"))
def hide(self):
self._opened = False
self.toplevel.destroy()
# Example use case
if __name__ == "__main__":
app = CTk()
app.geometry("500x350")
toast = ToastNotification(app, alert_sound=True)
button = CTkButton(app, text="Show toast", command=lambda: toast.show())
button.place(relx=0.5, anchor="center", rely=0.5)
app.mainloop()
Output
In the code:
CTkImage
for effective use case.Hope, it will be helpful for you.
@DimaTepliakov, This is because the
ttkbootstrap
tries to modify the color of the widgets placed in the main window. To efficiently change the color, ttkbootstrap recommends to useWindow()
class to initiate new window, and notTk()
. ...
Thank you very much! I observed the implementation of ToastNotification using ttkbootstrap.Window() in the example. I initially hoped to seamlessly integrate it into my customtkinter-based project with minimal adjustments.
I appreciate your effort in furnishing a comprehensive class that replicates ttkbootstrap.toast.ToastNotification within the customtkinter framework. Regrettably, upon pressing the button, the notification fails to appear. I will undertake debugging to identify the root cause of this issue.
@DimaTepliakov, This is because the
ttkbootstrap
tries to modify the color of the widgets placed in the main window. To efficiently change the color, ttkbootstrap recommends to useWindow()
class to initiate new window, and notTk()
. ...Thank you very much! I observed the implementation of ToastNotification using ttkbootstrap.Window() in the example. I initially hoped to seamlessly integrate it into my customtkinter-based project with minimal adjustments.
I appreciate your effort in furnishing a comprehensive class that replicates ttkbootstrap.toast.ToastNotification within the customtkinter framework. Regrettably, upon pressing the button, the notification fails to appear. I will undertake debugging to identify the root cause of this issue.
You are welcome. Is the provided class not working in your system? Please inform here further bugs if you are facing any.
Best regards.
@DimaTepliakov, This is because the
ttkbootstrap
tries to modify the color of the widgets placed in the main window. To efficiently change the color, ttkbootstrap recommends to useWindow()
class to initiate new window, and notTk()
. ...Thank you very much! I observed the implementation of ToastNotification using ttkbootstrap.Window() in the example. I initially hoped to seamlessly integrate it into my customtkinter-based project with minimal adjustments. I appreciate your effort in furnishing a comprehensive class that replicates ttkbootstrap.toast.ToastNotification within the customtkinter framework. Regrettably, upon pressing the button, the notification fails to appear. I will undertake debugging to identify the root cause of this issue.
You are welcome. Is the provided class not working in your system? Please inform here further bugs if you are facing any.
Best regards.
Ok, I have checked and it works, the only issue is with the _applying_position
function, I will have to fix the geometry because I see the full notification only if I set anchor="nw"
:
when I use anchor="ne":
"sw":
and with "se" I only hear the bell alert.
I've improved the _applying_position
function, ensuring that every anchor now functions correctly:
def _applying_position(self):
self.toplevel.update_idletasks()
self.toplevel.wm_overrideredirect(True)
screen_w = self.toplevel.winfo_screenwidth()
screen_h = self.toplevel.winfo_screenheight()
top_w = self.toplevel.winfo_reqwidth()
top_h = self.toplevel.winfo_reqheight()
padding_height = 40
# Define position coordinates
positions = {
"nw": f"+0+{padding_height}",
"ne": f"+{screen_w-top_w}+{padding_height}",
"sw": f"+0+{screen_h-top_h-padding_height}",
"se": f"+{screen_w-top_w}+{screen_h-top_h-padding_height}"
}
# # Place window at the specified position
self.toplevel.geometry(positions.get(self._anchor, "+0+0"))
Thanks alot.
@DimaTepliakov, Glad to hear that you have fixed the _applying_position
function and it worked for you. Appreciable! Thank you :)
Apologies for inconvenience, this class was made so quickly that's why there were bugs in the class. It also lacks some important options like transparency opacity, corner radius, padding etc.
Revised version with improved functionalities mentioned above:
Updated code:
from typing import Literal
from customtkinter import (
CTk,
CTkToplevel,
CTkFrame,
CTkImage,
CTkButton,
CTkLabel,
CTkFont,
ThemeManager
)
class ToastNotification():
"""Toast notification functionality for `customtkinter` inspired by Windows' notifications and as an
alternative to the `ttkbootstrap.toast.ToastNotification`.
Methods:
- show() -> Shows the toast box
- hide() -> Hides the toast box
"""
def __init__(self,
master: CTk = None,
title: str = "Toast Notification Title",
message: str = "This is the message that the Toast box contains.",
duration: int | None = 1000, # If None, will be disappear on click
alert_sound: bool = False,
icon: CTkImage | str | None = None,
size: tuple[int, int] = (350, 100),
anchor: Literal["w", "e", "n", "s", "nw", "ne", "se", "sw", "nsew"] = "se", # Also, supports reverse
padx: int = 20,
pady: int = 120,
opacity: float = 0.8,
corner_radius: float = None,
fg_color: str | tuple[str, str] | None = None
):
# Saving indicating variables
self._size = size
self._anchor = anchor
self.master = master
self._duration = duration
self._fg_color = fg_color
self._alert = alert_sound
# Saving co-operative variables
self._padx = padx
self._pady = pady
self._icon = icon
self._opened = False
self._title = title
self._message = message
# Getting the transparent color with radius
self._opacity = opacity
self._corner_radius = corner_radius
self._transparent_color = ThemeManager.theme["CTkToplevel"]["fg_color"]
def show(self):
if self._opened: return
else: self._opened = True
self.toplevel = CTkToplevel(self.master,
width=self._size[0], height=self._size[1])
self.__fill_items()
self._applying_position()
self.toplevel.bind("<ButtonPress>", lambda _: self.hide())
if self._alert:
self.toplevel.bell()
if self._duration is not None:
self.master.after(self._duration, lambda: self.hide())
def __fill_items(self):
self.container = CTkFrame(self.toplevel, fg_color=self._fg_color, corner_radius=self._corner_radius)
self.container.grid(sticky="nsew")
icon_label = CTkLabel(self.container, text="⚠" if not self._icon else self._icon if isinstance(self._icon, str) else "",
image=self._icon if not isinstance(self._icon, str) else None, font=CTkFont(size=30, weight="bold"))
icon_label.grid(row=0, column=0, rowspan=2, sticky="w", padx=10)
title_label = CTkLabel(self.container, text=self._title, font=CTkFont(weight="bold"), wraplength=self._size[0]/2)
title_label.grid(row=0, column=1, sticky="w", padx=(0, 20), pady=(10, 0))
message_label = CTkLabel(self.container, text=self._message, justify="left", wraplength=self._size[0]/1.25)
message_label.grid(row=1, column=1, sticky="w", padx=(0, 20), pady=(0, 10))
def _applying_position(self):
self.toplevel.update_idletasks()
self.toplevel.wm_overrideredirect(True)
self.toplevel.wm_attributes("-transparentcolor",
self._transparent_color[0 if self.toplevel._get_appearance_mode() == "light" else 1])
self.toplevel.wm_attributes("-alpha", self._opacity)
self._place_at_anchor()
def _place_at_anchor(self):
scaling = self.toplevel._get_window_scaling()
screen_width = self.toplevel.winfo_screenwidth()*scaling
screen_height = self.toplevel.winfo_screenheight()*scaling
# Getting box width and height
box_width = self.toplevel.winfo_reqwidth()
box_height = self.toplevel.winfo_reqheight()
self.toplevel.wm_overrideredirect(True)
anchors: dict = {
"se": (int(screen_width-box_width -self._padx), int(screen_height-box_height -self._pady)),
"sw": (int(self._padx), int(screen_height-box_height -self._pady)),
"ne": (int(screen_width-box_width -self._padx), int(self._pady/4)),
"nw": (int(self._padx), int(self._pady/4)),
"w": (int(self._padx), int(screen_height/2 - box_height) + self._pady),
"e": (int(screen_width-box_width -self._padx), int(screen_height/2 - box_height) + self._pady),
"n": (int(screen_width/2 - box_width/2), int(self._pady)),
"s": (int(screen_width/2 - box_width/2), int(screen_height - box_height -self._pady)),
"nsew": (int(screen_width/2 - box_width/2 + self._padx), int(screen_height/2 - box_height/2 + self._pady)),
}
# Getting the anchor handling the reverse case. NSEW if invalid anchor provided.
x, y = anchors.get(self._anchor, anchors.get(self._anchor[::-1], anchors["nsew"]))
self.toplevel.geometry(f"+{x}+{y}")
def hide(self):
self._opened = False
self.toplevel.destroy()
# Sample use case
from customtkinter import CTkRadioButton, StringVar
if __name__ == "__main__":
app = CTk()
app.geometry("900x600")
app.grid_rowconfigure((0, 1), weight=1)
anchors = ['w', 'e', 'n', 's', 'nw', 'ne', 'se', 'sw', 'nsew']
app.grid_columnconfigure(tuple(range(len(anchors))), weight=1)
toast = ToastNotification(app, fg_color="green", anchor="nsew")
button = CTkButton(app, text="Show toast", command=toast.show)
button.grid(row=0, column=0, columnspan=len(anchors))
# Placing options
radio_value = StringVar(value="nsew")
for col, anchor in enumerate(anchors):
CTkRadioButton(app, variable=radio_value, value=anchor, text=anchor,
command=lambda: setattr(toast, "_anchor", radio_value.get())).grid(row=1, column=col)
app.mainloop()
In the code:
['w', 'e', 'n', 's', 'nsew']
added.corner_radius
added.padx
and pady
introduced for flexible positioning.Important: Please do let me know whether it is still making issues on anchor positions or not.
Thank you for your co-operation. Hope, it will be helpful for you. Happy customtkinter :)
@dipeshSam Nice! with this updated version there is not issues with the anchor position, all of them works perfectly!
@dipeshSam Nice! with this updated version there is not issues with the anchor position, all of them works perfectly!
@DimaTepliakov, Glad to hear this! You are most welcome. Happy customtkinter :)
Description: I'm encountering a problem when using ttkbootstrap toast notifications in my CustomTkinter application. The toast notifications work as expected, but they inadvertently modify the overall theme of my application. I've provided a simplified code snippet below:
Issue: When the toast notification is displayed, it unexpectedly changes the theme of my entire application
Screenshot:
Additional Information: From my attempts to fix this, I have noticed that explicitly changing the appearance mode after showing the toast notification restores the correct theme evaluation settings. For example:
I believe there might be a more logical solution than this workaround.
I'm seeking assistance in understanding and resolving this theme modification issue caused by ttkbootstrap toast notifications in CustomTkinter. Any insights or suggestions to maintain a consistent theme would be highly valuable.
Thank you for your assistance.