Click here to Skip to main content
Click here to Skip to main content

A simple Autosave/Recovery paint application

, 8 May 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
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 application crashes. You forget to do File->Save or ‘Ctrl + S’, and data is completely lost. The efforts you have put have gone in vain. Maybe next time you’ll be more cautious and keep pressing ‘ctrl + s’ after certain interval of time. Maybe you can follow this 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 application crashes, user is prompted for loading the autosaved work. Microsoft word has this built in and can be configured as shown below. It cab accessed through Office button in the left top corner -> Word Options -> Save tab

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 feature. I’ll try to keep it simple.

Autosave Paint Application

List of features in the application are 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 – Exit the application.
  • Autosave – Saves the design at a specified interval to a hidden location in the background
  • Recovery – Load 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

<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 application and ensure its launched fine. I have used InkCanvas for drawing. Move the mouse over the central gray area.

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

1. New: Clears the design

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

/// <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 below code to save a design to a file.

        /// <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 main intention of this article is to demonstrate Autosave and recovery. There is an excellent article on Paint application using InkCanvas by Sacha Barber.

http://www.codeproject.com/Articles/19102/Adventures-into-Ink-API-using-WPF

3. Open: Opens a selected Ink serialized format (*.ink) file

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

 
        /// <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 below code to exit the application

        /// <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 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, directory needs to be hidden. Two helper methods MakeDirectoryHidden(…) and MakeFileHidden(…) have been added. Refer downloaded code for implementation.

 
        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 application and observe that Autosave directory will be created in the ..\\ AutosaveAndRecovery\AutosavePaint\bin\Debug\Autosave if 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.

 
        /// <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, 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.

 
        /// <summary>
        /// This method is periodicaaly 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.

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

 
        /// <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, file will be saved.

6. Recovery: Load the last auto saved file during startup after application restarted followed by application crash

This is the last feature and autosave is useless, if we can’t bring the last auto saved file back. Below flowchart depicts the recovery mechanism.

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

Application after design being auto-saved.

Conclusion

Autosave and recovery is a nice feature 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

http://www.codeproject.com/Articles/324/Autosave-and-Crash-Recovery

http://www.codeproject.com/Articles/4574/Save-and-Restore-User-Preferences?q=autosave

http://www.codeproject.com/Articles/18166/Scratchpad-An-Auto-Save-Notepad

http://www.codeproject.com/Articles/19102/Adventures-into-Ink-API-using-WPF

http://www.codeproject.com/Articles/16579/Saving-Rebuilding-InkCanvas-Strokes

http://www.codeproject.com/Articles/617868/Scribble-WPF-InkCanvas-Application-Using-PRISM-MVV

License

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

Share

About the Author

Praveen Raghuvanshi
Technical Lead 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

 
GeneralCool Feature..... PinmemberMember 77349588-May-14 9:34 
GeneralRe: Cool Feature..... PinprofessionalPraveen Raghuvanshi8-May-14 17:31 
Generaldude u r awesome :) Pinmembermanish 123458-May-14 8:38 
GeneralRe: dude u r awesome :) PinmemberPraveen Raghuvanshi8-May-14 8:43 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150123.1 | Last Updated 8 May 2014
Article Copyright 2014 by Praveen Raghuvanshi
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid