Correct way of binding xamarin forms picker value - c#

My issue is that I have 2 picker controls. 1 of these picker controls is bound to list a and one of these picker controls is bound to list b. Both of these controls display the data I need them to with the correct display bindings working fine.
My problem is when I bind the selectedItem property, it works for list a but doesn't for list b. I've checked that the code is literally a like for like copy of each other.
I have been using the syncfusion Combobox but switched to the picker as I thought there was an issue here but there isn't. Whatever is happening is totally down to whatever I'm doing.
The usage scenario is that I bring down a list of payment types from my API and populate a picker based on this. This works.
The datasource for my main view contains an ID. When I am modifying a record, I run a method called update to find the selectedItem. I'm not happy with this approach and would be interested to see what other people use.
The update method gets the datasources for the pickers and finds what I would expect to be the selected item. This works fine also but doesn't bind.
[Serializable]
public class PaymentInformation :BaseModel
{
public int? ID { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
public int? PaymentTypeId { get; set; }
public string PaymentTo { get; set; }
public string Location { get; set; }
public string Notes { get; set; }
public PersonInformation PersonBudget { get; set; }
public decimal AmountPaid { get; set; }
public decimal AmountReceived { get; set; }
public double TotalHours { get; set; }
public void Update(ObservableCollection<ResourceInformation> resources , ObservableCollection<PaymentTypeInformation> paymentTypes)
{
if(PaymentTypeId != null) this.PaymentTypeInformation1 = paymentTypes?.FirstOrDefault((paymentType) => paymentType.ID == PaymentTypeId.Value);
this.Resource = resources?.FirstOrDefault((resource) => resource.ResourceId == PersonBudget?.ID);
}
private PaymentTypeInformation _paymentTypeInformation;
private PaymentTypeInformation PaymentTypeInformation1 { get { return _paymentTypeInformation; } set { _paymentTypeInformation = value; OnPropertyChanged(nameof(PaymentTypeInformation1)); } }
private ResourceInformation _resource;
public ResourceInformation Resource { get { return _resource; } set { _resource = value; OnPropertyChanged(nameof(Resource)); } }
}
The underlying xaml is:
<Label Grid.Row="8" Grid.Column="0" Text="Payment Type:" />
<Picker BackgroundColor="White" Grid.Row="8" Grid.Column="1" ItemsSource="{Binding PaymentTypesDataSource}" ItemDisplayBinding="{Binding Path=DisplayText}" IsEnabled="{Binding IsProcessing, Converter={StaticResource reverseBoolConvertor}}" SelectedItem="{Binding DataSource.PaymentTypeInformation1, Mode=TwoWay}" />
The expected result is that the drop down initializes with the selectedItem which it doesn't (in one usage scenario - the other one works fine).

Couldn't see the wood for the trees.
private PaymentTypeInformation PaymentTypeInformation1
{
get
{
return _paymentTypeInformation;
}
set
{
_paymentTypeInformation = value;
OnPropertyChanged(nameof(PaymentTypeInformation1));
}
}
Can't bind to a private property - changed to public and immediately work. Stuck on this for a day as bonkers as that is to believe.

Related

How to send data from a picker IN a collectionview to an API endpoint using MVVM (Xamarin)

For my current project I am working on a cross-platform application. We are building this using Visual studio, cross platform-app.
In this project we use questionaires which we set in an SQL Database.
Using MVVM I can load this dynamically into the mobile app. (We do this because there are many platforms where we show this, so we choose to set it in the back-end, the questionaire with the answers to each question).
But with saving there seems to be an issue,
I can't seem to send data back to my back end
-> I want to send either the value or the string showed from the picker back to the back end (preferably using MVVM). If I can get the data in the viewmodel (or code behind if MVVM would not be possible), i would be helped enough with my question.
I build my questionaire using a collectionview which contains a DataTemplate with a picker inside.
QuestionaireView.xaml:
<ContentPage.BindingContext>
<viewModels:Questionaire_viewmodel/>
</ContentPage.BindingContext>
<CollectionView
x:Name="CollectionQuestionsView"
SelectionMode="None"
Margin="0,0,0,0" RemainingItemsThreshold="5"
BackgroundColor="Transparent"
SelectedItems="{Binding selectedPickers}"
ItemsSource="{Binding Questionaire.Vraag,Mode=TwoWay}">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Label Style="{StaticResource RegularLabelStyleSmallLogin}" Text="{Binding vraagstelling}" HorizontalTextAlignment="Start" VerticalTextAlignment="Start" LineBreakMode="WordWrap"/>
<Picker Style="{StaticResource PickRegularSmall}" SelectedItem="{Binding vraag_id}" x:Name="{Binding vraag_id}" ItemsSource="{Binding Antwoorden}" ItemDisplayBinding="{Binding antwoord_vraagstelling}"/>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<Frame Margin="0,0,0,20" Style="{StaticResource OrangeLoginFrame}">
<Grid Margin="-15" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Style="{StaticResource SemiBoldLabelStyleMedium}" Grid.Row="0" Grid.Column="0" Text="Opslaan"/>
<Button Style="{StaticResource LoginButton}" Command="{Binding SaveVragenlijstCompetentiesCommand}" Grid.Row="0" Grid.Column="0" />
</Grid>
</Frame>
<Button x:Name="questionaireButton" Command="{Binding getQuestionaireLoopbaancompetenties}" IsVisible="False"/>
So to explain the bindings (since some names are not English):
itemsource of collectionview: The questionaire.vraag retrieves a collection of questions out of the questionaire object (see the model.cs down below)
label text binding: Displays the question
picker selectedItem binding: vraag id results in a string (retrieved from the backend) which here specifly is vraag_127, vraag_128 (the numbers are an id of the question)
itemsource picker: binding antwoorden, these are the answers to the questions
in the frame below the collectionview, I made a button to (at least i tried) to send the data to the ViewModel.cs
The (hidden)button below the frame loads the data (I execute it in the code behind, not sure if this is the best option)
QuestionaireViewModel.cs
public string vraag_127 { get; set; }
public string vraag_128 { get; set; }
... and so on
public string vraag_161 { get; set; }
public string categorie_vraag { get; set; }
public string SelectedAnswer { get; set; }
public string vraagstelling { get; set; }
public string vraag_id { get; set; }
public string score_antwoord { get; set; }
public string antwoord_vraagstelling { get; set; }
public string default_question { get; set; }
public string description { get; set; }
private Questionaire_model _questionaire;
public Questionaire_model Questionaire
{
get { return _questionaire; }
set
{
_questionaire = value;
OnPropertyChanged();
}
}
private List<Questionaire_model> _vraag;
public List<Questionaire_model> Vraag
{
get { return _vraag; }
set
{
_vraag = value;
OnPropertyChanged();
}
}
private List<Questionaire_model> _antwoorden;
public List<Questionaire_model> Antwoorden
{
get { return _antwoorden; }
set
{
_antwoorden = value;
OnPropertyChanged();
}
}
public ICommand getQuestionaireLoopbaancompetenties
{
get
{
return new Command(async (boolean) =>
{
Questionaire = await _apiServices.GetQuestionaireQuestions("Competenties");
});
}
}
private ObservableCollection<vragenModel> _selectedPicker;
public ObservableCollection<vragenModel> SelectedPicker
{
get
{
return _selectedPicker;
}
set
{
_selectedPicker = value;
OnPropertyChanged();
}
}
public ICommand SaveVragenlijstCompetentiesCommand
{
get
{
return new Command(async () =>
{
var responseBool = await _apiServices.SaveCultuur(vraag_127,vraag_128, ... vraag_161)});
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyname = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
}
And my QuestionaireModel.cs:
public class Questionaire_model
{
public int id { get; set; }
public string default_question { get; set; }
public string description { get; set; }
public IList<Vragen> Vraag{ get; set; }
public class Vragen
{
public string categorie_vraag { get; set; }
public string SelectedAnswer { get; set; }
public string vraagstelling { get; set; }
public string vraag_id { get; set; }
public IList<antwoorden> Antwoorden { get; set; }
}
public class antwoorden
{
public string score_antwoord { get; set; }
public string antwoord_vraagstelling { get; set; }
}
}
The reason we use an collectionView, is because we use the template for various questionaires with various amount of questions and answer possibilities.
So at this moment, when I execute my save action, it gives "null" in my ViewModel - action
I have defined public class Questionaire_viewmodel : INotifyPropertyChanged above my viewmodel
So I hope someone can either help me with my code, that I missed something here
Or if I have to change something because it is simply impossible well then I would like to have an direction where to go to.
As I understand, you want your Picker.SelectedItem actually bind to QuestionaireViewModel.vrag_xxx properties. That won't work like this - what now happens is that your Picker.SelectedItem is bound to Vragen.vraag_id field and is not able to use it, as long as it expects antwoorden type there.
However, I see that you have SelectedAnswer property in Vragen, you can change it to be of antwoorden type and then fetch user choice from that field.
So, if your Vragen class is:
public class Vragen
{
public string categorie_vraag { get; set; }
public antwoorden SelectedAnswer { get; set; }
public string vraagstelling { get; set; }
public string vraag_id { get; set; }
public IList<antwoorden> Antwoorden { get; set; }
}
Then, you can change your binding for Picker to:
<Picker Style="{StaticResource PickRegularSmall}" SelectedItem="{Binding SelectedAnswer}" x:Name="{Binding vraag_id}" ItemsSource="{Binding Antwoorden}" ItemDisplayBinding="{Binding antwoord_vraagstelling}"/>
And, at last in SaveVragenlijstCompetentiesCommand you'll be able to get array of answers (sorted by question id):
Questionaire.Vraag.OrderBy(x => x.vraag_id).Select(x => x.SelectedAnswer.antwoord_vraagstelling)
That will give you array of answer texts. Still it doesn't conform to your apiServices.SaveCultuur call. I would strongly recommend to change that method - method that contains a lot of parameters is certainly a problem.
If you can change it to accept string[], then you will be able to pass Questionaire.Vraag.OrderBy(x => x.vraag_id).Select(x => x.SelectedAnswer.antwoord_vraagstelling) to it.
If you can't change apiServices, there's still possibility to call the method through reflection using array, but it's a bit tricky.

How to create custom shape button with fill color options in xamarin forms

When user click the favorite button the button filled with color. The button shape will be heart shape. Please suggest me your valuable suggestion to achieve this. I have attached the screenshot of the button, please refer,
Based on your question, You need two images favorite filled and unfilled. At first the 'isFavorite' property should false I think. In your ViewModel inside button action trigger
private void ButtonActionForFavouriteTriggered(object seletecedButtonItem)
{
System.Diagnostics.Debug.WriteLine("favourite button clickked");
var selectedList = (Result)seletecedButtonItem;
if (selectedList.isFavorite == "Fav.png")
{
// update api or local DB whatever on here
selectedList.isUserFavorite = false;
selectedList.isFavorite = "unFav.png"; // this will update in UI - changes from favorite image into unfavorite image
}
else
{
// update api or local DB whatever on here
selectedList.isUserFavorite = true;
selectedList.favourite = "Fav.png"; // this will update in UI
}
}
Here 'Result' is my model and inside your model you can use 'INotifyPropertyChanged' interface like this.
public class Result: INotifyPropertyChanged
{
public int id { get; set; }
public string country { get; set; }
public string name { get; set; }
public bool isUserFavorite { get; set; }
//Note: If you want to change any thing without reload cells, use this type and inherit 'INotifyPropertyChanged'
// Example: Favourite and UnFavourite button action, tapping any event change any text from cell
public string abbr {
get { return _abbr; }
set { _abbr = value; OnPropertyChanged("abbr"); }
}
public string area { get; set; }
public string largest_city { get; set; }
public string capital { get; set; }
public string isFavourite
{
get { return _fav; }
set {
_fav = value; OnPropertyChanged("isFavourite");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public string _fav;
public string _abbr;
private void OnPropertyChanged(string v)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(v));
}
}
and in xaml, inside the listview or some other;
<Button Grid.Column="3" x:Name="buttonFavourite" BackgroundColor="Transparent" Image="{Binding isFavourite}" Command="{Binding Source={x:Reference MyCollectionPage}, Path=BindingContext.FavouriteButtonAction}" CommandParameter="{Binding .}"/>

WPF DataGrid binding doesn't update

Here's my DataGrid:
<DataGrid x:Name="MoonMining"
ItemsSource="{Binding MarketData.MoonMinerals, ElementName=window}">
<DataGrid.DataContext>
<local:MoonMineral/>
</DataGrid.DataContext>
<DataGrid.Columns>
.. Yes i have columns and they are irrelevant to my question .
</DataGrid.Columns>
</DataGrid>
MarketData is a class which contains most of my programs logic. MoonMinerals is defined in that class:
public class MarketData
{
private ObservableCollection<MoonMineral> _moonMinerals = new ObservableCollection<MoonMineral>();
public ObservableCollection<MoonMineral> MoonMinerals
{
get { return _moonMinerals; }
set { _moonMinerals = value; }
}
}
And here's my MoonMineral class:
[NotifyPropertyChanged]
public class MoonMineral
{
public MoonMineral()
: this("Def", "Def")
{
}
public MoonMineral(string name, string rarity)
{
Name = name;
Rarity = rarity;
}
public string Name { get; set; }
public double Price { get; set; }
public double Volume { get; set; }
public string Rarity { get; set; }
public double TransportVolume { get; set; }
public double TransportCosts { get; set; }
public double GrossProfit { get; set; }
public double NetProfit { get; set; }
}
As you can see, I'm using PostSharp to clear up my code, but when I manually implement INotifyPropertyChanged I have the same problem.
Now the problem is that my DataGrid doesn't update by itself, I have to manually call this in a method which modifies MoonMinerals:
var bindingExpression = MoonMining.GetBindingExpression(ItemsControl.ItemsSourceProperty);
if (bindingExpression != null)
bindingExpression.UpdateTarget();
I know this isn't big of a deal, but I wanted to finally manage to bind data to ui entirely using xaml. All my previous attempts involved setting DataGrids ItemsSource property every time I updated the data.
To sum up comments you're implementing INotifyPropertyChanged interface for MoonMineral class and use ObservableCollection which will handle changes to the collection but there seems to be nothing in place to handle changes to MoonMinerals property
private ObservableCollection<MoonMineral> _moonMinerals = new ObservableCollection<MoonMineral>();
public ObservableCollection<MoonMineral> MoonMinerals
{
get { return _moonMinerals; }
set { _moonMinerals = value; }
}
You can either implement INotifyPropertyChanged interface in the class that exposes MoonMinerals property or change it to read-only and use only one instance of _moonMinerals and simply clear it and add/remove items
private readonly ObservableCollection<MoonMineral> _moonMinerals = new ObservableCollection<MoonMineral>();
public ObservableCollection<MoonMineral> MoonMinerals
{
get { return _moonMinerals; }
}
Also, as a side note, you don't need
<DataGrid.DataContext>
<local:MoonMineral/>
</DataGrid.DataContext>
as this will set DataContext of the DataGrid to new instance of MoonMineral. It works in your case as you change binding context of ItemsSource using ElementName so DataContext is not used in your case.

Combobox selected item from a different object

I have a combobox and the list is populated using the AccountType class and the list is getting populated properly.
However when I bind the selected Item property to the Selected Account which is class account. On page load the selected item is not getting updated. All the other controls like textbox are getting updated.
Any help will be highly appreciated.
View
ComboBox ItemsSource="{Binding AllAccountTypes}" DisplayMemberPath="AccountTypeName"
SelectedValuePath="AccountTypeName" SelectedItem="{Binding SelectedAccount}" />
AccountType class
public class AccountType:IAccountType
{
public string AccountTypeName { get; set; }
}
Account Class
public class Account: IAccount
{
public int AccountNo { get; set; }
public string AccountName { get; set; }
public string AccountTypeName { get; set; }
public int SubAccount { get; set; }
public string Description { get; set; }
public double Balance { get; set; }
public string Note { get; set; }
public bool Active { get; set; }
}
Selected Account in ViewModel
public IAccount SelectedAccount { get { return selectedAccount; }
set { selectedAccount = value; }
}
First, your ViewModel needs to be raising the PropertyChanged event of INotifyPropertyChanged.
Second, your binding should specify two-way binding:
<ComboBox ItemsSource="{Binding AllAccountTypes}" DisplayMemberPath="AccountTypeName"
SelectedValuePath="AccountTypeName" SelectedItem="{Binding SelectedAccount, Mode=TwoWay}" />
But third, and I think the main issue here, is that your Combo box is bound to a list of AccountTypes (i.e. IAccountType), yet you want the selected item to be an IAccount. But there is no property of type IAccount on an IAccountType.
So you need to bind the SelectedItem to an IAccountType property, or bind SelectedValue to a string property on your ViewModel. e.g.:
<ComboBox ItemsSource="{Binding AllAccountTypes}" DisplayMemberPath="AccountTypeName"
SelectedItem="{Binding SelectedAccountType, Mode=TwoWay}" />
and in your ViewModel have a property to bind to:
public IAccountType SelectedAccountType
{
get { return selectedAccountType; }
set
{
if (Equals(value, selectedAccountType)) return;
selectedAccountType = value;
OnPropertyChanged("SelectedAccountType");
}
}
This is because you are binding SelectedItem to an IAccount object, but you are selecting a string from your dropdown list.
I would bind to a string instead, and then in the setter do what needs to be done to set the SelectedAccount property, something like this:
public string SelectedAccountName
{
get { return _selectedAccountName; }
set
{
_selectedAccountName = value;
SelectedAccount = AllAccounts.Where(x => x.AccountName == _selectedAccountName).First();
}
}
With XAML like this (I added height and width values so the dropdown isn't massive):
<ComboBox Height="20" Width="100" ItemsSource="{Binding AllAccountTypes}" DisplayMemberPath="AccountTypeName" SelectedValuePath="AccountTypeName" SelectedItem="{Binding SelectedAccountName}" />

Json to C# object display in Windows Phone's ListBox

I used http://json2csharp.com to generate the C# class from the below JSON string.
{
"stationArr":[
{
"id":"9",
"name":"name9",
"sidebar":{
"original":"http://myurl.com/station_images/5/5_s.png",
"m":"http://myurl.com/station_images/5/m/5_s_m.png",
"s":"http://myurl.com/station_images/5/s/5_s_s.png"
}
},
{
"id":"3",
"name":"name3",
"sidebar":{
"original":"http://myurl.com/station_images/5/5_s.png",
"m":"http://myurl.com/station_images/5/m/5_s_m.png",
"s":"http://myurl.com/station_images/5/s/5_s_s.png"
}
]
"stationUrlMap":{
"9":"http://myurl.com/9_64",
"3":"http://myurl.com/3_64",
}
}
The generated classes are (I created different .cs for each class.
public class Sidebar
{
public string original { get; set; }
public string m { get; set; }
public string s { get; set; }
}
public class StationArr
{
public string id { get; set; }
public string name { get; set; }
public Sidebar sidebar { get; set; }
}
/*public class StationUrlMap
{
public string __invalid_name__9 { get; set; }
public string __invalid_name__3 { get; set; }
}
*/
public class StationList
{
public List<StationArr> stationArr { get; set; }
// public StationUrlMap stationUrlMap { get; set; } Dicarded it
}
I have discarded the StationUrlMap as I don't need it.
I am using the following code to create the Object
string resultString = sd.ReadToEnd();
StationList stations = JsonConvert.DeserializeObject<StationList>(resultString);
Debug.Writeline(stations.stationArr.Count); // gives Output 9 Which is correct.
I just don't know how to display the List of stations in the UI (using ListBox). Please guide me in the right direction.
Create a ListBox in the XAML
<ListBox x:Name="ListBoxStations" Height="500" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Width="450">
<Image Source="{Binding Path=sidebar.original}"/>
<TextBlock Text="{Binding Path=name}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And then in your cs file, after the json conversion do the following
StationList stations = JsonConvert.DeserializeObject<StationList>(resultString);
ListBoxStations.ItemsSource = stations.stationArr;
The above XAML code is just a sample, change based on your requirements.
There are two ways.
First you can set the listbox's ItemSource property to the list you just created.
Second, You can create an ObservableCollection of thing in your list, and bind the observable collection to the listbox's ItemSource property. Add items in the Observable Collection and these will be displayed in your listbox.
The second way gives you more control over the items in the lisbox. You can remove or add elements in the observable collection and that will reflect in the listbox contents. The first method will make the contents of the list read-only, and you will not be able to add or remove items from it.
http://msdn.microsoft.com/en-us/library/ms668604.aspx
http://msdn.microsoft.com/en-us/library/system.windows.controls.listbox%28v=vs.95%29.aspx

Categories