How to Print Webview Content in a Windows Store App

How do I print WebView content in a Windows Store App?

Sure, here you go.

First, you can resize the WebView to the actual content. Then, you scale the WebView back to the original size. It would require a script invoke and a ScaleTransform. Pretty simple, really.

Like this:

<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<WebView x:Name="MyWebView" Source="http://www.stackoverflow.com" />
</Grid>

void MyWebView_LoadCompleted(object sender, NavigationEventArgs e)
{
var _Original = MyWebView.RenderSize;

// ask the content its width
var _WidthString = MyWebView.InvokeScript("eval",
new[] { "document.body.scrollWidth.toString()" });
int _Width;
if (!int.TryParse(_WidthString, out _Width))
throw new Exception(string.Format("failure/width:{0}", _WidthString));

// ask the content its height
var _HeightString = MyWebView.InvokeScript("eval",
new[] { "document.body.scrollHeight.toString()" });
int _Height;
if (!int.TryParse(_HeightString, out _Height))
throw new Exception(string.Format("failure/height:{0}", _HeightString));

// resize the webview to the content
MyWebView.Width = _Width;
MyWebView.Height = _Height;

// scale the webview back to original height (can't do both height & width)
var _Transform = (MyWebView.RenderTransform as ScaleTransform)
?? (MyWebView.RenderTransform = new ScaleTransform()) as ScaleTransform;
var _Scale = _Original.Height / _Height;
_Transform.ScaleX = _Transform.ScaleY = _Scale;
}

This will result in a very tall, narrow WebView like this:

Sample Image

But that's not what you want.

Even though you can resize the resulting rectangle so that it is not so crazy shaped, the Print Contract in Windows 8 requires that you provide it with a single page. It does not do the pagination for you. As a result, what you really need is to retrieve the individual web site, one page at a time.

The first approach is the basis on how to do that. But you need to fix the size of the rectangle to the page size passed to you by Windows 8's Print task. This will be based on the user's printer selection. For example, Letter versus A4 (in UK). Then, using the Stretch property of the brush you can ensure it crops itself. Then, using the Transform property of the brush, you can slide it up and down inside the rectangle until it is revealing the page you want to print.

Here's how:

<Grid Background="Blue">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="995" />
<ColumnDefinition Width="300" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<WebView Grid.Column="0" x:Name="MyWebView" Source="http://www.stackoverflow.com" HorizontalAlignment="Right" />
<Rectangle Grid.Column="1" x:Name="MyWebViewRectangle" Fill="Red" />
<ScrollViewer Grid.Column="2" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<ItemsControl x:Name="MyPrintPages" VerticalAlignment="Top" HorizontalAlignment="Left">
<Rectangle Height="150" Width="100" Fill="White" Margin="5" />
<Rectangle Height="150" Width="100" Fill="White" Margin="5" />
<Rectangle Height="150" Width="100" Fill="White" Margin="5" />
<Rectangle Height="150" Width="100" Fill="White" Margin="5" />
<Rectangle Height="150" Width="100" Fill="White" Margin="5" />
</ItemsControl>
</ScrollViewer>
</Grid>

public MainPage()
{
this.InitializeComponent();
MyWebView.LoadCompleted += MyWebView_LoadCompleted;
}

void MyWebView_LoadCompleted(object sender, NavigationEventArgs e)
{
MyWebViewRectangle.Fill = GetWebViewBrush(MyWebView);
MyPrintPages.ItemsSource = GetWebPages(MyWebView, new Windows.Foundation.Size(100d, 150d));
MyWebView.Visibility = Windows.UI.Xaml.Visibility.Visible;
}

WebViewBrush GetWebViewBrush(WebView webView)
{
// resize width to content
var _OriginalWidth = webView.Width;
var _WidthString = webView.InvokeScript("eval",
new[] { "document.body.scrollWidth.toString()" });
int _ContentWidth;
if (!int.TryParse(_WidthString, out _ContentWidth))
throw new Exception(string.Format("failure/width:{0}", _WidthString));
webView.Width = _ContentWidth;

// resize height to content
var _OriginalHeight = webView.Height;
var _HeightString = webView.InvokeScript("eval",
new[] { "document.body.scrollHeight.toString()" });
int _ContentHeight;
if (!int.TryParse(_HeightString, out _ContentHeight))
throw new Exception(string.Format("failure/height:{0}", _HeightString));
webView.Height = _ContentHeight;

// create brush
var _OriginalVisibilty = webView.Visibility;
webView.Visibility = Windows.UI.Xaml.Visibility.Visible;
var _Brush = new WebViewBrush
{
SourceName = webView.Name,
Stretch = Stretch.Uniform
};
_Brush.Redraw();

// reset, return
webView.Width = _OriginalWidth;
webView.Height = _OriginalHeight;
webView.Visibility = _OriginalVisibilty;
return _Brush;
}

