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.Frame
class, adding a nested frame and a "vertical button" which toggles the nested frame.
Sketch: (Edit: The gray box should say Parent of contractible panel)
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"):
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()