I have a problem with my Xamarin.Forms app, specifically with a Picker which does not show the actual value of the SelectedItem property. I searched all available threads but none solved my issue. I have a ListView page with an ObservableCollection of "Surgery" objects, each having a property of type "Category" (see below).
public class Surgery
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string Name { get; set; }
public DateTime Date { get; set; }
[ForeignKey(typeof(Category))]
public int CategoryId { get; set; }
[ManyToOne(CascadeOperations = CascadeOperation.All)]
public Category Category { get; set; }
}
public class Category
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string Name { get; set; }
[OneToMany(CascadeOperations = CascadeOperation.All)]
public List<Surgery> Surgeries { get; set; }
}
I also have an EditPage with a picker:
<Picker Title="Category" x:Name="categoryPicker"
ItemsSource="{Binding Categories}"
SelectedItem="{Binding SelectedCategory, Mode=TwoWay}"
ItemDisplayBinding="{Binding Name}"
/>
And the corresponding EditPageViewModel:
public class EditPageViewModel : BaseViewModel
{
public Surgery Surgery { get; set; }
public Command LoadCategoriesCommand
{
get
{
return new Command(async () => await LoadCategoriesExecute());
}
}
public List<Category> categories;
public EditPageViewModel(Surgery surgery = null)
{
LoadCategoriesCommand.Execute(null);
Surgery = surgery ?? new Surgery { Date = DateTime.Today };
}
async Task LoadCategoriesExecute()
{
if (IsBusy)
return;
IsBusy = true;
try
{
Categories = await App.DataService.GetAllCategoriesAsync();
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
finally
{
IsBusy = false;
}
}
public string SurgeryName
{
get { return Surgery.Name; }
set
{
Surgery.Name = value;
OnPropertyChanged();
}
}
public DateTime SurgeryDate
{
get { return Surgery.Date; }
set
{
Surgery.Date = value;
OnPropertyChanged();
}
}
public Category SelectedCategory
{
get { return Surgery.Category; }
set
{
if(Surgery.Category != value)
Surgery.Category = value;
OnPropertyChanged();
}
}
public List<Category> Categories
{
get { return categories; }
set
{
categories = value;
OnPropertyChanged();
}
}
}
Creating a new Surgery object works perfectly fine with this code. It is saved to the database und displayed in the ListView page. The picker displays the List correctly and the Category can be chosen (which is also reflected in the UI).
THE PROBLEM: When I try to edit a Surgery object with a set Category property, the EditPage displays all properties and the corresponding value correctly except for the picker. The items are loaded and are also bound to the ItemSource - however, the picker is not set to the actual Category property of the Surgery object. Any ideas?
You need to define properties in your models with INotifyPropertyChanged like in your viewmodel.
Related
I am calling an API that returns nested classes (example below) and I am struggling to bind these to a Picker.
Is it possible to bind them nested classes to a picker as is? or do I need to somehow add them to a IList?
<Picker Title="Select a Currency" ItemsSource="{Binding CurrencyClass}" ItemDisplayBinding="{Binding currencyName}"/>
class MainPageViewModel : INotifyPropertyChanged
{
private Currencies _CurrencyClass;
public Currencies CurrencyClass
{
get { return _CurrencyClass; }
set
{
_CurrencyClass = value;
OnPropertyChanged();
}
}
}
This is a cut of the class they get desterilized too
public class Currencies
{
public class Rootobject
{
public Results results { get; set; }
}
public class Results
{
public XCD XCD { get; set; }
public EUR EUR { get; set; }
}
public class XCD
{
public string currencyName { get; set; }
public string currencySymbol { get; set; }
public string id { get; set; }
}
public class EUR
{
public string currencyName { get; set; }
public string currencySymbol { get; set; }
public string id { get; set; }
}
}
And this a cut of the json I am receiving.
{
"results": {
"XCD": {
"currencyName": "East Caribbean Dollar",
"currencySymbol": "$",
"id": "XCD"
},
"EUR": {
"currencyName": "Euro",
"currencySymbol": "€",
"id": "EUR"
}
}
}
So I figured out a work around for what I was aiming to achieve, this may not be a direct answer to my question but it is a solution for my issue.
I ended up just deserializing the JSON differently into a list of a Currency and then binding easily like you normally would.
class Currency
{
public string currencyName { get; set; }
public string currencySymbol { get; set; }
public string id { get; set; }
}
and how I deserialized it to fit in that class here, I parsed the JSON into a JObject and then for each child of each child I deserialize it into my Currency class.
List<Currency> cList = new List<Currency>();
HttpResponseMessage response = await client.GetAsync(urlAPI);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
JObject jo = JObject.Parse(responseBody);
var children = jo.SelectToken("results").Children();
foreach(var child in children)
{
var childrenOfChild = child.Children();
foreach(var c in childrenOfChild)
{
cList.Add(JsonConvert.DeserializeObject<Currency>(JsonConvert.SerializeObject(c)));
}
}
I have this Edit page where it has an Id passed from the Detail page. Then called web api service to get Categories as well as the Activity. This code below was adapted from the Add page btw.
It has 2 issues:
Managed to get the picker list BUT then not sure how to binding this from selected Activity.
How to save this as the binding name is different in XAML ie. Activity.Name VS Name etc etc. What did I do wrong?
How to solve these?
Code below:
ActivityEditPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage.Content>
<StackLayout Margin="5,5,5,5" VerticalOptions="StartAndExpand">
<Entry Placeholder="Name" Text="{Binding Activity.Name}"></Entry>
<Picker ItemsSource="{Binding Categories}" ItemDisplayBinding="{Binding Name}" SelectedItem="{Binding Activity.CategoryId}">
</Picker>
<Entry Placeholder="No of minutes" Text="{Binding Activity.NoOfMinutes}"></Entry>
<Editor Placeholder="Description" Text="{Binding Activity.Description}"></Editor>
<Button Text="Save" BackgroundColor="#340E22" FontSize="Small" TextColor="White" CornerRadius="30" HeightRequest="40" Command="{Binding SaveCommand}"></Button>
</StackLayout>
</ContentPage.Content>
Code behind:
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace AthlosifyMobileApp.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ActivityEditPage : ContentPage
{
public ActivityEditPage(int id)
{
InitializeComponent();
BindingContext = new ActivityEditViewModel(Navigation, id);
}
}
}
ActivityEditViewModel.cs:
using AthlosifyMobileApp.Helpers;
using AthlosifyMobileApp.Models;
using AthlosifyMobileApp.Services;
using AthlosifyMobileApp.Views;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace AthlosifyMobileApp.ViewModels
{
public class ActivityEditViewModel : BaseViewModel
{
private readonly ApiService apiService = new ApiService();
private int _id;
private string _name;
private string _description;
private int _noOfMinutes;
private int _categoryId;
private string _categoryName;
private Command _saveCommand;
private List<Category> _categories;
private Category _selectedCategory;
private Activity _activity;
public ActivityEditViewModel(INavigation navigation, int Id)
{
this.Navigation = navigation;
Task.Run(async () =>
{
await GetCategories();
Activity = await GetActivity(Id);
});
}
public INavigation Navigation { get; set; }
public Activity Activity
{
get { return _activity; }
set
{
_activity = value;
OnPropertyChanged();
}
}
public int Id
{
get { return _id; }
set
{
_id = value;
OnPropertyChanged();
}
}
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged();
SaveCommand.ChangeCanExecute();
}
}
public string Description
{
get { return _description; }
set
{
_description = value;
OnPropertyChanged();
}
}
public int NoOfMinutes
{
get { return _noOfMinutes; }
set
{
_noOfMinutes = value;
OnPropertyChanged();
}
}
public int CategoryId
{
get { return _categoryId; }
set
{
_categoryId = value;
OnPropertyChanged();
}
}
public string CategoryName
{
get { return _categoryName; }
set
{
_categoryName = value;
OnPropertyChanged();
}
}
public List<Category> Categories
{
get { return _categories; }
set
{
_categories = value;
OnPropertyChanged("Categories");
}
}
public Category SelectedCategory
{
get
{
return _selectedCategory;
}
set
{
_selectedCategory = value;
OnPropertyChanged();
}
}
public Command SaveCommand
{
get
{
return _saveCommand ?? (_saveCommand = new Command(ExecuteSaveCommand, CanSave));
}
}
async void ExecuteSaveCommand()
{
var newItem = new Activity
{
Id = Id,
OwnerId = Preferences.Get(Constant.Setting_UserId, ""),
Name = Name,
CategoryId = SelectedCategory.Id,
CategoryName = SelectedCategory.Name,
Description = Description,
NoOfMinutes = NoOfMinutes
};
var response = await apiService.UpdateActivity(Id, newItem);
if (!response)
{
await Application.Current.MainPage.DisplayAlert("Error", "Something wrong", "Alright");
}
else
{
await Navigation.PushAsync(new ActivityListPage());
}
}
bool CanSave()
{
return !string.IsNullOrWhiteSpace(Name);
}
public async Task<List<Category>> GetCategories()
{
CategoryResult categoryResult = await apiService.GetCategories();
return Categories = categoryResult.Results;
}
public async Task<Activity> GetActivity(int id)
{
Activity activity = await apiService.GetActivity(id);
return activity;
}
}
}
UPDATED CODE
Category.cs
public class Category
{
public int Id { get; set; }
public string OwnerId { get; set; }
public int ParentId { get; set; }
public string ParentName { get; set; }
public string Name { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime? ModifiedDate { get; set; }
}
Activity.cs:
public class Activity
{
public int Id { get; set; }
public string OwnerId { get; set; }
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int NoOfMinutes { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime? ModifiedDate { get; set; }
}
I'm doing a simple xamarin forms application where I have a TimeAttendance model, a list view page and a time entry page. When I select a time entry from the list it takes me to the add/edit page where I'm able to change all the dropdown/entry/timepicker values and it gets saved correctly.
The problem I'm having is that I'm calculating total hours based on start and end time. This value is getting saved correctly when I close the day but it's not updating in the UI even though the binding property is set. I can see in the backend code that the value gets updated but it's not reflected in the UI unless I close the day and open it again.
It looks like the binding from source to target it's only getting triggered on the first load, but I've been reading and these views are set to two-way binding by defualt.
xaml code:
<Label Text="Total Hours"/>
<Label x:Name="totalHours"
Text="{Binding TotalHours}"/>
<Button Text="CLOSE" Clicked="CloseDay_Clicked"/>
<Button Text="DELETE" Clicked="Delete_Clicked"/>
Code behind:
async void CloseDay_Clicked(object sender, EventArgs e)
{
var timeEntry = (TimeAttendance)BindingContext;
timeEntry.idUser = 1;
await App.Database.SaveTimeAttendanceAsync(timeEntry);
//await Navigation.PopAsync();
}
async void TimePicker_timeChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == TimePicker.TimeProperty.PropertyName)
{
var timeEntry = (TimeAttendance)BindingContext;
var totalHours = timeEntry.EndTime - timeEntry.StartTime;
timeEntry.TotalHours = totalHours.Hours + (totalHours.Minutes / 15) * 0.25;
}
}
The bindingContext is set form the list page:
async void OnListViewItemSelected(object sender, SelectedItemChangedEventArgs e)
{
await Navigation.PushAsync(new TimeAttendanceEntryPage
{
BindingContext = e.SelectedItem as TimeAttendance,
});
}
My timeattendance model:
public class TimeAttendance
{
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
public int idUser { get; set; }
public DateTime Date { get; set; } = DateTime.Today;
public string Commodity { get; set; }
public string Exception { get; set; }
public string RequestType { get; set; }
public string RequestId { get; set; }
public TimeSpan StartTime { get; set; }
public TimeSpan EndTime { get; set; }
public string Activity { get; set; }
public double TotalHours { get; set; }
}
As I said, the code is working fine for updating values closing and saving, I just don't know why when I update TotalHours it gets saved correctly but I cannot see the change in the label that is binded directly to this value.
you just should let change your Timeattendance model,let it implement the INotifyPropertyChanged interface like this:
public class TimeAttendance : INotifyPropertyChanged
{
...
double totalHours;
public double TotalHours {
set
{
if (totalHours != value)
{
totalHours = value;
OnPropertyChanged("TotalHours");
}
}
get
{
return totalHours;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I got the data from my SQLite Database I want to bind the data i got to my picker.How can I bind the data to picker? Below is the Contacts table and function
Picker ItemsSource="{Binding Retailer}" ItemDisplayBinding="{Binding RetailerName}"
[Table("tblContacts")]
public class ContactsTable
{
[PrimaryKey, MaxLength(100)]
public string ContactID { get; set; }
[MaxLength(80)]
public string FileAs { get; set; }
[MaxLength(50)]
public string FirstName { get; set; }
[MaxLength(30)]
public string MiddleName { get; set; }
[MaxLength(30)]
public string LastName { get; set; }
}
public FieldActivityForm ()
{
InitializeComponent ();
BindingContext = new FieldActivityFormViewModel();
tpTime.Time = DateTime.Now.TimeOfDay;
SetRetailerPicker();
}
public void SetRetailerPicker()
{
var db = DependencyService.Get<ISQLiteDB>();
var conn = db.GetConnection();
var getUser = conn.QueryAsync<ContactsTable>("SELECT FileAs FROM tblContacts");
var resultCount = getUser.Result.Count;
if (resultCount < 1)
{
//MessagingCenter.Send(this, "Http", Retailer);
}
else
{
var result = getUser.Result;
myPicker.ItemsSource = result;
}
}
In your XAML
<Picker x:Name="myPicker" ItemDisplayBinding="{Binding RetailerName}" />
in the code behind, after loading the data
myPicker.ItemsSource = result;
this assumes ContactsTable is an IEnumerable that contains a property RetailerName
I have a class in WCF:
[DataContract]
public class Usuario
{
[DataMember]
public int ID { get; set; }
[DataMember]
public string Nombre { get; set; }
[DataMember]
public string Contraseña { get; set; }
}
In WP7 Proyect read ObservableCollection from WCF and load a ListPicker with:
lpUsuarios.ItemSource = listUsuarios;
This work ok.
Now, in WP7 use "Usuario _usuario = new Usuario()" for local variable.
The problem is, if I save the variable _usuario with IsolatedStorage and then load and apply in: lpUsuarios.SelectedItem = _usuario, causes the error: SelectedItem must always be set to a valid value.
Example:
Usuarios _usuario = new Usuario();
private void ButtonSave_Click(object sender, RoutedEventArgs e)
{
var settings = IsolatedStorageSettings.ApplicationSettings;
_usuario = lpUsuarios.SelectedItem as Usuario;
settings.Add("test", _usuario);
settings.Save();
}
Now, close the application and then open:
private void ButtonLoad_Click(object sender, RoutedEventArgs e)
{
settings.TryGetValue<Usuario>("test", out _usuario);
lpUsuarios.SelectedItem = _usuario; <--- ERROR SelectedItem must....
}
In vs2010 debug, when open the application and load the variable _usuario, value is correct, but not work.
Error in: SelectedItem must always be set to a valid value, in ListPicker.cs
Location in ListPicker.cs:
// Synchronize SelectedIndex property
if (!_updatingSelection)
{
_updatingSelection = true;
SelectedIndex = newValueIndex;
_updatingSelection = false;
}
Any solution?
If I use SelectedIndex, work ok, thanks Etch.
But now, the problem is if I want use:
public override bool Equals(object obj)
{
return ID == (obj as Users).ID;
}
public override int GetHashCode()
{
throw new NotImplementedException();
}
Where implement that, ¿in WCF class, in ModelView?
In XAML use:
SelectedItem={Binding SelectedUser, Mode=TwoWay}"
And in ModelView use:
private Usuario selectedUser;
public Usuario SelectedUser
{
get
{
return selectedUser;
} //----------------if i use modelview, the error is produced here
set
{
selectedUser= value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SelectedUser"));
}
}
}
WCF class:
[DataContract]
public class Usuario
{
[DataMember]
public int ID { get; set; }
[DataMember]
public string Nombre { get; set; }
[DataMember]
public string Contraseña { get; set; }
}
Your collection doesn't have item that you want to select. Even if looks the same, smell the same, but it's a different object. Your Users class must override Equals method for this:
public class Users
{
public int ID { get; set; }
public string Nombre { get; set; }
public override bool Equals(object obj)
{
return ID == (obj as Users).ID;
}
public override int GetHashCode()
{
throw new NotImplementedException();
}
}
You can't select an item that is not one of the items in the collection bound to ItemsSource.
I ran into this problem just the other day. Exact problem. There is a bug in the Listpicker. There was a link I have at home that goes into the details of it, but the simple work around is to do what you did and use the SelectedIndex property instead.
By the way I found another question on this exact topic.