wpfxpsdocumentfilelockdocumentviewer

How do I get WPF's DocumentViewer to release its file lock on the source XPS Document?


After showing an XPS file in the WPF DocumentViewer, and closing the DocumentViewer instance, the XPS file is locked and I cannot delete it. I need to release the lock on the XPS file so I can delete it, write another one with the same name, and optionally display that new XPS file in a new DocumentViewer instance. I need to do this in the same app instance - without having to close the app (this is a Print Preview scenario).

In other words, how would I get the following code to run without throwing an exception at the "File.Delete(tempXpsFile);" statement?

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();

Closing the app does release the file lock, as mentioned in WPF DocumentViewer doesn't release the XPS file, but that is not an option in this scenario.


Solution

  • You need to close the System.IO.Packaging.Package from which the XpsDocument assigned to the viewer was opened. Further, if you want to be able to open the same file again within the same application session, you will have to remove the Package from the PackageStore. Closing the Package will release the file lock and allow you to delete the file, but you will not then be able to re-open that same file (or, more precisely, any file at that same location by the same name even if it has different content) until you remove the Package from the PackageStore.

    In the context of the code in the question, insert the following after the first previewWindow.ShowDialog(); before the 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);
    

    So the fixed code segment presented in the question becomes:

    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();
    

    Yes, I know I didn't open the XpsDocument with a Package - .NET did it "for" me behind the scenes and forgets to clean up after itself.