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

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}}"/>

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.

Fixed point button using absolute layout not responding to events

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 .

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.

How to bind data to Multiple listViews in TabbedPage

I have the following:
1) I have a TabbedPage which contain multiple Tabs.
2) Each Tab contains a page which has ListView
3) The data for each of ListView in each Tab base on a single Json result from REST service
Problem:
At where I should call and consume the Json REST web service and how to bind the data to each of the ListView?
say, at Code Behind of TabbedPage.xaml.cs
I start to call the Json Web service
var client = new System.Net.Http.HttpClient();
var response = await client.GetAsync("Http://Rest WebService");
string GeneralJson = await response.Content.ReadAsStringAsync();
GeneralClassforJsonList ObjGeneralList = new GeneralClassForJsonList();
if (GeneralJson != "")
{
ObjGeneralList = JsonConvert.DeserializeObject<GeneralClassforJsonList>(generalList);
}
var Product = ObjectGeneralList.Result.Products
var Service = ObjectGeneralList.Result.Services
I would like to know How to Binding the data to each of the listviews respectively
in product and service pages:
listviewProduct.ItemsSource = Product
listviewService.ItemsSource = Service
Update(1): Approach 1
In TabbedPage, it has 2 or more tab.
<?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:local ="clr-namespace:SembIWIS.View"
x:Class="myApp.MainPage">
<local:ProductPage>
</local:ProductPage>
<local:ServicePage>
</local:ServicePage>
</TabbedPage>
1)
I created a static Class in Model to store Json Result in string
public static class ProductServiceJson
{
public static string StrProductServiceJson;
}
2)
In TabbedPage.xaml.cs (Code Behind)
public partial class MainPage : TabbedPage
{
public MainPage()
{
InitializeComponent();
Init();
}
private void Init()
{
//- code here not execute.
//I start to call the REST api
var response = await client.GetAsync("Http://Rest WebService");
string GeneralJson = await response.Content.ReadAsStringAsync();
ProductServiceJson.StrProductServiceJson = GeneralJson;
}
}
3)
public partial class Product : ContentPage
{
public Product()
{
InitializeComponent();
SetUpListData();
}
private void SetUpListData()
{
//-- code
// how come code here will be executed but not code in tabbedPage.xaml.cs?
}
}
Problem:
1) Why code in TabbedPage.xaml.cs not execute but code in Product.xaml.cs (code behind of Product.xaml)?
2) When code excuted in Product.xaml.cs, the StrProductService is empty or null since web service call in TabbedPage.xaml.cs
3) Will code in Service.xaml.cs be executed?
4) What is the order of execution for TabbedPage, I mean which Tab will have the first execution and if there are 6 tabs?
TIA
Here is how you can do it. I didn't test that code, just wrote to give you an idea.
class ObjGeneral
{
public string Name{get;set;}
}
public class MyViewModel
{
public List<ObjGeneral> MyList{get;set;}
}
Then in page code
var myViewModel=new MyViewModel();
//populate your list
myViewModel.MyList=JsonConvert.DeserializeObject<List<ObjGeneral>>(generalList);
BindingContext=myViewModel;
Then in xaml
<ListView x:Name="toggleListView" ItemsSource="{Binding MyList}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label x:Name="lblPlaceName" Text="{Binding Name}" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

RelayCommand.CanExecute not updating IsEnabled in UI

