how to bind isenabled property to entry in MVVM when I click on submit button in xamarin forms - 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

Related

radio button not pre-selecting on screen loading

I feel this is weird. i can't get the Radio button to pre-select a saved value and it's driving me mad. I have this xaml:
<StackLayout Orientation="Horizontal" RadioButtonGroup.GroupName="Parities"
RadioButtonGroup.SelectedValue="{Binding Parity}">
<RadioButton Value="1" Content="Income" />
<RadioButton Value="-1" Content="Expense" />
<RadioButton Value="0" Content="Neutral" />
</StackLayout>
Furthermore, even if I replace SelectedValue with a hard coded literal value "1" (for Income), the radio button still show up blank. The only way that works is by setting IsChecked on each of the 3 options to have the them pre-selected.
What am I missing?
Based on your code ,I created a simple demo, but I couldn't reproduce this problem. It just works properly.
You can refer to the following code:
MyPage.xaml
<ContentPage.BindingContext>
<radiobuttondemos:MyViewModel></radiobuttondemos:MyViewModel>
</ContentPage.BindingContext>
<StackLayout>
<StackLayout Orientation="Horizontal" RadioButtonGroup.GroupName="{Binding GroupName}"
RadioButtonGroup.SelectedValue="{Binding Parity}">
<RadioButton Value="1" Content="Income" />
<RadioButton Value="-1" Content="Expense" />
<RadioButton Value="0" Content="Neutral" />
</StackLayout>
</StackLayout>
The MyViewModel.cs
public class MyViewModel : INotifyPropertyChanged
{
string groupName;
object parity;
public object Parity
{
get => parity;
set
{
parity = value;
OnPropertyChanged(nameof(Parity));
}
}
public MyViewModel () {
GroupName = "Parities";
Parity = "1";
}
public string GroupName
{
get => groupName;
set
{
groupName = value;
OnPropertyChanged(nameof(GroupName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Note:
In the constructor of MyViewModel, I initialize the value of variable Parity as follows:
Parity = "1";
And if we initialize a value as follows, the UI will not pre-select the saved value :
Parity = 1;

Xamarin.Forms How to active a button once the form is filled

<Frame HasShadow="False">
<StackLayout Orientation="Vertical" >
<Entry Placeholder="NAME" x:Name="name"></Entry>
<Entry Placeholder="SURNAME" x:Name="surname"></Entry>
<StackLayout Orientation="Horizontal" >
<Label Text="BIRTHDATE" VerticalOptions="Center" HorizontalOptions="Center" ></Label>
<DatePicker x:Name="birdthdate" HorizontalOptions="Center" VerticalOptions="Center"></DatePicker>
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="PICK YEARS" HorizontalOptions="Center" VerticalOptions="Center"></Label>
<Picker Title="YEARS" x:Name="years" HorizontalOptions="Center" VerticalOptions="Center"></Picker>
</StackLayout>
<StackLayout Spacing="0">
<Label Text="Number of docs:"></Label>
<Entry Keyboard="Numeric" x:name="docs"></Entry>
</StackLayout>
<Button Text="SAVE" TextColor="White" Padding="0,-20" BackgroundColor="#07987f" IsEnabled="false" >
</Button>
</StackLayout>
</Frame>
My idea is only when user will enter Name Surname Birdthdate Years NumberOfDocs the button will become enable and can save the data. Any suggestion how to do that?
Here is the logic for a simple login with login name + password, where the login button only gets enabled when LoginName and LoginPassword contains text:
private string _loginName;
public string LoginName
{
get { return _loginName; }
set
{
SetProperty(ref _loginName, value);
RaisePropertyChanged("IsLoginButtonEnabled");
}
}
private string _loginPassword;
public string LoginPassword
{
get { return _loginPassword; }
set
{
SetProperty(ref _loginPassword, value);
RaisePropertyChanged("IsLoginButtonEnabled");
}
}
public bool IsLoginButtonEnabled
{
get
{
if (!string.IsNullOrEmpty(LoginName) &&
!string.IsNullOrEmpty(LoginPassword))
{
return true;
}
return false;
}
}
Just extend this to your needs and it should work.
There are multiple ways you can do that. The easiest way is like the answer from Dennis Schröer. But it doesn't look like you are using MVVM so i have another solution using converters.
Change your button to this:
<Button Padding="0,-20"
BackgroundColor="#07987f"
Text="SAVE"
TextColor="White">
<Button.IsEnabled>
<MultiBinding Converter="{StaticResource EnableButtonConverter}">
<Binding Path="Text"
Source="{x:Reference name}" />
<Binding Path="Text"
Source="{x:Reference surname}" />
<Binding Path="Date"
Source="{x:Reference birdthdate}" />
<Binding Path="SelectedItem"
Source="{x:Reference years}" />
</MultiBinding>
</Button.IsEnabled>
</Button>
The property IsEnabled is bound to all the properties you want it to be dependent on.
The converter does the logic:
public class EnableButtonConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var name = (string)values[0];
var surname = (string)values[1];
//var date = (DateTime)values[2];
//var year = (string)values[3];
return !string.IsNullOrWhiteSpace(name) && !string.IsNullOrWhiteSpace(surname); //&& !year.Equals("YEARS"); //Todo: add a check for the date
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Put the Converter in your pages ResourceDictionary and you are good to go.
PS: It's better for performance to use Grid-layout instead of multiple StackLayouts

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 refresh UI of a picker control in xamarin forms

I have 3 picker controls and I am trying to bind a single list to all the 3 picker controls. If one option is selected in first picker control then the same option should not repeat in rest of the 2 picker controls.I am not able to figure out how to implement it.
I tried using Security_Question_1_SelectedIndexChanged() in MainPage.cs file but the UI is not getting updated.
MainPage.xaml:
<Label x:Name="Security_Questions" Margin="0,20,0,0" Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="0" Text="Security Questions" FontSize="Micro" TextColor="MediumVioletRed"></Label>
<Picker x:Name="Security_Question_1" ItemsSource="{Binding SecurityQuestions_List}" Title="Select question one" Grid.Column="0" Grid.Row="1" Margin="-4,0,0,0" FontSize="Micro">
</Picker>
<Entry x:Name="Security_Answer_1" Placeholder="Type answer" Grid.Column="1" Grid.Row="1" FontSize="Micro"/>
<Picker x:Name="Security_Question_2" ItemsSource="{Binding SecurityQuestions_List}" Title="Select question two" Grid.Column="0" Grid.Row="2" Margin="-4,0,0,0" FontSize="Micro">
</Picker>
<Entry x:Name="Security_Answer_2" Placeholder="Type answer" Grid.Column="1" Grid.Row="2" FontSize="Micro"/>
<Picker x:Name="Security_Question_3" ItemsSource="{Binding SecurityQuestions_List}" SelectedIndexChanged="Security_Question_3_SelectedIndexChanged" Title="Select question three" Grid.Column="0" Grid.Row="3" Margin="-4,0,0,0" FontSize="Micro">
MainPage.cs file:
public MainPage()
{
InitializeComponent();
this.BindingContext = new RegistrationPageViewModel();
}
private void Security_Question_1_SelectedIndexChanged(object sender, EventArgs e)
{
try
{
var t1 = ((Xamarin.Forms.Picker)sender).SelectedItem.ToString();
if (t1 == "What is your first vehicle number?")
{
this.Security_Question_2.ItemsSource.Remove("What is your first vehicle number?");
this.Security_Question_3.ItemsSource.Remove("What is your first vehicle number?");
}
else if (t1 == "What is your child's nick name?")
{
this.Security_Question_2.ItemsSource.Remove("What is your child's nick name?");
this.Security_Question_3.ItemsSource.Remove("What is your first vehicle number?");
}
else
{
this.Security_Question_2.ItemsSource.Remove("What is your first school name?");
this.Security_Question_3.ItemsSource.Remove("What is your first vehicle number?");
}
}
catch (Exception)
{
throw;
}
}
RegistrationPageViewModel:
public RegistrationPageViewModel()
{
_department = new List<string>()
{
"What is your first vehicle number?",
"What is your child's nick name?",
"What is your first school name?"
};
}
List<string> _department;
public List<string> SecurityQuestions_List
{
get { return _department; }
private set
{
_department = value;
OnPropertyChanged();
}
}
Any help is appreciated.
You can use converter to 2 other Entries at ItemSource while you use Data Binding, passing the SelectedItem from the Entry as Converter Parameter and inside the converter you can remove the selected item that you passed as parameter.
"I want to avoid the user to select duplicate value in each picker".
You can do something with property SelectedItemProperty, prob not the best way to do it, but one.
For each picker you bind property SelectedItemProperty to a property in the ViewModel. Setting null this property will do the job when a user select a value that is already set in the other picker. Let's say this with two pickers, you can easily adapt it to three pickers.
<Picker x:Name="Security_Question_1" .... SelectedItemProperty="SelectedItemPicker1">
</Picker>
<Picker x:Name="Security_Question_2" .... SelectedItemProperty="SelectedItemPicker2">
</Picker>
ViewModel
public string SelectedItemPicker1
{
get => _selectedItemPicker1;
set
{
if (_selectedItemPicker1== value) return;
if (value == _selectedItemPicker2)
{
_selectedItemPicker2 = null;
OnPropertyChanged("SelectedItemPicker2");
}
_selectedItemPicker1 = value;
OnPropertyChanged("SelectedItemPicker1");
}
}
public string SelectedItemPicker2
{
get => _selectedItemPicker2;
set
{
if (_selectedItemPicker2 == value) return;
_selectedItemPicker2 = value == _selectedItemPicker1 ? null : value;
OnPropertyChanged("SelectedItemPicker2");
}
}
I'm not fan of having such logic in setters but as I said there should be a better approach.

Xamarin Listview don't show the observable Collection

I'm using Xamarin.Forms MVVM to develop my app, and don't found what I'm doing wrong, I have an ObservableCollection with the values from web API, and when I set a break point all the values are good even in the view when I see the values of the binding source everything have the value, but the values are not showing up in my ListView.
Here is the ViewModel
class DatosMedicosViewModel : BaseViewModel
{
private ApiService apiService;
private ObservableCollection<Land> land;
private bool isRefreshing;
public ObservableCollection<Land> Lands
{
get { return this.land; }
set { SetValue(ref this.land, value); }
}
public bool IsRefreshing
{
get { return this.isRefreshing; }
set { SetValue(ref this.isRefreshing, value); }
}
public DatosMedicosViewModel()
{
this.apiService = new ApiService();
this.LoadLand();
}
private async void LoadLand()
{
this.IsRefreshing = true;
var connection = await this.apiService.CheckConnection();
if (!connection.IsSuccess)
{
this.IsRefreshing = false;
await Application.Current.MainPage.DisplayAlert(
"Error",
connection.Message,
"Accept");
await Application.Current.MainPage.Navigation.PopAsync();
return;
}
var response = await this.apiService.GetList<Land>(
"url Base",
"prefix",
"Controller");
if (!response.IsSuccess)
{
this.IsRefreshing = false;
await Application.Current.MainPage.DisplayAlert(
"Error",
response.Message,
"Accept"
);
return;
}
var list = (List<Land>)response.Result;
this.Lands = new ObservableCollection<Land>(list);
this.IsRefreshing = false;
}
public ICommand RefreshCommand
{
get
{
return new RelayCommand(LoadLand);
}
}
}
Here is the View
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ARLAPP.Views.ConsultaPage"
BackgroundColor="White"
BindingContext="{Binding Main, Source={StaticResource Locator}}"
Title="Lands">
<ContentPage.Content>
<StackLayout
BindingContext="{Binding Lands}"
Padding="5">
<StackLayout>
<Image
VerticalOptions="Center"
WidthRequest="300"
Source="UserIcon"
BackgroundColor="Transparent"/>
<Label Text="Mark"
VerticalOptions="Center"
HorizontalOptions="CenterAndExpand"
FontAttributes="Bold"
FontSize="Medium"/>
</StackLayout>
<StackLayout>
<ListView
SeparatorVisibility="Default"
FlowDirection="LeftToRight"
BackgroundColor="White"
ItemsSource="{Binding Lands}"
HasUnevenRows="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label
Grid.Column="2"
VerticalOptions="Center"
TextColor="Black"
Text="{Binding Currency}"/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</StackLayout>
</ContentPage.Content>
</ContentPage>
Here how I call the view
if (this.PageName == "Lands")
{
MainViewModel.GetInstance().Lands= new LandViewModel();
Application.Current.MainPage = new LandMasterPage();
}
Check your BindingContext. I think you are setting it wrong in your view.
In your top-level StackLayout you set the the BindingContext to your property: BindingContext="{Binding Lands}". And in your ListView you set the ItemsSource also to this property: ItemsSource="{Binding Lands}". That won't work because the ListView is trying to bind to a property Lands inside your BindingContext, which is also set to Lands.
Remove the BindingContext from your top-level StackLayout, because you don't need it.
Ensure the BindingContext of your page ConsultaPage is set to your view-model DatosMedicosViewModel.
Sample of setting the bindingcontext (abstract code):
var mypage = new ConsultaPage();
mypage.BindingContext = new DatosMedicosViewModel();
await Navigation.PushAsync(mypage);
// Load your data in OnAppearing() of the page-event
This should solve your binding-problem.
Side-Note: As Abdul Gani said in the comments: Ensure you implement the INotifyPropertyChanged interface, but I assume you do this already in your BaseViewModel and call the NotifyChanged-Event in your SetValue-Method.

Resources