Button not disabled on page load in Xamarin.Forms - xamarin

I have a button in my page. I want to disable this button on page load, but its not disabled.
XAML:
<Button IsEnabled="{Binding IsEnabledSaveBtn,Mode=TwoWay}" Text="Save" Command="{Binding SaveItem}" />
ViewModel:
bool _isEnabledSaveBtn = false;
public bool IsEnabledSaveBtn
{
get { return _isEnabledSaveBtn; }
set
{
_isEnabledSaveBtn = value;
OnPropertyChanged();
}
}
Inside ViewModel Constructor:
public CreateDiscountViewModel(INavigation navigation)
{
Navigation = navigation;
IsEnabledSaveBtn=False;
}
I'm also validating data on TextChange event and it's working fine. But I want to disable this button on page load.
How can I solve this?

You need the following changes:
In your XAML you do not need two-way binding :
<Button IsEnabled="{Binding IsEnabledSaveBtn}" Text="Save" Command="{Binding SaveItem}" />
Your Property should look something like this:
private bool _isEnabledSaveBtn;
public bool IsEnabledSaveBtn
{
get { return _isEnabledSaveBtn; }
set
{
_isEnabledSaveBtn = value;
OnPropertyChanged(nameof(IsEnabledSaveBtn));
}
}
And in your ViewModel constructor set the value:
public CreateDiscountViewModel(INavigation navigation)
{
Navigation = navigation;
IsEnabledSaveBtn = false;
}

Something seems to be wrong with your OnPropertyChanged event.
Try the following, it will work.
is Button Enabled:
bool _isEnabledSaveBtn = false;
public bool IsEnabledSaveBtn
{
get { return _isEnabledSaveBtn; }
set
{
_isEnabledSaveBtn = value;
OnPropertyChanged("IsEnabledSaveBtn");
}
}
OnPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
and in your contructor
IsEnabledSaveBtn = false;

Related

How to set/get the properties Grial Repeater?

refer to http://docs.grialkit.com/repeater.html, it got a property
call 'SelectedItem', i want to know the code in detail how to set or get it ?
Please help, thanks!
This is how you will get the SelectedItem, I am assuming you are following the MVVM approach.
XAML:-
<grial:Repeater Spacing="8"
Padding="10"
ScrollPadding="10"
ItemsSource="{Binding Items}"
SelectedItem= "{Binding SelectedItemObject}">
<grial:Repeater.ItemTemplate>
<!--DEFAULT ITEM TEMPLATE-->
</grial:Repeater.ItemTemplate>
</grial:Repeater>
ViewModel:
private ModelClass _selectedItemObject;
public ModelClass SelectedItemObject
{
get { return _selectedItemObject; }
set
{
_selectedItemObject = value;
OnPropertyChanged("SelectedItemObject");
OnItemSelected(SelectedItemObject);
}
}
private List<ModelClass> _items;
public List<ModelClass> Items
{
get{ return _items;}
set
{
_items = value;
OnPropertyChanged("Items");
}
}
private void OnItemSelected(ModelClass selectedItem)
{
if (selectedItem == null)
return;
// Perform your logic her on selection
}

Xamarin forms understanding ObservableCollection binding context

