简体   繁体   中英

WPF FixedPage with same instance of UserControl

First off, I'm not sure if I phrased the title correctly. There are UserControls which are added via a ViewModel and I find them by searching the VisualTree and add them to a ObservableCollection<Grid> . What I would like to do is Print each instance of the UserControl that I retrieve from the VisualTree into a FixedDocument but with each UserControl being on a single page until it fills that page and moves on to the next page.

Here is the code for my current Print Button Click Event:

private async void btnPrint_Click(object sender, RoutedEventArgs e)
    {
        try
        {
            if (tabMain.Items.Count > 0)
            {
                tab = new TabLayout();

                tab.Payslip = new ObservableCollection<Grid>();


                foreach (var grid in FindVisualChildren<Grid>(this))
                {
                    if (grid.Name == "pdfFile")
                    {
                        tab.Payslip.Add(grid);
                    }
                }

                FrameworkElement toPrint = new FrameworkElement();
                PrintDialog printDialog = new PrintDialog();

                PrintCapabilities capabilities = printDialog.PrintQueue.GetPrintCapabilities(printDialog.PrintTicket);
                Size pageSize = new Size(printDialog.PrintableAreaWidth, printDialog.PrintableAreaHeight);
                Size visibleSize = new Size(capabilities.PageImageableArea.ExtentWidth, capabilities.PageImageableArea.ExtentHeight);
                FixedDocument fixedDoc = new FixedDocument();


                StackPanel panel = new StackPanel(); //was trying to stack them in a stackpanel first but it threw an exception about same instance of usercontrol blah blah...


                foreach (var doc in tab.Payslip.ToList())
                {

                    double yOffset = 0;

                    doc.Measure((new Size(double.PositiveInfinity, double.PositiveInfinity)));
                    doc.Arrange(new Rect(new Point(0, 0), doc.DesiredSize));

                    Size size = doc.DesiredSize;


                    while (yOffset < size.Height)
                    {
                        VisualBrush vb = new VisualBrush(doc);
                        vb.Stretch = Stretch.None;
                        vb.AlignmentX = AlignmentX.Left;
                        vb.AlignmentY = AlignmentY.Top;
                        vb.ViewboxUnits = BrushMappingMode.Absolute;
                        vb.TileMode = TileMode.None;
                        vb.Viewbox = new Rect(0, yOffset, visibleSize.Width, visibleSize.Height);

                        FixedPage page = new FixedPage();
                        PageContent pageContent = new PageContent();
                        ((IAddChild)pageContent).AddChild(page);
                        fixedDoc.Pages.Add(pageContent);
                        page.Width = fixedDoc.DocumentPaginator.PageSize.Width;
                        page.Height = fixedDoc.DocumentPaginator.PageSize.Height;

                        Canvas canvas = new Canvas();
                        FixedPage.SetLeft(canvas, capabilities.PageImageableArea.OriginWidth);
                        FixedPage.SetTop(canvas, capabilities.PageImageableArea.OriginHeight);
                        canvas.Width = visibleSize.Width;
                        canvas.Height = visibleSize.Height;
                        canvas.Background = vb;
                        page.Children.Add(canvas);
                        yOffset += visibleSize.Height;
                    }
                }
                //printDialog.PrintDocument(fixedDoc.DocumentPaginator, "");
                ShowPrintPreview(fixedDoc);
            }
        }
        catch(Exception ex)
        {
            var exceptionDialog = new MessageDialog
            {
                Message = { Text = ex.ToString() }
            };

            await DialogHost.Show(exceptionDialog, "RootDialog");
        }
    }

This is how it looks when I try printing three Tabs (UserControls are hosted in Tabs): As you can see here it prints three separate pages

I want all three on one page and if I print 10 tabs, then it should fill the first page and go on to the next page.

The last time I asked a similar question I was questioned on whether I wrote the code. Bits and pieces of this code came from similar FixedDocument questions on StackOverflow but it has been edited to the point where it actually works for me. So yes I know that the FixedPage reference inside the foreach is whats causing the creation of the three separate pages and I do understand the code.

Summary:

What I want to know is how to get the UserControls from each Tab , onto a single page until its full, without getting the "Specified element is already the logical child of another element. Disconnect it first." error.

I ended up using ItemsControl to hold my ObservableCollection of Controls as a list and print from the ItemsControl using this XpsDocumentWriter method.

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.UpdateLayout();
                    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;
    }

PrintItemsTo(), displays a PrintDialog to print to any printer while WriteAllItems displays a Dialog to save as PDF.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM