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

Related

Xamarin Forms Picker Value Binding

When binding to a picker, you can use ItemDisplayBinding to bind the displayed value, but I do not see a way to map each item to a selection value. Because of this, I'm having to write some very convoluted code to keep my pickers in sync with data source changes.
Original Model
// NOTE: this implements INPC, just abbreviated for clarity
public class DataModel
{
public ICollection<DataItem> Items;
pubilc DataItem SelectedItem;
}
Original Picker:
<Picker Title="Select Item..."
ItemsSource="{Binding Items}"
ItemDisplayBinding="{Binding Name}"
SelectedItem="{Binding Path=SelectedItem}"></Picker>
New Model
// NOTE: this implements INPC, just abbreviated for clarity
public class DataModel
{
public ICollection<DataItems> Items;
public ICollection<string> ItemNames;
public DataItem SelectedItem;
public string SelectedItemName;
public DataModel()
{
this.PropertyChanged += (s, e) =>
{
// I feel like I shouldn't have to do this...
if(StringComparer.Ordinal.Equals(e.PropertyName, nameof(Items)))
{
if(!String.IsNullOrWhitespace(this.SelectedItemName))
{
this.SelectedItem = this.Items.FirstOrDefault(x => StringComparer.Ordinal.Equals(x.Name, this.SelectedItemName));
if (this.SelectedItem == null) { this.SelectedItemName = null; }
}
}
}
}
New Picker:
<Picker Title="Select Item..."
ItemsSource="{Binding ItemNames}"
SelectedItem="{Binding Path=SelectedItemName}"></Picker>
I would like to be able to do something like this:
<Picker Title="Select Item..."
ItemsSource="{Binding Items}"
ItemDisplayBinding="{Binding Name}"
ItemValueBinding="{Binding Name}"
SelectedItem="{Binding Path=SelectedItemName}"></Picker>
I do not need a reference to the item, I need a property off of it. In this way, when the Items collection changes, it automatically reselects the correct item if it's still present. I find that I'm adding a second collection everywhere with just the properties I want to choose and doing all this mapping. Every other platform I've worked on, this is pretty straight forward, so I feel like I have to be missing something with Xamarin.Forms.
I think you don't need to do this.The SelectedItem property data binds to the SelectedItem(in your original model) property of the connected view model, which is of type DataItem. Therefore, when the user selects an item in the Picker, the SelectedItem property will be set to the selected DataItem object automatically.
You could test it in its SelectedIndexChanged event like:
<Picker Title="Select Item..."
ItemsSource="{Binding Items}"
ItemDisplayBinding="{Binding Name}"
SelectedItem="{Binding Path=SelectedItem}"
SelectedIndexChanged="Picker_SelectedIndexChanged">
</Picker>
private void Picker_SelectedIndexChanged(object sender, EventArgs e)
{
Picker picker = sender as Picker;
DataItem dt = picker.SelectedItem as DataItem ;
Console.WriteLine(dt.Name); // you will see when you select a item,the SelectedItem will be changed automatically
}
And i suggest you use ObservableCollection<Item> Items, so it will automatically update your Items when it changes.

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.

How to implement a listView "quick filtering" like week calendar view?

I've created a customer specific task management app with tasks placed on specific dates (and sometime hours), but here the date is important.
I'm using a listView and have a DatePicker setting for selected other dates than today. So far so good.
I would like to implement a week quick-filter option so that e.g., the dates of the current week is displayed at the top of the list view and a click on a certain date would filter the listView accordingly. Kind of a standard outlook-like week view.
How would I do this in the best way?
CustomControl that I put above the listView?
ViewPager control?
Any ideas or suggestions much appreciated.
P.S. I need to be able to target both Android and iOS.
Set two Properties in the ViewModel one for containing all the Items EntireCollection and another to store the Filtered Items FilteredCollection. On button click derive the Filtered item from entire list using Where.
ViewModel
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<ListItem> filteredCollection;
public ObservableCollection<ListItem> FilteredCollection
{
get
{
return filteredCollection;
}
set
{
filteredCollection = value;
OnPropertyChanged();
}
}
private ObservableCollection<ListItem> entireCollection;
public ObservableCollection<ListItem> EntireCollection
{
get
{
return entireCollection;
}
set
{
entireCollection = value;
OnPropertyChanged();
}
}
public ViewModel()
{ ...
this.FilterCollection = this.EntireCollection;
...
}
}
Button clicked
void Button_Clicked(System.Object sender, System.EventArgs e)
{
DateTime selectedDate = ((DateTime)((sender as VisualElement).BindingContext)).Date;
viewModel.FilteredCollection = new ObservableCollection<ListItem>(viewModel.EntireCollection.Where(x =>
{
if (DateTime.Equals(x.DateAdded, selectedDate))
{
var asd = x.DateAdded.Day;
return true;
}
return false;
}));
}
XAML
<StackLayout>
<ScrollView
x:Name="calender"
Orientation="Horizontal">
<StackLayout
BackgroundColor="Blue"
BindableLayout.ItemsSource="{Binding Dates}"
Orientation="Horizontal">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Button
TextColor="White"
BackgroundColor="Blue"
Clicked="Button_Clicked"
Text="{Binding Day}"/>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</ScrollView>
<ListView
ItemsSource="{Binding FilteredCollection}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding Name}"/>
<Label Text="{Binding DateAdded}"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
Hope it helps!!

What's wrong with my Windows Phone 7 Databinding(no viewmodels used)?

I am having difficulty in databinding. I can successfully get the results but it just won't display. here is my code:
private List<FacebookFriend> friendList;
public List<FacebookFriend> FriendList
{
get { return friendList; }
set
{
friendList = value;
NotifyPropertyChanged("FriendList");
}
}
private void GetFbFriends()
{
var fb = new FacebookClient(_accessToken);
friendList = new List<FacebookFriend>();
fb.GetCompleted += (o, e) =>
{
if (e.Error != null)
{
return;
}
var result = (JsonObject)e.GetResultData();
foreach (var friend in (JsonArray)result["data"])
friendList.Add(new FacebookFriend()
{
Id = (string)(((JsonObject)friend)["id"]),
Name = (string)(((JsonObject)friend)["name"])
});
FriendList = friendList;
};
fb.GetAsync("me/friends");
}
then in the page's xaml:
<ListBox ScrollViewer.VerticalScrollBarVisibility="Auto" Grid.Row="2" Grid.ColumnSpan="3" ItemsSource="{Binding FriendList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Background="Red" Height="100" Width="300" Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}"/>
<TextBlock Text="{Binding Path=Id}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
It seems correct but still, it does not display anything. Any help is appreciated. Thanks so much!
Try using ObservableCollection<> instead of list<>. For more info please see this
Note: ObservableCollection is a generic dynamic data collection that provides notifications (using an interface "INotifyCollectionChanged") when items get added, removed, or when the whole collection is refreshed.

Resources