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;
            }
        }
    }
5
  • Try this link. social.msdn.microsoft.com/Forums/vstudio/en-US/… Commented Mar 5, 2015 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. Commented Mar 5, 2015 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 princexml.com/doc/dotnet? Ultimately printing from a PDF will produce much more consistent results once it hits paper... Commented Mar 13, 2015 at 0: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. Commented Mar 13, 2015 at 7:47
  • I could't resolver that's it IHTMLDocument2. I'm using core 5.0 Commented Jun 20, 2022 at 15:09

1 Answer 1

9
+50

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);
    }
}
Sign up to request clarification or add additional context in comments.

9 Comments

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)
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)
It will print using the default printer and the default settings, whatever they are.
Yep works out of the box. copy pasted this and checked the printer defaults - no problems :)
Is there an answer like this where the default printer cannot be changed and required printer is not the default one?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.