65.9K
CodeProject 正在变化。 阅读更多。
Home

一个简单的自动保存/恢复画图应用程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (9投票s)

2014 年 5 月 8 日

CPOL

5分钟阅读

viewsIcon

36934

downloadIcon

878

一个解释自动保存功能基本实现的应用程序

引言

当您花费很长时间在一个应用程序上,然后应用程序崩溃时,您会有多么恼火?您忘记了进行“文件 -> 保存”或“Ctrl + S”,导致数据完全丢失。您所付出的努力都白费了。也许下次,您会更加谨慎,并定期按下“Ctrl + S”。也许您会遵循几次,然后又忘了这样做。

应用程序崩溃可能发生的原因有很多,例如停电、应用程序中的错误导致应用程序崩溃或恶意病毒。您可以防范停电或病毒,但直到获得补丁/新版本之前,您无法应对错误。

背景

有些应用程序具有自动保存功能,可以在一定时间间隔内保存工作。如果应用程序崩溃,用户会被提示加载自动保存的工作。Microsoft Word 内置了此功能,可以按如下方式进行配置。可以通过左上角的 Office 按钮 -> Word 选项 -> 保存选项卡进行访问。

另一个例子是 Microsoft Outlook 和 Gmail 在一定时间间隔后保存电子邮件。

我将编写一个简单的画图应用程序,它可以在一定时间间隔内保存设计。它使用 WPF 和 C# 开发。该应用程序只是为了理解自动保存的概念,不包含 Microsoft Word 中的高级功能。但是,它可以扩展以创建这样的功能。我会尽量保持简单。

自动保存画图应用程序

应用程序功能列表如下

  • 新建 – 清除设计
  • 保存 – 将工作设计保存到指定位置
  • 打开 – 打开选定的 Ink 序列化格式 (*.ink) 文件
  • 退出 – 退出应用程序
  • 自动保存 – 在后台以指定的时间间隔将设计保存到隐藏位置
  • 恢复 – 应用程序崩溃后重新启动应用程序时,在启动时加载最后一次自动保存的文件

让我们开始创建应用程序。

代码

  • 打开 Visual Studio -> 新建项目 -> 其他项目类型 -> Visual Studio 解决方案 -> 空解决方案。将其命名为“AutosaveAndRecovery”。
  • 添加一个新的 WPF 项目“AutosavePaint”。
  • 用下面的 XAML 和 .cs 文件替换 MainWindow.xamlMainWindow.xaml.cs

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

我们已经构建了应用程序的结构,但没有功能。运行应用程序并确保它能正常启动。我使用了 InkCanvas 进行绘图。将鼠标移动到中间的灰色区域。

现在,我们将逐一添加上述功能。

1. 新建:清除设计

添加以下代码以清除画布,在按下“新建”按钮时,设计将被清除。

/// <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. 保存:将工作设计保存到指定位置

添加以下代码将设计保存到文件。

        /// <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;
        }

设计将以 .ink* 扩展名的文件保存。设计也可以保存为位图格式。为了保持代码简洁,我没有包含它,本文的主要目的是演示自动保存和恢复。 这是一篇精彩的文章,由 Sacha Barber 撰写,关于使用 InkCanvasPaint 应用程序。

3. 打开:打开选定的 Ink 序列化格式 (*.ink) 文件

添加以下代码以打开 *.ink 文件并解析引用。

        /// <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. 退出:退出应用程序

添加以下代码以退出应用程序

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

5. 自动保存:在后台以指定的时间间隔将设计保存到隐藏位置

为了实现自动保存,我们需要考虑以下几点。

  • 位置 – 自动保存文件的位置。我们将对其隐藏用户。
  • 后台操作 - 保存操作必须在后台的工作线程中执行,以使应用程序保持响应。
  • 自动保存间隔 – 这决定了两次连续保存操作之间的时间间隔。

首先定义位置。我使用了执行程序集的位置,并在其中创建了 Autosave 目录。此外,该目录需要被隐藏。添加了两个辅助方法 MakeDirectoryHidden(…)MakeFileHidden(…)。有关实现,请参阅下载的代码。

        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);
            }
        }

运行应用程序,如果在调试模式下运行应用程序,您会发现在 ..\\ AutosaveAndRecovery\AutosavePaint\bin\Debug\Autosave 目录下创建了 Autosave 目录。

为了在后台保存设计,我使用了 BackgroundWorker

已添加两个方法来实现此功能。

        /// <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);
            }
        }

现在,最后一件事情是以一定的间隔定期调用 SaveAync(…)。我选择了 20 秒作为保存间隔。为了定期调用它,我使用了 DispatcherTimerDispatcherTimer 定期调用以下方法。

        /// <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);
        }

为了启动/停止 Dispatcher,已添加窗口的 LoadedClosing 事件。

        /// <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;
            }
        }

运行应用程序,您会注意到在 20 秒后保存设计时发生了错误。引发的异常是跨线程异常。我们正在尝试从后台线程访问 UI 组件 (InkCanvas)。要克服这个问题,请像下面这样将 UI 访问调用封送回 UI 线程。

        /// <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();
            }
        }

运行应用程序,进行一些草图,20 秒后,文件将被保存。

6. 恢复:在应用程序崩溃后重新启动应用程序时,加载最后一次自动保存的文件

这是最后一个功能,如果我们无法恢复最后一次自动保存的文件,自动保存就没有意义了。下面的流程图描绘了恢复机制。

CheckAndLoadBackupFile(…) 方法负责恢复。它在应用程序启动后立即在 loaded 事件中被调用。此外,DeleteBackupFile(…) 在应用程序的 closing 事件中被调用。

设计自动保存后的应用程序。

结论

自动保存和恢复是在应用程序中让用户生活更舒适的绝佳功能。我只是介绍了如何实现它。它可以扩展为高级功能。它可以用于将设计、草图或表单数据保存到文件/数据库。任何提供打开/保存选项的应用程序都是此类应用程序的理想选择。

欢迎评论和建议!

参考文献

© . All rights reserved.