1

我有一个将 PowerPoint 文件存储在 SQL Server 数据库中的 WPF 应用程序。该应用程序有一个编辑按钮,可以打开给定的 PowerPoint 文件进行编辑。由于 PowerPoint 是基于文件的应用程序,我必须使用临时文件来加载和保存 PowerPoint。

我为此目的编写的辅助类有一个绑定到编辑按钮属性的IsEnabled属性;我们在编辑过程中禁用按钮。在编辑过程中,有一个ManualResetEvent暂停Edit此帮助程序类中的方法。我挂钩 Presentation.Saved 事件以检测用户何时将更改保存到其 PowerPoint。不用说,这一切都是精心策划的。

在测试过程中,我们发现,如果用户在没有保存更改的情况下关闭 PowerPoint,然后对随后的“是否要保存更改”对话框回答“是”, 则在事件执行Presentation.Saved后才会触发,Presentation.CloseFinal我们对此无能为力,为时已晚。 Presentation.CloseFinal是我们从磁盘中检索保存的文件并将其存储到数据库的地方。 Presentation.Saved如果用户单击 PowerPoint 中的“保存”按钮,则立即触发。

为了解决这个问题,我挂钩了PresentationClose事件并编写了以下代码。

// Detects when the user closes the PowerPoint after changes have been made.
private void Application_PresentationClose(Presentation presentation)
{
    // If the user has edited the presentation before closing PowerPoint,
    // this event fires twice.  The first time it fires, presentationSaved
    // is msoFalse.  That's how we know the user has edited the PowerPoint.
    if (presentation.Saved == MsoTriState.msoFalse)
        IsDirty = true;
}

