So when I run my code I get this error:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\sandw\AppData\Local\Programs\Python\Python313\Lib\tkinter\__init__.py", line 2074, in __call__
return self.func(*args)
~~~~~~~~~^^^^^^^
File "d:\BluCerulean\app.py", line 483, in calendar_window
CW(self, self.settings.get("user"), master=cal_win)
~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "d:\BluCerulean\calendar_editor.py", line 42, in __init__
self.cal = TKCal(left_frame, selectmode='day', date_pattern='yyyy-mm-dd', font=("Segoe UI", 14))
~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\sandw\AppData\Local\Programs\Python\Python313\Lib\site-packages\tkcalendar\calendar_.py", line 224, in __init__
ttk.Frame.__init__(self, master, class_=classname, cursor=curs, name=name)
~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\sandw\AppData\Local\Programs\Python\Python313\Lib\site-packages\ttkbootstrap\style.py", line 5209, in __init__
self.configure(style=ttkstyle)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
File "C:\Users\sandw\AppData\Local\Programs\Python\Python313\Lib\site-packages\tkcalendar\calendar_.py", line 1612, in configure
self[item] = value
~~~~^^^^^^
File "C:\Users\sandw\AppData\Local\Programs\Python\Python313\Lib\site-packages\tkcalendar\calendar_.py", line 517, in __setitem__
if key not in self._properties:
^^^^^^^^^^^^^^^^
AttributeError: 'Calendar' object has no attribute '_properties'
Is it because my tkcalendar cannot put a calendar in a ttkbootstrap app?
This is my calendar function of my app.py code (the code where I call the calendar file)
# ---------------- CALENDAR ----------------
def calendar_window(self):
# Ensure root is visible
if not self.root.winfo_viewable():
self.root.deiconify()
# Open the calendar in a Toplevel
cal_win = Toplevel(self.root)
cal_win.title("Blu Cerulean - Calendar")
cal_win.state("zoomed")
from calendar_editor import CalendarWindow as CW
# Pass the App instance, user, and the Toplevel as parent
CW(self, self.settings.get("user"), master=cal_win)
if __name__ == "__main__":
root = Tk()
app = App(root)
root.mainloop()
and this is my calendar_editor.py code (the build of the calendar window)
import os
import json
import tkinter as tk
from tkinter import Toplevel, simpledialog, messagebox, Frame, Label, Button, Listbox, Text, END
from tkcalendar import Calendar as TKCal
from tkinter import ttk # plain ttk
REMINDERS_FILENAME = "reminders.json"
class CalendarWindow:
def __init__(self, app, username, master=None):
self.app = app
self.username = username or "guest"
self.app_folder = getattr(app, 'app_folder', os.getcwd())
self.cal_folder = os.path.join(self.app_folder, 'calendar', self.username)
os.makedirs(self.cal_folder, exist_ok=True)
self.reminders_path = os.path.join(self.cal_folder, REMINDERS_FILENAME)
self.reminders = self._load_reminders()
# Use the passed parent if provided, else create new Toplevel
self.win = master if master is not None else Toplevel(app.root)
self.win.title("Blu Cerulean - Calendar")
self.win.state("zoomed") # maximize
# Main frames
main_frame = Frame(self.win) # plain Frame, not ttkbootstrap
main_frame.pack(fill="both", expand=True, padx=10, pady=10)
left_frame = Frame(main_frame)
left_frame.grid(row=0, column=0, sticky="nsew", padx=(0,10))
right_frame = Frame(main_frame)
right_frame.grid(row=0, column=1, sticky="nsew")
main_frame.columnconfigure(0, weight=1)
main_frame.columnconfigure(1, weight=1)
main_frame.rowconfigure(0, weight=1)
# Calendar (plain tkcalendar inside plain Frame)
self.cal = TKCal(left_frame, selectmode='day', date_pattern='yyyy-mm-dd', font=("Segoe UI", 14))
self.cal.pack(fill="both", expand=True)
self.cal.bind("<<CalendarSelected>>", lambda e: self.refresh_list())
# Listbox + description frame
list_desc_frame = Frame(right_frame)
list_desc_frame.pack(fill="both", expand=True)
# Listbox
list_frame = Frame(list_desc_frame)
list_frame.pack(side="top", fill="both", expand=True)
self.list_label = Label(list_frame, text="Reminders for selected day:")
self.list_label.pack(anchor="w")
self.listbox = Listbox(list_frame)
self.listbox.pack(fill="both", expand=True)
self.listbox.bind("<<ListboxSelect>>", lambda e: self.show_description())
# Description box
desc_frame = Frame(list_desc_frame)
desc_frame.pack(side="top", fill="both", expand=True, pady=(10,0))
desc_label = Label(desc_frame, text="Description:")
desc_label.pack(anchor="w")
self.desc_text = Text(desc_frame, height=6)
self.desc_text.pack(fill="both", expand=True)
# Buttons
btn_frame = Frame(right_frame)
btn_frame.pack(side="bottom", pady=10, fill="x")
Button(btn_frame, text="Add Reminder", command=self.add_reminder).pack(side="left", padx=5)
Button(btn_frame, text="Delete Selected", command=self.delete_reminder).pack(side="left", padx=5)
Button(btn_frame, text="Refresh", command=self.refresh_list).pack(side="left", padx=5)
Button(btn_frame, text="Add Description", command=self.add_description).pack(side="left", padx=5)
Button(btn_frame, text="Edit Description", command=self.edit_description).pack(side="left", padx=5)
Button(btn_frame, text="Remove Description", command=self.remove_description).pack(side="left", padx=5)
self.refresh_list()
# ---------------- Reminder management ----------------
def _load_reminders(self):
if os.path.exists(self.reminders_path):
try:
with open(self.reminders_path, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception:
return {}
return {}
def _save_reminders(self):
try:
os.makedirs(self.cal_folder, exist_ok=True)
with open(self.reminders_path, 'w', encoding='utf-8') as f:
json.dump(self.reminders, f, indent=2)
except Exception as e:
messagebox.showerror("Error", f"Failed to save reminders: {e}")
def refresh_list(self):
self.listbox.delete(0, END)
self.desc_text.delete("1.0", END)
date = self.cal.get_date()
day_reminders = self.reminders.get(date, [])
for rem in day_reminders:
self.listbox.insert(END, f"{rem.get('title', '')}: {rem.get('description','')}")
def add_reminder(self):
date = self.cal.get_date()
title = simpledialog.askstring("Add Reminder", f"Reminder title for {date}:")
if title:
description = self.desc_text.get("1.0", END).strip()
self.reminders.setdefault(date, [])
self.reminders[date].append({"title": title, "description": description})
self._save_reminders()
self.refresh_list()
self.desc_text.delete("1.0", END)
def delete_reminder(self):
selection = self.listbox.curselection()
if not selection: return
date = self.cal.get_date()
index = selection[0]
try:
self.reminders[date].pop(index)
if not self.reminders[date]: del self.reminders[date]
self._save_reminders()
self.refresh_list()
self.desc_text.delete("1.0", END)
except Exception as e:
messagebox.showerror("Error", f"Failed to delete reminder: {e}")
# ---------------- Description management ----------------
def show_description(self):
selection = self.listbox.curselection()
if not selection: return
date = self.cal.get_date()
index = selection[0]
description = self.reminders[date][index].get("description", "")
self.desc_text.delete("1.0", END)
self.desc_text.insert("1.0", description)
def add_description(self):
selection = self.listbox.curselection()
if not selection: messagebox.showinfo("Info", "Select a reminder first."); return
date = self.cal.get_date()
index = selection[0]
desc = self.desc_text.get("1.0", END).strip()
if not desc: messagebox.showinfo("Info", "Enter a description to add."); return
if self.reminders[date][index].get("description","").strip():
messagebox.showinfo("Info", "Description already exists. Use Edit Description instead."); return
self.reminders[date][index]["description"] = desc
self._save_reminders()
self.refresh_list()
self.listbox.selection_set(index)
def edit_description(self):
selection = self.listbox.curselection()
if not selection: messagebox.showinfo("Info", "Select a reminder first."); return
date = self.cal.get_date()
index = selection[0]
if not self.reminders[date][index].get("description","").strip():
messagebox.showerror("Error", "Description not found."); return
desc = self.desc_text.get("1.0", END).strip()
if not desc: messagebox.showinfo("Info", "Enter a description to update."); return
self.reminders[date][index]["description"] = desc
self._save_reminders()
self.refresh_list()
self.listbox.selection_set(index)
def remove_description(self):
selection = self.listbox.curselection()
if not selection: messagebox.showinfo("Info", "Select a reminder first."); return
date = self.cal.get_date()
index = selection[0]
if not self.reminders[date][index].get("description","").strip():
messagebox.showerror("Error", "Description not found."); return
self.reminders[date][index]["description"] = ""
self._save_reminders()
self.refresh_list()
self.listbox.selection_set(index)
# ---------------- Test Run ----------------
if __name__ == '__main__':
root = tk.Tk()
root.withdraw()
class DummyApp:
def __init__(self, root): self.root=root; self.app_folder=os.getcwd()
app = DummyApp(root)
CalendarWindow(app, "testuser")
root.mainloop()
I have tried putting my calendar window in a ttk frame, but that did not work either. I honestly do not know why this is happening, first time running into this error, asked my programming friends, they had no answers, and I even asked multiple AI, to no avail.
Problem is because ttkbootstrap
expects widget which can use styles and it tries to set some style in self._properties['styl']
, but tkcalendar
wasn't created to use styles and it doesn't have self._properties
and self._properties['styl']
and this raises error.
Minimal working code which raises the same error:
import tkinter as tk
import tkcalendar
import ttkbootstrap
root = tk.Tk()
# OK if not imported `ttkbootstrap`
# ERROR when imported `ttkbootstrap`
cal = tkcalendar.Calendar(root)
cal.pack()
root.mainloop()
Some workaround is to create own class which inherits from Calendar
and add missing _properties['style']
and later use this class in own code.
class MyCal(tkcalendar.Calendar):
_properties = {'style':None}
cal = MyCal(root)
Problems:
I'm not sure but later it may need to add other missing elements.
It changes style in some elements (but rest is the same) and it may not look correctly.
module last update had in 2019 so any mistakes you have to fix on your own.
On Linux it looks like this:
without tkbootstrap
:
with tkbootstrap
:
Full working code for tests:
import tkinter as tk
import tkcalendar
import ttkbootstrap
class MyCal(tkcalendar.Calendar):
_properties = {'style':None}
root = tk.Tk()
root.title('tkCalendar')
# OK if not imported `ttkbootstrap`
# ERROR when imported `ttkbootstrap`
#cal = tkcalendar.Calendar(root, locale='en_US')
#cal.pack()
# OK
cal = MyCal(root, locale='en_US')
cal.pack()
root.mainloop()
Repo: tkcalendar