Search code examples
wpfxamlserializationflowdocumentopen-packaging-convention

Serializing WPF FlowDocuments to/from PackagePart (including images, etc.)


I want to be able to store multiple FlowDocuments within a single package, including images, etc. within each document. However, none of the methods I've seen for saving (and loading) Xaml FlowDocuments seem capable of this.

  • TextRange.Save with DataFormats.Xaml strips images and other embedded content
  • TextRange.Save with DataFormats.XamlPackage creates a whole new package, rather than allowing me to treat the document and included images as parts within the package I'd be storing it in
  • XamlWriter looks like it could be good for this, but I can't figure out how to find all the embedded objects for putting in their own parts (although I certainly know how to handle them once I've found them). On the other end, I haven't a clue how to make everything load properly later on.

It's pretty annoying that there's no one-stop way of serializing a FlowDocument and its images, etc. to a PackagePart. If anyone's figured out a good way of doing this, how'd you pull it off?

UPDATE 2011-07-03 00:22: Using XamlWriter and some extra code from this question I've been able to build a happy little OPC-compliant package which can hold multiple FlowDocuments including their images, as PackageParts. However, going the other way (from PackagePart to FlowDocument) is failing, because no matter how I try to load the document, I get XamlParseExceptions telling me that

'Initialization of 'System.Windows.Media.Imaging.BitmapImage' threw an exception.'

So, the question now becomes, how do I manhandle XamlReader.Load and/or my part's stream in order to get the related images loaded properly?


Solution

  • Figured it out. The solution is to manually process the Xaml document before handing it over to XamlReader. Images (and other elements stored as their own PackageParts) need to have the BitmapImage.UriSource property set to include the package Uri (for example, "./Image1.png" in /Content/Document.xaml to "pack://file:,,,C:,Projects,Package.pak/Content/Image1.png").

    Two caveats, however:

    1. There's an issue with PackUriHelper.Create(Uri,Uri) however. Instead of using

      PackUriHelper.Create(packUri, part.Uri))
      

      you have to use

      new Uri(packUri.ToString() + value)
      

      where value is part.Uri with the initial / removed. If you don't do this, instead of getting a proper Uri like above, you get one with an additional comma after the package file name, which confuses and annoys XamlReader.

    2. You need to use FileShare.Read when opening the package, as XamlReader will try and open it itself. By default, Package.Open locks out anyone else trying to open the package, and XamlReader.Load will throw a WebException if it can't get into the package itself.