I am developing a Windows Universal App which is hosting a web app using webview.
steps are followed as like.
Creating a Blank universal window app. Creating a Splash screen. Set
splash screen as starting page. After all activity i would like to
navigate the Main page which is having a web view control.
Setting a url example "http:www.google.come" as source for the web view. everything it works a fine but the main page takes time, where i would like to see the same splash screen till it loads.
Code for Navigation i am using
this.Frame.Navigate(typeof(MainPage));
full source code
public sealed partial class ExtentedSpash : Page
{
public ProgressMessage Progress;
public ExtentedSpash()
{
this.InitializeComponent();
Progress = ProgressMessage.GetMessage();
DataContext = Progress;
Window.Current.Activate();
Loaded += Splash_Loaded;
}
private async void Splash_Loaded(object sender, RoutedEventArgs e)
{
await Initialize();
Window.Current.Activate();
await ClearBrowserCache();
Window.Current.Activate();
//Task.WaitAll(TaskList.ToArray());
await StartApplication();
}
public async Task Initialize()
{
Progress.ActionMessage = "Initialize the controls";
await Task.Delay(TimeSpan.FromSeconds(10));
}
public async Task ClearBrowserCache()
{
Progress.ActionMessage = "Clear Browser Cache";
await Task.Delay(TimeSpan.FromSeconds(10));
}
public async Task StartApplication()
{
Progress.ActionMessage = "Loading";
await Task.Delay(TimeSpan.FromSeconds(10));
this.Frame.Navigate(typeof(MainPage));
}
private void btnMain_Click(object sender, RoutedEventArgs e)
{
}
}
public class ProgressMessage : INotifyPropertyChanged
{
private string statusMessage;
public string StatusMessage
{
get { return statusMessage; }
set
{
statusMessage = value;
RaiseProperChanged();
}
}
private string actionMessage;
public string ActionMessage
{
get { return actionMessage; }
set
{
actionMessage = value;
RaiseProperChanged();
}
}
private bool showProgress;
public bool ShowProgress
{
get { return showProgress; }
set { showProgress = value;
RaiseProperChanged();
}
}
public static ProgressMessage GetMessage()
{
var msg = new ProgressMessage()
{
StatusMessage = "Initializing Application",
ActionMessage = "One moment please...",
showProgress = true
};
return msg;
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaiseProperChanged(
[CallerMemberName] string caller = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(caller));
}
}
}
}
I want "On Loading" message should show til it fully loads the application.
As we've discussed, if you just want to cover the WebView when it's source is not complete loaded, you can do it like this:
<Page.Resources>
<Storyboard x:Key="MyTextSTD" x:Name="MyTextSTD" RepeatBehavior="Forever">
<ColorAnimationUsingKeyFrames Storyboard.TargetName="tbbrush" Storyboard.TargetProperty="Color" Duration="0:0:10">
<DiscreteColorKeyFrame KeyTime="0:0:0" Value="Red" />
<LinearColorKeyFrame KeyTime="0:0:5" Value="Blue" />
<LinearColorKeyFrame KeyTime="0:0:10" Value="Purple" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<WebView Source="https://msdn.microsoft.com/library/windows/apps/xaml/mt244352.aspx" NavigationCompleted="WebView_NavigationCompleted">
</WebView>
<Grid x:Name="loadingGrid" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Visibility="Visible">
<TextBlock Text="On Loading..." FontSize="50" VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock.Foreground>
<SolidColorBrush x:Name="tbbrush" />
</TextBlock.Foreground>
</TextBlock>
</Grid>
</Grid>
And code behind:
public MainPage()
{
this.InitializeComponent();
MyTextSTD.Begin();
}
private void WebView_NavigationCompleted(WebView sender, WebViewNavigationCompletedEventArgs args)
{
loadingGrid.Visibility = Visibility.Collapsed;
}
Here is quite simple, I use a TextBlock and some color animation to show the message. And this message will disappear when the source of WebView is fully loaded.
Related
I use a base content page for all pages in my app. I want to add a gradient to the content page so I created LocalGradientContentPage and had BaseContentPage inherit LocalGradientContentPage. Each platform has a custom renderer and everything works perfectly on Android. The android custom renderer is called when BaseContentPage is used. The issue is with iOS. iOS never calls the custom renderer when using BaseContentPage. It only calls the custom renderer if I use LocalGradientContentPage directly. All my classes follow.
LocalGradientContentPage.cs
namespace MyNamespace.Forms
{
public class LocalGradientContentPage : ContentPage
{
public static readonly BindableProperty EndColorProperty = BindableProperty.Create(
nameof(EndColor),
typeof(Color),
typeof(LocalGradientContentPage),
Color.White);
public Color EndColor
{
get => (Color)GetValue(EndColorProperty);
set => SetValue(EndColorProperty, value);
}
public static readonly BindableProperty StartColorProperty = BindableProperty.Create(
nameof(StartColor),
typeof(Color),
typeof(LocalGradientContentPage),
Color.Black);
public Color StartColor
{
get => (Color)GetValue(StartColorProperty);
set => SetValue(StartColorProperty, value);
}
}
}
Android: LocalGradientContentPageRenderer.cs
[assembly: ExportRenderer(typeof(LocalGradientContentPage),typeof(LocalGradientContentPageRenderer))]
namespace MyNamespace.DroidRenderers
{
public class LocalGradientContentPageRenderer : PageRenderer
{
public LocalGradientContentPageRenderer(Context context) : base(context)
{
}
private Xamarin.Forms.Color StartColor { get; set; }
private Xamarin.Forms.Color EndColor { get; set; }
protected override void DispatchDraw(Canvas canvas)
{
var gradient = new LinearGradient(0, 0, 0, Height,
StartColor.ToAndroid(),
EndColor.ToAndroid(),
Shader.TileMode.Mirror);
var paint = new Paint()
{
Dither = true,
};
paint.SetShader(gradient);
canvas.DrawPaint(paint);
base.DispatchDraw(canvas);
}
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
base.OnElementChanged(e);
if (e.OldElement != null || Element == null)
{
return;
}
if (!(e.NewElement is LocalGradientContentPage page)) return;
StartColor = page.StartColor;
EndColor = page.EndColor;
}
}
}
iOS:LocalGradientContentPage.cs
[assembly: ExportRenderer(typeof(LocalGradientContentPage), typeof(LocalGradientContentPageRenderer))]
namespace MyNamespace.iOSRenderers
{
public class LocalGradientContentPageRenderer : PageRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (e.OldElement != null) return;
if (e.NewElement is LocalGradientContentPage page)
{
var gradientLayer = new CAGradientLayer
{
Frame = View.Bounds,
Colors = new CGColor[] { page.StartColor.ToCGColor(), page.EndColor.ToCGColor() }
};
View.Layer.InsertSublayer(gradientLayer, 0);
}
}
}
}
BaseContentPage.cs
public partial class BaseContentPage : LocalGradientContentPage
{
public BaseContentPage() : base()
{
InitializeComponent();
}
}
Using like this works:
<views:LocalGradientContentPage
StartColor="Blue"
EndColor="HotPink">
</views:LocalGradientContentPage>
Using like this does not work and the iOS LocalGradientContentPageRenderer is never called:
<views:BaseContentPage
StartColor="Blue"
EndColor="HotPink">
</views:LocalGradientContentPage>
I've do not have linking but just in case I did instantiate LocalGradientContentPage in AppDelegate.cs. It does hit the constructor when instantiated this way.
_ = new LocalGradientContentPage();
Also, as stated earlier, this is working fine in Android and calls the custom renderer when BaseContentPage is used.
I'm really at a loss as to why this isn't working. Any help would be appreciated.
Well, I use you code and add the views:BaseContentPage to a tabbedPage, it works well.
I use it like:
<?xml version="1.0" encoding="utf-8" ?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:views="clr-namespace:App629"
mc:Ignorable="d"
x:Class="App629.TabbedPage1">
<!--Pages can be added as references or inline-->
<views:BaseContentPage
StartColor="Blue"
EndColor="HotPink"/>
<ContentPage Title="Tab 2" />
<ContentPage Title="Tab 3" />
<views:BaseContentPage StartColor="Red" EndColor="HotPink">
</views:BaseContentPage>
</TabbedPage>
Some suggestions:
I'm using the latest Xamarin.forms version.
Check if there are any other codes in the BaseContentPage that affect the custom renderer?
Try the code as me to check if it works?
Add a breakPoint there in the custom renderer to check if it hit the code.
how to stop the youtube video in web view when going back are OnDisappearing method is called.
I have the following XAML web view:
<ContentPage.Content>
<StackLayout>
<ActivityIndicator x:Name="Progress" IsEnabled="true" IsVisible="true" IsRunning="true" Color="Blue"/>
<WebView x:Name="webView" VerticalOptions="FillAndExpand" Navigated="Handle_Navigated" Navigating="Handle_Navigating" />
</StackLayout>
</ContentPage.Content>
and have the following (trimmed) code within my .cs file:
string name = o.Link;
string youtubeLink = "https://www.youtube.com/embed/";
string url = string.Concat(youtubeLink,name);
webView.Source = url;
}
protected override void OnDisappearing()
{
base.OnDisappearing();
// webView.IsEnabled = false;
}
When the page calls OnDisappearing method I want to stop the webview youtube video but that is currently not what is happening.
What you want is to stop rendering the WebView. You can either set the WebView Source to a random page or use this:
Interface
public interface ICustomWebViewRenderer
{
void Pause();
void Resume();
}
Xamarin.Forms CustomWebView:
public class CustomWebView : WebView
{
public ICustomWebViewRenderer Renderer;
public void Pause()
{
if (renderer != null) renderer.Pause();
}
public void Resume()
{
if (renderer != null) renderer.Resume();
}
}
Xamarin.Android CustomWebViewViewRenderer:
public class CustomWebViewRenderer : WebViewRenderer, ICustomWebViewRenderer
{
Android.Webkit.WebView webView;
public CustomWebViewRenderer(Android.Content.Context context) : base(context) { }
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
webView = Control;
if (e.OldElement != null)
(e.OldElement as CustomWebView).Renderer = null;
if (e.NewElement != null)
(e.NewElement as CustomWebView).Renderer = this;
}
public void Pause()
{
if (webView != null) webView.OnPause();
}
public void Resume()
{
if (webView != null) webView.OnResume();
}
}
Example:
XAML:
<local:CustomWebView x:Name="webView" Source="https://youtube.com" />
OnDisappearing / OnAppearing:
protected override void OnDisappearing()
{
webView.Pause();
base.OnDisappearing();
}
protected override void OnAppearing()
{
base.OnAppearing();
webView.Resume();
}
Does anyone encounter annoyed issue when update background color of cell in xamarin form?
I have notification list, when user clicks on item, it's marked as read then background color will be updated.
It works perfectly on android but iOS.
Most of the time the color is changed back to origin.
In the example, cell background is blue if notification is read.
When user clicks on cell, cell background should be red, but it's not changed most of time.
Notification Model
public class Notification : ObservableObject
{
private string _message;
private bool _isRead;
public string Message
{
get => _message;
set => SetProperty(ref _message, value);
}
public bool IsRead
{
get => _isRead;
set => SetProperty(ref _isRead, value);
}
}
ViewModel
public class MainPageViewModel : BaseViewModel
{
private ObservableCollection<Notification> _notifications;
public ObservableCollection<Notification> Notifications
{
get => _notifications;
set => SetProperty(ref _notifications, value);
}
public ICommand TappedCommand => new Command((o => OnTapped(o)));
public MainPageViewModel()
{
_notifications = new ObservableCollection<Notification>()
{
new Notification()
{
IsRead = false,
Message = "First notification"
},
new Notification()
{
IsRead = false,
Message = "Second notification"
},
new Notification()
{
IsRead = false,
Message = "Third notification"
},
new Notification()
{
IsRead = false,
Message = "Fourth notification"
}
};
}
private void OnTapped(object o)
{
if(!(o is Notification noti)) return;
noti.IsRead = true;
}
}
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:NotificationTest"
x:Class="NotificationTest.MainPage">
<ContentPage.Content>
<ListView x:Name="NotiList" ItemsSource="{Binding Notifications}" ItemTapped="OnTapped" ItemSelected="OnSelected" CachingStrategy="RecycleElement">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid BackgroundColor="{Binding IsRead,Converter={StaticResource ReadToColorConverter}}">
<Label Text="{Binding Message}"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage.Content>
Page code behind ( just work around to hide selected line color in listview
public partial class MainPage : ContentPage
{
private MainPageViewModel _vm;
public MainPage()
{
InitializeComponent();
_vm = new MainPageViewModel();
BindingContext = _vm;
}
private void OnTapped(object sender, ItemTappedEventArgs e)
{
_vm.TappedCommand.Execute(e.Item);
}
private void OnSelected(object sender, SelectedItemChangedEventArgs e)
{
NotiList.SelectedItem = null;
}
}
The problem was caused by the default behavior when selecting cell in iOS.
The default selected color will cover the red color , so it didn't get working.
Refer to my recent answer Here
Test
I am developing an App using Xamarin.Forms for listing the news from different sources. I use a webView to open the link corresponding to the news. But I want to show the progress while loading the webpage into web view, like the progress bar on Safari App. For this I have used the ProgressBar element like this:
<StackLayout>
<!-- WebView needs to be given height and width request within layouts to render. -->
<ProgressBar Progress ="" HorizontalOptions="FillAndExpand" x:Name="progress"/>
<WebView x:Name="webView"
HeightRequest="1000"
WidthRequest="1000"
VerticalOptions= "FillAndExpand"
Navigating="webOnNavigating"
Navigated="webOnEndNavigating"/>
</StackLayout>
and in the code I have used
void webOnNavigating (object sender, WebNavigatingEventArgs e)
{
progress.IsVisible = true;
}
void webOnEndNavigating (object sender, WebNavigatedEventArgs e)
{
progress.IsVisible = false;
}
But I want to show also the progress of loading the data, not just an indication that is loading and load. I want the user to know that the data are loading. Is there a way to achieve this.
The implementations should be platform specific via custom renders. Luckily this topics has been discussed already for different platforms here on SO.
The Android version based on this thread:
[assembly: ExportRenderer(typeof(WebView), typeof(GenericWebViewRenderer))]
namespace WebViewWithProgressBar.Droid
{
public class GenericWebViewRenderer : WebViewRenderer
{
Context ctx;
public GenericWebViewRenderer(Context context) : base(context)
{
ctx = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
if (Control == null)
return;
var progressBar = new Android.Widget.ProgressBar(ctx, null, Android.Resource.Attribute.ProgressBarStyleHorizontal);
Control.SetWebChromeClient(new MyWebChromeClient(progressBar));
Control.AddView(progressBar);
}
class MyWebChromeClient : Android.Webkit.WebChromeClient
{
Android.Widget.ProgressBar progressBar;
public MyWebChromeClient(Android.Widget.ProgressBar progressBar)
{
this.progressBar = progressBar;
}
public override void OnProgressChanged(Android.Webkit.WebView view, int newProgress)
{
progressBar.SetProgress(newProgress, true);
}
}
}
}
On iOS it is a bit trickier, here is a very simple mock that does it job pretty well:
[assembly: ExportRenderer(typeof(WebView), typeof(GenericWebViewRenderer))]
namespace WebViewWithProgressBar.iOS
{
public class GenericWebViewRenderer : ViewRenderer<WebView, UIWebView>
{
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
if (Control == null)
{
var progressBar = new UIProgressView(UIProgressViewStyle.Bar);
progressBar.TintColor = UIColor.Green;
progressBar.TrackTintColor = UIColor.Black;
progressBar.ProgressTintColor = UIColor.Red;
var webView = new UIWebView(Frame);
webView.AddSubview(progressBar);
SetNativeControl(webView);
Control.Delegate = new MyUIWebViewDelegate(progressBar);
webView.LoadRequest(new NSUrlRequest(new NSUrl("https://google.com")));
}
}
class MyUIWebViewDelegate : UIWebViewDelegate
{
UIProgressView progressBar { get; }
public MyUIWebViewDelegate(UIProgressView progressBar)
{
this.progressBar = progressBar;
}
public override void LoadStarted(UIWebView webView)
{
progressBar.SetProgress(0.1f, false);
}
public override void LoadingFinished(UIWebView webView)
{
progressBar.SetProgress(1.0f, true);
}
public override void LoadFailed(UIWebView webView, NSError error)
{
// TODO:
}
}
}
}
For more details please check here.
P.S.: This code examples are available on github.
i have an combox control defined with events in my mainpage.xaml
<Grid x:Name="LayoutRoot">
<ComboBox SelectionChanged="ComboBox_SelectionChanged"></ComboBox>
</Grid>
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
}
now how do we defined events for combox control in mvvm model .
and how do we bind the collection list to combo box. i am using SL 3
thanks
prince
In your xaml, you can bind the ItemSource and SelectedItem as shown below:
MainPage.xaml
<UserControl x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:App1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.DataContext>
<local:MainPage_ViewModel/>
</UserControl.DataContext>
<Grid x:Name="LayoutRoot" Background="White">
<ComboBox ItemsSource="{Binding MyItems}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}" SelectionChanged="ComboBox_SelectionChanged" Height="30" Width="100"/>
</Grid>
In the MainPage.xaml.cs, your Selection changed method could just call the method on your ViewModel since you are using SL3:
MainPage.xaml.cs
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
private MainPage_ViewModel viewModel
{
get { return this.DataContext as MainPage_ViewModel; }
}
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.viewModel.SelectionChanged();
}
}
Your ViewModel would have the MyItems collection and the SelectedItem to bind to:
MainPage_ViewModel.cs
public class MainPage_ViewModel : INotifyPropertyChanged
{
public ObservableCollection<string> MyItems
{
get { return myItems; }
set { myItems = value; }
}
private ObservableCollection<string> myItems = new ObservableCollection<string>() { "One", "Two", "Three" };
public string SelectedItem
{
get { return selectedItem; }
set { selectedItem = value; }
}
private string selectedItem = string.Empty;
public void SelectionChanged()
{
//Perform logic that needs to happen when selection changes
}
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Depending on what you were using your SelectionChanged method for, you may no longer need it since this would bind the SelectedItem to the ViewModel.
Hope this helps!