Fixed point button using absolute layout not responding to events - xamarin.forms

I have a listview on a content page. I have placed a fixed point button(think that is proper name) on the page using AvsoluteLayout. I have the button going to the top of the view using RaiseChild. The click event is not firing on the button but on the list view.
my on appearing where bubblebutton is the item at issue
protected override void OnAppearing()
{
base.OnAppearing();
IsBusy = true;
if(viewModel.PlayerActivities == null)
{
viewModel.LoadPlayerActivites.Execute(null);
}
IsBusy = false;
grid.RaiseChild(bubblebutton);
bubblebutton.Clicked += Bubblebutton_Clicked;
}
```
```
<AbsoluteLayout >
<ImageButton x:Name="bubblebutton"
BackgroundColor="Transparent"
Source="st_fab_button.png"
AbsoluteLayout.LayoutBounds="3,500,700,80"
Clicked="bubblebutton_Clicked"
IsEnabled="True"
/>
</AbsoluteLayout>
```
[button click is not happening][1]
Thank you for any help sorry for poor clip art skill
[1]: https://i.stack.imgur.com/QJixO.png

Welcome to SO !
If using Button inside Item of ListView , should use the MVVM architecture to do . That means we should use Binding model to get the click event .
Such as modified code as follow :
```
<AbsoluteLayout >
<ImageButton x:Name="bubblebutton"
BackgroundColor="Transparent"
Source="st_fab_button.png"
AbsoluteLayout.LayoutBounds="3,500,700,80"
Command="{Binding MyCommand}"
IsEnabled="True"
/>
</AbsoluteLayout>
```
Then in your ViewModle should declare the MyCommand :
public ICommand MyCommand { private set; get; }
public ViewModel()
{
MyCommand = new Command(
execute: () =>
{
// do some thing
RefreshCanExecutes();
},
canExecute: () =>
{
// return !IsEditing;
});
ยทยทยท
}
In addition , you also can pass Parameters , more info can have a look at this document .

Related

Xamarin View freeze when raising OnPropertyChanged on value binded to Xamarin community toolkit BadgeView

I'm currently struggling with a weird behavior concerning Xamarin Community Toolkit BadgeView component.
The component is used in the TitleView of my page like this:
<TabbedPage>
<Shell.TitleView>
<Grid ColumnDefinitions="6*,1*">
<Image Source="logo" HorizontalOptions="Center" Margin="0,2,0,2"/>
<StackLayout Grid.Column="1" Orientation="Horizontal">
<Label x:Name="For testing only" Text="{Binding NotificationsNumber}" VerticalOptions="Center"/>
<StackLayout VerticalOptions="Center" HorizontalOptions="EndAndExpand">
<StackLayout.GestureRecognizers>
<TapGestureRecognizer Command="{Binding OpenNotificationCommand}" NumberOfTapsRequired="1"/>
</StackLayout.GestureRecognizers>
<xct:BadgeView Text="{Binding NotificationsNumber}" BackgroundColor="#c1121f" TextColor="White" FontSize="Caption" AutoHide="True">
<Image>
<Image.Source>
<FontImageSource FontFamily="FASolid" Color="White" Size="Large" Glyph="{x:Static icons:FontAwesomeIcons.Bell}"/>
</Image.Source>
</Image>
</xct:BadgeView>
</StackLayout>
</StackLayout>
</Grid>
</Shell.TitleView>
Page content
</TabbedPage
For testing i added the label above with x:Name="for testing only" with Text Bindable Property binded to my property and the value update well without any concern.
In my ViewModel the property NotificationsNumber is initialized in the method InitializeAsync called by the constructor of the viewModel:
public class HomeViewModel : INotifyPropertyChanged, ApiViewModelBase
{
public event PropertyChangedEventHandler? PropertyChanged;
private int _notificationsNumber = 0;
public HomeViewModel(IApiClient client) : base(client)
{
OpenNotificationCommand = new Command(async () => await Shell.Current.GoToAsync($"{nameof(PlaceholderPage)}"));
InitializeAsync();
}
public ICommand OpenNotificationCommand { get; }
public int NotificationsNumber
{
get => _notificationsNumber;
private set => SetProperty(ref _notificationsNumber, value);
}
private async void InitializeAsync()
{
await RunInSafeScope(async () =>
{
// API call made with an instance of custom Http client instance
var notificationCountTask = HttpClient.GetWithRetryAsync<ValueResult<int>>(ApiRoutes.NOTIFICATION_COUNT);
var htmlSource = new HtmlWebViewSource();
await Task.WhenAll(notificationCountTask);
// notificationCountTask.Result.Value return 2 and update NotificationsNumber Property
NotificationsNumber = notificationCountTask.Result.Value;
}, (ex) =>
{
if (ex is ApiRequestException exception && exception.StatusCode == System.Net.HttpStatusCode.Unauthorized)
throw new Exception("Erreur", "Unauthorized");
else
throw new Exception("Erreur", "An internal error occured");
});
}
protected bool SetProperty<T>(ref T backingStore, T value, [CallerMemberName] string propertyName = "", Action? onChanged = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected async Task RunInSafeScope(Func<Task> tryScope, Action<Exception> catchScope, Action? finallyScope = null)
{
try
{
await tryScope.Invoke();
}
catch (Exception ex)
{
catchScope.Invoke(ex);
}
finally
{
finallyScope?.Invoke();
}
}
}
For the sack of clarity i simplified the ViewModel and displayed only methods or properties or instructions usefull for this context.
So What is happening here is when i call the InitializeAsync the api call is made successfully then i set the value of NotificationsNumber property. The SetProperty method is raised, backing field is updated then OnPropertyChanged is invoked then i go back in the getter returning the updated value for finally having no response after that the screen remain freezed like if it was a deadlock.
I precise in the InitializeAsync() method i instantiate other properties with exactly the same process and there is no problems at all, that's why i think the problem is coming from the BindableProperty of the BadgeView component making an infinite loop or something of this kind.
I can't figure it out how to check if my assumptions are true, or test further.
Thanks in advance for your help!
Yes, it is the case as you said.
And I have created a new issue about this problem, you can follow it up here: https://github.com/xamarin/XamarinCommunityToolkit/issues/1900.
Thanks for your feedback and support for xamarin.
Best Regards.

how to bind isenabled property to entry in MVVM when I click on submit button in xamarin forms

I am facing an issue when I submit my form in xamarin form using mvvm architecture my form UI is still able and user can interact while fetching the data from server. I want to disable my UI elements when my submit button is running to fetch the data from server. Actually, I want to bind isEnabled property in my viewmodel. But I do not know how to set it to bool value from my view model and then bind it to the UI elements. What i need to add in my set function so that when someone click on submit button my UI elements will be inactive and user can not edit till the response comes from server.
what to do please assist. Here is my code.
Blockquote
<StackLayout>
<Entry x:Name="entryFullName"
Text="{Binding FullName}"
Placeholder="Full Name"
IsEnabled="{Binding block}"
/>
<Picker x:Name="pickerGender"
Title="Gender"
ItemsSource="{Binding Genders}"
SelectedItem="{Binding SelectedGender}"
IsEnabled="{Binding gender}"
/>
</StackLayout>
<StackLayout>
<Button x:Name="btnSubmit"
Command="{Binding SubmitCommand}"
Text="Submit"
/>
</StackLayout>
<ActivityIndicator IsVisible="{Binding IsBusy}" IsRunning="{Binding IsBusy}" />
here is my code for my viewmodel submit button function
Blockquote
private string _Block;
public string Block
{
get { return _Block }
set { _Block = value; OnPropertyChanged(); }
}
private void OnSubmit()
{
if (string.IsNullOrEmpty(this.FullName))
{
this.ErrorOccurred?.Invoke(this, "Please enter full name");
return;
}
Device.BeginInvokeOnMainThread(async () => await this.SaveProfile();
}
first, bind all of your IsEnabled properties to the same VM property
<Entry x:Name="entryFullName" IsEnabled="{Binding NotBusy}" ... />
<Picker x:Name="pickerGender" IsEnabled="{Binding NotBusy}" ... />
...
<Button x:Name="btnSubmit" IsEnabled="{Binding NotBusy}" ... />
then in your MV create a bool property
private bool _NotBusy = true;
public bool NotBusy
{
get { return _NotBusy }
set { _NotBusy = value; OnPropertyChanged(); }
}
finally, when saving set the property
private void OnSubmit()
{
if (string.IsNullOrEmpty(this.FullName))
{
this.ErrorOccurred?.Invoke(this, "Please enter full name");
return;
}
NotBusy = false;
Device.BeginInvokeOnMainThread(async () => await this.SaveProfile();
}
you can add a property IsNotSubmitting,
private bool _isNotSubmitting = true;
public bool IsNotSubmitting {
get => _isNotSubmitting ;
set {
_isNotSubmitting = value;
OnPropertyChanged();
}
}
binding in Xaml:
<Entry x:Name="entryFullName"
Text="{Binding FullName}"
Placeholder="Full Name"
IsEnabled="{Binding IsNotSubmitting}" />
now you can set "IsNotSubmitting=false" in the beginning of method SubmitCommand, and you can set "IsNotSubmitting=true" when the commiting is finished

An emptyView for loading data and another for when there is no data available

I have a case of using a CarouselView that is displayed based on certain data brought from an API, the point is that I need to see a certain view or at least text while the API data is being downloaded and another one in case That there is no data.
I tried to get to this using RefreshView and EmptyView but I cannot achieve the required behavior, I can make an EmptyView appear immediately the data begins to load since at that moment the ItemSource is null, then when the data reaches the app the Carousel appears , which seems to me quite ugly, the ideal would be to show some view that next to the RefreshView indicator shows that the data is loading and then in case of not bringing any data show a view that of the feedback that API data did not return .
I hope I have made myself understood and I hope someone can give me an idea on how to achieve this behavior.
MyViewModel:
public MyViewModel()
{
IsRefreshing = true;
Things = new ObservableCollection<Things>();
var t = Task.Run(async () =>
{
await LoadThings();
});
Task.WhenAll(t);
IsRefreshing = false;
}
private async Task LoadThings()
{
Things = new List<Thing>(await App.WebApiManager.GetThingsAsync(Id));
}
My IsRefreshing property is linked to the IsRefreshing property in the RefreshView that encompasses my CarouselView
I think you could use two empty view and switch between them when the refreshing status changes, and here is the code:
add two content view in in XAML and set default empty view to LoadingData:
<ContentPage.Resources>
<ContentView x:Key="LoadingData">
<StackLayout>
<Label Text="Loading data..."
Margin="10,25,10,10"
FontAttributes="Bold"
FontSize="18"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center" />
</StackLayout>
</ContentView>
<ContentView x:Key="NoDataLoaded">
<StackLayout>
<Label Text="No items to display."
Margin="10,25,10,10"
FontAttributes="Bold"
FontSize="18"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center" />
</StackLayout>
</ContentView>
</ContentPage.Resources>
<StackLayout Margin="20">
<RefreshView IsRefreshing="{Binding IsRefreshing}"
Command="{Binding RefreshCommand}">
<CarouselView x:Name="carouselView"
EmptyView="{StaticResource LoadingData}">
... ...
and in code, show different empty view accordingly:
public partial class HorizontalPullToRefreshPage : ContentPage
{
AnimalsViewModel viewModel;
public HorizontalPullToRefreshPage()
{
InitializeComponent();
viewModel = new AnimalsViewModel();
this.BindingContext = viewModel;
viewModel.PropertyChanged += ViewModel_PropertyChanged;
}
private void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName.Equals("IsRefreshing"))
{
if (viewModel.IsRefreshing && viewModel.Animals.Count==0)
{
carouselView.EmptyView = Resources["LoadingData"];
}
else if (!viewModel.IsRefreshing && viewModel.Animals.Count == 0)
{
carouselView.EmptyView = Resources["NoDataLoaded"];
}
}
}
protected override async void OnAppearing()
{
base.OnAppearing();
await Task.Delay(2000);
carouselView.ItemsSource = viewModel.Animals;
}
}
then, every time the property IsRefreshing changed, you got a chance to switch the empty view.
Hope it helps.

Update display of one item in a ListView's ObservableCollection

I have a ListView which is bound to an ObservableCollection.
Is there a way to update a single cell whenever a property of a SomeModel item changed, without reloading the ListView by changing the ObservableCollection?
(Question is copied from https://forums.xamarin.com/discussion/40084/update-item-properties-in-a-listviews-observablecollection, as is my answer there.)
As I can see you are trying to use MVVM as a pattern for your Xamarin.Forms app. You are already using the ObservableCollection for displaying a list of the data. When a new item is added or removed from collection UI will be refreshed accordingly and that is because the ObserverbleCollection is implementing INotifyCollectionChanged.
What you want to achieve with this question is next behaviour, when you want to change the particular value for the item in the collection and update the UI the best and simplest way to achieve that is to implement INotifyPropertyChanged for a model of the item from your collection.
Bellow, I have a simple demo example on how to achieve that, your answer is working as I can see but I am sure this example would be nicer for you to use it.
I have simple Button with command and ListView which holds my collection data.
Here is my page, SimpleMvvmExamplePage.xaml:
<StackLayout>
<Button Text="Set status"
Command="{Binding SetStatusCommand}"
Margin="6"/>
<ListView ItemsSource="{Binding Cars}"
HasUnevenRows="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Vertical"
Margin="8">
<Label Text="{Binding Name}"
FontAttributes="Bold" />
<StackLayout Orientation="Horizontal">
<Label Text="Seen?"
VerticalOptions="Center"/>
<CheckBox IsChecked="{Binding Seen}"
Margin="8,0,0,0"
VerticalOptions="Center"
IsEnabled="False" />
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
The basic idea from this demo is to change the value of the property Seen and set value for the CheckBox when the user clicks on that Button above the ListView.
This is my Car.cs class.
public class Car : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged();
}
}
private bool seen;
public bool Seen
{
get { return seen; }
set
{
seen = value;
OnPropertyChanged();
}
}
// Make base class for this logic, something like BindableBase
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
In the full demo example which is on my Github, I am using my BindableBase class where I handle raising the INotifyPropertyChanged when some property value is changed with this SetProperty method in the setter of the props.
You can find the implementation here: https://github.com/almirvuk/Theatrum/tree/master/Theatrum.Mobile/Theatrum.Mobile
The last thing to show is my ViewModel for this page, and inside of the ViewModel, I will change the value of Seen property to True for items in the collection when the user clicks on the Button above the ListView. Here is my SimpleMvvmExamplePageViewModel.cs
public class SimpleMvvmExamplePageViewModel
{
public ObservableCollection<Car> Cars { get; set; }
public ICommand SetStatusCommand { get; private set; }
public SimpleMvvmExamplePageViewModel()
{
// Set simple dummy data for our ObservableCollection of Cars
Cars = new ObservableCollection<Car>()
{
new Car()
{
Name = "Audi R8",
Seen = false
},
new Car()
{
Name = "BMW M5",
Seen = false
},
new Car()
{
Name = "Ferrari 430 Scuderia",
Seen = false
},
new Car()
{
Name = "Lamborghini Veneno",
Seen = false
},
new Car()
{
Name = "Mercedes-AMG GT R",
Seen = false
}
};
SetStatusCommand = new Command(SetStatus);
}
private void SetStatus()
{
Car selectedCar = Cars.Where(c => c.Seen == false)
.FirstOrDefault();
if (selectedCar != null)
{
// Change the value and update UI automatically
selectedCar.Seen = true;
}
}
}
This code will help us to achieve this kind of behaviour: When the user clicks on the Button we will change value of the property of the item from collection and UI will be refreshed, checkbox value will be checked.
The final result of this demo could be seen on this gif bellow.
P.S. I could combine this with ItemTapped event from ListView but I wanted to make this very simple so this example is like this.
Hope this was helpful for you, wishing you lots of luck with coding!
Any UI associated with a model item will be refreshed, if replace the item with itself, in the Observable Collection.
Details:
In ViewModel, given property:
public ObservableCollection<Item> Items { get; set; } = new ObservableCollection<Item>();
Where Item is your model class.
After adding some items (not shown), suppose you want to cause item "item" to refresh itself:
public void RefreshMe(Item item)
{
// Replace the item with itself.
Items[Items.IndexOf(item)] = item;
}
NOTE: The above code assumes "item" is known to be in "Items". If this is not known, test that IndexOf returns >= 0 before performing the replacement.
In my case, I had a DataTemplateSelector on the collection, and the item was changed in such a way that a different template was required. (Specifically, clicking on the item toggled it between collapsed view and expanded/detailed view, by the TemplateSelector reading an IsExpanded property from the model item.)
NOTE: tested with a CollectionView, but AFAIK will also work with the older ListView class.
Tested on iOS and Android.
Technical Note:
This replacement of an item presumably triggers a Replace NotifyCollectionChangedEvent, with newItems and oldItems both containing only item.

Xamarin Forms -> Activity Indicator not working if Commands of statements to be executed

Using Visual Studio 2017 Community 15.8.1
This is after going through all options of stackoverflow regarding ActivityIndicator. So though it may be a duplication but nothing is helping me out.
So finally decided to post my workouts and get best help from here.
What I have tried till now:-
1. {Binding IsLoading} + INotifyPropertyChanged + public void RaisePropertyChanged(string propName) + IsLoading = true; concept.
2. ActivityIndicator_Busy.IsVisible = false; (Direct control accessed)
These two approaches were mostly recommended and I went into depth of each since lot of hours in last few weeks. But nothing got crack.
What I achieved?:-
ActivityIndicator_Busy.IsVisible = false; concept is working smooth only when I put return before executing the statements (for testing purpose); statement on Button Clicked event. (Attached Image)
But as soon as I remove the return; On Pressing Button, directly after some pause, the HomePage Opens.
MY Questions:-
1. This is particular with the current scenario how to get the ActivityIndicator run Immediately when user clicks the Welcome Button.
2. Pertaining to same, When app starts there is also a blank white screen coming for few seconds almost 30 seconds which I also I want to show ActivityIndicator. But dont know how to impose that logic at which instance.
My Inputs
My MainPage.xaml File:-
(Edited 06-Sept-2018 09.11 pm)
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage x:Name="page_main_page"
NavigationPage.HasBackButton="False"
NavigationPage.HasNavigationBar="False"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:appNutri"
BindingContext="{x:Reference page_main_page}"
x:Class="appNutri.MainPage">
<ContentPage.Content>
<StackLayout BackgroundColor="White"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand">
<StackLayout>
<Image x:Name="Image_Welcome"
Source="welcome.png"
HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand"
WidthRequest="300"
HeightRequest="300" />
<Button x:Name="Button_Welcome"
Clicked="Button_Welcome_Clicked"
Text="Welcome!"
BackgroundColor="DeepSkyBlue"
HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand"
TextColor="White"
HeightRequest="60" />
</StackLayout>
<StackLayout BackgroundColor="White"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand">
<ActivityIndicator
x:Name="ActivityIndicator_Busy"
Color="Black"
IsEnabled="True"
HorizontalOptions="Center"
VerticalOptions="Center"
IsRunning="{Binding Source={x:Reference page_main_page}, Path=IsLoading}"
IsVisible="{Binding Source={x:Reference page_main_page}, Path=IsLoading}" />
</StackLayout>
</StackLayout>
</ContentPage.Content>
</ContentPage>
My MainPage.cs Code:-
(Edited on 06-Sept-2018 09.13 pm)
using appNutri.Model;
using SQLite;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace appNutri
{
public partial class MainPage : Xamarin.Forms.ContentPage, INotifyPropertyChanged
{
private bool isLoading;
public bool IsLoading
{
get
{
return isLoading;
}
set
{
isLoading = value;
RaisePropertyChanged("IsLoading");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public MainPage()
{
InitializeComponent();
BindingContext = this;
}
protected override void OnAppearing()
{
base.OnAppearing();
BindingContext = this;
}
protected async void Button_Welcome_Clicked(object sender, EventArgs e)
{
IsLoading = true;
await Select_Local_User_Information();
IsLoading = false;
}
private async Task Select_Local_User_Information()
{
IsLoading = true;
string where_clause = "";
try
{
Sql_Common.Database_Folder_Path = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
string Database_Full_Path = Path.Combine(Sql_Common.Database_Folder_Path, Sql_Common.Database_Name);
SQLiteConnection connection = new SQLiteConnection(Database_Full_Path);
//connection.DropTable<User_Master>();
//connection.Delete(connection.Table<User_Master>());
//connection.CreateTable<User_Master>(CreateFlags.ImplicitPK | CreateFlags.AutoIncPK);
connection.CreateTable<User_Master>();
int count = connection.ExecuteScalar<int>("Select count(*) from User_Master");
if (count == 0)
{
connection.DropTable<User_Master>();
connection.CreateTable<User_Master>();
//IsLoading = false;
//IsBusy = false;
await Navigation.PushAsync(new User_Register_Page());
}
else
{
Sql_Common.User_Logged = true;
var Local_User_Data = connection.Table<User_Master>().ToList();
User_Master.Logged_User_Details_Container.First_Name = Local_User_Data[0].First_Name;
User_Master.Logged_User_Details_Container.Cell1 = Local_User_Data[0].Cell1;
where_clause = " Upper ( First_Name ) = " + "'" + User_Master.Logged_User_Details_Container.First_Name.ToUpper().Trim() + "'" + " and " +
" Cell1 = " + "'" + User_Master.Logged_User_Details_Container.Cell1.Trim() + "'";
int records = Sql_Common.Get_Number_Of_Rows_Count("User_Master", where_clause);
if (records == 0)
{
connection.DropTable<User_Master>();
connection.CreateTable<User_Master>();
IsLoading = false;
await Navigation.PushAsync(new User_Register_Page());
}
else
{
User_Master.User_Master_Table(where_clause, User_Master.Logged_User_Details_Container);
IsLoading = false;
await Navigation.PushAsync(new User_Home_Page());
}
}
connection.Close();
}
catch (SQLiteException ex)
{
string ex_msg = ex.Message;
}
IsLoading = false;
}
}
}
04-Oct-2018
Finally resolved with This Answer
Update 2018-09-10
You think that you have implemented INotifyPropertyChanged by adding INotifyPropertyChanged to your class definition and adding the event
public event PropertyChangedEventHandler PropertyChanged;
along with its event invocator
public void RaisePropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
Anyway, since ContentPage already implements INotifyPropertyChanged, adding those did not implement INotifyPropertyChanged. ContentPage already defines the event (or rather BindableObjectfrom which ContentPage indirectly inherits). Any object that relies on being informed about property changes in your page will subscribe to the PropertyChanged event of the ancestor and not the PropertyChanged event you defined, hence the ActivityIndicator will not update.
Just remove the event you defined and call OnPropertyChanged instead of RaisePropertyChanged() and you should be fine.
private bool isLoading;
public bool IsLoading
{
get
{
return isLoading;
}
set
{
isLoading = value;
OnPropertyChanged();
}
}
Since OnPropertyChanged is declared as
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
you don't have to pass the property name by hand. The compiler will do that for you beacsue of the CallerMemberNameAttribute.
End Update
The XAML extension {Binding IsLoading} binds the ActivityIndicator to the BindingContext of your page. By default the BindingContext is null, hence there is nothing to bind to and all your efforts are to no avail.
With a viewmodel
The preferred solution would be to use a viewmodel and assign it to MainPage.BindingContext, e.g.
var page = new MainPage()
{
BindingContext = new MainPageViewModel()
}
but if you take that road, you should move all of your UI logic to that viewmodel and encapsulate your SQL access and business logic in other classes, to keep the viewmodel clean from resource accesses and business logic. Having the resource accesses and logic in code behind may work for that small example, but is likely to become an unmaintainable mess.
Without a viewmodel
Anyway, you don't have to use a viewmodel to use bindings. You can set the BindingContext for the page (or some children) or use the Source of the BindingExtension
Setting the BindingContext
The BindingContext is passed from any page or view to it's children. You first have to give your page a name with x:Name="Page" (don't have to use Page, anyway, you can't use the class name of your page) and set the BindingContext to that page
<ContentPage ...
x:Name="Page"
BindingContext="{x:Reference Page}"
...>
now binding to IsLoading should work.
Using Source in the Binding
If you want to reference something else than the BindingContext of a view, BindingExtension has a property Source. You have to give a name to your page, too (see above)
<ContentPage ...
x:Name="Page"
...>
and now you can reference this in your binding
<ActivityIndicator
...
IsRunning="{Binding Path=IsLoading, Source={x:Reference Page}}"
IsVisible="{Binding Path=IsLoading, Source={x:Reference Page}}"/>

Resources