israel-dryer / ttkbootstrap

A supercharged theme extension for tkinter that enables on-demand modern flat style themes inspired by Bootstrap.
MIT License
1.9k stars 381 forks source link

ttkbootstrap overriding methods should call original method #252

Open alejandroautalan opened 2 years ago

alejandroautalan commented 2 years ago

Desktop (please complete the following information):

OS: Xubuntu 20.04 ttkbootstrap==1.7.4

Describe the bug

Hello. I'm trying to make my projects compatible with ttkbootstrap.

I have custom widgets that use the same "tkinter api style" for configuring widgets. I mean I override the methods "configure", "cget", etc, to add custom options. The current implementation of ttkbootstrap breaks all those custom widgets.

To Reproduce

This is an example of a custom widget:

placeholder.py:

import tkinter as tk
import tkinter.ttk as ttk
import ttkbootstrap

class EntryWithPlaceholder(ttk.Entry):
    def __init__(self, master=None, widget=None, **kw):
        self.placeholder = kw.pop('placeholder', '')
        super().__init__(master, widget, **kw)

        self.bind("<FocusIn>", self.foc_in)
        self.bind("<FocusOut>", self.foc_out)

        self.__put_placeholder()

    def __put_placeholder(self):
        if not self.get():
            self.insert(0, self.placeholder)

    def foc_in(self, *args):
        if self.get() == self.placeholder:
            self.delete('0', 'end')

    def foc_out(self, *args):
        self.__put_placeholder()

    def configure(self, cnf=None, **kw):
        args = tk._cnfmerge((cnf, kw))
        key = 'placeholder'
        if cnf == key:
            return (key, self.cget(key))
        if key in args:
            self.placeholder = args[key]
            self.__put_placeholder()
            kw.pop(key, None)
        else:
            return super().configure(cnf, **kw)

    config = configure

    def cget(self, key):
        if key == 'placeholder':
            return self.placeholder
        return super().cget(key)

    __getitem__ = cget

if __name__ == "__main__":
    root = tk.Tk()
    username = EntryWithPlaceholder(root)
    username.configure(bootstyle='primary')
    password = EntryWithPlaceholder(root)
    password.configure(style='MyPasswordEntry.TEntry')

    username.pack()
    password.pack()

    def on_click():
        password.configure(placeholder='--PASSWORD--')
        username['placeholder'] = '--USERNAME--'

    btn = ttk.Button(root, text='Test', takefocus=True, command=on_click)
    btn.pack()
    root.mainloop()

If the program is run and the button clicked the following error is shown:

fades  -d ttkbootstrap placeholderentry.py 
*** fades ***  2022-04-13 01:40:13,956  INFO     Hi! This is fades 9.0, automatically managing your dependencies
*** fades ***  2022-04-13 01:40:13,955  INFO     The latest version of 'ttkbootstrap' is 1.7.4 and will use it.
Exception in Tkinter callback
Traceback (most recent call last):
  File "/usr/lib/python3.8/tkinter/__init__.py", line 1892, in __call__
    return self.func(*args)
  File "placeholderentry.py", line 61, in on_click
    username['placeholder'] = '--USERNAME--'
  File "/home/alejandro/.local/share/fades/b4df8e60-2d20-423f-8ef4-4bdae9831a2a/lib/python3.8/site-packages/ttkbootstrap/style.py", line 5002, in __setitem
    return _configure(self, **{key: val})
  File "/home/alejandro/.local/share/fades/b4df8e60-2d20-423f-8ef4-4bdae9831a2a/lib/python3.8/site-packages/ttkbootstrap/style.py", line 4911, in configure
    func(self, **kwargs)
  File "/home/alejandro/.local/share/fades/b4df8e60-2d20-423f-8ef4-4bdae9831a2a/lib/python3.8/site-packages/ttkbootstrap/style.py", line 4911, in configure
    func(self, **kwargs)
  File "/usr/lib/python3.8/tkinter/__init__.py", line 1646, in configure
    return self._configure('configure', cnf, kw)
  File "/usr/lib/python3.8/tkinter/__init__.py", line 1636, in _configure
    self.tk.call(_flatten((self._w, cmd)) + self._options(cnf))
_tkinter.TclError: unknown option "-placeholder"

Expected behavior

ttkbootstrap overriding methods should call original methods that override.

For each overriden function (configure, cget, __getitem__, __setitem__), ttkboostrap should process its own options ('bootstyle', 'style') and for any other option, call the original function.

Screenshots

No response

Additional context

No response

israel-dryer commented 2 years ago

You can fix this by also overriding the setter.

def __setitem__(self, key, val):
    return self.configure(self, **{key: val})    

I may not need these artifacts anymore and can comment them out. I went through a lot of iterations on the backend. But, I'll need to do some testing first. https://github.com/israel-dryer/ttkbootstrap/blob/2a298e6a3d80f499da142d978ed298eb349c739e/src/ttkbootstrap/style.py#L5000-L5011