PowerPoint Interop: как определить, когда пользователь закрывает PowerPoint, не сохраняя свои изменения?

У меня есть приложение WPF, которое хранит файлы PowerPoint в базе данных SQL Server. В приложении есть кнопка редактирования, которая открывает для редактирования указанный файл PowerPoint. Поскольку PowerPoint является файловым приложением, я должен использовать временные файлы для загрузки и сохранения PowerPoints.

Вспомогательный класс, который я написал для этой цели, имеет свойство, которое привязывается к кнопке редактирования 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
    }
}

0 ответов

Другие вопросы по тегам