How can we assign Application.Current to BindingContext - xamarin

Iam very much new to xamarin and wondering about how BindingContext is used.
I was going through a tutorial wherein they used BindingContext = Application.Current
according to the docs Application.Current should return Application.
so how can this above statement work?

Firstly, create one property in APp.cs, implement interface INotifyPropertyChanged.
public partial class App : Application, INotifyPropertyChanged
{
private string _str;
public string str
{
get { return _str; }
set
{
_str = value;
RaisePropertyChanged("str");
}
}
public App()
{
InitializeComponent();
str = "this is test";
MainPage = new NavigationPage(new simplecontrol.Page26());
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
protected override void OnStart()
{
// Handle when your app starts
}
protected override void OnSleep()
{
// Handle when your app sleeps
}
protected override void OnResume()
{
// Handle when your app resumes
}
}
Then binding Application.Current for ContentPage BindingContext.
<ContentPage.Content>
<StackLayout>
<!--<local:View2 Items="{Binding modelas}" />-->
<Label
HorizontalOptions="CenterAndExpand"
Text="{Binding str}"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage.Content>
this.BindingContext = Application.Current;

Related

Why the RefreshView not updated my child element when I refresh?

Here's my xaml file.
<RefreshView x:Name="myRefreshView" Refreshing="myRefreshView_RefreshingAsync" RefreshColor="#b52b2b">
<ScrollView>
<StackLayout>
<Label Text="{Binding firstName }" />
</StackLayout>
</ScrollView>
</RefreshView>
Here's my .cs file which has a function
namespace Health.Views
{
public partial class LandingPage : ContentPage
{
public string firstName { set; get; }
public LandingPage()
{
firstName = "Mary";
this.BindingContext = this;
}
async void myRefreshView_RefreshingAsync(Object sender, System.EventArgs e)
{
await Task.Delay(3000);
firstName = "John";
myRefreshView.IsRefreshing = false;
}
}
}
The problem is when I refresh, the name is not change to "John". Not sure what else I have to add.
You need to implement INotifyPropertyChanged.
public class LandingPage : ContentPage, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _firstName;
public string firstName {
get{return _firstName;}
set
{
_firstName = value;
OnPropertyChanged("firstName");
} }
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}

BInding textColor in Xamarin