然后,我检查我的IsDirty属性Presentation.CloseFinal并将 PowerPoint 保存到数据库中(如果设置为true.

不幸的是,有一个无法预料的皱纹;似乎没有可靠的方法来确定用户是否放弃了他们的更改。此事件第二次触发时,无论用户对“保存您的更改吗?”的回答如何,该Presentation.Saved属性始终设置为。MsoTriState.msoTrue对话。

如果用户放弃更改,PowerPoint Interop 中是否有办法避免将未更改的文件保存到数据库的成本?


作为参考,这里是辅助类的全部内容:

using Microsoft.Office.Core;
using Microsoft.Office.Interop.PowerPoint;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Xps.Packaging;
using PropertyChanged;  // PropertyChanged.Fody 

namespace Helpers
{
    /// <summary>
    /// Helper class for PowerPoint file handling
    /// </summary>
    [AddINotifyPropertyChangedInterface]
    public sealed class PowerPointApplication : IDisposable
    {
        private Application _application;
        private Presentation _presentation;
        private string _tempFolder;
        private string _pptPath;
        private string _extension;

        /// <summary>
        /// Used to block the Edit method until the PowerPoint presentation is closed.
        /// </summary>
        private ManualResetEvent manualResetEvent = new ManualResetEvent(false);

        public byte[] PptData { get; private set; }
        public byte[] PptxData { get; private set; }
        public byte[] JpgData { get; private set; }

        /// <summary>
        /// Indicates whether any instance of PowerPointApplication is in an edit state.
        /// Used to disable Edit PowerPoint buttons.
        /// </summary>
        public static bool IsEditing { get; private set; }

        /// <summary>
        /// Indicates if the PowerPoint file has been saved after changes were made to it.
        /// </summary>
        public bool IsSaved { get; set; }

        /// <summary>
        /// Indicates if the PowerPoint file has been changed but not saved.
        /// </summary>
        public bool IsDirty { get; set; }

        public PowerPointApplication()
        {
            _tempFolder = Path.GetTempPath();

            if (!Directory.Exists(_tempFolder))
                Directory.CreateDirectory(_tempFolder);

            _application = new Application();
            _application.PresentationSave += Application_PresentationSave;
            _application.PresentationClose += Application_PresentationClose;
            _application.PresentationCloseFinal += Application_PresentationCloseFinal;
        }

        // Detects when the user presses the "Save" button in PowerPoint
        private void Application_PresentationSave(Presentation presentation)
        {
            IsSaved = true;
        }

        // Detects when the user closes the PowerPoint after changes have been made.
        private void Application_PresentationClose(Presentation presentation)
        {
            // If the user has edited the presentation before closing PowerPoint,
            // this event fires twice.  The first time it fires, presentationSaved
            // is msoFalse.  That's how we know the user has edited the PowerPoint.
            // It fires again after the users has responded to the "save changes?" dialog.
            if (presentation.Saved == MsoTriState.msoFalse)
                IsDirty = true;
        }

        private void Application_PresentationCloseFinal(Presentation presentation)
        {
            if ((IsDirty || IsSaved) && File.Exists(_pptPath))
            {
                var data = File.ReadAllBytes(_pptPath);

                if (_extension == "pptx")
                {
                    PptxData = data;
                    PptData = GetPpt(presentation);
                }
                else
                {
                    PptData = data;
                    PptxData = GetPptx(presentation);
                }
                JpgData = GetJpg(presentation);

                IsSaved = true;
                IsDirty = false;
            }
            manualResetEvent.Set();
            IsEditing = false;

            Task.Run(() => DeleteFileDelayed(_pptPath));
        }

        /// <summary>
        /// Waits for PowerPoint to close, and then makes a best effort to delete the temp file.
        /// </summary>
        private static void DeleteFileDelayed(string path)
        {
            if (path == null) return;
            var file = new FileInfo(path);

            Thread.Sleep(5000);
            try
            {
                file.Delete();
            }
            catch { }
        }

        /// <summary>
        /// Opens the provided PowerPoint byte array in PowerPoint and displays it.
        /// </summary>
        public void Edit(byte[] data, string ext = "xml")
        {
            _extension = ext;
            _pptPath = GetTempFile(_extension);

            if (data == null)
            {
                // Open a blank presentation and establish a save path.
                _presentation = _application.Presentations.Add(MsoTriState.msoTrue);
                _presentation.SaveAs(_pptPath, PpSaveAsFileType.ppSaveAsXMLPresentation);
                IsSaved = false;
            }
            else
            {
                // Save the data to a file and open it.
                File.WriteAllBytes(_pptPath, data);
                _presentation = _application.Presentations.Open(_pptPath);
                IsEditing = true;
            }

            // Make sure IsEnabled state of WPF buttons is properly set.
            ApplicationHelper.DoEvents();
            Thread.Sleep(100);
            ApplicationHelper.DoEvents();

            // Wait for PowerPoint to exit.
            manualResetEvent.WaitOne();
        }

        /// <summary>
        /// Opens the provided PowerPoint byte array in PowerPoint without displaying it.
        /// </summary>
        public void Open(byte[] data, string ext = "xml")
        {
            _extension = ext;
            _pptPath = GetTempFile(ext);
            File.WriteAllBytes(_pptPath, data);
            _presentation = _application.Presentations.Open(_pptPath, WithWindow: MsoTriState.msoFalse);
            IsEditing = true;
        }

        public void Close()
        {
            _presentation.Close();
        }

        public byte[] GetJpg() { return GetJpg(_presentation); }
        /// <summary>
        /// Returns a byte array containing a JPEG image of the first slide in the PowerPoint.
        /// </summary>
        public byte[] GetJpg(Presentation presentation)
        {
            presentation.SaveCopyAs(_tempFolder, PpSaveAsFileType.ppSaveAsJPG, MsoTriState.msoFalse);
            byte[] result = File.ReadAllBytes(Path.Combine(_tempFolder, "Slide1.jpg"));
            File.Delete(Path.Combine(_tempFolder, "Slide1.jpg"));
            return result;
        }

        public byte[] GetPptx() { return GetPptx(_presentation); }

        public byte[] GetPptx(Presentation presentation)
        {
            var path = Path.ChangeExtension(_pptPath, "pptx");
            presentation.SaveCopyAs(path, PpSaveAsFileType.ppSaveAsOpenXMLPresentation, MsoTriState.msoFalse);
            byte[] result = File.ReadAllBytes(path);
            return result;
        }

        public byte[] GetPpt(Presentation presentation)
        {
            var path = Path.ChangeExtension(_pptPath, "ppt");
            presentation.SaveCopyAs(path, PpSaveAsFileType.ppSaveAsPresentation, MsoTriState.msoFalse);
            byte[] result = File.ReadAllBytes(path);
            return result;
        }

        /// <summary>
        /// Returns an XPS document of the presentation.
        /// </summary>
        public XpsDocument ToXps(string pptFilename, string xpsFilename)
        {
            var presentation = _application.Presentations.Open(pptFilename, MsoTriState.msoTrue, MsoTriState.msoFalse, MsoTriState.msoFalse);
            presentation.ExportAsFixedFormat(xpsFilename, PpFixedFormatType.ppFixedFormatTypeXPS);
            return new XpsDocument(xpsFilename, FileAccess.Read);
        }

        /// <summary>
        /// Returns a path to a temporary working file having the specified extension.
        /// </summary>
        private string GetTempFile(string extension)
        {
            return Path.Combine(_tempFolder, Guid.NewGuid() + "." + extension);
        }

        #region IDisposable implementation
        public void Dispose()
        {
            _application.PresentationSave -= Application_PresentationSave;
            _application.PresentationClose -= Application_PresentationClose;
            _application.PresentationCloseFinal -= Application_PresentationCloseFinal;

            IsEditing = false;
        }
        #endregion
    }
}
4

0 回答 0