I have a Windows Phone 8 app and I have a RelayCommand Instance called DiscoverExpansionModulesCommand. I have a button with the Command property bound to DiscoverExpansionModulesCommand. When the app first loads, the button is enabled or disabled properly. However, when on the page and I want to change whether the command can execute, the method CanExecuteDiscoverExpansionModulesCommand() properly fires and it returns the proper true or false value, but the button does not reflect it. Why isn't button updating it's UI? I found another article on this issue here http://social.msdn.microsoft.com/Forums/en-US/silverlightarchieve/thread/48a341e4-f512-4c33-befd-b614404b4920/
My ViewModel:
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;
using MAL.Portable.Commands;
using MAL.Portable.Message;
using MAL.Portable.Model;
using System;
using System.Collections.Generic;
using System.Windows.Input;
namespace MAL.Portable.ViewModel
{
public class SettingsViewModel : ViewModelBase
{
// Define an observable collection property that controls can bind to.
private List<Setting> settings;
private String controllerUrl;
private String controllerPort;
private String temperature;
private Wifi wifi;
private Boolean connected;
private Boolean checkingConnection;
public SettingsViewModel()
{
DiscoverExpansionModulesCommand = new RelayCommand(OnDiscoverExpansionModules, CanExecuteDiscoverExpansionModulesCommand);
Messenger.Default.Register<RetrieveSettingsMessage>
(
this, (action) => RetrievedListsMessage(action)
);
Messenger.Default.Send<GetSettingsMessage>(new GetSettingsMessage());
}
public ICommand DiscoverExpansionModulesCommand
{
get;
private set;
}
public String ConnectionStatus
{
get
{
if (checkingConnection)
return "checking";
else
return connected ? "connected" : "not connnected";
}
}
private Boolean CanExecuteDiscoverExpansionModulesCommand()
{
return connected;
}
private void OnDiscoverExpansionModules()
{
}
private void CheckConnection()
{
wifi = null;
if (!String.IsNullOrWhiteSpace(ControllerUrl) && !String.IsNullOrWhiteSpace(ControllerPort) && !checkingConnection)
{
checkingConnection = true;
wifi = new ReefAngelWifi(controllerUrl, controllerPort);
wifi.TestConnectionComplete += wifi_TestConnectionComplete;
wifi.RequestFail += wifi_RequestFail;
wifi.BeginTestConnection();
}
}
private void wifi_RequestFail(object sender, RequestExceptionEventArgs e)
{
connected = false;
checkingConnection = false;
RaisePropertyChanged("ConnectionStatus");
}
private void wifi_TestConnectionComplete(object sender, TestConnectionEventArgs e)
{
connected = e.TestSuccessful;
checkingConnection = false;
DiscoverExpansionModulesCommand.CanExecute(null);
RaisePropertyChanged("ConnectionStatus");
RaisePropertyChanged("DiscoverExpansionModulesCommand");
}
private object RetrievedListsMessage(RetrieveSettingsMessage action)
{
settings = action.Settings;
CheckConnection();
return null;
}
private String GetStringValue(String key)
{
if (settings == null) return String.Empty;
var item = settings.Find(x => x.Key == key);
if (item == null) return String.Empty;
else return item.Value;
}
private Boolean GetBooleanValue(String key)
{
if (settings == null) return false;
var item = settings.Find(x => x.Key == key);
if (item == null) return false;
else return Boolean.Parse(item.Value);
}
}
}
And the XAML
<phone:PhoneApplicationPage
xmlns:ReefAngel="clr-namespace:MAL.WindowsPhone8"
xmlns:Controls="clr-namespace:Telerik.Windows.Controls;assembly=Telerik.Windows.Controls.Input"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Platform.WP8"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:telerikPrimitives="clr-namespace:Telerik.Windows.Controls;assembly=Telerik.Windows.Controls.Primitives"
x:Class="MAL.WindowsPhone8.ReefAngel.SettingsPage"
xmlns:converter="clr-namespace:MAL.WindowsPhone8.Converters"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
DataContext="{Binding Settings, Source={StaticResource Locator}}"
SupportedOrientations="Portrait" Orientation="Portrait"
mc:Ignorable="d"
shell:SystemTray.IsVisible="True">
<phone:PhoneApplicationPage.Resources>
<converter:BooleanToStringConverter x:Key="temperatureConverter" TrueString="Celsius" FalseString="Fahrenheit" />
<converter:BooleanToStringConverter x:Key="timeFormatConverter" TrueString="24 hour" FalseString="12 hour" />
<converter:BooleanToStringConverter x:Key="dateFormatConverter" TrueString="dd/mm/yyyy" FalseString="mm/dd/yyyy" />
</phone:PhoneApplicationPage.Resources>
<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" >
<phone:Pivot Title="{Binding LocalizedResources.ApplicationTitle, Source={StaticResource LocalizedStrings}, StringFormat='\{0\} Settings'}">
<phone:PivotItem Header="connection">
<Grid>
<StackPanel Margin="12,0,0,0">
<TextBlock Margin="0,20,0,0" TextWrapping="Wrap" Text="Reef Angel Wifi Address"/>
<TextBox Height="72" TextWrapping="Wrap" Text="{Binding ControllerUrl, Mode=TwoWay}"/>
<TextBlock Margin="0,20,0,0" TextWrapping="Wrap" Text="Reef Angel Wifi Port"/>
<TextBox Height="72" TextWrapping="Wrap" Text="{Binding ControllerPort, Mode=TwoWay}"/>
<StackPanel Orientation="Horizontal">
<TextBlock Margin="0,20,0,0" TextWrapping="Wrap" Text="Reef Angel Wifi Status : "/>
<TextBlock Margin="0,20,0,0" TextWrapping="Wrap" Text="{Binding ConnectionStatus, Mode=OneWay}"/>
</StackPanel>
</StackPanel>
</Grid>
</phone:PivotItem>
<phone:PivotItem Header="expansion">
<Grid>
<Button Content="Discover Expansion Modules" x:Name="DiscoverButton" Command="{Binding DiscoverExpansionModulesCommand, Mode=OneWay}" />
</Grid>
</phone:PivotItem>
</phone:Pivot>
</Grid>
</phone:PhoneApplicationPage>
I am using the MVVM Light Portable Class Libraries.
You need to call RelayCommand.RaiseCanExecuteChanged() when the conditions you evaluate inside your CanExecute method change.
Edit
private void wifi_RequestFail(object sender, RequestExceptionEventArgs e)
{
connected = false;
checkingConnection = false;
RaisePropertyChanged("ConnectionStatus");
DiscoverExpansionModulesCommand.RaiseCanExecuteChanged();
}
private void wifi_TestConnectionComplete(object sender, TestConnectionEventArgs e)
{
connected = e.TestSuccessful;
checkingConnection = false;
DiscoverExpansionModulesCommand.CanExecute(null);
RaisePropertyChanged("ConnectionStatus");
RaisePropertyChanged("DiscoverExpansionModulesCommand");
DiscoverExpansionModulesCommand.RaiseCanExecuteChanged();
}
This will not cause a loop as it only tells the RelayCommand to re-execute the specified CanExecute method. In your case this only means that the property CanExecuteDiscoverExpansionModulesCommand is read.
It appears to be a cross threading issue. And figuring out how to call a Dispatcher in the PCL was tricky, but I found it here: Update UI thread from portable class library

Resources