Home
Reading
Searching
Subscribe
Sponsors
Statistics
Posting
Contact
Spam
Lists
Links
About
Hosting
Filtering
Features Download
Marketing
Archives
FAQ
Blog
 
Gmane
From: Vadim Zeitlin <vadim-dz6zkcJxDG0gsBAKwltoeQ <at> public.gmane.org>
Subject: RFD: persistent controls
Newsgroups: gmane.comp.lib.wxwidgets.devel
Date: Thursday 13th November 2008 12:53:28 UTC (over 8 years ago)
Hello,

 We discussed this many times in the past but this time I'd finally like to
actually do something about it and add some support for persistent controls
to wxWidgets. First, however, I'd like to understand what exactly do we
need so let me describe what we're trying to do and ask for your opinions
about how to do it best.

 So what is this about? "Persistent controls" are simply windows which
automatically save their state when they're destroyed and restore it when
they're created the next time. Here are some (realistic) examples together
with the implications one can draw from each of them:

E1. wxFrame can save its position and size as well as its state (maximized/
    minimized/normal). This example shows that restoring is not always
    trivial as the frame should verify that the restored geometry doesn't
    move it completely off screen as can -- and often does -- happen with
    naive implementation if a display was removed since the last program
    execution.

E2. Various controls can save their selection/state: e.g. wxNotebook can
    open itself on the same page as was used the last time. A wxCheckBox in
    some dialog can save its previous value and so on. This example shows
    that saving shouldn't always be automatic: for the controls inside a
    dialog we should arguably only save their state if the dialog wasn't
    cancelled.

E3. A wxTreeCtrl can store the expanded/collapsed state of its branches.
    This is almost indispensable as soon as there are more than a few items
    in the tree as opening/closing them every time the program is
    relaunched is too inconvenient. This example shows that stored values
    can be relatively complex.

E4. Layout ("perspective") of wxAuiManager should be saved/restored too.
    This example is important because wxAuiManager is not a wxWindow so it
    shows that we shouldn't assume that we always work with windows (unless
    we want to make this a special case).

Notice that I don't cover the so called "persistent message boxes" here
(message boxes with "Don't ask me again" checkbox) as this is something
rather different because the saved value of the control determines whether
the dialog is shown at all while in all the examples above the saved state
is simply restored.


 Before speaking about implementation I'd also like to list a few
constraints on it/requirements which I can see (please add more to this
list):

R1. It should be possible to extend the mechanism used for the standard
    wx classes such as those listed above to custom classes. E.g. if I have
    my own wxBogoCtrl I should be able to make it persistent by saving and
    restoring the value of bogosity that it contains as easily as possible.

R2. It should avoid introducing unnecessary link-time dependencies. E.g.
    even though the persistence code may have support for wxPropertyGrid,
    using persistent wxFrame (by far the most common case probably)
    shouldn't require linking with propgrid library.

R3. It should play nice with XRC, i.e. it should be possible to specify
    that a control should be persistent in XRC easily. E.g. just by using
     if we don't need any other information and reuse the name
    for persistence purpose or  otherwise.


 Now let's finally turn to the interesting question: how to implement this?
I'm naturally thinking about saving/restoring the persistent controls state
to/from wxConfig. This might not be totally general but I think it should
cover the needs of a vast majority of wx users and it's always possible to
derive a custom class deriving from wxConfigBase if you really need to
store your frame geometry in a relational database. There is however one
questions I'm not sure about:

    Q1: Should we always use the global wxConfig or allow specifying a
        different wxConfig object to use for persistent controls?

Of course, using the global config object should be the most common case
(all the persistent controls state would be saved under a special key
reserved for this, say wxWidgets/Persistence, so it wouldn't interfere with
the rest of application data stored there). But I can imagine that you
might want to use wxFileConfig for the application options because, for
example, you want to facilitate sharing it between Windows and Linux but
still use wxRegConfig for storing the persistent controls data because this
is something inherent to the local machine anyhow. OTOH I think it should
be enough to allow specifying config object for all persistent controls,
i.e. there is no need for anything more fine-grained. What do you think?


 The format of the data stored in wxConfig is not really important, it can
