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