Xamarin.Forms UWP - Print Webview with pagination support - c#

I'm working on Xamarin.Forms based project, trying to print the webview content with pagination.
I've referred the following link already:
How do I print WebView content in a Windows Store App?
But unfortunately this way is not working with Xamarin.Forms because the way demonstrated in the above link is by filling the rectangle(windows shape) using webviewbrush (both the rectangle and webviewbrush are platform dependent controls to UWP). The problem is we can't use webviewbrush to draw rectangle(Xamarin Forms shape).
As a workaround I did the following:
created a boxview in xamarin forms PCL project
created a renderer for this boxview in UWP project(this will give us the windows rectangle shape) and then I did put this rectangle shape into one of the public static properties in PCL project's App.cs class to make it available for Webviewrenderer.cs class for filling this rectangle with webviewbrush.
I can able to access it from webviewrenderer.cs class from UWP project but the problem is the printer dialog shows an empty page.
Pagination works just fine as demonstrated in the above stack overflow link, but all pages are being empty.
Apparently the problem would be with rectangle. Because the same logic is just works fine if I create a native UWP project and the printer dialog shows the webpage as well.
Any help would be much appreciated.
Thanks in advance!

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.

Related

Does not display a logo Xamarin

public class SplashPage : ContentPage
{
Image splashImage;
public SplashPage()
{
NavigationPage.SetHasNavigationBar(this, false);
var sub = new AbsoluteLayout();
splashImage = new Image
{
Source = "Logo.png",
WidthRequest = 100,
HeightRequest = 100
};
AbsoluteLayout.SetLayoutFlags(splashImage, AbsoluteLayoutFlags.PositionProportional);
AbsoluteLayout.SetLayoutBounds(splashImage,new Rectangle(0.5,0.5,AbsoluteLayout.AutoSize,AbsoluteLayout.AutoSize));
sub.Children.Add(splashImage);
this.BackgroundColor = Color.FromHex("#429de3");
}
protected override async void OnAppearing()
{
base.OnAppearing();
await splashImage.ScaleTo(1, 2000);
await splashImage.ScaleTo(0.9, 1500,Easing.Linear);
await splashImage.ScaleTo(150, 1200, Easing.Linear);
Application.Current.MainPage = new NavigationPage(new Page1());
}
}
I recently started studying xamarin and decided to make a splash screen, everything seems to work, the screensaver itself is there, but there is no logo on the screensaver itself, although I threw it into the drawable folder for android and into resources in iOS.
The problem is that you haven't set the content of the page. You've added an image to the layout, but you haven't set the pages's Content to that layout.
Adding:
Content = sub;
will fix your problem.
If I could offer a couple of suggestions to help you debug this sort of thing in future.
A good way to test layout code is to set the background colour on individual elements. There's a great library called Debug Rainbows, that'll do a lot of the work for you.
Also, try and avoid using the more complex layouts, unless you've got a good reason to do so. A simple ContentView or StackLayout would suffice here.
The Xamarin Content Page's Content property is just a View, so there's no reason you could just set the page's content to a full screen image - if you wanted to.
Have fun.

System.InvalidOperationException: 'Cannot assign a native control without an Element;

I am working on a project "CoManga" and I wanted to add advertisements in it. Implementing ads on UWP seemed straight forward, like Android and iOS. However, I'm stuck now.
Anyways, I followed this tutorial by James Montemagno and added everything. I even see the test advertisements, which is great. However, when I try to move away from that page (when I press "BACK Button") and go to previous page, I get an error.
This is the error :
Setting up AdControlView in UWP throws System.InvalidOperationException: 'Cannot assign a native control without an Element; Renderer unbound and/or disposed. Please consult Xamarin.Forms renderers for reference implementation of OnElementChanged.'.
It is thrown at line number 50, where I set the SetNativeControl(adView);. I've commented it out right now, but as soon as I un-comment it, I see this error.
Can someone help me out here with this.
Setting up AdControlView in UWP throws System.InvalidOperationException: 'Cannot assign a native control without an Element; Renderer unbound and/or disposed. Please consult Xamarin.Forms renderers for reference implementation of OnElementChanged.
The reason is that xamarin Element has released but SetNativeControl invoked again cause the native control can't find the matched xamarin Element when page going back. So you could set a flag (isRegist) to record the registed ad.
public class AdViewRenderer : ViewRenderer<AdControlView, AdControl>
{
string bannerId = "test";
AdControl adView;
string applicationID = "3f83fe91-d6be-434d-a0ae-7351c5a997f1";
bool isRegist = false;
protected override void OnElementChanged(ElementChangedEventArgs<AdControlView> e)
{
base.OnElementChanged(e);
if (Control == null && isRegist != true)
{
CreateNativeAdControl();
SetNativeControl(adView);
isRegist = true;
}
}
private void CreateNativeAdControl()
{
if (adView != null)
return;
var width = 300;
var height = 50;
if (AnalyticsInfo.VersionInfo.DeviceFamily == "Windows.Desktop")
{
width = 728;
height = 90;
}
// Setup your BannerView, review AdSizeCons class for more Ad sizes.
adView = new AdControl
{
ApplicationId = applicationID,
AdUnitId = bannerId,
HorizontalAlignment = Windows.UI.Xaml.HorizontalAlignment.Center,
VerticalAlignment = Windows.UI.Xaml.VerticalAlignment.Bottom,
Height = height,
Width = width
};
}
}

