Click here to Skip to main content
15,885,309 members
Articles / Desktop Programming / WPF

A Simple Autosave/Recovery Paint Application

Rate me:
Please Sign up or sign in to vote.
4.93/5 (9 votes)
16 Dec 2018CPOL5 min read 34.4K   839   14   8
An application explaining the basic implementation of Autosave feature

Introduction

How irritated you will be when you are working on an application for a long time, and the application crashes. You forget to do File->Save or ‘Ctrl + S’, and data is completely lost. The efforts you have put in have gone in vain. Maybe next time, you’ll be more cautious and keep pressing ‘ctrl + s’ after a certain interval of time. Maybe you can follow this a few times, and forget to do this thereafter.

An application crash can happen for different reasons such as power went off, a bug in the application that might bring down the application or malicious virus. You can take care of the power or virus, but not the bug till you get a patch/new version.

Background

There are applications having a feature of Autosave wherein the work is saved at a certain interval. If an application crashes, a user is prompted for loading the autosaved work. Microsoft Word has this built in and can be configured as shown below. It can be accessed through the office button in the left top corner -> Word Options -> Save tab.

Image 1

Another example is Microsoft Outlook and Gmail saving emails after a certain interval.

I am going to write a simple paint application that saves the design at a certain interval. It’s developed using WPF and C#. The application is just to understand the concept of Autosave and doesn’t include the advanced features present in Microsoft Word. However, it can be extended to create such a feature. I’ll try to keep it simple.

Autosave Paint Application

List of features in the application is as follows:

  • New – Clears the design
  • Save – Saves the working design to the specified location
  • Open – Opens a selected Ink serialized format (*.ink) file
  • Exit – Exits the application
  • Autosave – Saves the design at a specified interval to a hidden location in the background
  • Recovery – Loads the last auto saved file during startup after application restarted followed by application crash

Let’s start and create the application.

Code

  • Open Visual Studio -> New Project -> Other Project Types -> Visual Studio Solutions -> Blank Solution. Give it a name as “AutosaveAndRecovery”.
  • Add a new WPF project “AutosavePaint”.
  • Replace MainWindow.xaml and MainWindow.xaml.cs with below XAML and .cs file.

MainWindow.xaml

XML
<Window x:Class="AutosavePaint.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Menu HorizontalAlignment="Left" Height="38" VerticalAlignment="Top"
                                         Width="517" RenderTransformOrigin="0.32,-0.079">
            <MenuItem Header="File">
                <MenuItem Command="ApplicationCommands.New">
                    <MenuItem.CommandBindings>
                        <CommandBinding Command="ApplicationCommands.New"
                                        Executed="New"
                                        CanExecute="CanNew"/>
                    </MenuItem.CommandBindings>
                </MenuItem>

                <MenuItem Header="Open" Command="ApplicationCommands.Open"
                          HorizontalAlignment="Left" Width="157.507"
                          RenderTransformOrigin="0.502,0.502" Height="26" Margin="0,0,-13.17,0">
                    <MenuItem.CommandBindings>
                        <CommandBinding Command="ApplicationCommands.Open"
                                        Executed="Open"
                                        CanExecute="CanOpen"/>
                    </MenuItem.CommandBindings>
                </MenuItem>
                <MenuItem Header="Save" Margin="0,0,12,0" Command="ApplicationCommands.Save"
                           Height="25" RenderTransformOrigin="0.552,0.482">
                    <MenuItem.CommandBindings>
                        <CommandBinding Command="ApplicationCommands.Save"
                                        Executed="Save"
                                        CanExecute="CanSave"/>
                    </MenuItem.CommandBindings>
                </MenuItem>
                <MenuItem Header="Exit" Command="ApplicationCommands.Close">
                    <MenuItem.CommandBindings>
                        <CommandBinding Command="ApplicationCommands.Close"
                                        Executed="Exit"
                                        CanExecute="CanExit"/>
                    </MenuItem.CommandBindings>
                </MenuItem>
            </MenuItem>
        </Menu>
        <InkCanvas Name="inkCanvas" HorizontalAlignment="Left" Height="229"
             Margin="10,43,0,0" VerticalAlignment="Top" Width="497" Background="#FF9C9898"/>
        <StatusBar x:Name="statusBar" Height="33" Margin="10,277,10,0"
            VerticalAlignment="Top" Background="#FFD4CFCF">
            <StatusBarItem x:Name="statusBarItem" Content="StatusBarItem"
                 Height="33" VerticalAlignment="Top"/>
        </StatusBar>
    </Grid>
</Window>

MainWindow.xaml.cs

We have structured the application without functionality. Run the application and ensure it's launched fine. I have used InkCanvas for drawing. Move the mouse over the central gray area.

Image 2

Now, we will add the features mentioned above one by one.

1. New: Clears the Design

Add the below code to clear the canvas and on a press of New, design should be cleared.

C#
/// <summary>
/// Clears the design
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void New(object sender, ExecutedRoutedEventArgs e)
{
     inkCanvas.Strokes.Clear();
     statusBarItem.Content = "Ready";
}

