Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Silent print HTML file in C# using WPF

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;
            }
        }
    }
like image 858
Martin Clemens Bloch Avatar asked Mar 05 '15 22:03

Martin Clemens Bloch


1 Answers

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);
    }
}
like image 88
Simon Mourier Avatar answered Oct 18 '22 14:10

Simon Mourier