I have a Metro App and I am attempting to Print the Content of a WebView
control. Using the MSDN Print Sample as my source reference. I simply change the XAML in the printableArea
as follows:
<RichTextBlock>
<Paragraph>
<InlineUIContainer>
<WebView Width="800" Height="2000" Source="http://www.stackoverflow.com"/>
</InlineUIContainer>
</Paragraph>
</RichTextBlock>
This works partially. The problem is that the Visible
area in the specified dimensions is Printed, i.e. The area that can be scrolled does not Print and also does not show up as Multiple Pages in the PrintPreview.
I'm almost there, would appreciate a little bit of help to get this to work as expected.
I haven't found any samples anywhere, which tackle this specific problem.
I have even tried the solutions here: http://social.msdn.microsoft.com/Forums/en-US/winappswithcsharp/thread/5edcb239-7a5b-49f7-87e3-e5a253b809c4
I am not the first to have experienced same/similar problem: http://social.msdn.microsoft.com/Search/en-US/?Refinement=112&query=print%20webview#refinementChanges=180&pageNumber=1&showMore=false
Willing to give anyone who can solve this problem a 100 point bounty. Would appreciate a walkthrough, sample code or mock project as a solution.
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:
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.
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!!