如何使WPF的DocumentViewer释放其对源XPS文档的文件锁定?

9
在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 doesn't release the XPS file中提到的那样,但在这种情况下不是一个选项。
2个回答

14
您需要关闭打开XpsDocument的System.IO.Packaging.Package。此外,如果您想在同一应用程序会话中再次打开同一文件,则必须从PackageStore中删除该Package。关闭Package将释放文件锁定并允许您删除该文件,但您将无法重新打开相同的文件(或更准确地说,任何具有相同名称但内容不同的位置上的文件),直到您从PackageStore中删除该Package。
在问题代码的上下文中,在第一个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();

是的,我知道我没有使用Package打开XpsDocument——.NET在幕后为我完成了这项工作,但它忘记了在完成后清理。


这个问题困扰了我一段时间,我的一个查看器可以下载文档,但是在设置文档后,即使应用了这些更改,它仍然会失败。当我加载文档时,我正在处理原始文档,这将导致重新加载文档时释放失败。 - Brett Ryan

6
不确定此问题最初是针对哪个 .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 应该处理的内部包管理事项中。

这可能是最简单的方法! - Moses Aprico

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接