WebView to WebViewBrush - XAML

I am trying to convert my WebView to a WebViewBrush in order to print it, in my UWP (C#/XAML) app.
I've set up my WebView, and the Brush such that when I click a button the WebView is hidden and the WebViewBrush gets displayed.
This is the XAML:
<WebView ext:Extension.HtmlString="{Binding Html}"
x:Name="saveWebView"
Grid.Row="1"
Grid.Column="0" />
<Rectangle Height="400" x:Name="saveWebViewBrush" />
When I click the button to show the Brush, basically it's only taking a snapshot of what was visible in the WebView. What I want is to take a snapshot of the entire WebView (and not the scrollbars either!).
The only other person I've seen attempt this was https://stackoverflow.com/a/17222629/2884981 - but unfortunately that's a few years old, and when I try that solution I get a million errors stemming from InvokeScript being obsolete and InvokeScriptAsync causes breaking changes.
The C# code when I press the button is this:
private async void OnButtonClick(object sender, RoutedEventArgs e)
{
//make the rectangle visible when you want something over the top of the web content
saveWebViewBrush.Visibility = Windows.UI.Xaml.Visibility.Visible;
//if the rectangle is visible, then hit the webview and put the content in the webviewbrush
if (saveWebViewBrush.Visibility == Windows.UI.Xaml.Visibility.Visible)
{
WebViewBrush b = new WebViewBrush();
b.SourceName = "saveWebView";
b.Redraw();
saveWebViewBrush.Fill = b;
saveWebView.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
}
}
If anyone knows how to convert this whole WebView to a WebView brush I'd be most grateful.
EDIT
To explain the "why", I am trying to print the contents of a WebView. It seems from what I have read that this is not possible, unless I convert it to a WebViewBrush. But if anyone has any alternative ideas I am all ears!
If anyone knows how to convert this whole WebView to a WebView brush I'd be most grateful.
To achieve this, you can try with following method:
private async void OnButtonClick(object sender, RoutedEventArgs e)
{
int width;
int height;
// get the total width and height
var widthString = await saveWebView.InvokeScriptAsync("eval", new[] { "document.body.scrollWidth.toString()" });
var heightString = await saveWebView.InvokeScriptAsync("eval", new[] { "document.body.scrollHeight.toString()" });
if (!int.TryParse(widthString, out width))
{
throw new Exception("Unable to get page width");
}
if (!int.TryParse(heightString, out height))
{
throw new Exception("Unable to get page height");
}
// resize the webview to the content
saveWebView.Width = width;
saveWebView.Height = height;
WebViewBrush b = new WebViewBrush();
b.SourceName = "saveWebView";
b.Redraw();
saveWebViewBrush.Fill = b;
}
Please note that WebViewBrush.Redraw method happens asynchronously. So to make sure we can get the complete snapshot, we'd better not hide the WebView or we can add some delay before hide the WebView like:
await Task.Delay(500);
saveWebView.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
Once we have the "saveWebViewBrush" Rectangle, we can refer to the Printing sample or this answer to print it.

Convert Xamarin.Forms StackLayout to Android View

I am trying to convert Xamarin.Forms stack layout to Android native View class to use it in IWindowManager.AddView method.
Conversion code is
static class ViewToAndroidViewConverter
{
public static Android.Views.View GetNativeView(this VisualElement view)
{
if (Platform.GetRenderer(view) == null)
Platform.SetRenderer(view, Platform.CreateRenderer(view));
var renderer = Platform.GetRenderer(view);
renderer.Tracker.UpdateLayout();
return renderer.ViewGroup;
}
}
And this is StackLayout that I am trying to convert
StackLayout testView = new StackLayout();
testView.Children.Add(new Label { Text = "Incoming call text" });
testView.Children.Add(new Label { Text = incomingNumber });
testView.BackgroundColor = Color.Green;
windowManager.AddView( testView.GetNativeView(),
new WindowManagerLayoutParams {
Title = "Call title sample",
Gravity = GravityFlags.Center,
Type = WindowManagerTypes.SystemAlert,
Height = 500,
Width = 1080
});
The problem is that this test view is shown, but without label contents (solid green rectangle only).
Is there any way to create an android native view from stack layout with full contents?

Resizing custom user control according to data in the webBrowser control docked in it

I have a webBrowser control named webBrowser1 that is added and docked as DockStyle.Full on a custom user control. The web-browser accepts some HTML text dynamically and displays it. I disabled the scroll bars of the webBrowser control. My problem is that whenever the content is somewhat lengthy, the webBrowser hides it from below. But the requirement of my project objective is that the webBrowser must not show either scroll bars or it should not hide some of the content. The content must be completely shown as it is without scrolling. That means the user control on which the webBrowser is docked must resize itself according to webBrowser's content. So, can anyone please suggest me how to achieve this? I searched all over the internet and SO but found nothing.
You can get the current size of HTML window via WebBrowser.Document.Window.Size and resize the container control accordingly. Depending on how your WebBrowser control content receives dynamic updates, you'd probably need to do this after each update. You could also try WebBrowser.Document.Body.ScrollRectangle if Document.Window.Size doesn't grow in the expected way.
[EDITED] The following code works for me (IE10):
private void Form1_Load(object sender, EventArgs e)
{
this.BackColor = System.Drawing.Color.DarkGray;
this.webBrowser.ScrollBarsEnabled = false;
this.webBrowser.Dock = DockStyle.None;
this.webBrowser.Location = new System.Drawing.Point(0, 0);
this.webBrowser.Size = new System.Drawing.Size(320, 200);
DownloadAsync("http://www.example.com").ContinueWith((task) =>
{
var html = task.Result;
MessageBox.Show(String.Format(
"WebBrowser.Size: {0}, Document.Window.Size: {1}, Document.Body.ScrollRectangle: {2}\n\n{3}",
this.webBrowser.Size,
this.webBrowser.Document.Window.Size,
this.webBrowser.Document.Body.ScrollRectangle.Size,
html));
this.webBrowser.Size = this.webBrowser.Document.Body.ScrollRectangle.Size;
}, TaskScheduler.FromCurrentSynchronizationContext());
}
async Task<string> DownloadAsync(string url)
{
TaskCompletionSource<bool> onloadTcs = new TaskCompletionSource<bool>();
WebBrowserDocumentCompletedEventHandler handler = null;
handler = delegate
{
this.webBrowser.DocumentCompleted -= handler;
// attach to subscribe to DOM onload event
this.webBrowser.Document.Window.AttachEventHandler("onload", delegate
{
// each navigation has its own TaskCompletionSource
if (onloadTcs.Task.IsCompleted)
return; // this should not be happening
// signal the completion of the page loading
onloadTcs.SetResult(true);
});
};
// register DocumentCompleted handler
this.webBrowser.DocumentCompleted += handler;
// Navigate to url
this.webBrowser.Navigate(url);
// continue upon onload
await onloadTcs.Task;
// the document has been fully loaded, can access DOM here
// return the current HTML snapshot
return ((dynamic)this.webBrowser.Document.DomDocument).documentElement.outerHTML.ToString();
}
To resize your usercontrol you first need to get the size needed by the content. This can be achived with TextRender.MeasureText, like so:
public static int GetContentHeight(string content, Control contentHolder, Font contentFont)
{
Font font = (contentFont != null) ? contentFont : contentHolder.Font;
Size sz = new Size(contentHolder.Width, int.MaxValue);
int padding = 3;
int borders = contentHolder.Height - contentHolder.ClientSize.Height;
TextFormatFlags flags = TextFormatFlags.WordBreak;
sz = TextRenderer.MeasureText(content, contentHolder.Font, sz, flags);
int cHeight = sz.Height + borders + padding;
return cHeight;
}
In your case it's a bit more tricky, as the text contains HTML-tags wich needs to be filtered away, to get the correct height.. I belive this can be achived with RegEx or a simple algo wich removes all content between < and > from a string.. You may also have to create special handlig for some HTML-tags (I.E Lists)

Categories