pythontkinterattributeerrortkcalendarttkbootstrap

Is my tkcalendar clashing with ttkbootstrap?


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.


Solution

  • 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:

    On Linux it looks like this:

    without tkbootstrap :

    enter image description here

    with tkbootstrap:

    enter image description here


    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