Here is a sample of the multi-page xps document generation code. I tried a wide variety of techniques, some more elegant than others.
Method 1: Single Xps.DocumentWriter.Write(Visual)
This approach is by far the most elegant and would be perfect if it actually worked as I expect it to. However, everything that I am seeing is that this will not automatically paginate the visual that you pass into your document.
Package package = Package.Open(documentStream, FileMode.Create); XpsDocument xpsDoc = new XpsDocument(package); XpsDocumentWriter xpsWriter = XpsDocument.CreateXpsDocumentWriter(xpsDoc); xpsWriter.Write(element); xpsDoc.Close(); package.Close();
Result: All content displays correctly but appears to run off the page. The document is always a single page.
Method 2: Manual FixedPage Document Construction
This technique is probably the most work in terms of code generation. It uses the structures that you will actually see in an XPS document’s XAML file. Manually constructing these is obviously very tedious and error prone, therefore not an ideal solution. Nevertheless, I wanted to try it out to see if I could break through the 1 page barrier.
#region FixedPage Sequence Package package = Package.Open(documentStream, FileMode.Create); XpsDocument xpsDoc = new XpsDocument(package); XpsDocumentWriter xpsWriter = XpsDocument.CreateXpsDocumentWriter(xpsDoc); // create the empty XPS Document objects FixedDocumentSequence fixedDocSeq = new FixedDocumentSequence(); #region Page 1 DocumentReference docRef1 = new DocumentReference(); FixedDocument fixedDoc1 = new FixedDocument(); PageContent pageContent1 = new PageContent(); FixedPage fixedPage1 = new FixedPage(); // add the visual object the FixedPage element.AllContent.Children.Remove(element.Page1Content); fixedPage1.Children.Add(element.Page1Content); // add the FixedPage to the PageContent collection pageContent1.BeginInit(); ((IAddChild)pageContent1).AddChild(fixedPage1); pageContent1.EndInit(); // add the page content to the document reference collection ((IAddChild)fixedDoc1).AddChild(pageContent1); docRef1.BeginInit(); docRef1.SetDocument(fixedDoc1); docRef1.EndInit(); #endregion #region Page 2 DocumentReference docRef2 = new DocumentReference(); FixedDocument fixedDoc2 = new FixedDocument(); PageContent pageContent2 = new PageContent(); FixedPage fixedPage2 = new FixedPage(); element.AllContent.Children.Remove(element.Page2Content); // add the visual object the FixedPage fixedPage1.Children.Add(element.Page2Content); // add the FixedPage to the PageContent collection pageContent1.BeginInit(); ((IAddChild)pageContent2).AddChild(fixedPage2); pageContent1.EndInit(); // add the page content to the document reference collection ((IAddChild)fixedDoc2).AddChild(pageContent2); docRef2.BeginInit(); docRef2.SetDocument(fixedDoc2); docRef2.EndInit(); #endregion // add the document reference to the FixedDocumentSequence (fixedDocSeq as IAddChild).AddChild(docRef1); (fixedDocSeq as IAddChild).AddChild(docRef2); // write the fixed document sequence as an XPS Document // using the XpsDocumentWriter created earlier xpsWriter.Write(fixedDocSeq); // and close the document xpsDoc.Close(); package.Close(); #endregion
Result: This technique resulted in the content from page 1 and page 2 overlapping each other on the first page of the document, with the second page remaining blank. At least we got a second page this time! But why is the content not being rendered there?
Method 3: VisualsToXpsDocument batch writes
This is a little more elegant than Method #2 but still not as clean as the single operation approach of Method #1. According to the documentation that I found, this is the recommended approach for multi-page document generation.
Package package = Package.Open(documentStream, FileMode.Create); XpsDocument xpsDoc = new XpsDocument(package); XpsDocumentWriter xpsWriter = XpsDocument.CreateXpsDocumentWriter(xpsDoc); VisualsToXpsDocument visWriter = xpsWriter.CreateVisualsCollator() as VisualsToXpsDocument; List<UIElement> elementsToWrite = new List<UIElement>(); FrameworkElement page1 = element.Page1Content; element.AllContent.Children.Remove(page1); FrameworkElement page2 = element.Page2Content; element.AllContent.Children.Remove(page2); UserControl uc1 = new UserControl(); uc1.DataContext = element.DataContext; uc1.Content = page1; uc1.UpdateLayout(); UserControl uc2 = new UserControl(); uc2.DataContext = element.DataContext; uc2.Content = new Rectangle() { Fill = Brushes.Red, Width = 100, Height = 100 }; uc2.UpdateLayout(); elementsToWrite.Add(uc1); elementsToWrite.Add(uc2); visWriter.BeginBatchWrite(); foreach (UIElement subElement in elementsToWrite) { visWriter.Write(subElement); } visWriter.EndBatchWrite(); xpsDoc.Close(); package.Close();
Result: With this approach my first page was pristine—all the data bindings fired and the layout was accurate but again, from the 2nd page and on we get nothing but blank white pages.
THE PROBLEM: How do I get more than one page to render?
THE SOLUTION: Coming Up…
It seems like this is a lot harder than it should be. I documented my adventures in multi-page xps generation (with databinding) here:http://www.bradcurtis.com/2010/02/06/document-and-report-generation-using-xaml-wpf-databinding-and-xps/
LikeLike
I think this article can be of benefit for creating multipage XPS docs using VisualsToXpsDocument and the explanation of why pages 2 onwards are blank.
http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/86fe1c78-ad44-4324-9c98-3aa0881f6432/
LikeLike
Method 2 goes haywire because you are using the wrong FixedPage:
fixedPage1.Children.Add(element.Page2Content);
should be:
fixedPage2.Children.Add(element.Page2Content);
There are a number of subsequent locations in the code where “1” was not correctly replaced with “2”.
LikeLike
Where’s part 2?
LikeLike
Method 3 WILL work for page 2 and on, if you will run this method per UIElement:
subElement.Arrange(new Rect(new Size(subElement.ActualWidth, subElement.ActualHeight)));
visWriter.Write(subElement);
LikeLike
necessary cast to FrameworkElement is a must, of course.
LikeLike