be either a simple key=value (e.g. MyNotebook=3 in the Notebook subsection
of the above mentioned wxWidgets/Persistence key) or a subgroup (e.g.
Frames/MyFrame with values such as Width=1000 or Maximized=True). But we do
need a unique (at least among the objects of this type) name for each
persistent window ("MyNotebook" or "MyFrame" in the above examples). So the
second question is:

    Q2: Can we reuse the current wxWindow::GetName() for this or do we need
        a separate "persistent name" attribute?

The advantage of reusing the name are obvious: it's simple and all classes
already have it. The problems with it that I see are:

- Although it seems unlikely, it could be already used for something else
  by people (I don't believe any wx port, possibly except for the moribund
  wxMotif, uses it for anything)

- It's not clear that this name has anything to do with persistence. In
  particular we still need to explicitly indicate that the window should
  be persistent if we reuse it, while we could consider the presence of a
  non empty "persistent name" as the indicator that it should be persistent
  on its own.

- wxAuiManager doesn't have it -- not being a wxWindow. So we'd either need
  to add name to it explicitly or it would need to use the name of the
  managed window/frame (which might not be such a bad idea).

Personally I'm still undecided about this one, do you see any arguments
for/against it other than the ones listed above?



 Finally the real question is, of course, how is this going to work at API
level. I see several approaches:

