1

Hi i'm a new programmer and thanks to the help of a guy from another one of my questions. I managed to preserve TextBox data when closing and reopening the app via this method:

public Form1()
{
    InitializeComponent();

    InitializeSavedValues();
    textBox1.TextChanged += textBox1_TextChanged;
}

private void InitializeSavedValues()
{
    textBox1.Text = (string)Properties.Settings.Default["TextBoxValue"];
}

private void textBox1_TextChanged(object sender, EventArgs e)
{
    Properties.Settings.Default["TextBoxValue"] = ((TextBox)sender).Text;
    Properties.Settings.Default.Save();
}

Would there be a way for me to use something similar with a RichTextBox when I close and reopen the form?

IVSoftware
  • 5,732
  • 2
  • 12
  • 23
pip
  • 23
  • 7
  • Hi again! Before moving on to RTF please take a critical look at the code we wrote for getting you quick-started with settings last time. Do you notice how inefficient it is? (I hoped you would.) It makes a disk write every time you press a key! Can you come up with a couple good ways to save reliably but way less frequently? We'll need that and more for RTF. – IVSoftware May 25 '20 at 07:24
  • Alrighty i'll take a longer look at it. I guess i am jumping ahead a little too fast – pip May 25 '20 at 17:11

1 Answers1

1

This is a good follow-up question to your SO post about saving text box between launches. I'm upvoting it, because it touches so many key points (and it's ok if you don't learn all of them at one time). TextBox often contains such a small amount of data that it's easy to save almost anywhere. We could improve the above code by saving only when the [Enter] key is pressed OR when the TextBox loses focus.

RichTextBox control is different. The document it displays might be quite large. For example, images (which are already big) are stored as text streams (which are even bigger). Generally it's not practical to use Properties.Settings to store it. As far as "when to save" we can't rely on the Enter key because that just inserts a newline character into the multiline control.

Good news: For all the controls you asked about, the basic flow is the same. When we know "what we want to do" but not "how we're going to do it" we can make a To-Do list that has a keyword of interface.

interface IPersistCommon // Things we need our custom control to do.
{
    SaveType SaveType { get; set; } // Possible ways to save
    void Save();                    // Save in the manner selected by SaveType
    void Load();                    // Load in the manner selected by SaveType
}

The SaveType is an enumeration we made up ourselves. It describes possible ways to store data for different controls we design. We have to think about capacity, speed, and portability across platforms like WinOS, Android, and iOS. Here are a few possibilities:

enum SaveType
{
    AppProperties,      // Like the textbox code shown above
    WindowsRegisty,     // A traditional method, but Windows Only
    File,               // For example, an RTF file in Local AppData (cross-platform)
    FileDataStore,      // Mobile cross-platform
    FileDataStoreJSON,  // Serialize the object's content AND SETTINGS ('enabled' etc.)
    SQLite              // Mobile platforms also available
}

The rest is almost too easy! Take a control that has most of the functionality we want (in this case RichTextBox) and use inheritance to make a custom class that adds the additional functionality that we declared in our interface. This literally forces us to implement everything our To-Do list demands (otherwise it won't even build).

class PersistRichTextBox    // Our own class...
    : RichTextBox           // ...that inherits the regular one
    , IPersistCommon        // ... and MUST implement SaveType, Save() and Load()
{ }

Implement SaveType with Browsable attribute.

[Browsable(true)]
public SaveType SaveType { get; set; }

...this way it's visible in Design Mode:

Forms Designer view

For RichTextBox, set it to SaveType.File which says to store RTF data in the AppData folder in a file with *.rtf extension when we implement the Save method:

    public void Save()
    {
        switch (SaveType)
        {
            case SaveType.AppProperties:
                // This would be a concern if the RTF for example
                // holds an image file making it gigantic.
                Properties.Settings.Default[Name] = Rtf;
                Properties.Settings.Default.Save();
                break;
            case SaveType.File:
                File.WriteAllText(FileName, Rtf);
                break;
            case SaveType.FileDataStore:
            case SaveType.FileDataStoreJSON:
            case SaveType.WindowsRegisty:
            case SaveType.SQLite:
            default:
                throw new NotImplementedException("To do!");
        }
        Debug.WriteLine("Saved");
    }

For RichTextBox, do Load from the same file:

    public void Load()
    {
        if (!DesignMode)
        {
            BeginInit();
            switch (SaveType)
            {
                case SaveType.AppProperties:
                    Rtf = (string)Properties.Settings.Default[Name];
                    break;
                case SaveType.File:
                    if(File.Exists(FileName))
                    {
                        Rtf = File.ReadAllText(FileName);
                    }
                    break;
                case SaveType.FileDataStore:
                case SaveType.FileDataStoreJSON:
                case SaveType.WindowsRegisty:
                case SaveType.SQLite:
                default:
                    throw new NotImplementedException("To do!");
            }
            EndInit();
        }
    }

Finally, as far as "when" to save, if user pastes an image or presses a key, wait for about a second to see of they're still typing. If there is inactivity after the interval time-out, do an auto-save.

    protected override void OnTextChanged(EventArgs e)
    {
        // This will pick up Paste operations, too.
        base.OnTextChanged(e);
        if(!_initializing)
        {
            // Restarts a short inactivity WDT and autosaves when done.
            WDT.Stop();
            WDT.Start(); 
        }
    }

    // Timeout has expired since the last change to the document.
    private void WDT_Tick(object sender, EventArgs e)
    {
        WDT.Stop();
        Save();
    }

... where...

    public PersistRichTextBox()
    {
        WDT = new Timer();
        WDT.Interval = 1000;
        WDT.Tick += WDT_Tick;
    }
    Timer WDT;

You can clone the full, working sample from our GitHub repo.

IVSoftware
  • 5,732
  • 2
  • 12
  • 23
  • ok thanks for giving me some tips on how to make the code more efficient. I will mess around with it a bit using what you suggested, and when or if i think i have something that works well-ish i'll be back! – pip May 26 '20 at 01:22
  • thanks alot for helping me along! I'll take a long look at it and do my best to wrap my head around everything. – pip May 27 '20 at 19:32
  • 1
    For sure! There are _so many people_ on SO and in real life who have helped me throughout my coding career and I'm happy to pass on a little knowledge when I can. – IVSoftware May 27 '20 at 23:47