IEnumerable<FrameworkElement> GetWebPages(WebView webView, Windows.Foundation.Size page)
{
// ask the content its width
var _WidthString = webView.InvokeScript("eval",
new[] { "document.body.scrollWidth.toString()" });
int _ContentWidth;
if (!int.TryParse(_WidthString, out _ContentWidth))
throw new Exception(string.Format("failure/width:{0}", _WidthString));
webView.Width = _ContentWidth;

// ask the content its height
var _HeightString = webView.InvokeScript("eval",
new[] { "document.body.scrollHeight.toString()" });
int _ContentHeight;
if (!int.TryParse(_HeightString, out _ContentHeight))
throw new Exception(string.Format("failure/height:{0}", _HeightString));
webView.Height = _ContentHeight;

// how many pages will there be?
var _Scale = page.Width / _ContentWidth;
var _ScaledHeight = (_ContentHeight * _Scale);
var _PageCount = (double)_ScaledHeight / page.Height;
_PageCount = _PageCount + ((_PageCount > (int)_PageCount) ? 1 : 0);

// create the pages
var _Pages = new List<Windows.UI.Xaml.Shapes.Rectangle>();
for (int i = 0; i < (int)_PageCount; i++)
{
var _TranslateY = -page.Height * i;
var _Page = new Windows.UI.Xaml.Shapes.Rectangle
{
Height = page.Height,
Width = page.Width,
Margin = new Thickness(5),
Tag = new TranslateTransform { Y = _TranslateY },
};
_Page.Loaded += (s, e) =>
{
var _Rectangle = s as Windows.UI.Xaml.Shapes.Rectangle;
var _Brush = GetWebViewBrush(webView);
_Brush.Stretch = Stretch.UniformToFill;
_Brush.AlignmentY = AlignmentY.Top;
_Brush.Transform = _Rectangle.Tag as TranslateTransform;
_Rectangle.Fill = _Brush;
};
_Pages.Add(_Page);
}
return _Pages;
}

So the UI will be something like this, where the left column is the WebView, the second column (middle) is the all-in-one like our first solution, and the third, is a repeater showing the individual pages ready to print.

Sample Image

Of course the magic is really in the GetWebPages() method! I don't mind saying, it's a simple marvel made pretty easy by the way C# and Transforms work.

Please note, this is not complete. Yeah, it breaks up the page for you, but I can't be sure how much margin you want on your page. So the required tweaking is tiny, but I wanted to mention it. This is 98% of the code you will need to break up a WebView and prep if for the Windows 8 Print Task in response for the paginate event. Then pass the rectangles to it one at a time.

Having said that, this is probably the most comprehensive solution to this problem on the internet. :)

Best of luck!!

Xamarin.Forms UWP - Print Webview with pagination support

Pagination works just fine as demonstrated in the above stack overflow link, but all pages are being empty.

I have realized the basic printing feature according to your process. I placed the GetWebViewBrush method in the webviewrenderer. Unfortunately, the same issue occur in my side.

async Task<WebViewBrush> GetWebViewBrush(Windows.UI.Xaml.Controls.WebView webView)
{
// resize width to content
var _OriginalWidth = webView.Width;
var _WidthString = await webView.InvokeScriptAsync("eval",
new[] { "document.body.scrollWidth.toString()" });
int _ContentWidth;

if (!int.TryParse(_WidthString, out _ContentWidth))
throw new Exception(string.Format("failure/width:{0}", _WidthString));

webView.Width = _ContentWidth;

// resize height to content
var _OriginalHeight = webView.Height;

var _HeightString = await webView.InvokeScriptAsync("eval",
new[] { "document.body.scrollHeight.toString()" });
int _ContentHeight;

if (!int.TryParse(_HeightString, out _ContentHeight))
throw new Exception(string.Format("failure/height:{0}", _HeightString));

webView.Height = _ContentHeight;

// create brush
var _OriginalVisibilty = webView.Visibility;

webView.Visibility = Windows.UI.Xaml.Visibility.Visible;

var _Brush = new WebViewBrush
{
Stretch = Stretch.Uniform,
SourceName = webView.Name
};
// _Brush.SetSource(webView);

_Brush.Redraw();

// reset, return
webView.Width = _OriginalWidth;
webView.Height = _OriginalHeight;
webView.Visibility = _OriginalVisibilty;
return _Brush;
}

When I was debuging, I found webView.Name has never been set value, So I make a new Name property for the customWebView.

public static readonly BindableProperty NameProperty = BindableProperty.Create(
propertyName: "Name",
returnType: typeof(string),
declaringType: typeof(MyWebView),
defaultValue: default(string));

public string Name
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}

}

And set the Name property of native control(Windows.UI.Xaml.Controls.WebView) with the following code in OnElementChanged method.

if (e.NewElement != null)
{
Control.Name = (Element as MyWebView).Name;
Control.NavigationCompleted += Control_NavigationCompleted;
}

Surprisingly, the page is no longer empty.
Sample Image



Related Topics



Leave a reply



Submit