I'm having some issue getting my ObservableCollection to bind to alexrainman CarouselView.
After reading some basic articles I created my view model:
public class PostObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
string postOwner = string.Empty;
string id = string.Empty;
string profileimage = string.Empty;
string post = string.Empty;
List<string> postimages = null;
public string PostOwner
{
set
{
if (postOwner != value)
{
postOwner = value;
OnPropertyChanged("PostOwner");
}
}
get
{
return postOwner;
}
}
public string Id {
set
{
if (id != value)
{
id = value;
OnPropertyChanged("Id");
}
}
get
{
return id;
}
}
public string Post
{
set
{
if (post != value)
{
post = value;
OnPropertyChanged("Post");
}
}
get
{
return post;
}
}
public string ProfileImage
{
set
{
if (profileimage != value)
{
profileimage = value;
OnPropertyChanged("ProfileImage") ;
}
}
get
{
return profileimage;
}
}
public List<string> PostImages
{
set
{
if (postimages != value)
{
postimages = value;
OnPropertyChanged("PostImages");
}
}
get
{
return postimages;
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I retrieve my data via a REST call to my server:
public static bool GetMyPostData(ref ObservableCollection<PostObject> myPosts, string groupid, string apikey)
{
try
{
string newURL = URL + "GetPosts";
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
MultipartFormDataContent formdata = new MultipartFormDataContent
{
{ new StringContent(apikey), "apikey" },
{ new StringContent(groupid), "groupid" }
};
HttpResponseMessage response = client.PostAsync(newURL, formdata).Result; // Blocking call! Program will wait here until a response is received or a timeout occurs.
if (response.IsSuccessStatusCode)
{
try
{
myPosts = response.Content.ReadAsAsync<ObservableCollection<PostObject>>().Result;
}
catch (Exception e)
{
Debug.WriteLine(e);
return false;
}
}
}
return true;
}
catch (Exception ex)
{
Debug.WriteLine(ex);
return false;
}
}
Which works I get my data correctly, now I set up my Binding context like so:
ObservableCollection<PostObject> GroupPosts = new ObservableCollection<PostObject>();
public Posts (GroupInfo ginfo)
{
InitializeComponent ();
GroupTitle.Text = ginfo.Title;
CurrentGroupInfo = ginfo;
GetDataPosts();
BindingContext = GroupPosts;
}
public void GetDataPosts()
{
try
{
GroupPosts.Clear();
if (RestController.GetMyPostData(ref GroupPosts, CurrentGroupInfo.Id.ToString(), apikey))
{
Debug.WriteLine("Data downloaded");
}
}
catch(Exception e)
{
Debug.WriteLine(e.Message);
}
And finally I have my XAML set up like this:
<?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:controls="clr-namespace:ImageCircle.Forms.Plugin.Abstractions;assembly=ImageCircle.Forms.Plugin"
xmlns:cv="clr-namespace:CarouselView.FormsPlugin.Abstractions;assembly=CarouselView.FormsPlugin.Abstractions"
NavigationPage.HasNavigationBar="True"
NavigationPage.HasBackButton="False"
NavigationPage.BackButtonTitle="Back"
x:Class="forms.Posts">
<NavigationPage.TitleView>
<StackLayout Orientation="Horizontal" VerticalOptions="Center" Spacing="10" >
<Label x:Name="GroupTitle" TextColor="White" FontSize="Medium"/>
</StackLayout>
</NavigationPage.TitleView>
<ContentPage.ToolbarItems>
<ToolbarItem Name="iconexample" Icon="settings.png" Priority="0" Order="Primary" />
</ContentPage.ToolbarItems>
<ContentPage.Content>
<cv:CarouselViewControl x:Name="carousel"
ItemsSource="{Binding PostImages}"
ShowArrows="true"
ShowIndicators="true"
Orientation="Horizontal">
</cv:CarouselViewControl>
</ContentPage.Content>
</ContentPage>
However I get an error
Unhandled Exception:
System.NullReferenceException: Object reference not set to an instance of an object.
So I'm not sure what I'm missing or I need to read up on this a little more? any help would be great.
You want to do a few changes here:
Change the field definition to a property, you won't be able to bind to the field:
public ObservableCollection<PostObject> GroupPosts { get; } = new ObservableCollection<PostObject>();
If you updating the reference then you have to raise property changed event, so your property definition should look like that:
private ObservableCollection<PostObject> _groupPosts = new ObservableCollection<PostObject>();
public ObservableCollection<PostObject> GroupPosts
{
get { return _groupPosts; }
set
{
_groupPosts = value;
RaisePropertyChanged(.....); // here you should notify your binding that value has changed
}
}
Because you are trying to pass this list by reference (ref parameter), you won't be able to compile it with a property so it's better just to return value from your data provider and then apply it:
GroupPosts.Clear();
var newData = RestController.GetMyPostData(CurrentGroupInfo.Id.ToString(), apikey);
GroupPosts = newData;
it's a bad practice to pass the observable collection to an underlying data provider because it will limit it to operate on UI thread only (otherwise after updating the collection on non-ui thread you can crash the app). But this is a top for another post :)

Xamarin Forms Webview - Scroll event

I need to know when the user has ended the scroll on a webview displaying a agreement, to display an "Accept" only when the user has read this.
Here is my webview (xaml) :
<WebView x:Name="webView" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<WebView.Source>
<HtmlWebViewSource Html="{Binding Content}" />
</WebView.Source>
How can I do that (.cs side) ?..
What is the best practice ?
Xamarin Forms - Targets : iOS & Android
Thanks for your help ;)
EDIT (iOs/Android working solution) :
Here is what I implemented to listen scroll inside the webview (JS event):
Custom WebView renderer :
namespace xxx.Views.Common.Controls
{
public class WebViewer : WebView
{
public static BindableProperty EvaluateJavascriptProperty =
BindableProperty.Create(nameof(EvaluateJavascript), typeof(Func<string, Task<string>>), typeof(WebViewer),
null, BindingMode.OneWayToSource);
public static BindableProperty RefreshProperty =
BindableProperty.Create(nameof(Refresh), typeof(Action), typeof(WebViewer), null,
BindingMode.OneWayToSource);
public static BindableProperty GoBackProperty =
BindableProperty.Create(nameof(GoBack), typeof(Action), typeof(WebViewer), null,
BindingMode.OneWayToSource);
public static BindableProperty CanGoBackFunctionProperty =
BindableProperty.Create(nameof(CanGoBackFunction), typeof(Func<bool>), typeof(WebViewer), null,
BindingMode.OneWayToSource);
public static BindableProperty GoBackNavigationProperty =
BindableProperty.Create(nameof(GoBackNavigation), typeof(Action), typeof(WebViewer), null,
BindingMode.OneWay);
public Func<string, Task<string>> EvaluateJavascript
{
get { return (Func<string, Task<string>>) this.GetValue(EvaluateJavascriptProperty); }
set { this.SetValue(EvaluateJavascriptProperty, value); }
}
public Action Refresh
{
get { return (Action) this.GetValue(RefreshProperty); }
set { this.SetValue(RefreshProperty, value); }
}
public Func<bool> CanGoBackFunction
{
get { return (Func<bool>) this.GetValue(CanGoBackFunctionProperty); }
set { this.SetValue(CanGoBackFunctionProperty, value); }
}
public Action GoBackNavigation
{
get { return (Action) this.GetValue(GoBackNavigationProperty); }
set { this.SetValue(GoBackNavigationProperty, value); }
}
}
}
Android Renderer :
[assembly: ExportRenderer(typeof(WebViewer), typeof(WebViewRender))]
namespace xxx.Droid.Renderers
{
public class WebViewRender : WebViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
var oldWebView = e.OldElement as WebViewer;
if (oldWebView != null)
{
oldWebView.EvaluateJavascript = null;
}
var newWebView = e.NewElement as WebViewer;
if (newWebView != null)
{
newWebView.EvaluateJavascript = async js =>
{
ManualResetEvent reset = new ManualResetEvent(false);
var response = "";
Device.BeginInvokeOnMainThread(() =>
{
System.Diagnostics.Debug.WriteLine("Javascript Send: " + js);
this.Control?.EvaluateJavascript(js, new JavascriptCallback(r =>
{
response = r;
reset.Set();
}));
});
await Task.Run(() => { reset.WaitOne(); });
if (response == "null")
{
response = string.Empty;
}
return response;
};
}
if (this.Control != null && e.NewElement != null)
{
this.SetupControl();
}
}
/// <summary>
/// Sets up various settings for the Android WebView
/// </summary>
private void SetupControl()
{
// Ensure common functionality is enabled
this.Control.Settings.DomStorageEnabled = true;
this.Control.Settings.JavaScriptEnabled = true;
// Must remove minimum font size otherwise SAP PDF's go massive
this.Control.Settings.MinimumFontSize = 0;
// Because Android 4.4 and below doesn't respect ViewPort in HTML
if (Build.VERSION.SdkInt < BuildVersionCodes.Lollipop)
{
this.Control.Settings.UseWideViewPort = true;
}
}
}
internal class JavascriptCallback : Java.Lang.Object, IValueCallback
{
private readonly Action<string> _callback;
public JavascriptCallback(Action<string> callback)
{
this._callback = callback;
}
public void OnReceiveValue(Java.Lang.Object value)
{
System.Diagnostics.Debug.WriteLine("Javascript Return: " + Convert.ToString(value));
this._callback?.Invoke(Convert.ToString(value));
}
}
public class WebViewChromeClient : WebChromeClient
{
readonly Action<IValueCallback, Java.Lang.String, Java.Lang.String> callback;
public WebViewChromeClient(Action<IValueCallback, Java.Lang.String, Java.Lang.String> callback)
{
this.callback = callback;
}
//For Android 4.1+
[Java.Interop.Export]
public void openFileChooser(IValueCallback uploadMsg, Java.Lang.String acceptType, Java.Lang.String capture)
{
this.callback(uploadMsg, acceptType, capture);
}
// For Android 5.0+
public override bool OnShowFileChooser(WebView webView, IValueCallback filePathCallback,
FileChooserParams fileChooserParams)
{
return base.OnShowFileChooser(webView, filePathCallback, fileChooserParams);
}
}
}
iOS Renderer :
[assembly: ExportRenderer(typeof(WebViewer), typeof(WebViewRender))]
namespace xxx.iOS
{
public class WebViewRender : WebViewRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
//if (this.NativeView != null && e.NewElement != null)
//{
// this.InitializeCommands((WebViewer) e.NewElement);
//}
var webView = e.NewElement as WebViewer;
if (webView != null)
{
webView.EvaluateJavascript = js => { return Task.FromResult(this.EvaluateJavascript(js)); };
}
}
private void InitializeCommands(WebViewer element)
{
var ctl = (UIWebView) this.NativeView;
ctl.ScalesPageToFit = true;
}
}
}
xaml Display :
<pages:xxxContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:pages="clr-namespace:xxx.Views"
xmlns:controls="clr-namespace:xxx.Views.Common.Controls;assembly=xxx.View"
x:Class="xxx.Views.AboutPage">
<pages:xxxInfoContentPage.PageView>
<StackLayout HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand">
<controls:WebViewer x:Name="webView" EvaluateJavascript="{Binding EvaluateJavascript}" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<WebView.Source>
<HtmlWebViewSource Html="{Binding Content}" />
</WebView.Source>
</controls:WebViewer>
</StackLayout>
</pages:DriversInfoContentPage.PageView>
</pages:DriversInfoContentPage>
JS inside webview (redirection when the bottom is reached) :
<script type="text/javascript">
function init() {
window.onscroll = function(ev) {
if ((window.innerHeight + window.pageYOffset) >= document.body.offsetHeight) {
window.location.hash = "bottom";
location.reload();
}
};
}
</script>
<body onload="init()">
<!-- Scrolled content-->
</body>
Catching & Canceling Navigating Event xaml.xs side :
public AboutPage()
{
this.webView.Navigating += this.NavigatingEvent;
}
private void NavigatingEvent(object sender, WebNavigatingEventArgs e)
{
if (e.Url.Contains("bottom") || e.Url.Contains("about:blank"))
{
e.Cancel = true;
// ACTION WHEN BOTTOM IS REACHED HERE
}
}
I would recommend making your C# access the javascript.
See my tutorial:
https://www.youtube.com/watch?v=_0a7NzkNl-Q