I1. The original persistent controls, implemented in Mahogany sources, were
    a set of wxPFoo ("P" for "persistent") classes with each of them
    deriving from wxFoo and providing a special ctor with the "name"
    parameter in front of all the parameters of the base class, see

    http://mahogany.svn.sf.net/viewvc/mahogany/trunk/M/include/wx/persctrl.h?view=markup

    I mention this implementation only for completeness because I don't
    really think this is the right thing to do because:

        - This doesn't play nice with XRC at all as you need to subclass
          all the controls.
        - Duplicating all the ctors is not great.
        - This is too intrusive: existing code will need to be modified
          to e.g. inherit MyFrame from wxPFrame and not wxFrame and so on.

    However this approach does provide some nice flexibility which risks to
    be difficult to realize using the other approaches: for example, it
    defines wxPTextEntry class which inherits from wxComboBox and not
    wxTextCtrl. But maybe this isn't necessary any longer now that we have
    support for auto completion in wxTextCtrl itself.

    Using these classes is trivial: you just need to create the object of
    wxPFoo type and then you can use it just as wxFoo (in fact you may
    forget that it's a wxPFoo immediately after creating it).


I2. When I first realized the shortcomings of the above approach, my next
    idea was to implement persistence by providing a special event handler
    which could be pushed on the control to make it persistent, e.g.

        wxCheckBox *box = new wxCheckBox(...);
        box->PushEventHandler(new wxPersistentCheckBox(box, "MyBox"));

    The way it works (or rather should work, as I didn't test it, so please
    point out any glaring problems you see with this implementation) is
    that the state is restored either in wxPersistentFoo ctor (this is why
    it needs the first argument) or in the first EVT_SIZE handler call: the
    latter is necessary to support persistent notebooks or tree controls,
    as we can't restore their selection/branches state before the
    pages/items are added to them and this happens after their creation and
    it may be impractical or impossible (for controls loaded from XRC) to
    move PushEventHandler() call after adding all items.

    In any case, it is saved when it catches the window destruction event.
    This supposes that we can still get the control state from
    EVT_WINDOW_DESTROY handler, I hope this is true in all ports, does
    anybody know if this is not the case?

    The advantages of this approach are that it doesn't require much
    changes to the existing code and satisfies R1 nicely (you can always
    define your own wxPersistentBogosity and we'd have helper base class
    for these event handlers to make implementing them as easy as
    possible). It also trivially satisfies R2: if you don't use
    wxPersistentPropGrid, you don't need to link with propgrid neither.

    It also works with XRC but unfortunately all existing handlers would
    need to be modified to support persistence as this can't be handled at
    the common code level because there is no way to find the right event
    handler to create without introducing unwanted link dependencies.

    But it does have a couple of problems too:

        - It is somewhat error-prone as nothing prevents you from pushing a
          wrong event handler on the control.

        - It works with windows only, i.e. not wxAuiManager.

        - In general, the event handlers stack mechanism in wxWindow is
          fragile as it's too easy to delete a wrong handler by mistake.
          But this is probably an argument in favour of trying to fix this
          somehow rather than not using this mechanism at all.


I3. The next step in my thinking was to try to avoid pushing event handlers
    and, as we don't want to use different class (I1), this means using
    Connect(). So the next idea is to have a global "persistence manager"
    object with which any windows which need to save their state could be
    registered with. Registering would entail connecting to the window
    destruction event and saving the state when the manager receives it.

    Using this would be simple enough too:

            wxCheckBox *box = new wxCheckBox(...);
            wxThePersistenceManager.Add(box, "MyBoxPersistentName");

    This is as non-intrusive as I2 but it's not really clear if this can be
    implemented to satisfy requirements R1 and R2 above. In fact I don't
    see how to do it, does anybody?


 Some other ideas which I considered but which don't seem to be worse
pursuing:

I4. Add persistence support to wxWindow itself. I.e. have virtual functions
    for saving/restoring state as well as a wxConfig pointer inside every
    window. This doesn't seem right however as most of the windows are not
    persistent and it seems wrong to put this functionality at this level
    anyhow. The only variant in which this could be done would be if we
    decided that only persistent frames (example E1) were worth supporting
    but not any other cases.

I5. Have a wrapper object adding persistence to an existing window. This
    would probably need to be a template class to allow retrieving the
    object of the correct type from it. The details are a bit hazy but I
    think the requirements R1 and R2 would be easier to satisfy in this
    approach. I'm less sure about R3 (XRC-friendliness) though.

    Using it would look like this:

            class MyDialog : public wxDialog {
                ....
                wxPersistent m_persCbox;
            };

            MyDialog::MyDialog(wxWindow *parent) {
                wxCheckBox *box = new wxCheckBox(this, ...);
                m_persCbox.Init(box, "MyBoxPersistentName");
            }

    This is trickier than before because you need to ensure that the
    wxPersistent object is destroyed just before its associated window is.
    This is automatic in the example above but could be more difficult to
    ensure in other cases and so seems to lose to the other approaches from
    the most important ease of use point of view.



 To summarize, it seems that the most promising approach is to define the
event handlers adding persistence to the controls. I don't especially like
this approach but I don't see anything better -- and this is why I'd really
welcome any ideas.

 Also, some problems remain unresolved with all or almost all (some of them
don't appear with I1) ideas above:

P1. It would be really better, especially for the frames, to be able to
    restore the state before creating the control, e.g. create the frame
    with the correct geometry instead of creating it with default size and
    then moving it in place. This is not as bad as it might be because the
    frame is initially hidden and so the user wouldn't see it but it still
    results in unnecessary layout being done and would be better avoided.

P2. None of the approaches except I1 work with non-windows as they rely on
    being able to catch window destruction event to save state so they
    don't work with wxAuiManager. The only way to solve this that I see is
    to save wxAuiManager state at the managed window level.

P3. EVT_SIZE hack mentioned above is rather ugly. I don't see how to avoid
    it but maybe we could at least make it more palatable by adding a
    special EVT_WINDOW_REALIZED event which would be supposed to be sent
    exactly once when the window is shown for the first time? I think it
    could be useful in contexts other than persistent state restore too.

P4. None of the approaches above addresses the issue of example E2: how to
    avoid saving the state if the dialog is cancelled? I think we could
    have some EVT_CANCEL_DIALOG which would be sent to all dialog children
    if it's being cancelled by user. Again, I think this could be useful in
    other contexts.

There are probably other problems which I didn't notice, please let me know
if you see anything not mentioned above, it would be nice to know about
them before starting to implement this.


 I know it was a long message and thanks for reading until here but I'd
really like to have a nice way to support this persistence of windows and I
hope to see replies with ideas/suggestions/critics which will help us to
find it.

 Thanks in advance!
VZ
 
CD: 4ms