Following the answer in the following link
"
Xamarin WebView scale to fit "
I've managed to fit the html live stream to the phone screen while it is in portrait mode. The result in portrait is acceptable. But when the screen rotates to landscape mode it exceeds the limits of the screen and in order to view the whole stream it requires for the user to scroll. I've found the following topic but it does not seem to work
Xamarin webview does not fill screen
What needs to be done in order for the stream to fit without scrolling. I feel that its simply. Do I follow the correct path?
I am using a custom renderer for the WebView
public class CustomWebViewRenderer : WebViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
if (Control != null)
{
Control.Settings.BuiltInZoomControls = true;
Control.Settings.DisplayZoomControls = false;
Control.Settings.SetSupportZoom(true);
Control.Settings.LoadWithOverviewMode = true;
Control.Settings.UseWideViewPort = true;
}
/*
* The code for the second link
*
* Control.Settings.JavaScriptEnabled = true;
Control.EvaluateJavascript("document.documentElement.style.height = screen.height + 'px';", null);
Control.EvaluateJavascript("document.documentElement.style.width = screen.width + 'px';", null);
*/
}
}
The xaml code of the page is
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
NavigationPage.HasNavigationBar="False"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:androidclient="clr-namespace:AndroidClient"
x:Class="AndroidClient.LiveViewPage">
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<Button x:Name="ExitFullScreenBtn" Clicked="ExitFullScreenBtn_Clicked"
HorizontalOptions="End" VerticalOptions="Start" WidthRequest="50"
Text="X"></Button>
<androidclient:CustomWebView x:Name="Browser" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand"
></androidclient:CustomWebView>
</StackLayout>
</ContentPage>
The c# for the same page
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class LiveViewPage : ContentPage
{
public LiveViewPage()
{
InitializeComponent();
var url = "http://192.168.1.7:8080/";
Browser.Source = url; }
private void ExitFullScreenBtn_Clicked(object sender, EventArgs e)
{
Console.WriteLine("Closed Live");
Navigation.PopAsync();
}
}
Related
I'm converting an UWP application to Xamarin in the aim to use it on both Android and Windows devices.
I never used Xamarin before and I assume i'm doing a beginer mistake.
On my MainPage everything is OK :
But when I'm clicking on the "Options" Button it didn't load a new page exept a grey banner in the upper part of the window :
To navigate from one page to another I followed this explanation : Microsoft doc navigation
There's my code to change page in the MainPage.xaml.cs :
async void ButtonOptions_Click(object sender, EventArgs args)
{
try
{
button_options.Source = "Assets/Option_Icon_1.png";
Application.Current.MainPage = new NavigationPage(new MainPage());
var OptionsView = new OptionsView();
await Device.InvokeOnMainThreadAsync(() => Navigation.PushAsync(OptionsView, true));
//this.Frame.Navigate(typeof(StorageView1));
}
catch (Exception ex) { InterpretException("MainPage.ButtonStorage_Click()", ex); }
}
And my OptionsView.xaml :
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Stock_Manager_Xamarin.OptionsView"
Title="Second Page">
<ContentPage.Content>
<StackLayout HorizontalOptions="Center" VerticalOptions="Center">
<StackLayout Orientation="Horizontal">
<Label Text="Name:" HorizontalOptions="FillAndExpand" />
<Label Text="{Binding Name}" FontSize="Medium" FontAttributes="Bold" />
</StackLayout>
<Button x:Name="navigateButton" Text="Previous Page"/>
</StackLayout>
</ContentPage.Content>
I tried different version of the xaml code without any change and I can't find a working sample.
Could someone can explain me where I'm doing a mistake ?
Wrap current page inside navigation stack in App.cs, and set it as MainPage, so that we can do the navigation operation .
public App()
{
InitializeComponent();
MainPage = new NavigationPage(new MainPage());
}
As Jason mentioned , it is no need to set MainPage again , just navigate directly , modify your code as below .
void ButtonOptions_Click(object sender, EventArgs args)
{
try
{
button_options.Source = "Assets/Option_Icon_1.png";
var OptionsView = new OptionsView();
Navigation.PushAsync(OptionsView, true);
}
catch (Exception ex) { InterpretException("MainPage.ButtonStorage_Click()", ex); }
}
I've made a Custom webview renderer for iOS where I manually replace a custom WebView with a WKWebView. However, my main purpose is to make the WKWebView adjust it's height according to it's contents. It's contents consist of predefined HTML string with body.
Right now it cuts the Webview off and the rest of the content is crammed up in a scrollview. The images and text that I put before the webview also stay fixed in place, while I want them to scroll down too. I do not want a scrollbar for the webview, I just want the webview to be true size to it's contents so that the user can swipe up to look at the entire text.
CustomWebViewRenderer.cs
[assembly: ExportRenderer(typeof(PostWebView), typeof(Yoors.iOS.CustomWebViewRenderer))]
namespace Yoors.iOS
{
public class CustomWebViewRenderer : ViewRenderer<PostWebView, WKWebView>
{
WKWebView _wkWebView;
protected override void OnElementChanged(ElementChangedEventArgs<PostWebView> e)
{
base.OnElementChanged(e);
if (Control == null)
{
WKWebViewConfiguration config = new WKWebViewConfiguration();
_wkWebView = new WKWebView(Frame, config);
SetNativeControl(_wkWebView);
}
if (e.NewElement != null)
{
HtmlWebViewSource source = (Xamarin.Forms.HtmlWebViewSource)Element.Source;
string headerString = "<header><meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0'></header>";
string html= headerString + source.Html;
Console.WriteLine("Height" + Element);
_wkWebView.LoadHtmlString(html, baseUrl: null);
_wkWebView.ScrollView.ScrollEnabled = false;
_wkWebView.SizeToFit();
}
}
}
PostWebView.xaml
<?xml version="1.0" encoding="UTF-8"?>
<WebView xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="Yoors.Views.Templates.PostWebView" x:Name="WebViewer" BackgroundColor="White" Margin="0, 10, 0, 0" VerticalOptions="FillAndExpand" HeightRequest="1000">
<WebView.Source>
<HtmlWebViewSource Html="{Binding Data.Content}" />
</WebView.Source>
</WebView>
PostView.xaml
<ContentPage.Content>
<ScrollView>
<StackLayout BackgroundColor="White" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
<!-- Header of the post-->
<Image HeightRequest="125" Aspect="AspectFill" Source="{Binding Post.ImageUrl}" />
<StackLayout BackgroundColor="White" Padding="1" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand" Margin="10, 0,10, 0">
<Label Text="{Binding Post.Title}" FontFamily="{StaticResource BoldFont}" FontSize="20" />
<Label Text="{Binding Post.CreatedOn.DisplayText}" FontSize="12" />
<!-- Content of the post in a HTML view-->
</StackLayout>
<templates:PostWebView VerticalOptions="FillAndExpand" BindingContext="{Binding Post}">
</templates:PostWebView>
<templates:CommentView BindingContext="{Binding CommentsViewModel}">
<x:Arguments>
<ListViewCachingStrategy>RecycleElement</ListViewCachingStrategy>
</x:Arguments>
</templates:CommentView>
</StackLayout>
</ScrollView>
</ContentPage.Content>
I thought placing VerticalOptions= "FillAndExpand" for the PostWebView, would make it fit the size of it's contents, without creating a scollbar and therefore not fixing the other contents in place, but it just does this: Does anyone know how to help?
I answered my own question, by making a CustomNavigationDelegate that registers when the Webview has finished loading.
CustomNavigationDelegate.cs
public class CustomWKNavigationDelegate : WKNavigationDelegate
{
CustomWebViewRenderer _webViewRenderer;
public CustomWKNavigationDelegate(CustomWebViewRenderer webViewRenderer)
{
_webViewRenderer = webViewRenderer;
}
public override async void DidFinishNavigation(WKWebView webView, WKNavigation navigation)
{
var wv = _webViewRenderer.Element as PostWebView;
if (wv != null)
{
await System.Threading.Tasks.Task.Delay(100); // wait here till content is rendered
wv.HeightRequest = (double)webView.ScrollView.ContentSize.Height;
}
}
}
Then in the OnElementChanged of the CustomWebViewRenderer i showed above I assign the Navigation Delegate to the WKWebView.
CustomWebViewRenderer.cs
...
protected override void OnElementChanged(ElementChangedEventArgs<PostWebView> e)
{
base.OnElementChanged(e);
if (Control == null)
{
WKWebViewConfiguration config = new WKWebViewConfiguration();
_wkWebView = new WKWebView(Frame, config);
_wkWebView.NavigationDelegate = new CustomNavigationDelegate(this);
...
This will make the WKWebView have the size of it's contents !!
Previously I had white screen in webview area and also dynamic height was not working. But now this works fine! Also posted here - https://github.com/xamarin/Xamarin.Forms/issues/1711
[assembly: ExportRenderer(typeof(ExtendedWebView), typeof(ExtendedWebViewRenderer))]
namespace Project.iOS.Renderers
{
class ExtendedWebViewRenderer : WkWebViewRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
try
{
base.OnElementChanged(e);
NavigationDelegate = new ExtendedWKWebViewDelegate(this);
// For fixing white flash issue in webview on dark themes/backgrounds and disable webview's scrolling
if (NativeView != null)
{
var webView = (WKWebView)NativeView;
webView.Opaque = false;
webView.BackgroundColor = UIColor.Clear;
webView.ScrollView.ScrollEnabled = false;
}
}
catch (Exception ex)
{
Console.WriteLine("Error at ExtendedWebViewRenderer OnElementChanged: " + ex.Message);
}
}
}
class ExtendedWKWebViewDelegate : WKNavigationDelegate
{
ExtendedWebViewRenderer webViewRenderer;
public ExtendedWKWebViewDelegate(ExtendedWebViewRenderer _webViewRenderer)
{
webViewRenderer = _webViewRenderer ?? new ExtendedWebViewRenderer();
}
public override async void DidFinishNavigation(WKWebView webView, WKNavigation navigation)
{
var wv = webViewRenderer.Element as ExtendedWebView;
if (wv != null && webView != null)
{
await System.Threading.Tasks.Task.Delay(100); // wait here till content is rendered
if (webView.ScrollView != null && webView.ScrollView.ContentSize != null)
{
wv.HeightRequest = (double)webView.ScrollView.ContentSize.Height;
}
}
}
The answer above is correct, but ONLY if you do not set a higher value initially for the CustomWebView in the xaml / code behind.
I had to set some initial size in the code behind, because otherwise the webview wouldn't show up at all:
var myDisplay = DeviceDisplay.MainDisplayInfo;
mywebView.HeightRequest = myDisplay.Height;
mywebView.WidthRequest = myDisplay.Width;
But with this setting, the CustomNavigationDelegate would always report the same size (2208px) which is the device display height set in the initialization. This seems to override the later recalculation.
I fixed it by setting a lower initial hight, which will then dynamically adjust to the bigger required height of the content:
var myDisplay = DeviceDisplay.MainDisplayInfo;
mywebView.HeightRequest = 800;
mywebView.WidthRequest = myDisplay.Width;
I'm having a problem with the PanGestuer behaviour in Xamarin Form. This is my page:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Xamarin_Test"
x:Class="Xamarin_Test.MainPage">
<StackLayout x:Name="parentFrame" Margin="100" BackgroundColor="Gray">
<AbsoluteLayout x:Name="parentLayout" BackgroundColor="Red">
<local:PanContainer x:Name="parentContainer" BackgroundColor="Yellow">
<Image x:Name="panImage" Source="MonoMonkey.jpg"
WidthRequest="{Binding Path=Width, Source={x:Reference Name=parentFrame}}"
HeightRequest="{Binding Path=Height, Source={x:Reference Name=parentFrame}}"/>
</local:PanContainer>
</AbsoluteLayout>
</StackLayout>
</ContentPage>
PanContainer.cs
public class PanContainer : ContentView
{
double x, y;
public PanContainer()
{
var panGesture = new PanGestureRecognizer();
panGesture.PanUpdated += OnPanUpdated;
GestureRecognizers.Add(panGesture);
}
void OnPanUpdated(object sender, PanUpdatedEventArgs e)
{
switch (e.StatusType)
{
case GestureStatus.Running:
// Translate and ensure we don't pan beyond the wrapped user interface element bounds.
Content.TranslationX = Math.Max(Math.Min(0, x + e.TotalX), -Math.Abs(Content.Width - App.ScreenWidth));
Content.TranslationY = Math.Max(Math.Min(0, y + e.TotalY), -Math.Abs(Content.Height - App.ScreenHeight));
break;
case GestureStatus.Completed:
// Store the translation applied during the pan
x = Content.TranslationX;
y = Content.TranslationY;
break;
}
}
}
The image loads with correct size (within the StackLayout borders, for better testing I colorized the elements). The panning works to the left and to the upper way of the image, but overlays the white area. How can I handle panning, so that the moved image is only visible within my panContainer?
I am developing an app using Xamarin, and currently trying to display the screen width to the (I later plan to base sizes of certain objects off this)
I am attempting to use bindings to do this, but unfortunately it doesn't appear to work as expected. It does not throw an exception, just the label that I am attempting to bind has no text value.
I am sure I will be using bindings quite a lot during the rest of the project, so I would be greatly appreciative of any advice.
Please consider the following code:
C#:
using Xamarin.Forms;
using XLabs.Ioc;
using XLabs.Platform.Device;
namespace TimerStyles
{
public partial class SavedTimesPage : ContentPage
{
string screenWidthText = "";
public SavedTimesPage()
{
InitializeComponent();
this.screenWidth = getScreenWidth();
}
public string screenWidth
{
protected set
{
screenWidthText = value;
}
get { return screenWidthText; }
}
public string getScreenWidth()
{
var dev = Resolver.Resolve<IDevice>();
var display = dev.Display;
double ScreenWidthInches = (double)display.Width / display.Xdpi;
var ScreenWidth = (ScreenWidthInches.ToString());
return ScreenWidth;
}
public string getScreenHeight()
{
var dev = Resolver.Resolve<IDevice>();
var display = dev.Display;
double ScreenHeightInches = (double)display.Height / display.Ydpi;
var ScreenHeight = (ScreenHeightInches.ToString());
return ScreenHeight;
}
}
}
Xaml:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:TimerStyles="clr-namespace:TimerStyles;assembly=TimerStyles"
x:Class="TimerStyles.SavedTimesPage"
NavigationPage.HasNavigationBar="false">
<ContentPage.Content>
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" BackgroundColor="#232224">
<Label Text="x" TextColor="Blue" FontSize="Large"/>
<Label Text="{Binding screenWidth}" TextColor="Blue" FontSize="Large"/>
<Label Text="x" TextColor="Blue" FontSize="Large"/>
</StackLayout>
</ContentPage.Content>
</ContentPage>
This was solved by creating a "view-model" class to act as an intermediary between the view and the model, as per the following link:
https://developer.xamarin.com/guides/xamarin-forms/xaml/xaml-basics/data_bindings_to_mvvm/
(Obtained by following further on the link given in comments by cristallo)
Hopefully this will help anyone in the space-age future with the same issue.
I have been using Xamarin Forms to develop iOS and Android applications. I want to access a StackLayout that is within a TabbedPage so I can make it visible or hidden whenever the user changes tabs, but when I try to access the StackLayout I get "This does not exist in the current context". Here is my XAML code and my CS code.
CS
using System;
using System.Collections.Generic;
using Xamarin.Forms;
namespace DebuggerTestAndroidIOS
{
public partial class PatientTabPage : TabbedPage
{
public PatientTabPage ()
{
InitializeComponent ();
ItemsSource = PatientDataModel.tabs;
//vitalSignsStack.IsVisible = true;
this.CurrentPageChanged += (object sender, EventArgs e) => {
var i = this.Children.IndexOf(this.CurrentPage);
System.Diagnostics.Debug.WriteLine("Page No:"+i);
if (i == 1){
vitalSignsStack.IsVisible = true;
}
};
}
}
}
XAML
<?xml version="1.0" encoding="UTF-8"?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DebuggerTestAndroidIOS.PatientTabPage">
<TabbedPage.ItemTemplate>
<DataTemplate>
<ContentPage Title ="{Binding TabName}">
<!--Parent Wrapper layout-->
<StackLayout Orientation="Vertical" BackgroundColor="White">
<StackLayout x:Name="vitalSignsStack" Orientation="Horizontal" IsVisible="false">
<Image Source="VitalSigns.png" HorizontalOptions="Center"/>
</StackLayout>
</StackLayout><!--End parent wrapper-->
</ContentPage>
</DataTemplate>
</TabbedPage.ItemTemplate>
</TabbedPage>
An element is only going to be accesible within the context of the page that created it - in this case, the ContentPage.
If you want to reach it from outside of the ContentPage, you will need to add a public method or property to the ContentPage that exposes it.
You cannot access a control inside a DataTemplate with its name. The problem is, that it will be repeated and so this name would exist multiple times, what is not allowed.
But why don't you create a new Page like this:
public partial class PatientTabContentPage : TabbedPage
{
public PatientTabContentPage ()
{
InitializeComponent ();
}
public HideVitalSignsStack(bool true){
vitalSignsStack.IsVisible = true;
}
}
And change DataTemplate to
<DataTemplate>
<PatientTabContentPage Title ="{Binding TabName}">
</DataTemplate>
Then hide the stackpanel with
this.CurrentPageChanged += (object sender, EventArgs e) => {
var page = CurrentPage as PatientTabContentPage;
var i = this.Children.IndexOf(this.CurrentPage);
System.Diagnostics.Debug.WriteLine("Page No:"+i);
if (i == 1){
page.HideVvitalSignsStack(true);
}
};
Thanks for your efforts. I tried them, still was not able to access the StackLayouts. I modified a little bit my code, this helped a lot and made everything easier: Creating different layouts for each tab