INotifyPropertyChanged Xamarin string value just disappears

I have a Xamarin.Forms project with a ListView populated with an ObservableCollection. The class (object) that is each item in the ObservableCollection implements INotifyPropertyChanged. A Color property toggles fine in the UI but a string property disappears and never returns.
I get the initial values from a webservice but then just do a completely static change of the values, for debugging and I still can't figure it out.
At the top of the my ContentPage class I have this:
public ObservableCollection<GroceryItem> oc;
After my webservice has returned with the data I put the data in the ObservableCollection and make that, the ItemsSource for the listview. Like this:
lvGroceries.ItemsSource = oc;
All that works great.
The XAML
<ListView x:Name="lvGroceries" ItemTapped="GroceryPdsItemTapped" >
<ListView.ItemTemplate >
<DataTemplate>
<ViewCell>
<AbsoluteLayout VerticalOptions="Fill">
<Label Text="{Binding GroceryName}" AbsoluteLayout.LayoutBounds="0,0,200,40" ></Label>
<Label Text="{Binding strHomeLoc}" AbsoluteLayout.LayoutBounds="200,0,100,40" ></Label>
<Label Text="{Binding isNeeded}" AbsoluteLayout.LayoutBounds="300,0,50,40" ></Label>
<Label Text="someText" BackgroundColor="{Binding myBackgroundColor}" AbsoluteLayout.LayoutBounds="350,0,50,40" ></Label>
</AbsoluteLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The class - GroceryItem
public class GroceryItem : INotifyPropertyChanged
{
public GroceryItem() { }
public event PropertyChangedEventHandler PropertyChanged;
private string privateIsNeeded;
public string isNeeded
{
get { return privateIsNeeded; }
set
{
privateIsNeeded = value;
OnPropertyChanged();
}
}
private Color theColor;
public Color myBackgroundColor
{
get { return theColor; }
set
{
theColor = value;
OnPropertyChanged();
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The click handler. I grab an item from the ObservableCollection and change the two properties.
public void GroceryPdsItemTapped(object obj, ItemTappedEventArgs e)
{
if (e.Item == null)
{
return;
}
var g = ((GroceryItem)e.Item);
foreach (var gr in oc)
{
if (gr.GroceryId == "27769")
{ // the UI changes because the myBackgroundColor property in the GroceryItem class is watching for a value change
gr.myBackgroundColor = (gr.myBackgroundColor == Color.Yellow) ? Color.Blue : Color.Yellow;
gr.isNeeded = (gr.isNeeded == "true" || gr.isNeeded == "blah" || gr.isNeeded == "false") ? "notblah" : "blah";
}
}
}
The Color toggles fine in the UI but the isNeeded string value disappears on the first tap and never re-appears
Ideas?
A couple issues here. First is that you need to make your ObservableCollection run OnPropertyChanged() when it has been changed like so:
private ObservableCollection<GroceryItem> _oc;
public ObservableCollection<GroceryItem> oc {
get { return _oc ?? (_oc = new ObservableCollection<GroceryItem>()); }
set {
if(_oc != value) {
_oc = value;
OnPropertyChanged();
}
}
}
Now you should really have all of this in a ViewModel but since you do not, you need to set your ContentPage as the BindingContext from within your code-behind like this:
public partial class MyGroceryPage : ContentPage {
public MyGroceryPage() { BindingContext = this; }
}
You also need to be binding your ObservableCollection to your ListView.ItemSource instead of assigning it. That looks like this:
<ListView ItemSource="{Binding oc}"/>
If you do the above and then you go into your code behind and execute lvGroceries.ItemsSource = oc; then that would overwrite the binding that you did in your XAML, so do not do that. Instead, when you get data from your web service, you would just assign it to your existing ObservableCollection:
public async Task GetGroceryData() {
List<GroceryItem> myData = await GroceryService.GetGroceriesAsync();
oc = new ObservableCollection<GroceryItem>(myData);
}
Try all of that first and if your items are still not updating you might want to try removing them from your ObservableCollection, changing the properties, then adding them back in:
public void GroceryPdsItemTapped(object obj, ItemTappedEventArgs e) {
if (e.Item == null) { return; }
var g = ((GroceryItem)e.Item);
foreach (var gr in oc.ToList()) {
if (gr.GroceryId == "27769") { // the UI changes because the myBackgroundColor property in the GroceryItem class is watching for a value change
oc.Remove(gr);
gr.myBackgroundColor = (gr.myBackgroundColor == Color.Yellow) ? Color.Blue : Color.Yellow;
gr.isNeeded = (gr.isNeeded == "true" || gr.isNeeded == "blah" || gr.isNeeded == "false") ? "notblah" : "blah";
oc.Add(gr);
}
}
}

How to create a clickable label to open an external url using external browser in Xamarin.Forms?

Isn't there anything like a LinkButton in Xamarin?
I want to create a label with the looks of an url link that once tapped opens the external browser.
I also need to know how to open the device's external browser (to open an specified url) in a Portable Project.
Thanks
The Xamarin.Forms way to open an URL string in the default mobile browser:
Device.OpenUri(new Uri("http://example.com"))
While a Forms' Label does not have a click event, you can add a TapGestureRecognizer so when the label is tapped it executes Device.OpenUri.
Example:
var myURLLabel = new Label
{
Text = "https://xamarin.com"
};
myURLLabel.GestureRecognizers.Add(new TapGestureRecognizer
{
Command = new Command(() => {
Device.OpenUri(new Uri(myURLLabel.Text));
})
});
Xamarin-Forms-Labs' ExtendedLabel allows you style a label's text as underlined....
Ref: https://github.com/XLabs/Xamarin-Forms-Labs/wiki/ExtendedLabel
Create a label that can display underlined (or use Xamarin Forms Labs ExtendedLabel):
using Xamarin.Forms;
public class ExtendedLabel : Label
{
public static readonly BindableProperty IsUnderlinedProperty = BindableProperty.Create<ExtendedLabel, bool>(p => p.IsUnderlined, false);
public bool IsUnderlined
{
get { return (bool)GetValue(IsUnderlinedProperty); }
set { SetValue(IsUnderlinedProperty, value); }
}
// ...other custom properties
}
Handle the IsUnderlined property in your custom renderers:
public class ExtendedLabelRenderer : LabelRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
// ...
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == ExtendedLabel.IsUnderlinedProperty.PropertyName)
{
RefreshControl(Control);
}
}
private void RefreshControl(UILabel control)
{
if (control == null) return;
// Apply based on IsUnderlined property
if (!string.IsNullOrEmpty(control.Text)) control.AttributedText = new NSMutableAttributedString(control.Text, underlineStyle: view.IsUnderlined ? NSUnderlineStyle.Single : NSUnderlineStyle.None);
// ...apply based on other custom properties
}
}
Add your label control to YourPage.xaml and add a gesture recognizer to handle tap:
<controls:ExtendedLabel
Text="View in browser"
TextColor="Blue"
IsUnderlined="True">
<controls:ExtendedLabel.GestureRecognizers>
<!--TODO:<TapGestureRecognizer Command="TapCommand" /> Couldn't do this for some reason so use Tapped handler-->
<TapGestureRecognizer
Tapped="OnOpenInBrowserTapGestureRecognizerTapped"
NumberOfTapsRequired="1" />
</controls:ExtendedLabel.GestureRecognizers>
</controls:ExtendedLabel>
Handle the tap in code behind YourPage.xaml.cs:
private void OnOpenInBrowserTapGestureRecognizerTapped(object sender, EventArgs args)
{
var labelViewSender = (ExtendedLabel)sender;
labelViewSender.Opacity = 0.6;
labelViewSender.FadeTo(1);
var viewModel = BindingContext as YourPageViewModel;
if (viewModel == null) return;
if (viewModel.NavigateToUrlCommand.CanExecute(null)) viewModel.NavigateToUrlCommand.Execute(null);
}
The view model:
public class YourPageViewModel : ViewModelBase
{
public const string NavigateToUrlMessage = "NavigateToUrl";
private string _url;
public string Url
{
get { return _url; }
set { SetProperty(ref _url, value); }
}
//...
private Command _navigateToUrlCommand;
public ICommand NavigateToUrlCommand
{
get { return _navigateToUrlCommand ?? (_navigateToUrlCommand = new Command(param => NavigateToUrl(), CanNavigateToUrl)); }
}
public bool CanNavigateToUrl(object parameter) => true;
private void NavigateToUrl()
{
MessagingCenter.Send(this, NavigateToUrlMessage, Url);
}
//...
}
Subscribe to and handle the NavigateToUrlMessage message in the YourPage.xaml.cs code behind:
protected override void OnAppearing()
{
MessagingCenter.Subscribe<YourPageViewModel, string>(this, YourPageViewModel.NavigateToUrlMessage, (sender, args) =>
{
var context = (BindingContext as YourPageViewModel);
if (context == null) return;
if (string.IsNullOrEmpty(args)) return;
Device.BeginInvokeOnMainThread(() =>
{
Device.OpenUri(new Uri(args));
});
});
//...
base.OnAppearing();
}
protected override void OnDisappearing()
{
MessagingCenter.Unsubscribe<YourPageViewModel, string>(this, YourPageViewModel.NavigateToUrlMessage);
//...
base.OnDisappearing();
}
For Xamarin.Forms 3.2 and above
<Label>
<Label.FormattedText>
<FormattedString>
<Span Text="{Binding Url, Mode=OneWay}" TextColor="Blue">
<Span.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding TapCommand, Mode=OneWay}"
CommandParameter="https://www.xamarin.com"/>
</Span.GestureRecognizers>
</Span>
</FormattedString>
</Label.FormattedText>
</Label>
You may use commands with or without parameters, or just the tapped event.
Example :
<Label>
<Label.FormattedText>
<FormattedString>
<Span Text="Xamarin" TextColor="Blue">
<Span.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding TapCommand, Mode=OneWay}"
CommandParameter="https://www.xamarin.com"/>
</Span.GestureRecognizers>
</Span>
</FormattedString>
</Label.FormattedText>
</Label>
in the ViewModel :
private ICommand _tapCommand;
public ICommand TapCommand =>
_tapCommand ?? (_tapCommand = new Command<string>(OpenUrl));
void OpenUrl(string url)
{
Device.OpenUri(new Uri(url));
}

Resources