5

EDIT: Completely rewrote question and added bounty.

Based on many tutorials and stackoverflow questions I can now:

  • Print multiple pages together as one document.
  • Print actual content.
  • Align content correctly on page.
  • Print correct size documents.

The solution requires the HTML document to have some white space in the bottom and the style tag html{ overflow:hidden; } - to hide the scrollbars and to allow scrolling to be used for pagination - but I can accept this.

The ONLY remaining problem is that WPF does not render the parts of the webbrowser that are off screen.

This means that if I tilt my computer screen I can print correctly, but if I do not the document cuts off the lower part.

I tried rendering to a bitmap, but when I print the resulting image as my visual the pages are empty.

If you know how to force WPF to fully render or how to correctly render to bitmap, please help me.

Print window XAML: (printing WPF only works on UI thread, otherwise nothing is rendered...)

<Window x:Class="CardLoader2000.PrintWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="PrintWindow" Height="1139" Width="820">
    <Grid x:Name="grid">
        <!--The alignment and size of the webbrowser is reflected in the print. If larger than document
        it will be cut. The width here corresponds to A4 paper width with a little margin-->
        <WebBrowser x:Name="webBrowser" Height="1089" Width="770" VerticalAlignment="Top" Margin="0,10,0,0"/>
    </Grid>
</Window>

Print window code-behind:

public partial class PrintWindow : Window
    {
        public PrintWindow(string path)
        {
            InitializeComponent();

            FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read);

            webBrowser.NavigateToStream(fs);

            ContentRendered += OnContentRendered;
        }

        private void OnContentRendered(object sender, EventArgs eventArgs)
        {
            PrintDialog pd = new PrintDialog
            {
                PrintTicket = new PrintTicket
                {
                    Duplexing = Duplexing.TwoSidedLongEdge,
                    OutputColor = OutputColor.Monochrome,
                    PageOrientation = PageOrientation.Portrait,
                    PageMediaSize = new PageMediaSize(794, 1122),
                    InputBin = InputBin.AutoSelect
                }
            };

            //Ok, final TODO: Page only renders what is on the PC screen...
            WebPaginator paginator = new WebPaginator(webBrowser, 1089, 1122, 794);

            pd.PrintDocument(paginator, "CustomerLetter");

            Close();
        }
    }

Custom paginator:

public class WebPaginator : DocumentPaginator
    {
        private readonly WebBrowser webBrowser;
        private readonly int pageScroll;
        private Size pageSize;

        public WebPaginator(WebBrowser webBrowser, int pageScroll, double pageHeight, double pageWidth)
        {
            this.webBrowser = webBrowser;
            this.pageScroll = pageScroll;
            pageSize = new Size(pageWidth, pageHeight);
        }

        public override DocumentPage GetPage(int pageNumber)
        {
            HTMLDocument htmlDoc = webBrowser.Document as HTMLDocument;
            if (htmlDoc != null) htmlDoc.parentWindow.scrollTo(0, pageScroll * pageNumber);
            Rect area = new Rect(pageSize);

            return new DocumentPage(webBrowser, pageSize, area, area);
        }

        public override bool IsPageCountValid
        {
            get { return true; }
        }

        /// <summary>
        /// Returns one less than actual length.
        /// Last page should be whitespace, used for scrolling.
        /// </summary>
        public override int PageCount
        {
            get
            {
                var doc = (IHTMLDocument2)webBrowser.Document;
                var height = ((IHTMLElement2)doc.body).scrollHeight;
                int tempVal = height*10/pageScroll;
                tempVal = tempVal%10 == 0
                    ? Math.Max(height/pageScroll, 1)
                    : height/pageScroll + 1;
                return tempVal > 1 ? tempVal-1 : tempVal;
            }
        }

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

        /// <summary>
        /// Can be null.
        /// </summary>
        public override IDocumentPaginatorSource Source
        {
            get
            {
                return null;
            }
        }
    }
