如何让 WPF 的 DocumentViewer 释放其对源 XPS 文档的文件锁定?
-
22-07-2019 - |
题
在 WPF DocumentViewer 中显示 XPS 文件并关闭 DocumentViewer 实例后,XPS 文件被锁定,无法删除它。我需要释放 XPS 文件上的锁定,以便可以将其删除,写入另一个同名文件,并可以选择在新的 DocumentViewer 实例中显示该新 XPS 文件。我需要在同一个应用程序实例中执行此操作 - 无需关闭应用程序(这是打印预览场景)。
换句话说,我将如何在“ 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 文件, ,但在这种情况下这不是一个选项。
解决方案
您需要关闭从其中分配给观察者XpsDocument被打开的System.IO.Packaging.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 应该处理的内部包管理内容。