Open chillb0nes opened 12 months ago
I have the same question.
According to wx.agw.persist.FindHandler
, it seems that we can define _persistentHandler
.
However, unlike HANDLERS
, pObject
is not passed as an argument of _persistentHandler
, so it is unable to create an AbstractHandler
subclass instance.
https://github.com/wxWidgets/Phoenix/blob/5622abb73deaa26dc2f6dc4cd8b4b2050396b49a/wx/lib/agw/persist/persist_handlers.py#L2573-L2585
How should I implement _persistentHandler
?
I tried
wx.agw.persist.HANDLERS.insert(0, ("myhandler", (mycontrol,)))
but cannot create an instance of the handler.
That is because it uses eval()
and local namespace is different.
Here is a monkeypatch
import wx.lib.agw.persist.persistencemanager
OLD_FIND_HANDLER = wx.lib.agw.persist.persistencemanager.FindHandler
USER_HANDLERS = []
def FindHandler(pObject):
window = pObject.GetWindow()
klass = window.__class__
for handler, subclasses in USER_HANDLERS:
for subclass in subclasses:
if issubclass(klass, subclass):
return handler(pObject)
return OLD_FIND_HANDLER(pObject)
def register_persistent_handler(handler, subclasses):
if not isinstance(subclasses, (list, tuple)):
subclasses = [subclasses]
USER_HANDLERS.append((handler, subclasses))
wx.lib.agw.persist.persistencemanager.FindHandler = FindHandler
I've made a small runnable app that hopefully helps you in making your own persistance handler.
run the app, enter some text in the text control. when you hit enter, the text will be displayed right beside the text control. When you exit the app, the text that is in the text control will be saved and on next start will be restored to the text control (but not to the static text control).
import os
import wx
from wx.lib.agw.persist import AbstractHandler
import wx.lib.agw.persist as persist
from wx.lib.agw import aui
PERSISTANCE_DATA = os.path.join(os.path.split(__file__)[0], f'{os.path.basename(__file__)}.ini')
class MyCustomCtrl(wx.Panel):
def __init__(self, *args, **kwds):
super().__init__(*args, **kwds)
self.SetName('MyCustomCtrl')
sizer = wx.BoxSizer(wx.HORIZONTAL)
self.txt_ctrl = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_PROCESS_ENTER)
self.label = wx.StaticText(self, wx.ID_ANY, "Startup Text")
sizer.Add(self.txt_ctrl, 0, wx.EXPAND | wx.ALL, 10)
sizer.Add(self.label, 0, wx.EXPAND | wx.ALL, 10)
self.SetSizer(sizer)
self.txt_ctrl.Bind(wx.EVT_TEXT_ENTER, self.on_text_enter)
def on_text_enter(self, event):
self.label.SetLabel(event.GetString())
class MyCustomCtrlHandler(AbstractHandler):
""" Persistance Handler for MyCustomCtrl. """
def Save(self):
my_ctrl: MyCustomCtrl = self._window
txt = my_ctrl.txt_ctrl.GetValue()
self._pObject.SaveValue('value_of_text_ctrl', txt)
return True
def Restore(self):
my_ctrl: MyCustomCtrl = self._window
txt = self._pObject.RestoreValue('value_of_text_ctrl')
if not txt:
return False
my_ctrl.txt_ctrl.SetValue(txt)
return True
def GetKind(self):
return 'MyCustomCtrl'
class MyFrame(wx.Frame):
def __init__(self, parent, title):
super().__init__(parent, -1, title, name='persist_demo_frame')
self.aui_mgr = aui_mgr = aui.AuiManager()
aui_mgr.SetManagedWindow(self)
self.persist_mgr = persist_mgr = persist.PersistenceManager.Get()
persist_mgr.SetManagerStyle(persist.PM_DEFAULT_STYLE)
persist_mgr.SetPersistenceFile(PERSISTANCE_DATA)
panel = wx.Panel(self)
self.custom = MyCustomCtrl(panel, wx.ID_ANY)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.custom, 0, wx.ALL, 10)
panel.SetSizer(sizer)
panel.Layout()
sizer = wx.BoxSizer()
sizer.Add(panel, 1, wx.EXPAND)
self.SetSizer(sizer)
self.Fit()
self.CenterOnScreen(wx.BOTH)
self.persist_mgr.RegisterAndRestore(self)
self.persist_mgr.Register(self.custom, MyCustomCtrlHandler)
self.persist_mgr.Restore(self.custom)
self.Bind(wx.EVT_CLOSE, self.on_close)
def on_close(self, event):
self.persist_mgr.SaveAndUnregister(self.custom)
self.persist_mgr.SaveAndUnregister(self)
event.Skip()
class MyApp(wx.App):
def OnInit(self):
frame = MyFrame(None, "Simple wxPython App")
self.SetTopWindow(frame)
frame.Show(True)
return True
app = MyApp()
app.MainLoop()
@topic2k Thank you. It is very helpful. My goal was to override the default handler for ComboBox(it does not save edited value), so some additional code was needed, but it was much nicer.
def persistent_manager_register_all(manager, window):
# register all child controls without Restore() call
if window.GetName() not in persist.BAD_DEFAULT_NAMES and persist.HasCtrlHandler(window):
manager.Register(window)
for child in window.GetChildren():
persistent_manager_register_all(manager, child)
def persistent_manager_restore_all(manager):
# restore all registered controls
for name, obj in list(manager._persistentObjects.items()): # NOTE: private attribute
manager.Restore(obj.GetWindow())
def persistent_manager_register(manager, window, handler):
# override
manager.Unregister(window)
manager.Register(window, handler)
then
editable_comboxes = [
self.cbo_divergence,
self.cbo_convergence,
self.cbo_zoed_resolution,
self.cbo_edge_dilation,
self.cbo_fps,
self.cbo_crf,
]
self.persistence_manager = persist.PersistenceManager.Get()
self.persistence_manager.SetManagerStyle(persist.PM_DEFAULT_STYLE)
self.persistence_manager.SetPersistenceFile(CONFIG_PATH)
persistent_manager_register_all(self.persistence_manager, self)
for control in editable_comboxes:
persistent_manager_register(self.persistence_manager, control, EditableComboBoxPersistentHandler)
persistent_manager_restore_all(self.persistence_manager)
Hello! I want to clarify the question in the issue title. From https://docs.wxpython.org/wx.lib.agw.persist.html#defining-custom-persistent-windows:
However, just implementing an AbstractHandler subclass is not enough, it is also must be registered in some way. I could not find any automatic handler discovery mechanism in the code, and my custom handler is never called. What is the right way to make custom widget persistent?