locked
Paginate and print an ItemsControl RRS feed

  • Question

  • I've had several goes at this over the past few months and it seems from this http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=1516229&SiteID=1 that others are having the same problem.

     

    I have an ItemsControl bound to a DataTable and need to print the items one to a page.  The ItemTemplate is set to a DataTemplate which formats and displays the DataRowView.

     

    I've tried several approaches (see below) but I always get the first page correctly and all subsequent pages are blank.  The items have all generated and if I call XamlWriter.Save for each item it outputs a ContentPresenter with the ContentTemplate showing my DataTemplate visual tree and the Content set to a DataRowView - but XpsViewer shows a blank page.

     

    How can I get it to print each ItemContainer?

     

    Thanks

    Michael

     

     

    Version 1:-

    public void SaveItemsTo(ItemsControl ic, String filename)

    {

          Package package = Package.Open(filename, FileMode.Create, FileAccess.ReadWrite);

          XpsDocument doc = new XpsDocument(package);

     

          XpsDocumentWriter xdw = XpsDocument.CreateXpsDocumentWriter(doc);

     

          // write visuals using a batch operation

          VisualsToXpsDocument collator = (VisualsToXpsDocument)xdw.CreateVisualsCollator();

     

          collator.BeginBatchWrite();

     

          foreach (object item in ic.Items)

          {

                Debug.WriteLine(item.ToString());

                UIElement elm = (UIElement)(ic.ItemContainerGenerator.ContainerFromItem(item));

                Visual myVisual = elm;

                collator.Write(myVisual);

          }

     

          collator.EndBatchWrite();

     

          doc.Close();

          package.Close();

    }

     

     

    Version 2:- (using DocumentPaginator)

    public void PrintItems(ItemsControl ic)

    {

          LocalPrintServer ps = new LocalPrintServer();

          PrintQueue pq = ps.DefaultPrintQueue;

          ItemsControlPaginator paginator = new ItemsControlPaginator(ic, pq);

          XpsDocumentWriter writer = PrintQueue.CreateXpsDocumentWriter(pq);

          writer.Write(paginator, pq.UserPrintTicket);

    }

     

    public class ItemsControlPaginator : DocumentPaginator

    {

          #region Fields and Constants

     

          private ItemsControl _ic;

          private PrintQueue      _pq;

          private PrintTicket     _pt;

          private Size            _pageSize;

     

          List<DocumentPage>      _pages;

     

          #endregion

     

          #region Contructors

     

          public ItemsControlPaginator(ItemsControl ic, PrintQueue pq)

          {

                _ic = ic;

                _pq = pq;

     

                if (_pq == null)

                      return;     //    no printer available

     

                // Get the media size.

                _pt = _pq.UserPrintTicket;

     

                _pageSize = new Size(_pt.PageMediaSize.Width.Value, _pt.PageMediaSize.Height.Value);

          }

     

          #endregion

     

          #region Overrides

     

          public override bool IsPageCountValid

          {

                get

                {

                      if (_pages == null)

                            EnsureItemsGenerated();

     

                      return true;

                }

          }

     

          public override int PageCount

          {

                get { return _pages.Count; }

          }

     

          public override Size PageSize

          {

                get { return _pageSize; }

                set { _pageSize = value; }

          }

     

          public override DocumentPage GetPage(int pageNumber)

          {

                return _pages[pageNumber];

          }

     

          public override IDocumentPaginatorSource Source

          {

                get { return null; }

          }

     

          #endregion

     

          private void EnsureItemsGenerated()

          {

                _pages = new List<DocumentPage>(_ic.Items.Count);

     

                IItemContainerGenerator generator = _ic.ItemContainerGenerator;

     

                // Get the generator position of the first data item

                int firstItemIndex = 0, lastItemIndex = _ic.Items.Count - 1;

                GeneratorPosition startPos = generator.GeneratorPositionFromIndex(firstItemIndex);

     

                using (generator.StartAt(startPos, GeneratorDirection.Forward, true))

                {

                      for (int itemIndex = firstItemIndex; itemIndex <= lastItemIndex; ++itemIndex)

                      {

                            bool newlyRealized;

     

                            // Get or create the child

                            UIElement child = generator.GenerateNext(out newlyRealized) as UIElement;

                            if (newlyRealized)

                            {

                                  Debug.WriteLine("generator.PrepareItemContainer");

                                  generator.PrepareItemContainer(child);

                            }

     

                            child.Measure(_pageSize);

                            child.Arrange(new Rect(0, 0, _pageSize.Width, _pageSize.Height));

                            child.UpdateLayout();

     

                            DebugSaveAsXaml(child, itemIndex);

     

                            _pages.Add(new DocumentPage(child));

                      }

                }

          }

     

          private void DebugSaveAsXaml(Object child, int pageNumber)

          {

                String filename = "Test.xaml";

                FileStream file = new FileStream(filename, FileMode.Append, FileAccess.Write);

                XamlWriter.Save(child, file);

                file.Close();

          }

    }

     

     

    Wednesday, June 27, 2007 9:00 PM

