I had a go with this because I thought it sounded interesting. It was a lot more work than anticipated, so you get a real freebee here
First let's explain some things, the base starting point is to write a class that inherits
Component, and not Control, that's pretty basic. Named it "
SaveProvider".
Then about the ErrorProvider component, a
key functionality of this component is that it dynamically adds a property to all controls in form, you add one provider to form and can set custom Error text for any control targeting this component. How is that done? The first thing I did was to use
.Net Reflector to read the code for the ErrorProvider control, it's always useful to see how others are doing stuff, especially if you don't have a clue where to start looking. The internal code is quite complex, but it was easy to find out that it decorates a
ProvideProperty attribute for the Error property. The help page explain that
IExtenderProvider interface is needed, it only has one member and the content I got from Reflector and the ErrorProvider class, basically it filters which controls to provide this property for. Then the help page explains the special way to write this property with the Get/Set methods, not very different from a regular property.
The component you asked about should provide three value options as I see it (NoSave, AlwaysSave, CanSave), which in basic code translates to an enumeration, I defined this as a
Mode enum, and defined the extender property as
Savable. These are the three values the developer chooses at design time. If Savable mode is CanSave then user can choose at runtime if it should be saved by clicking the checkbox icon, I added this later as a
UserSave property (Boolean).
Now getting deeper into things, since this component provides this property for any number of controls it naturally needs to manage the state for all these controls, a lookup
table is required. I used an internal Dictionary where each control instance is the key. Several values needs to be kept for each control so I defined an internal class ControlItem to keep these, and this is the value object in the Dictionary. Also this loosely based on the ErrorProvider code.
Using a ControlItem class was also derived from the fact that I needed to
paint the icons for each control the provider attached to, not an easy task that took some thinking. The internal code for ErrorProvider is very complex at this point, it basically uses the WM_Paint message and does all kinds of fancy stuff with unmanaged device contexts. I took a slightly different approch by using the Paint event of the control parent, since each control is wrapped in its own ControlItem class it is fairly simple to handle this even with each possibly having their own parent container, or the same, no need to worry about which event is already handled or not.
Much about the same control
events is used similar to ErrorProvider, (ParentChanged, VisibleChanged, LocationChanged, HandleCreated, HandleDestroyed) ie whenever it matters where to paint the icon, or matters which controls events to listen to. The parent controls Paint and MouseDown events handles the core functionality for this component.
That's pretty much it. Then a fair amount of time was spent
debugging, much can go wrong in designer where it suddenly crashes because your class is accessing objects that has ceased to exist, surprises are abound (and why was it still painting there!?...). More than a few times I had to go into form designer code and manually remove the component to continue development. IDisposable support was added for the ControlItem class to release handlers, and a trick was finally needed to make the component
Dispose when the form was (the special IContainer constructor).
Hopefully with the help of these descriptions and reading the code a few times this all will make sense, and the component be of use. Opposite of the ErrorProvider I added the icon in front of the control.
Attached is the component class and a very basic sample project.
As for
saving the states of the saveable controls, that's up to you, or the client code, I left a short code sample in the form load event that shows how to set the UserSave state for a control, which you'd do at startup when this state info is retrieved from your storage, and a sample loop through the controls to get the needed info about Savable mode and UserSave state at runtime.