In a Xamarin app I’m trying to bind a textcolor with a property in Message model.
public class Message : INotifyPropertyChanged
{
public string text { get; set; }
public Color color { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
}
The task is, when I click on a label in a collectionview the text should change to Gray.
I can change the color in the ObservableCollection:
this.messages = new ObservableCollection();
(that’s works, and if I delete an entry in the ObservableCollection the screen updates)
But when I change the color in the message model, the screen doesn’t update.
I use MVVMhelpers, and I would like to use that to solve the problem, if possible.
best regards..
You could change the item color to gray when you click the item to triger the SelectionChanged event of CollectionView.
Xaml:
<CollectionView ItemsSource="{Binding messages}" SelectionMode="Single" SelectionChanged="CollectionView_SelectionChanged">
<CollectionView.ItemTemplate>
<DataTemplate>
<Label Text="{Binding text}" TextColor="{Binding color}"></Label>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
Code behind:
public partial class Page2 : ContentPage
{
public ObservableCollection<Message> messages { get; set; }
public Page2()
{
InitializeComponent();
messages = new ObservableCollection<Message>()
{
new Message(){ text="A", color="Red"},
new Message(){ text="B", color="Red"},
new Message(){ text="C", color="Red"},
};
this.BindingContext = this;
}
private void CollectionView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var previousItem = e.PreviousSelection.FirstOrDefault() as Message;
var currentItem = e.CurrentSelection.FirstOrDefault() as Message;
currentItem.color = "Gray";
if (previousItem!=null)
{
previousItem.color = "Red";
}
}
}
public class Message : INotifyPropertyChanged
{
private string _text;
public string text
{
get
{
return _text;
}
set
{
_text = value;
OnPropertyChanged("text");
}
}
private string _color;
public string color
{
get
{
return _color;
}
set
{
_color = value;
OnPropertyChanged("color");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
super, great thanks.
I should also add
<DataTemplate x:DataType="{x:Type Models:Message}">

Pass OnAppearing to a ViewModel in Xamarin Forms MVVM?

I have business logic that loops around and does waits and other things. Currently this is in the code behind.
From what I have been able to read this is the wrong place and I should be placing this in the viewModel (correct me if wrong). If that's the case then should I have an OnAppearing method in my VM and if so how should I pass the OnAppearing to the View Model?
Currently my page OnAppearing looks like this:
protected async override void OnAppearing()
{
base.OnAppearing();
Title = Settings.mode.Text() + " Deck";
vm.LearnViewVisible = Settings.mode.IsLearn();
vm.PracticeViewVisible = Settings.mode.IsPractice();
vm.QuizViewVisible = Settings.mode.IsQuiz();
vm.QuizStartViewVisible = false;
If I am to be moving most of the business logic to the ViewModel then would that mean that all of this would move to an OnAppearing() method I create in the ViewModel?
Other way is using Behaviors.Forms from David Britch
...
<ContentPage.Behaviors>
<behaviors:EventHandlerBehavior EventName="Appearing">
<behaviors:InvokeCommandAction Command="{Binding PageAppearingCommand}" />
</behaviors:EventHandlerBehavior>
<behaviors:EventHandlerBehavior EventName="Disappearing">
<behaviors:InvokeCommandAction Command="{Binding PageDisappearingCommand}" />
</behaviors:EventHandlerBehavior>
</ContentPage.Behaviors>
...
Original
Or Xamarin Community Toolkit EventToCommandBehavior
<ContentPage.Behaviors>
<xct:EventToCommandBehavior
EventName="Appearing"
Command="{Binding PageAppearingCommand}" />
<xct:EventToCommandBehavior
EventName="Disappearing"
Command="{Binding PageDisappearingCommand}" />
</ContentPage.Behaviors>
Related Question: EventHandlerBehavior vs EventToCommandBehavior
This is how i link my Viewmodel. I would recommend setting up a ViewModelBase with : VModelActive and VModelInactive
Code Behind:
public partial class YourClass : ContentPage
{
ViewModelClass viewModelClass;
public YourClass()
{
InitializeComponent();
viewModelClass = new ViewModelClass();
this.BindingContext = viewModelClass;
}
protected override void OnAppearing()
{
base.OnAppearing();
viewModelClass.VModelActive(this, EventArgs.Empty);
}
protected override void OnDisappearing()
{
base.OnDisappearing();
viewModelClass.VModelInactive(this, EventArgs.Empty);
}
}
View Model
public override void VModelActive(Page sender, EventArgs eventArgs)
{
base.VModelActive(sender, eventArgs);
//your code
}
public override void VModelInactive(Page sender, EventArgs eventArgs)
{
base.VModelInactive(sender, eventArgs);
//your code
}
I prefer a pattern I first encountered in some Realm sample code.
A ViewModel base provides empty overrideable OnAppearing/Disappearing
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (Equals(field, value))
{
return false;
}
field = value;
OnPropertyChanged(propertyName);
return true;
}
internal virtual void OnAppearing() { }
internal virtual void OnDisappearing() { }
}
User classes descend from a base that conditionally invokes the VM.
public class BasePage : ContentPage
{
protected override void OnAppearing()
{
base.OnAppearing();
(BindingContext as BaseViewModel)?.OnAppearing();
}
protected override void OnDisappearing()
{
base.OnDisappearing();
(BindingContext as BaseViewModel)?.OnDisappearing();
}
}
// used as
public class JournalEntryDetailsViewModel : BaseViewModel
Warning: if you change the base class like this you need to use it in the XAML - use a scoped version of BasePage instead of the <ContentPage top element.
Otherwise you will get an error [CS0263] Partial declarations of 'JournalEntriesPage' must not specify different base classes
<?xml version="1.0" encoding="UTF-8"?>
<v:BasePage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:v="clr-namespace:QuickJournal.Views"
x:Class="QuickJournal.Views.JournalEntriesPage"
Title="Journal"
x:Name="page">
<ContentPage.ToolbarItems>
<ToolbarItem Text="Add" Command="{Binding AddEntryCommand}" />
</ContentPage.ToolbarItems>
<ContentPage.Content>
Here is example from my solution
public partial class TaskDetailsPage : MvvmContentPage
{
private readonly TaskDetailsViewModel _model;
public TaskDetailsPage()
{
InitializeComponent();
Shell.SetNavBarIsVisible(this, true);
Shell.SetTabBarIsVisible(this, false);
_model = BindingContext as TaskDetailsViewModel;
}
protected override string NavigationRoute => UniqeCodes.Routes.TaskDetailsPage;
protected override void OnAppearing()
{
_model.Init();
}
}

My UI did not response for orientation change

I am developing a native mobile app for all platforms. I have created my own theme content page. Then after deployment on android when I make phone landscape it did not respond. what's the reason here.
Here is my base content page.
public abstract class BaseContentPage : ContentPage
{
public readonly BaseViewModel BaseViewModel;
protected bool _isNavigated = false;
public BaseContentPage(BaseViewModel baseViewModel)
{
BaseViewModel = baseViewModel;
}
public abstract void Navigate(SelectedItemChangedEventArgs e);
protected abstract override void OnAppearing();
protected override void OnDisappearing()
{
_isNavigated = true;
}
public BaseContentPage()
{
}
}
here Xaml
<views:BaseContentPage
xmlns:views="clr-namespace:DipsDemoXaml.Views"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:Resource="clr-namespace:DipsDemoXaml.Resources"
x:Class="DipsDemoXaml.Views.WardListPage" Title="{x:Static Resource:AppResources.WardListPageTitle}">
<StackLayout BackgroundColor="{StaticResource DefaultBackgroundColor}" Orientation="Vertical" x:Name="s1">
I even try this also in code behind constructor I call size changed and create a method called Wardpagesizechanged.
public WardListPage(WardListPageViewModel wardListViewModel) : base(wardListViewModel)
{
InitializeComponent();
this.SizeChanged += wardpagesizechanged;
}
Wardpagesizechanged method
private void wardpagesizechanged(object sender, EventArgs e)
{
if(this.Width> this.Height)
{
s1.Orientation = StackOrientation.Horizontal;
}
else
{
s1.Orientation = StackOrientation.Vertical;
}
}
what is the problem here, I am clueless

How to update progressbar from customwebview renderer in xamarin forms for android?

I'm currently developing an app in Xamarin Forms with an Android background so I wanted to create an Android app first and an iOS app later.
I'm new to Xamarin Forms and I'm struggling on how to update a ProgressBar from a WebView using a custom renderer for the WebView.
In Android, you can do something like this, with the ProgressBar and WebView that are in the main_layout.xml
public class MainActivity extends Activity {
private ProgressBar progressBar;
private WebView webView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
progressBar = (ProgressBar) findViewById(R.id.progress);
webView = (AdvancedWebView) findViewById(R.id.webView);
// webview initialisation
webView.setWebChromeClient(new WebChromeClient(){
#Override
public void onProgressChanged(WebView view, int newProgress) {
// update progressbar progress
progressBar.setProgress(newProgress);
}
});
webView.setWebViewClient(new WebViewClient(this) {
#Override
public void onPageFinished(WebView view, String url) {
// hide progressbar when it's done
progressBar.setVisibility(View.GONE);
}
});
}
}
In Xamarin Forms I have this layout in MainPage.xaml in the shared project
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
x:Class="MainPage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
NavigationPage.HasNavigationBar="False">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ProgressBar
x:Name="progress"
Grid.Row="3"
HorizontalOptions="FillAndExpand"
Progress="0"
VerticalOptions="Center" />
<WebView
x:Name="webview"
Grid.Row="0"
Grid.RowSpan="4"
Grid.Column="0"
HorizontalOptions="FillAndExpand"
IsVisible="False"
Source="https://google.com"
VerticalOptions="FillAndExpand" />
</Grid>
public partial class App : Application
{
public App ()
{
InitializeComponent();
MainPage = new MainPage();
}
}
And this custom WebView render for android in the android project
[assembly: ExportRenderer(typeof(WebView), typeof(CustomWebviewRenderer))]
namespace MyApp.Droid
{
public class CustomWebviewRenderer: WebViewRenderer
{
private readonly Context context;
public CustomWebviewRenderer(Context context) : base(context)
{
this.context = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
var formsWebView = e.NewElement as Xamarin.Forms.WebView;
if (formsWebView != null)
{
var webView = Control as Android.Webkit.WebView;
webView.SetWebViewClient(new CustomWebViewClient());
webView.SetWebChromeClient(new CustomWebChromeClient());
webView.Settings.LoadWithOverviewMode = true;
webView.Settings.UseWideViewPort = true;
SetNativeControl(webView);
}
}
private class ScoritoWebChromeClient : WebChromeClient
{
public override void OnProgressChanged(Android.Webkit.WebView view, int newProgress)
{
// how to update progressbar progress?
base.OnProgressChanged(view, newProgress);
}
}
private class CustomWebViewClient : WebViewClient
{
public override void OnPageFinished(Android.Webkit.WebView view, string url)
{
// how to hide progressbar?
base.OnPageFinished(view, url);
}
public override void OnPageStarted(Android.Webkit.WebView view, string url, Bitmap favicon)
{
base.OnPageStarted(view, url, favicon);
}
public override bool ShouldOverrideUrlLoading(Android.Webkit.WebView view, IWebResourceRequest request)
{
return base.ShouldOverrideUrlLoading(view, request);
}
}
}
}
How can I update the ProgressBar that I have in my MainPage.xaml from the OnPageFinished in the CustomWebviewRenderer class to show the webpage has finished loading? Should I use the MainActivity.cs in the Android project?
Can someone point me in the right direction on how to solve this?
Update:
Create a custom WebView class in your PCL:
public class CustomWebView: WebView
{
public static readonly BindableProperty ActionProperty = BindableProperty.Create(propertyName: nameof(Action), returnType: typeof(Action),
declaringType: typeof(CustomWebView),
defaultValue: null,
defaultBindingMode: BindingMode.OneWay);
public void InvokeAction()
{
if (Action == null || data == null)
{
return;
}
Action.Invoke();
}
public Action Action
{
get { return (Action)GetValue(ActionProperty); }
set { SetValue(ActionProperty, value); }
}
}
Use this CustomWebView with its namespace like this:
<namespace:CustomWebView x:Name="webview"
Grid.Row="0"
Grid.RowSpan="4"
Grid.Column="0"
HorizontalOptions="FillAndExpand"
IsVisible="False"
Source="https://google.com"
VerticalOptions="FillAndExpand"/>
But if you still insist to use your renderer what you can do is update the progress bar using the element property and get a callback or change a property something like below:
[assembly: ExportRenderer(typeof(CustomWebView), typeof(CustomWebviewRenderer))]
namespace MyApp.Droid
{
public class CustomWebviewRenderer: WebViewRenderer
{
private readonly Context context;
public CustomWebviewRenderer(Context context) : base(context)
{
this.context = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
var formsWebView = e.NewElement as Xamarin.Forms.WebView;
if (formsWebView != null)
{
var webView = Control as Android.Webkit.WebView;
webView.SetWebViewClient(new CustomWebViewClient());
webView.SetWebChromeClient(new CustomWebChromeClient());
webView.Settings.LoadWithOverviewMode = true;
webView.Settings.UseWideViewPort = true;
SetNativeControl(webView);
}
}
private class ScoritoWebChromeClient : WebChromeClient
{
public override void OnProgressChanged(Android.Webkit.WebView view, int newProgress)
{
// how to update progressbar progress?
base.OnProgressChanged(view, newProgress);
}
}
private class CustomWebViewClient : WebViewClient
{
private _webView;
public CustomWebViewClient (Xamarin.Forms.WebView webView)
{
_webView=webView;
}
public override void OnPageFinished(Android.Webkit.WebView view, string url)
{
// how to hide progressbar?
_webView.InvokeAction();
base.OnPageFinished(view, url);
}
public override void OnPageStarted(Android.Webkit.WebView view, string url, Bitmap favicon)
{
base.OnPageStarted(view, url, favicon);
}
public override bool ShouldOverrideUrlLoading(Android.Webkit.WebView view, IWebResourceRequest request)
{
return base.ShouldOverrideUrlLoading(view, request);
}
}
}
}
Note that this is just an example and can be changed as per your requirements.
In case it doesn't work or you have queries revert!
So after 10 days I have finally found a solution to this by using bindableproperties...
So I've added a CustomWebView in the PCL
public class CustomWebView : WebView
{
public static readonly BindableProperty PageProgressProperty = BindableProperty.Create(
nameof(PageProgress),
typeof(int),
typeof(CustomWebView),
default(int));
public int PageProgress
{
get => (int)GetValue(PageProgressProperty);
set => SetValue(PageProgressProperty, value);
}
}
Also a CustomProgressBar in the PCL
public class CustomProgressBar : ProgressBar
{
public CustomProgressBar()
{
}
public static readonly BindableProperty CurrentProgressProperty =
BindableProperty.Create(
nameof(CurrentProgress),
typeof(int),
typeof(CustomProgressBar),
default(int),
propertyChanged: CustomProgressPropertyChanged);
private static void CustomProgressPropertyChanged(BindableObject sender, object oldValue, object newValue)
{
CustomProgressBar thisProgressBar = (CustomProgressBar)sender;
double percent = Convert.ToDouble(newValue) / 100;
thisProgressBar.ProgressTo(percent, 100, Easing.Linear);
}
public int CurrentProgress
{
get => (int)GetValue(CurrentProgressProperty);
set => SetValue(CurrentProgressProperty, value);
}
}
And now I can update the progress from my CustomWebviewRenderer as follows
public class CustomWebviewRenderer : WebViewRenderer
{
private readonly Context context;
public CustomWebviewRenderer(Context context) : base(context)
{
this.context = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
var formsWebView = e.NewElement as WebView;
if (e.OldElement == null)
{
var x = Control;
var webView = Control as Android.Webkit.WebView;
webView.SetWebViewClient(new CustomWebViewClient(Element));
webView.SetWebChromeClient(new CustomWebChromeClient(Element));
webView.Settings.LoadWithOverviewMode = true;
SetNativeControl(webView);
}
}
private class CustomWebViewClient : WebViewClient
{
private readonly WebView formsWebView;
public CustomWebViewClient(WebView webView)
{
formsWebView = webView;
}
public override void OnReceivedError(Android.Webkit.WebView view, IWebResourceRequest request, WebResourceError error)
{
base.OnReceivedError(view, request, error);
}
public override void OnPageFinished(Android.Webkit.WebView view, string url)
{
base.OnPageFinished(view, url);
}
public override void OnPageStarted(Android.Webkit.WebView view, string url, Bitmap favicon)
{
base.OnPageStarted(view, url, favicon);
}
public override bool ShouldOverrideUrlLoading(Android.Webkit.WebView view, IWebResourceRequest request)
{
return base.ShouldOverrideUrlLoading(view, request);
}
}
private class CustomWebChromeClient : WebChromeClient
{
private WebView element;
public CustomWebChromeClient(WebView element)
{
this.element = element;
}
public override void OnProgressChanged(Android.Webkit.WebView view, int newProgress)
{
base.OnProgressChanged(view, newProgress);
element.SetValueFromRenderer(CustomWebView.PageProgressProperty, newProgress);
}
}
}
And finally in the .xaml views these properties: BindingContext and CurrentProgress. The BindingContext is set to the webview, and the CurrentProgress is a custom property bound to PageProgress which is a property that lives in the CustomWebView
<local:CustomProgressBar
x:Name="progress"
Grid.Row="3"
BindingContext="{x:Reference webview}"
CurrentProgress="{Binding Path=PageProgress}"
HorizontalOptions="FillAndExpand"
ProgressColor="#FFB800"
VerticalOptions="Center" />
<local:CustomWebView
x:Name="webview"
Grid.Row="0"
Grid.RowSpan="4"
Grid.Column="0"
HorizontalOptions="FillAndExpand"
IsVisible="False"
VerticalOptions="FillAndExpand" />
So yeah, took a while to figure this out...

Resources