Home > Software design >  How can I set the default container in a decorator class for tkinter.Frame?
How can I set the default container in a decorator class for tkinter.Frame?

Time:01-29

I would like to create a contractible panel in a GUI, using the Python package tkinter.

My idea is to create a decorator for the tkinter.Frameclass, adding a nested frame and a "vertical button" which toggles the nested frame.

Sketch: (Edit: The gray box should say Parent of contractible panel)

enter image description here

I got it to toggle just fine, using the nested frame's grid_remove to hide it and then move the button to the left column (otherwise occupied by the frame).

Now I want to be able to use it like any other tkinter.Frame, but let it target the nested frame. Almost acting like a proxy for the nested frame. For example, adding a tkinter.Label (the green Child component in the sketch) to the decorator should add the label to the nested frame component (light yellow tk.Frame in the sketch) not the decorator itself (strong yellow ContractiblePanel in the sketch).


Minimal example: (omitting the toggling stuff and any "formatting"):

(screenshot

CodePudding user response:

First of all it is kinda gross to use this code and it's very confusing. So I'm really not sure if you really want to take this route. However, it is possible to achieve it.

The basic idea is to have a wrapper and to pretend the wrapper is the actual object you can lie with __str__ and __repr__ about what the class really is. That is not what a proxy means.

class WrapperClass:

    def __init__(self, master=None, **kwargs):
        self._wrapped_frame = tk.Frame(master, **kwargs)
        self._panel = tk.Frame(self._wrapped_frame)
        self._toggle = tk.Button(self._wrapped_frame, text='<', command=self._toggle_panel)

        self._wrapped_frame.grid(row=0, column=0, sticky='nsw')
        self._panel.grid(row=0, column=0, sticky='nsw')
        self._toggle.grid(row=0, column=1, sticky='nsw')
        return None

    def _toggle_panel(self):
        print('toggle')

    def __str__(self):
        return self._panel._w
    __repr__ = __str__

You can do even more confusing things by delegate the lookup-chain to the _wrapped_frame inside the WrapperClass this enables you to call on the instance of WrapperFrame() methods like pack or every other method. It kinda works similar for inheritance with the difference that by referring to the object, you will point to different one.

I don't recommend using this code by the way.

import tkinter as tk

NONE = object()
#use an object here that there will no mistake

class WrapperClass:

    def __init__(self, master=None, **kwargs):
        self._wrapped_frame = tk.Frame(master, **kwargs)
        self._panel = tk.Frame(self._wrapped_frame)
        self._toggle = tk.Button(self._wrapped_frame, text='<', command=self._toggle_panel)

        self._wrapped_frame.grid(row=0, column=0, sticky='nsw')
        self._panel.grid(row=0, column=0, sticky='nsw')
        self._toggle.grid(row=0, column=1, sticky='nsw')
        return None

    def _toggle_panel(self):
        print('toggle')

    def __str__(self):
        return self._panel._w
    __repr__ = __str__

    def __getattr__(self, name):
        #when wrapper class has no attr name
        #delegate the lookup chain to self.frame
        inreturn = getattr(self._wrapped_frame, name, NONE)
        if inreturn is NONE:
            super().__getattribute__(name)
        return inreturn

root = tk.Tk()
wrapped_frame = WrapperClass(root, bg='red', width=200, height=200)
root.mainloop()
  • Related