具有相同 UserControl 实例的 WPF FixedPage

WPF FixedPage with same instance of UserControl

首先,我不确定标题的措辞是否正确。 UserControls 是通过 ViewModel 添加的,我通过搜索 VisualTree 找到它们并将它们添加到 ObservableCollection<Grid>。我想做的是将我从 VisualTree 检索到的 UserControl 的每个实例打印到 FixedDocument 中,但每个 UserControl 都在一个页面上,直到它填满该页面并移至下一页。

这是我当前的打印按钮单击事件的代码:

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");
        }
    }

这是我尝试打印三个选项卡时的样子(用户控件托管在选项卡中): As you can see here it prints three separate pages

我希望所有三个都在一页上,如果我打印 10 个标签,那么它应该填满第一页并转到下一页。

上次问类似的问题被问到我写代码了吗。此代码的点点滴滴来自 Whosebug 上的类似 FixedDocument 问题,但它已被编辑到对我实际有效的程度。所以是的,我知道 foreach 中的 FixedPage 引用是导致创建三个单独页面的原因,我确实理解代码。

总结:

我想知道的是如何将每个 Tab 中的 UserControls 放到一个页面上,直到它填满,而不会出现 "Specified element is already the logical child of another element. Disconnect it first." 错误。

我最终使用 ItemsControlControlsObservableCollection 作为列表保存,并使用此 XpsDocumentWriter 方法从 ItemsControl 打印。

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(),显示一个 PrintDialog 以打印到任何打印机,而 WriteAllItems 显示一个对话框以另存为 PDF。