Как мне заставить DocumentViewer WPF снять блокировку файла в исходном документе XPS?
-
22-07-2019 - |
Вопрос
После отображения файла XPS в WPF DocumentViewer и закрытия экземпляра DocumentViewer файл XPS блокируется, и я не могу его удалить.Мне нужно снять блокировку файла XPS, чтобы я мог удалить его, записать другой файл с тем же именем и, при необходимости, отобразить этот новый файл XPS в новом экземпляре DocumentViewer.Мне нужно сделать это в том же экземпляре приложения, не закрывая его (это сценарий предварительного просмотра перед печатью).
Другими словами, как бы я получил следующий код, чтобы запустить, не бросая исключение в «file.delete (tempxpsfile)»; заявление?
var tempXpsFile = @"c:\path\to\Temporary.xps";
var previewWindow = new Window();
var docViewer = new DocumentViewer();
previewWindow.Content = docViewer;
GenerateXpsFile(tempXpsFile);
var xpsDocument = new XpsDocument(tempXpsFile);
previewWindow.ShowDialog();
File.Delete(tempXpsFile); //this will throw an exception due to a file lock on tempXpsFile
GenerateXpsFile(tempXpsFile); //assume this generates a different file
//otherwise the scenario doesn't make sense as we could just skip the above delete
//and this statement and re-use the same file
previewWindow = new Window();
docViewer = new DocumentViewer();
previewWindow.Content = docViewer;
previewWindow.ShowDialog();
Закрытие приложения снимает блокировку файла, как указано в WPF DocumentViewer не выпускает файл XPS, но в данном сценарии это не вариант.
Решение
Вам необходимо закрыть System.IO.Packaging.Package, из которого был открыт XpsDocument, назначенный для средства просмотра. Кроме того, если вы хотите снова открыть тот же файл в том же сеансе приложения, вам придется удалить Package из хранилища PackageStore. Закрытие пакета снимет блокировку файла и позволит вам удалить файл, но вы не сможете повторно открыть этот же файл (или, точнее, любой файл в том же месте с тем же именем, даже если он имеет другой контент), пока вы не удалите пакет из PackageStore.
В контексте кода в вопросе вставьте следующее после первого previewWindow.ShowDialog (); перед File.Delete (tempXpsFile);
//Get the Uri from which the system opened the XpsPackage and so your XpsDocument
var myXpsUri = xpsDocument.Uri; //should point to the same file as tempXpsFile
//Get the XpsPackage itself
var theXpsPackage = System.IO.Packaging.PackageStore.GetPackage(myXpsUri);
//THIS IS THE KEY!!!! close it and make it let go of it's file locks
theXpsPackage.Close();
//if you don't remove the package from the PackageStore, you won't be able to
//re-open the same file again later (due to System.IO.Packaging's Package store/caching
//rather than because of any file locks)
System.IO.Packaging.PackageStore.RemovePackage(myXpsUri);
Таким образом, сегмент фиксированного кода, представленный в вопросе, становится следующим:
var tempXpsFile = @"c:\path\to\Temporary.xps";
var previewWindow = new Window();
var docViewer = new DocumentViewer();
previewWindow.Content = docViewer;
GenerateXpsFile(tempXpsFile);
var xpsDocument = new XpsDocument(tempXpsFile);
previewWindow.ShowDialog();
//BEGIN NEW CODE
var myXpsUri = xpsDocument.Uri; //should point to the same file as tempXpsFile
var theXpsPackage = System.IO.Packaging.PackageStore.GetPackage(myXpsUri);
theXpsPackage.Close();
System.IO.Packaging.PackageStore.RemovePackage(myXpsUri);
//END NEW CODE
File.Delete(tempXpsFile); //this will succeed now
GenerateXpsFile(tempXpsFile);
previewWindow = new Window();
docViewer = new DocumentViewer();
previewWindow.Content = docViewer;
previewWindow.ShowDialog();
Да, я знаю, что не открывал XpsDocument с пакетом - это сделал .NET для " я за кадром и забывает убирать за собой.
Другие советы
Не уверен, к какой версии .Net изначально был задан этот вопрос, и могло ли это измениться между 3.x и 4.x, но, судя по некоторым исследованиям .Net 4.0, похоже, что решение может быть довольно сложным. проще, чем это.
XpsDocument реализует IDisposable, что указывает на необходимость Dispose() после использования.Проблема в том, что IDisposable.Dispose() реализован так, что он скрыт, поэтому вы не можете вызвать его напрямую.Вместо этого вам нужно вызвать Close().Использование dotPeek для анализа XpsDocument.Dispose():
- XpsDocument.Close() вызывает XpsDocument.Dispose()
- XpsDocument.Dispose() вызывает XpsManager.Close()
- XpsManager.Close() вызывает XpsManager.RemovePackageReference()
- XpsManager.RemovePackageReference() вызывает PackageStore.RemovePackage() и Package.Close()
Поэтому, если я что-то не упускаю, простое выполнение Close() XpsDocument (что вы все равно должны сделать) должно привести к тому же результату без необходимости копаться во внутренних материалах управления пакетами, которые должен обрабатывать XpsDocument.