2. Save: Saves the Working Design to the Specified Location

Add the below code to save a design to a file.

C#
/// <summary>
/// Saves currently working design
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void Save(object sender, ExecutedRoutedEventArgs e)
{
    var saveFileDialog = new SaveFileDialog();
    saveFileDialog.Filter = "Ink Serialized Format (*.isf)|*.isf|";
    if (saveFileDialog.ShowDialog() == true)
    {
        Save(saveFileDialog.FileName);
    }
}

/// <summary>
/// Saves the design to the specified file
/// </summary>
/// <param name="fileName">File to be saved</param>
/// <returns>true, if saving is successful</returns>
private bool Save(string fileName)
{
    FileStream fs = null;
    try
    {
        fs = File.Open(fileName, FileMode.Create);
        inkCanvas.Strokes.Save(fs);
    }
    catch (Exception ex)
    {
        throw ex;
    }
    finally
    {
        if (fs != null)
            fs.Close();
    }
    return true;
}

The design will be saved in a file with extension *.ink. The design can be saved in bitmap format as well. I haven’t included it in order to keep code simple and the main intention of this article is to demonstrate Autosave and recovery. Here is an excellent article on Paint application using InkCanvas by Sacha Barber.

3. Open: Opens a Selected Ink Serialized Format (*.ink) File

Add the below code to open a *.ink file and resolve the references.

C#
/// <summary>
/// Opens a selected design
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void Open(object sender, ExecutedRoutedEventArgs e)
{
    OpenFileDialog openFileDialog = new OpenFileDialog();
    openFileDialog.CheckFileExists = true;
    openFileDialog.Filter = "Ink Serialized Format (*.isf)|*.isf|" +
                 "All files (*.*)|*.*";
    if (openFileDialog.ShowDialog(this) == true)
    {
        string fileName = openFileDialog.FileName;
        if (!fileName.ToLower().EndsWith(".isf"))
        {
            MessageBox.Show("The requested file is not a Ink Serialized Format
                             file\r\n\r\nplease retry", Title);
        }
        else
        {
            if (Open(fileName))
            {
                statusBarItem.Content = "Loaded";
            }
            else
            {
                statusBarItem.Content = "An error occured while opening the file";
            }
        }
    }
}

/// <summary>
/// Opens the specified file in canvas
/// </summary>
/// <param name="fileName">File to be opened</param>
/// <returns>true, if opening is successful</returns>
private bool Open(string fileName)
{
    FileStream fs = null;
    try
    {
        this.inkCanvas.Strokes.Clear();
        fs = new FileStream(fileName, FileMode.Open);
        inkCanvas.Strokes = new System.Windows.Ink.StrokeCollection(fs);
    }
    catch (Exception)
    {
        return false;
    }
    finally
    {
        if (fs != null)
            fs.Close();
    }
    return true;
}

4. Exit: Exit the Application

Add the below code to exit the application:

C#
/// <summary>
/// Exits the application
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void Exit(object sender, ExecutedRoutedEventArgs e)
{
    this.Close();
}

5. Autosave: Saves the Design at a Specified Interval to a Hidden Location in the Background

In order to implement Autosave, we need to consider the below things.

  • Location – Location of autosaved files. We’ll hide it from the user.
  • Background operation - Save operation has to be performed in the background in the worker thread so that application remains responsive.
  • Autosave interval – This determines the interval between two consecutive save operation.

Let’s define the location first. I have used the executing assembly location and created Autosave directory in it. Also, the directory needs to be hidden. Two helper methods, MakeDirectoryHidden(…) and MakeFileHidden(…) have been added. Refer to the downloaded code for implementation.

C#
private readonly string _backupFilePath;
private string _backupFile;

private const string BACKUP_FILE_NAME = "PaintAppBackup.bk";

/// <summary>
/// Constructor to initialize this class
/// </summary>
public MainWindow()
{
    InitializeComponent();

    statusBarItem.Content = "Ready";

    _backupFilePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) +
                      "\\" + BACKUP_DIRECTORY;
    _backupFile = _backupFilePath + "\\" + BACKUP_FILE_NAME;
    if (!Directory.Exists(_backupFilePath))
    {
        Directory.CreateDirectory(_backupFilePath);
        MakeDirectoryHidden(_backupFilePath);
    }
}

Run the application and observe that Autosave directory will be created in the ..\\ AutosaveAndRecovery\AutosavePaint\bin\Debug\Autosave if the application is running in the debug mode.

In order to save the design in the background, I have used BackgroundWorker.

Two methods have been added to achieve it.

C#
/// <summary>
/// This is called when saving of design asynchronously is completed
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnSaveAsyncCompletion(object sender, RunWorkerCompletedEventArgs e)
{
    // First, handle the case where an exception was thrown.
    if (e.Error != null)
    {
        statusBarItem.Content = "An error occured while saving the design";
        DeleteBackupFile();
    }
    else
    {
        // Finally, handle the case where the operation
        // succeeded.
        statusBarItem.Content = "Saved";
        MakeFileHidden(_backupFile);
    }
}