Martin Clemens Bloch
  • 1,047
  • 1
  • 12
  • 28
  • Try this link. https://social.msdn.microsoft.com/Forums/vstudio/en-US/63ae5345-b2ca-45a9-9113-0ddc43e9925b/print-functionality-dialog-less-using-wpf-webbrowser-control?forum=wpf – Ayyappan Subramanian Mar 05 '15 at 22:54
  • Printing with IHTMLDocument2 also gives an empty document and always prompts the user, even if you set ShowUI to false in the execCommand call. – Martin Clemens Bloch Mar 05 '15 at 22:58
  • Is it an absolute requirement to print directly from the UI? I mean from the WebBrowser? Have you considered passing the same URL independently to a tool better designed for this purpose? Like maybe http://www.princexml.com/doc/dotnet/? Ultimately printing from a PDF will produce much more consistent results once it hits paper... – Daniel C Mar 13 '15 at 00:24
  • Well no and I'm beginning to realize that the webbrowser control approach is downright impossible. Its not a real WPF control, but a wrapped COM control. This means it will not do what you tell it to do. I managed to get it to think the screen is larger than it is, but since its a COM control it didn't render anyway - just black. I also tried a RenderRotateTransform, but the content in the control stays un-rotated. I will consider your link/solution, but I'm a bit tired of trying random things now. I will probably change my approach entirely. – Martin Clemens Bloch Mar 13 '15 at 07:47
  • I could't resolver that's it IHTMLDocument2. I'm using core 5.0 – Marinpietri Jun 20 '22 at 15:09

1 Answers1

9

You could use the standard IE's print feature (through the ExecWB method), like this:

public partial class PrintWindow : Window
{
    public PrintWindow()
    {
        InitializeComponent();
        webBrowser.Navigate("http://www.google.com");
    }

    // I have added a button to demonstrate
    private void Button_Click(object sender, RoutedEventArgs e)
    {
        // NOTE: this works only when the document as been loaded
        IOleServiceProvider sp = webBrowser.Document as IOleServiceProvider;
        if (sp != null)
        {
            Guid IID_IWebBrowserApp = new Guid("0002DF05-0000-0000-C000-000000000046");
            Guid IID_IWebBrowser2 = new Guid("D30C1661-CDAF-11d0-8A3E-00C04FC9E26E");
            const int OLECMDID_PRINT = 6;
            const int OLECMDEXECOPT_DONTPROMPTUSER = 2;

            dynamic wb; // will be of IWebBrowser2 type, but dynamic is cool
            sp.QueryService(IID_IWebBrowserApp, IID_IWebBrowser2, out wb);
            if (wb != null)
            {
                // note: this will send to the default printer, if any
                wb.ExecWB(OLECMDID_PRINT, OLECMDEXECOPT_DONTPROMPTUSER, null, null);
            }
        }
    }

    [ComImport, Guid("6D5140C1-7436-11CE-8034-00AA006009FA"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IOleServiceProvider
    {
        [PreserveSig]
        int QueryService([MarshalAs(UnmanagedType.LPStruct)] Guid guidService, [MarshalAs(UnmanagedType.LPStruct)]  Guid riid, [MarshalAs(UnmanagedType.IDispatch)] out object ppvObject);
    }
}
Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • My upvote, nice answer in my opinion (it can be called directly from webBrowser_LoadCompleted as per your note), I too would have suggested to use IE's print feature in this case, even if my personal preference goes to using a tool (like SharpDevelopReporting) and printing from PDF (as already written in the comments above) –  Mar 13 '15 at 19:26
  • This works, but will it print as duplex/two-sided? (I don't have access to my printer right now, only the XPS file printer) – Martin Clemens Bloch Mar 14 '15 at 10:26
  • It will print using the default printer and the default settings, whatever they are. – Simon Mourier Mar 14 '15 at 11:28
  • Yep works out of the box. copy pasted this and checked the printer defaults - no problems :) – Martin Clemens Bloch Mar 15 '15 at 16:44
  • Is there an answer like this where the default printer cannot be changed and required printer is not the default one? – Slate May 15 '17 at 14:52
  • And how can print to PDF without querying the pathfile? I mean using the standard Windows XPS/PDF printer... – tpgalan Feb 26 '18 at 13:38
  • @tpgalan - you should ask another question – Simon Mourier Feb 26 '18 at 15:34
  • I have followed your answer but What if I want to show Print Dialog instead of default print option it should ask to choose printer? can you please provide code snippet for that. – Ravi Mali Mar 05 '20 at 06:17
  • @RaviMali - this is a different, you should ask a question. – Simon Mourier Mar 05 '20 at 06:52