UPDATE 3
There's a bounty on this question now. The sample project and problem brief are at the bottom of the page under TL;DR; so save yourself some reading and skip to the end!
...
I've been trying to get this highly detailed, upvoted answer from JerryNixon working using a statically loaded page via NavigateToString. I don't see why this shouldn't work, but admittedly there are parts of the code that absolutely baffle me.
The original answer is here but I'll replicate the code here for completeness sake. Jerry provides the following sample / solution to those who want to print from a WebView
<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;
}
My only change is to remove the Source
attribute from the webview and add this line to the MainPage()
method
var html = await Windows.Storage.PathIO.ReadTextAsync("ms-appx:///Assets/sherlock.html");
MyWebView.NavigateToString(html);
Sherlock.html is a excerpt from the Arthur Conan Doyle story, The Red-Headed League, the full code of this HTML page is here - note that a segment near the end repeats several times. This was done in testing, I'll explain below
This sample initially worked for me, on a small file. (Sherlock.html without the padded chapters)
However when I get above a certain file-size (usually around 4000 words+) the following behaviour occurs with the sample.
The WebView shrinks to a very narrow view
(see update notes on why this is struck out)
and then the screen goes gray. (I feel a little silly posting a screenshot of a gray screen, but I'm trying to be thorough here)
The app does NOT crash. No debugging messages appear, no exceptions occur. It is as if the . It's not the splash screen (which I've set blue) and it's not the app background (darker gray). It's as if something else is loaded over the top of the page.Grid
is simply removed.
I've tried adjusting every part of the code, but I can't seem to isolate what causes the issue. Furthermore, parts of this code just confuse the hell out of me. For instance, in the Load_Completed
Method
MyWebViewRectangle.Fill = GetWebViewBrush(MyWebView);
MyPrintPages.ItemsSource = GetWebPages(MyWebView, new Windows.Foundation.Size(100d, 150d));
MyWebView.Visibility = Windows.UI.Xaml.Visibility.Visible;
Line1: MyWebViewRectangle
is filled with a shot of the WebView
. Why? This control is never referenced again. I guess it could be for the user to get a clearer view of the page, but it seems to serve no programmatic purpose
Line3: MyWebView.Visibility
is set to Visible
This happens again later, in GetWebViewBrush
.
var _OriginalVisibilty = webView.Visibility;
webView.Visibility = Windows.UI.Xaml.Visibility.Visible;
// and a few lines later...
webView.Visibility = _OriginalVisibilty;
As far as I can tell, the webview is never Collapsed
, yet it is set to Visible
three times.
If anyone can explain any of this to me (maybe even JerryNixon himself?) I would really appreciate it. I've spent several months working on an app and this is the last piece of the puzzle. I can't launch without the ability to print!
UPDATE
Made some discoveries this morning. I had been setting the width of the WebView
in XAML. Without this even the smallest files were failing. Based on this discovery I set the width in the html file itself to 800px, and it worked. Hooray, problem solved, right? Unfortunately, no. I tried with a longer file again (the full version of The Red-headed league, here) and the same issue is happening again. It's now gotten a lot weirder.
I inserted a breakpoint at webView.Height = _ContentHeight;
in the GetWebPages method. This shows me that _ContentHeight is exactly "13241".
However, inserting a breakpoint makes the code work. Once I hit the break and continue, it all seems to load. Although oddly again, the WebView is invisible until I mouseover the scrollbar.
If I remove the breakpoint, I get the grey screen again.
If I manually set _ContentHeight
to 13230, the page loads. This seems to be the magic number. Anything higher than this and the page fails. However, if I change the width of the html to less than 800px, it also fails. So logically, there's some kind of magic ratio of 800:13230 pixels (or 1:16.5375). If I go over this ratio, I set off the gray screen of death. I can disable the Red rectangle and restrict GetWebPages
to 3 pages, and the gray screen is still there. That tells me that the issue is with the WebView
itself
An issue that goes away if I set a breakpoint.
Also note the extra whitespace in MyWebViewRectangle
and extra blank pages in MyPrintPages
when it does work:
Note: I've also added NateDiamond's suggestions of the events LongRunningScriptDetected
, UnsafeContentWarningDisplaying
, and UnviewableContentIdentified
Here's the complete source of my sample project: https://onedrive.live.com/redir?resid=EE99EF9560A6740E!110509&authkey=!ACrZgm6uq5k5yck&ithint=file%2czip
UPDATE 2
Some success.
I put some headers into my sample file (found the "bottom" of each computed page by trial and error) and upped the page size so I could see the markers. Although there are 9 pages, only 5 and a small part of 6 are rendered. 9 Page objects are created by GetWebPages
, but only 5ish have rendered HTML content. The rest are empty
(Note, I changed the background colors as the red on blue was giving me a migraine)
I've gone through the code and made some modifications, and now I no longer get the gray screen at all. However, about 2 out of every 3 times I run it, only the first page is rendered, and it is cropped. This seems to be totally random. When 9 pages are rendered not all have content, as stated.
Here are my changes:
added new method Resize_WebView()
public void ResizeWebView()
{
OriginalHeight = MyWebView.Height;
var _WidthString = MyWebView.InvokeScript("eval", new[] { "document.body.scrollWidth.toString()" });
int _ContentWidth;
if (!int.TryParse(_WidthString, out _ContentWidth))
throw new Exception(string.Format("failure/width:{0}", _WidthString));
MyWebView.Width = _ContentWidth;
// ask the content its height
var _HeightString = MyWebView.InvokeScript("eval", new[] { "document.body.scrollHeight.toString()" });
int _ContentHeight;
if (!int.TryParse(_HeightString, out _ContentHeight))
throw new Exception(string.Format("failure/height:{0}", _HeightString));
MyWebView.Height = _ContentHeight;
}
in MyWebView_LoadCompleted
commented out the rectangle fill, added call to ResizeWebView():
void MyWebView_LoadCompleted(object sender, NavigationEventArgs e)
{
ResizeWebView();
//MyWebViewRectangle.Fill = GetWebViewBrush(MyWebView);
MyPrintPages.ItemsSource = GetWebPages(MyWebView, new Windows.Foundation.Size(300d, 450d));
}
in GetMyWebPages
changed where the brush is loaded:
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;
// Manually set _ContentHeight: comment out the above block and uncomment this to test.
// int _ContentHeight = 13230;
// webView.Height = _ContentHeight;*/
// how many pages will there be?
//var _Scale = page.Width / _ContentWidth;
var _Scale = page.Width / webView.Width; // now sourced directly from webview
//var _ScaledHeight = (_ContentHeight * _Scale);
var _ScaledHeight = (webView.Height * _Scale); // now sourced directly from webview
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 },
};
var _Brush = GetWebViewBrush(webView);
_Brush.Stretch = Stretch.UniformToFill;
_Brush.AlignmentY = AlignmentY.Top;
_Brush.Transform = _Page.Tag as TranslateTransform;
_Page.Fill = _Brush;
/*_Page.Loaded += async (s, e) =>
{
var _Rectangle = s as Windows.UI.Xaml.Shapes.Rectangle;
var _Brush = await GetMyWebViewBrush(webView);
_Brush.Stretch = Stretch.UniformToFill;
_Brush.AlignmentY = AlignmentY.Top;
_Brush.Transform = _Rectangle.Tag as TranslateTransform;
_Rectangle.Fill = _Brush;
};*/
_Pages.Add(_Page);
}
return _Pages;
}
removed now unnecessary resize code from GetWebView
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;
}
This is becoming a seriously epic question.
TL;DR;
Here is the updated project. https://onedrive.live.com/redir?resid=EE99EF9560A6740E!110545&authkey=!AMcnB_0xKRhYHIc&ithint=file%2czip
Load and observe. there are 9 pages in the document.
- GetWebPages occasionally loads only one page, cropped
- the rest of the time, GetWebPages loads pages 1-5, part of 6 and 3 blank pages
This is pretty much all you need to know to answer this question.
I've put a bounty on this question. I'm looking for resolution of this issue, and if possible a way to add padding to each page so that the text does not run to the edges.