3

I have written the following chunk of code that prints my ListBox perfectly when being sent to a physical printer, however when trying to send it to the XPS printer driver or using the XpsDocumentWriter class (I assume they use the same code under the hood) I receive the following exception:

System.ArgumentException was unhandled by user code Message=Width and Height must be non-negative. Source=ReachFramework StackTrace: at System.Windows.Xps.Serialization.VisualSerializer.WriteTileBrush(String element, TileBrush brush, Rect bounds)

The exception obviously points to an item not having a correct width/height however I have debugged the code when sending it to the different printers (physical and XPS driver) and I haven't been able to find any differences.

Below is how I create the visual to send to the printer:

private ScrollViewer GeneratePrintableView()
    {
        ScrollViewer scrollView = new ScrollViewer();

        Grid grid = new Grid { Background = Brushes.White, Width = this.myListBox.ActualWidth, Height = this.myListBox.ActualHeight };

        grid.RowDefinitions.Add(new RowDefinition());
        grid.RowDefinitions[0].Height = new GridLength(0, GridUnitType.Auto);
        grid.RowDefinitions.Add(new RowDefinition());
        grid.RowDefinitions[1].Height = new GridLength(0, GridUnitType.Auto);

        // Add the title and icon to the top
        VisualBrush titleClone = new VisualBrush(this.TitleBar);
        var titleRectangle = new Rectangle { Fill = titleClone, Width = this.TitleBar.ActualWidth, Height = this.TitleBar.ActualHeight };
        grid.Children.Add(titleRectangle);
        Grid.SetRow(titleRectangle, 0);

        this.myListBox.Width = this.myListBox.ActualWidth;
        this.myListBox.Height = this.myListBox.ActualHeight;

        VisualBrush clone = new VisualBrush(this.myListBox) { Stretch = Stretch.None, AutoLayoutContent = true };
        var rectangle = new Rectangle { Fill = clone, Width = this.myListBox.ActualWidth, Height = this.myListBox.ActualHeight };
        Border border = new Border { Background = Brushes.White, Width = this.myListBox.ActualWidth, Height = this.myListBox.ActualHeight };
        border.Child = rectangle;
        grid.Children.Add(border);
        Grid.SetRow(border, 1);

        scrollView.Width = this.myListBox.ActualWidth;
        scrollView.Height = this.myListBox.ActualHeight;
        scrollView.Content = grid;
        scrollView.VerticalScrollBarVisibility = ScrollBarVisibility.Hidden;

        return scrollView;
    }

Here is the GetPage override in my DocumentPaginator implementation:

public override DocumentPage GetPage(int pageNumber)
    {
        Page page = new Page();
        double z = 0.0;

        this.grid = new Grid();
        this.grid.RowDefinitions.Add(new RowDefinition());
        this.grid.RowDefinitions[0].Height = new GridLength(0, GridUnitType.Auto);

        this.grid.Children.Add(this.printViewer);
        Grid.SetRow(this.printViewer, 0);

        //Adjusting the vertical scroll offset depending on the page number
        if (pageNumber + 1 == 1) //if First Page
        {
            this.printViewer.ScrollToVerticalOffset(0);
            this.printViewer.UpdateLayout();
        }
        else if (pageNumber + 1 == _verticalPageCount) //if Last Page
        {
            if (this.printViewer.ScrollableHeight == 0) //If printing only single page and the contents fits only on one page
            {
                this.printViewer.ScrollToVerticalOffset(0);
                this.printViewer.UpdateLayout();

            }
            else if (this.printViewer.ScrollableHeight <= this.printViewer.Height) //If scrollconentheight is less or equal than scrollheight
            {
                this.printViewer.Height = this.printViewer.ScrollableHeight;
                this.printViewer.ScrollToEnd();
                this.printViewer.UpdateLayout();
            }
            else //if the scrollcontentheight is greater than scrollheight then set the scrollviewer height to be the remainder between scrollcontentheight and scrollheight
            {
                this.printViewer.Height = (this.printViewer.ScrollableHeight % this.printViewer.Height) + 5;
                this.printViewer.ScrollToEnd();
                this.printViewer.UpdateLayout();
            }
        }
        else //Other Pages
        {
            z = z + this.printViewer.Height;
            this.printViewer.ScrollToVerticalOffset(z);
            this.printViewer.UpdateLayout();
        }

        page.Content = this.grid; //put the grid into the page
        page.Arrange(new Rect(this.originalMargin.Left, this.originalMargin.Top, this.ContentSize.Width, this.ContentSize.Height));
        page.UpdateLayout();

        return new DocumentPage(page);
    }