Now, the last thing is to call SaveAync(…) periodically at a certain interval. I have chosen 20 seconds as the saving interval. In order to call it periodically, I have used DispatcherTimer. The DispatcherTimer calls below method periodically.

C#
/// <summary>
/// This method is periodically called at a specified interval
/// </summary>
/// <param name="o"></param>
/// <param name="e"></param>
void PeriodicSave(object o, EventArgs e)
{
    statusBarItem.Content = "Saving";

    if (!Directory.Exists(_backupFilePath))
    {
        Directory.CreateDirectory(_backupFilePath);
        MakeDirectoryHidden(_backupFilePath);
    }
    DeleteBackupFile();
    SaveAsync(_backupFile);
}

In order to start/stop the Dispatcher, Window’s Loaded and Closing events have been added.

C#
/// <summary>
/// Called when window is loaded
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Window_Loaded(object sender, RoutedEventArgs e)
{
    _dispatcherTimer = new DispatcherTimer();
    _dispatcherTimer.Interval = TimeSpan.FromMilliseconds(AUTOSAVE_INTERVAL_SECONDS * 1000);
    _dispatcherTimer.Tick += PeriodicSave;
    _dispatcherTimer.Start();

    statusBarItem.Content = "Ready";
}

/// <summary>
/// Called when window is closing
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Window_Closing(object sender, CancelEventArgs e)
{
    if (_dispatcherTimer != null)
    {
        _dispatcherTimer.Stop();
        _dispatcherTimer = null;
    }
}

Run the application and observe an error occurred while saving the design after 20 seconds. The exception raised is a cross-thread exception. We are trying to access the UI component (InkCanvas) from a background thread. To overcome marshal UI access call to UI thread as shown below.

C#
/// <summary>
/// Saves the design to the specified file
/// </summary>
/// <param name="fileName">File to be saved</param>
/// <returns>true, if saving is successful</returns>
private bool Save(string fileName)
{
    FileStream fs = null;
    try
    {
        fs = File.Open(fileName, FileMode.Create);
        ExecuteOnUIThread(() => inkCanvas.Strokes.Save(fs)); <<ç=
    }
    catch (Exception ex)
    {
        throw ex;
    }
    finally
    {
        if (fs != null)
            fs.Close();
    }
    return true;
}

/// <summary>
/// Marshall the call to the UI thread
/// </summary>
/// <param name="action"></param>
private void ExecuteOnUIThread(Action action)
{
    var dispatcher = Application.Current.Dispatcher;
    if (dispatcher != null)
    {
        dispatcher.Invoke(action);
    }
    else
    {
        action();
    }
}

Run the application, sketch something and after 20 seconds, the file will be saved.

6. Recovery: Load the Last Autosaved File During Startup After Application Restarted Followed by an Application Crash

This is the last feature and autosave is useless if we can’t bring the last autosaved file back. The below flowchart depicts the recovery mechanism.

Image 3

A method CheckAndLoadBackupFile(…) does the recovery. It’s called in the loaded event as soon as the application starts. Also, DeleteBackupFile(…) is called in the closing event of the application.

Application after design being auto-saved.

Image 4

Conclusion

Autosave and recovery are nice features to have in an application that makes users' life comfortable. I have just given an introduction on how to implement it. It can be extended with advanced features. It can be used to save designs, sketches or forms data into a file/database. Any application providing options for Open/Save is a prime candidate for this kind of application.

Comments and suggestions are Welcome!

References

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Harman International
India India
I am a Microsoft .Net developer having experience in developing enterprise applications using technologies such as WPF, Winform. Always look towards making the life simple by developing GUI based applications.

Comments and Discussions

 
QuestionNice solution Pin
asiwel20-Dec-18 15:16
professionalasiwel20-Dec-18 15:16 
GeneralRe: Nice solution Pin
Praveen Raghuvanshi9-Oct-19 7:04
professionalPraveen Raghuvanshi9-Oct-19 7:04 
QuestionBut if it crashes between delete and save... Pin
Marc Clifton19-Dec-18 2:28
mvaMarc Clifton19-Dec-18 2:28 
AnswerRe: But if it crashes between delete and save... Pin
Praveen Raghuvanshi9-Oct-19 7:05
professionalPraveen Raghuvanshi9-Oct-19 7:05 
GeneralCool Feature..... Pin
Aradhana Singh Raghuvanshi8-May-14 8:34
Aradhana Singh Raghuvanshi8-May-14 8:34 
GeneralRe: Cool Feature..... Pin
Praveen Raghuvanshi8-May-14 16:31
professionalPraveen Raghuvanshi8-May-14 16:31 
Generaldude u r awesome :) Pin
manish 123458-May-14 7:38
manish 123458-May-14 7:38 
GeneralRe: dude u r awesome :) Pin
Praveen Raghuvanshi8-May-14 7:43
professionalPraveen Raghuvanshi8-May-14 7:43 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.