Answers

  • I finally figured it out.  The VisualsToXpsDocument collator doesn't like ContentPresenters.

     

    The workaround is to use VisualTreeHelper.GetChild() to get the root of the visual tree (as generated for the DataTemplate) and pass that to VisualsToXpsDocument.Write().

     

    I've attached a code sample below in case anyone else might find it useful.

     

    Michael

     

    public void PrintItems(ItemsControl ic)

    {

          //    Use default printer

          LocalPrintServer ps = new LocalPrintServer();

          PrintQueue pq = ps.DefaultPrintQueue;

          XpsDocumentWriter xdw = PrintQueue.CreateXpsDocumentWriter(pq);

          WriteAllItems(ic, xdw);

    }

     

    public void PrintItemsTo(ItemsControl ic, String jobName)

    {

          PrintDialog dlg = new PrintDialog();

          dlg.UserPageRangeEnabled = true;

          if (dlg.ShowDialog().GetValueOrDefault())

          {

                PageRange range = dlg.PageRange;

                //    range check - user selection starts from 1

                if (range.PageTo > ic.Items.Count)

                    range.PageTo = ic.Items.Count;

     

                dlg.PrintQueue.CurrentJobSettings.Description = jobName;

     

                XpsDocumentWriter xdw = PrintQueue.CreateXpsDocumentWriter(dlg.PrintQueue);

                if (dlg.UserPageRangeEnabled == false || range.PageTo < range.PageFrom)

                      WriteAllItems(ic, xdw);

                else

                      WriteSelectedItems(ic, xdw, range);

          }

    }

     

    private void WriteAllItems(ItemsControl ic, XpsDocumentWriter xdw)

    {

          PageRange range = new PageRange(1, ic.Items.Count);

          WriteSelectedItems(ic, xdw, range);

    }

     

    private void WriteSelectedItems(ItemsControl ic, XpsDocumentWriter xdw, PageRange range)

    {

          IItemContainerGenerator generator = ic.ItemContainerGenerator;

     

          // write visuals using a batch operation

          VisualsToXpsDocument collator = (VisualsToXpsDocument)xdw.CreateVisualsCollator();

     

          collator.BeginBatchWrite();

          if (WritePageRange(collator, generator, range))

                collator.EndBatchWrite();

    }

     

    private bool WritePageRange(VisualsToXpsDocument collator, IItemContainerGenerator generator, PageRange range)

    {

          //    Get the generator position of the first data item

          //    PageRange reflects user's selection starts from 1

          GeneratorPosition startPos = generator.GeneratorPositionFromIndex(range.PageFrom-1);

          //    If PageFrom > PageTo, print in reverse order

          //    UPDATE: never occurs; PrintDialog always 'normalises' the PageRange

          GeneratorDirection direction = (range.PageFrom <= range.PageTo) ? GeneratorDirection.Forward : GeneratorDirection.Backward;

     

          using (generator.StartAt(startPos, direction, true))

          {

                for (int numPages = Math.Abs(range.PageTo - range.PageFrom) + 1; numPages > 0; --numPages)

                {

                      bool newlyRealized;

     

                      // Get or create the child

                      UIElement next = generator.GenerateNext(out newlyRealized) as UIElement;

                      if (newlyRealized)

                      {

                            generator.PrepareItemContainer(next);

                      }

     

                      //    The collator doesn't like ContentPresenters and produces blank pages

                      //    for pages 2-n.  Finding the child of the ContentPresenter and writing

                      //    that solves seems to solve the problem

                      if (next is ContentPresenter)

                      {

                            ContentPresenter presenter = (ContentPresenter)next;

                            presenter.ApplyTemplate();    //    not sure if this is necessary

                            DependencyObject child = VisualTreeHelper.GetChild(presenter, 0);

                            if (child is UIElement)

                                  next = (UIElement)child;

                      }

     

                      try

                      {

                            collator.Write(next);

                      }

                      catch

                      {

                            //    if user prints to Microsoft XPS Document Writer

                            //    and cancels the SaveAs dialog, we get an exception.

                            return false;

                      }

                }

          }

     

          return true;

    }

    Friday, June 29, 2007 12:38 PM