0

I have an App created in WPF using a plain MVVM framework based on this. At the end of the workflow I have a long view with information that I need to print to PDF (I will initially print it to XPS as it's the easiest way).

I have found that there will be issues with pagination, which can be fixed using FlowDocument as explained here.

My current issue is... If I have all the information in the view, and I don't access the view directly (because this is MVVM), how do I access the instance of the view that has all the information and feed it to the FlowDocument? How do I read the current instance of the view from my view model instance?

In short: I'd basically be thinking to read the instance of <Grid> ... </Grid> with all it's children and actual text etc,... as displayed in the App and feed this to FlowDocument... but how can I do it from the view-model?

Emmanuel DURIN
  • 4,803
  • 2
  • 28
  • 53
gbdavid
  • 1,639
  • 18
  • 40

2 Answers2

0

I would not try to read the view - too complicated.

And a view component can't have two parents.

I would better make a (many) UserControl(s) of the view that should go in XPS.

Then I would instanciate the same UserControl in the FlowDocument providing the same ViewModel as DataContext

Regards

Emmanuel DURIN
  • 4,803
  • 2
  • 28
  • 53
  • Hi, this looks like a good option... My view are user controls, but they are being instantiated directly in the root view using datatemplate and accessed that way from the view model. I just add ` ` then just access the view bindings from the view model... hmm not sure if this means I need a different setup to instantiate the view a different way (by code rather than in the XAML?) – gbdavid Oct 01 '17 at 16:33
  • Hope it is a good option for you ;-). I don't think the way the view is instanciated is really important. But the ViewModel should be the same. – Emmanuel DURIN Oct 01 '17 at 16:38
0

I would imagine that you would not need to use any visual object in order to print your document. Mainly because you may not not the page size or orientation that the end user will be using to print the document.

Below is a basic Print Manager I got from a book, and from this basic concept I have managed all of my printing needs.

using System.IO;
using System.Printing;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;

namespace MyTextEditor
{
    public class PrintManager
    {
        public static readonly int DPI = 96;
        private readonly RichTextBox _textBox;

        public PrintManager(RichTextBox textBox)
        {
            _textBox = textBox;
        }

        public bool Print()
        {

            PrintDialog dlg = new PrintDialog();

            if (dlg.ShowDialog() == true)
            {

                PrintQueue printQueue = dlg.PrintQueue;
                DocumentPaginator paginator = GetPaginator(
                    printQueue.UserPrintTicket.PageMediaSize.Width.Value,
                    printQueue.UserPrintTicket.PageMediaSize.Height.Value);

                dlg.PrintDocument(paginator, "TextEditor Printing");

                return true;
            }

            return false;
        }

        public DocumentPaginator GetPaginator(
            double pageWidth,
            double pageHeight)
        {
            TextRange originalRange = new TextRange(
                _textBox.Document.ContentStart,
                _textBox.Document.ContentEnd);

            MemoryStream memoryStream = new MemoryStream();
            originalRange.Save(memoryStream, DataFormats.Xaml);

            FlowDocument copy = new FlowDocument();

            TextRange copyRange = new TextRange(
                copy.ContentStart,
                copy.ContentEnd
                );

            copyRange.Load(memoryStream, DataFormats.Xaml);

            DocumentPaginator paginator = ((IDocumentPaginatorSource)copy).DocumentPaginator;

            return new PrintingPaginator(
                paginator,
                new Size(
                    pageWidth,
                    pageHeight),
                    new Size(
                        DPI,
                        DPI));



        }

        public void PrintPreview()
        {
            PrintPreviewDialog dlg = new PrintPreviewDialog(this);
            dlg.ShowDialog();
        }
    }
}

/// <summary>
/// This custom paginator wraps the original and adds some additional 
/// functionality. Notice that many of the properties pass through to
/// the orginal. The constructor begins by setting the original's page 
/// size to a size reflected by the requested size and margins and then
/// asks the underlying paginator to figure out how many pages we have.
/// 
/// The next function worth noting is the overide method GetPage. WPF 
/// calls this method each time it requires a page for display or print.  
/// </summary>
public class PrintingPaginator : DocumentPaginator 
{

    private readonly DocumentPaginator _originalPaginator;
    private readonly Size _pageSize;
    private readonly Size _pageMargin;

    public PrintingPaginator( DocumentPaginator paginator,
                              Size pageSize,
                              Size margin )
    {
        _originalPaginator = paginator;
        _pageSize = pageSize;
        _pageMargin = margin;


        _originalPaginator.PageSize = new Size(
            _pageSize.Width - _pageMargin.Width * 2,
            _pageSize.Height - _pageMargin.Height * 2);

        _originalPaginator.ComputePageCount();
    }


    public override bool IsPageCountValid
    {
        get
        {
            return _originalPaginator.IsPageCountValid;
        }
    }

    public override int PageCount
    {
        get
        {
            return _originalPaginator.PageCount;
        }
    }

    public override Size PageSize
    {
        get
        {
            return _originalPaginator.PageSize;
        }
        set
        {
            _originalPaginator.PageSize = value;
        }
    }

    public override IDocumentPaginatorSource Source
    {
        get
        {
            return _originalPaginator.Source;
        }
    }


    /// <summary>
    /// GetPage 
    /// 1. Obtain the original page form the original paginator
    /// 2. Wrap the page in a ContainerVisual, to make it easier manipulate.
    /// 3. Use a TranslateTransform to move the contents fo the ContainerVisual in line with the margins
    /// 4. Returns a new page object with its content and bleed adjusted in line with the margins
    /// </summary>
    /// <param name="pageNumber">
    /// Specifies the page number to be return by GetPage
    /// </param>
    /// <returns></returns>
    public override DocumentPage GetPage(int pageNumber)
    {
        DocumentPage originalPage = _originalPaginator.GetPage(pageNumber);
        ContainerVisual fixedPage = new ContainerVisual();

        fixedPage.Children.Add(originalPage.Visual);

        fixedPage.Transform = new TranslateTransform(
            _pageMargin.Width,
            _pageMargin.Height);

        return new DocumentPage(
                            fixedPage,
                            _pageSize,
                            AdjustForMargins(originalPage.BleedBox),
                            AdjustForMargins(originalPage.ContentBox)
                            );

    }

    private Rect AdjustForMargins(Rect rect)
    {
        if (rect.IsEmpty) return rect;
        else
        {
            return new Rect(
                            rect.Left + _pageMargin.Width,
                            rect.Top + _pageMargin.Height,
                            rect.Width,
                            rect.Height);
        }
    }



}