Interestingly if I change the Fill of rectangle to a Brush instead of clone then I do not receive the exception and the outputted file is the correct size.

I have spent over a day trying to debug why this isn't working and I am hoping that someone out there has either seen a similar issue or is able to point out any mistakes I am making.

Thanks for any responses.

Bijington
  • 3,661
  • 5
  • 35
  • 52

4 Answers4

2

I had to give up finding a solution with VisualBrush. If there is a GroupBox in the Visual for the brush I could never get it to produce a XPS file. It always fails with

System.ArgumentException was unhandled by user code Message=Width and Height must be non-negative. Source=ReachFramework StackTrace: at System.Windows.Xps.Serialization.VisualSerializer.WriteTileBrush(String element, TileBrush brush, Rect bounds)

The workaround was to clone the content that should go in the VisualBrush (Is there an easy/built-in way to get an exact copy (clone) of a XAML element?) and use that directly in a Grid instead of an VisualBrush

Hans Karlsen
  • 2,275
  • 1
  • 15
  • 15
  • There certainly do seem to be some nuances around this area. We ended up using Teleriks library to help with rendering our to PDF – Bijington Aug 26 '17 at 19:18
1

Have you checked the value of ActualWidth and ActualHeight of myListBox when the VisualBrush is being created? I don't know from where myListBox comes, but if it is not rendered by the time you are generating your xps document you may run into problems. You can try to manually force the control to render and see if it makes any difference.

Community
  • 1
  • 1
Arthur Nunes
  • 6,718
  • 7
  • 33
  • 46
  • I can confirm that the ActualHeight and ActualWidth properties are both valid values at the time the VisualBrush has been created and myListBox is displayed in the Application at the time of printing so it is definitely rendered. I have tried displayed the rectangle that I put the VisualBrush in and that works fine. The part that is confusing me is that the code works fine when sending it to a physical printer but not when sending it to the XPS driver. – Bijington Dec 18 '12 at 16:35
  • Have you tried the RenderTargetBitmap approach suggested in the link in the answer? Maybe the issue is due to using the VisualBrush. – Arthur Nunes Dec 18 '12 at 16:41
  • Thank you for the suggestion however I would like to find an approach that doesn't use RenderTargetBitmap, our original approach used this and we found issues with blurry output plus the application would run out of memory. – Bijington Dec 19 '12 at 09:15
1

I was unable to rectify the problem however using this link Paginated printing of WPF visuals I was able to find a suitable solution to allow printing of complicated visuals within my WPF application.

Bijington
  • 3,661
  • 5
  • 35
  • 52
1

It's 2016 now and it's still not fixed. The problem is using TileBrush or any descendant type (VisualBrush in your case). If you use absolute mapping, it works, it's the relative mapping that causes the problem. Calculate the final size yourself and set Viewport to this size, ViewportUnits to Absolute. Also make sure you don't use Stretch.

Gábor
  • 9,466
  • 3
  • 65
  • 79
  • We actually switched to using a third party library that printed to PDF in the end as the customer changed their mind (thankfully) about the file format they wanted output. I'll keep this in mind though for any future printing. – Bijington Nov 21 '16 at 11:30
  • I happen to need both. :-) Actually, with the .NET source code now open, you can check out the `WriteTileBrush()` function that goes wrong. There is a comment there for another fix but this one must have slipped through. Although not obvious from the source code where exactly the problem is (it creates several `Rect`s and the actual size depends on the input), it sure gave me the idea to try to work around those calculations somehow. – Gábor Nov 21 '16 at 12:04