Combobox selected item from a different object - c#

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

Related

Modyfing an item in a list of a list doesn't update the UI

I have pretty complicated case where data from API comes in the form of a list which contain lists and these inner lists (there is multiple of them btw) finally contain entries. So what I did is that I have collection view and in that collection view I have another collection view which serves as a template.
So in this inner collection view I have selected item binded to a Entry type property from my viewmodel. I can easily gather the data about the clicked item but trying to modify anything will not update the UI. Which might be because I'm modifying this property in a viewmodel instead of in list where I have it. (I have INotifyPropertyChanged implemented) So I'm looking for solution to solve this.
I will send my view , viewmodel, and my classes for you to see what exactly is going on.
View:
<CollectionView ItemsSource="{Binding AnimListsList}"
BackgroundColor="Transparent"
ItemsLayout="VerticalList"
SelectionMode="None"
x:Name="CollectionView1">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Label Text="{Binding name}" FontSize="30">
</Label>
<CollectionView ItemsSource="{Binding entries}"
BackgroundColor="Transparent"
ItemsLayout="VerticalList"
SelectionMode="Single"
SelectedItem="{Binding BindingContext.Entry, Source={x:Reference Name=AnimeListPagee}}"
x:Name="CollectionView2">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Label Text="{Binding media.Title.Romaji}"></Label>
<Label Text="{Binding progress}"></Label>
<Frame BackgroundColor="Green">
<StackLayout>
<Frame BackgroundColor="Yellow" xct:TouchEffect.NativeAnimation="True"
xct:TouchEffect.Command="{Binding BindingContext.InfoCommand, Source={x:Reference Name=AnimeListPagee}}"></Frame>
<Frame BackgroundColor="Red" xct:TouchEffect.NativeAnimation="True"
xct:TouchEffect.Command="{Binding BindingContext.InfoCommand2, Source={x:Reference Name=AnimeListPagee}}"></Frame>
</StackLayout>
</Frame>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
My property to which I bind to as selected item in my collection view
public ListEntry entry;
public ListEntry Entry
{
get => entry;
set
{
if (value == entry)
return;
entry = value;
OnPropertyChanged();
}
}
A task that is fired by command binded to one of the frames in my view
async Task Info()
{
Console.WriteLine($"{Entry.media.Title.Romaji} +");
Entry.progress++;
}
Relevant part of my model:
public class Media
{
public string Id { get; set; }
public string Description { get; set; }
public string BannerImage { get; set; }
public int? AverageScore { get; set; }
public int Favourites { get; set; }
public TitleType Title { get; set; }
public CoverImageType CoverImage { get; set; }
}
public class MediaListCollection
{
public ObservableCollection<lists> lists { get; set; }
}
public class lists
{
public string name { get; set; }
public bool isCustomList { get; set; }
public bool isSplitCompletedList { get; set; }
public string status { get; set; }
public ObservableCollection<ListEntry> entries { get; set; }
}
public class ListEntry : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public int progress;
public Media media { get; set; }
public int Progress
{
get => progress;
set
{
if (value == progress)
return;
progress = value;
OnPropertyChanged();
}
}
public string status { get; set; }
}
The ultimate goal is to modify the selected entry's progress property. Trying to do it the easy way
Entry.progress++ by modifying the property in the viewmodel did not work. I want these two frames in my view to increase and decrease it as I wish. The reason it is not working is probably the one I mentioned so I am looking for solution to find the right item in the list that is in the list, modify progress property and most importantly make the UI update accordingly. It would be so much easier to do it if it was just a one normal list.
I tried implementing INotifyPropertyChanged to the relevant class and property in my model but that's throwing me an exception when querying to API.
Newtonsoft.Json.JsonSerializationException: 'A member with the name 'progress' already exists on 'OtakuApp.Models.ListEntry'. Use the JsonPropertyAttribute to specify another name.
This is my code for querying and adding
string token = Preferences.Get("token", "default_value");
var anilistGraphQlHttpClient =
new GraphQLHttpClient("https://graphql.anilist.co", new NewtonsoftJsonSerializer());
anilistGraphQlHttpClient.HttpClient.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
var requestAnimeList = new GraphQLRequest(
"query { MediaListCollection(userId: 5154007, type: ANIME) { lists { name isCustomList isSplitCompletedList status entries { media { id episodes title { romaji } } progress status } } } } ");
var graphQlResponseAnimeList =
await anilistGraphQlHttpClient.SendQueryAsync<ResponseType>(requestAnimeList);
foreach (var name in graphQlResponseAnimeList.Data.MediaListCollection.lists)
{
AnimListsList.Add(name);
}

Correct way of binding xamarin forms picker value

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.

ComboBox Binding on First Item in UWP C#

is there a way when I build an UWP app to select the first entry in a combobox in pure XAML? Normally I would set IsSynchronizedWithCurrentItem="True", but in UWP I only get an error that it cannot be set.
The SelectedItem does not work, only if I set it explicitly to the same as the ItemsSource with an [0] after it, but then the second ComboBox does not update on changes and shows empty entries again.
Here is my code:
<ComboBox x:Name="MainSystemComboBox"
ItemsSource="{Binding Path=EditRulesViewModel.RuleSet.RuleMainSystems}"
SelectedItem="{Binding Path=EditRulesViewModel.RuleSet.RuleMainSystems, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
DisplayMemberPath="Name" SelectedValuePath="Id" />
<ComboBox x:Name="SubSystemComboBox"
ItemsSource="{Binding Path=SelectedItem.RuleSubSystems, ElementName=MainSystemComboBox}"
DisplayMemberPath="Name" SelectedValuePath="Id" />
Oh and it shows to me in the designer that the Path of the second ComboBox is incorrect, because it couldn't resolve property 'RuleSubSystems' in data context of type 'object', but after compiling it works well (except the selection of the first entry). Is there a cleaner way of binding one ComboBox to another?
Objects are simple
public class RuleMainSystem
{
public string Name { get; set; }
// Some more single properties...
public ObservableCollection<RuleSubSystem> RuleSubSystems { get; set; }
}
and
public class RuleSubSystem
{
public string Name { get; set; }
// Some more single properties...
}
The SelectedItem does not work, only if I set it explicitly to the same as the ItemsSource with an [0] after it
You bind the ItemsSource and SelectedItem to the same one property RuleMainSystems, it's not correct. You would have to bind the SelectedItem to a specific item. E.g, RuleMainSystems[0]
but then the second ComboBox does not update on changes and shows empty entries again.
That's because you have not bound SelectedItem, you need to do like the following:
<ComboBox x:Name="SubSystemComboBox"
ItemsSource="{Binding Path=SelectedItem.RuleSubSystems, ElementName=MainSystemComboBox}" SelectedItem="{Binding Path=SelectedItem.RuleSubSystems[0], ElementName=MainSystemComboBox}"
DisplayMemberPath="Name" SelectedValuePath="Id" />
Updated on [2018/5/28]
<ComboBox x:Name="MainSystemComboBox"
ItemsSource="{Binding Path=RuleMainSystems}"
SelectedItem="{Binding Path=MySelectedItem, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
DisplayMemberPath="Name" SelectedValuePath="Id"/>
<ComboBox x:Name="SubSystemComboBox"
ItemsSource="{Binding RuleSubSystems}" SelectedItem="{Binding SubSelectedItem,Mode=TwoWay}"
DisplayMemberPath="Name" SelectedValuePath="Id" />
public class RuleMainSystem
{
public string Name { get; set; }
// Some more single properties...
public ObservableCollection<RuleSubSystem> RuleSubSystems { get; set; }
}
public class RuleSubSystem
{
public string Name { get; set; }
// Some more single properties...
}
public MainPage()
{
this.InitializeComponent();
this.DataContext = new EditRulesViewModel();
}
public class EditRulesViewModel:ViewModelBase
{
public ObservableCollection<RuleMainSystem> RuleMainSystems { get; set; }
private RuleMainSystem _MySelectedItem;
public RuleMainSystem MySelectedItem
{
get { return _MySelectedItem; }
set
{
_MySelectedItem = value;
RuleSubSystems = MySelectedItem.RuleSubSystems;
SubSelectedItem = MySelectedItem.RuleSubSystems.FirstOrDefault();
RaisePropertyChanged("MySelectedItem");
}
}
private ObservableCollection<RuleSubSystem> _RuleSubSystems;
public ObservableCollection<RuleSubSystem> RuleSubSystems
{
get { return _RuleSubSystems; }
set
{
_RuleSubSystems = value;
RaisePropertyChanged("RuleSubSystems");
}
}
private RuleSubSystem _SubSelectedItem;
public RuleSubSystem SubSelectedItem
{
get { return _SubSelectedItem; }
set
{
_SubSelectedItem = value;
RaisePropertyChanged("SubSelectedItem");
}
}
public EditRulesViewModel()
{
RuleMainSystems = new ObservableCollection<RuleMainSystem>();
ObservableCollection<RuleSubSystem> SubSystems = new ObservableCollection<RuleSubSystem>();
SubSystems.Add(new RuleSubSystem() {Name="Sub1" });
SubSystems.Add(new RuleSubSystem() {Name="Sub2" });
ObservableCollection<RuleSubSystem> SubSystems1 = new ObservableCollection<RuleSubSystem>();
SubSystems1.Add(new RuleSubSystem() { Name = "Sub3" });
SubSystems1.Add(new RuleSubSystem() { Name = "Sub4" });
RuleMainSystems.Add(new RuleMainSystem() {Name="Rule1",RuleSubSystems = SubSystems });
RuleMainSystems.Add(new RuleMainSystem() { Name = "Rule2", RuleSubSystems = SubSystems1 });
MySelectedItem = RuleMainSystems.FirstOrDefault();
}
}

how to set combobox default value in wpf using binding in mvvm when manually setting combobox items

I am using mvvm in wpf. I am setting a form to update user data. I have a combobox to select gender of user. i have added combobox items manually in source. when loading data to form all fields other fields are displaying correctly. but combobox is not displaying anything. I have used twoWay binding and the values i am selecting from form are getting in the viewModel.I have been searching for hours and found many similar problem, but nothing worked for me. I am inserting my code segment bellow. Please give me a solution.
<ComboBox
Grid.Column="2"
SelectedItem="{Binding SelectedEmployees.gender, Mode=TwoWay}"
SelectedValue="{Binding SelectedEmployees.gender, Mode=TwoWay}"
>
<ComboBoxItem Content="Male"/>
<ComboBoxItem Content="Female"/>
</ComboBox>
my viewModel code is as bellow
class EmployeesModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private iServiceClient serviceClient = new iServiceClient();
public EmployeesModel()
{
this.RefreshEmployees();
}
private void RefreshEmployees()
{
this.serviceClient.GetAllEmployeesCompleted += (s, e) =>
{
this.employees = e.Result;
};
this.serviceClient.GetAllEmployeesAsync();
}
private IEnumerable<Employee> employees;
public IEnumerable<Employee> Employees
{
get
{
return this.employees;
}
set
{
this.employees = value;
this.OnPropertyChanged("Employees");
}
}
private Employee selectedEmployees;
public Employee SelectedEmployees
{
get
{
return this.selectedEmployees;
}
set
{
this.selectedEmployees = value;
this.OnPropertyChanged("SelectedEmployees");
}
}
public void OnPropertyChanged(string PropertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
}
and SelectedEmployees class is
public class Employee
{
[Key]
public int id { get; set; }
public DateTime JoiningDate { get; set; }
public string name { get; set; }
public string gender { get; set; }
public string mobile { get; set; }
public string post { get; set; }
public string salaryType { get; set; }
public decimal salary { get; set; }
public string docname { get; set; }
public int validit { get; set; }
}
I suspect that SelectedEmployees.gender is not type comboboxitem.
Taking the shortcut of creating comboboxitems directly in the combobox is a bad move.
When I do:
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<Window.DataContext>
<local:MainWIndowViewModel/>
</Window.DataContext>
<Window.Resources>
<x:Array Type="sys:String" x:Key="Genders">
<sys:String>Male</sys:String>
<sys:String>Female</sys:String>
</x:Array>
</Window.Resources>
<Grid>
<ComboBox
SelectedItem="{Binding gender, Mode=TwoWay}"
ItemsSource="{StaticResource Genders}"
/>
</Grid>
I get a string instead of a comboboxitem in my bound gender.
You probably want something rather more like that.
This is probably the best approach, particularly if you mean to learn MVVM: Use an enum type for Gender. "LOL" is never a valid gender so don't let anybody try to use it. Populate the ComboBox by binding it to a static collection. Initialize SelectedEmployees.gender to the value you want to be the default and the binding will take care of the rest.
<ComboBox
SelectedItem="{Binding SelectedEmployees.gender}"
ItemsSource="{Binding SelectedEmployees.Genders}"
/>
C#
public class SelectedEmployeesViewModel : ViewModelBase
{
/* ...other stuff... */
private Gender _gender = Gender.Male;
public Gender gender
{
get { return _gender; }
set
{
if (value != _gender)
{
_gender = value;
OnPropertyChanged();
}
}
}
}
public enum Gender
{
Male, Female
}
public static class EnumValues
{
public static IEnumerable<Gender> Genders => Enum.GetValues(typeof(Gender)).Cast<Gender>();
}
There are other approaches. I advise against going with a string, but this is illustrative at least:
private String _gender = "Male";
public String gender
{
get { return _gender; }
set
{
if (value != _gender)
{
_gender = value;
OnPropertyChanged();
}
}
}
Does your SelectedEmployees class implement INotifyPropertyChanged, and does SelectedEmployees.gender raise PropertyChanged when its value changes?
Get rid of Mode=TwoWay on the binding; you don't need to do that explicitly. It's the default for any binding you put on ComboBox.SelectedValue or on ComboBox.SelectedItem.
As Andy pointed out in comments, your SelectedValue and SelectedItem are both going to be instances of ComboBoxItem, because that's how you populated your ComboBox. The string you want is in the Content property of the ComboBoxItems, so use SelectedValuePath to tell the ComboBox about that, and bind to the SelectedValue property. SelectedItem will be the ComboBoxItem itself, which is useless to you.
<ComboBox
SelectedValue="{Binding SelectedEmployees.gender}"
SelectedValuePath="Content"
>
<ComboBoxItem Content="Male" />
<ComboBoxItem Content="Female" />
</ComboBox>
Here's another approach: Populate the ComboBox with strings.
<ComboBox
SelectedItem="{Binding SelectedEmployees.gender}"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
>
<sys:String>Male</sys:String>
<sys:String>Female</sys:String>
</ComboBox>
See Andy's answer for yet another way to populate the ComboBox with strings via ItemsSource.

Listbox in other Listbox, Itemsource, Binding

I would like to know How to refresh our ListBox item.
I tried OnPropertyChanged method, ObservableCollection, but it didn't work. I tried set again the itemsource property, so that worked, but now I have got 2 ListBox and now it's complicated. It's a wp7 project there is the main interface. You can see I have 2 listbox
<ListBox Name="lsbNameDays" ItemsSource="ComplexNameDays">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding NameDay.Name}" FontSize="50"/>
<ListBox ItemsSource="ComplexNameDays.FacebookFriends" x:Name="asdf">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Lastname}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
there is the properties:
List<SelectedNameDays> complexNameDays;
public List<SelectedNameDays> ComplexNameDays
{
get { return complexNameDays; }
set
{
complexNameDays = value;
OnPropertyChanged("ComplexNameDays");
}
}
public class SelectedNameDays : Notifier
{
NameDay _nameday;
public NameDay NameDay
{
get { return _nameday; }
set { _nameday = value; OnPropertyChanged("NameDay"); }
}
public List<FacebookFriend> FacebookFriends { get; set; }
public SelectedNameDays()
{
_nameday = new NameDay();
}
}
public class FacebookFriend
{
public long Id { get; set; }
public string Name { get; set; }
public string Firstname { get; set; }
public string Lastname { get; set; }
public string Birthday { get; set; }
public string Gender { get; set; }
public Uri Picture { get; set; }
}
The begining of the code is correct, that works, because when the Constructor set the datas I set retry the itemsource of lbsNameDays, but i cant find the "asdf" listbox, i can't set their datas again.
so the 2 main question are that.
1. how can i fire the property changed if that, and the observable collecton doesn't works.
2. how can I use asdf listbox in the datatemplate
thank the answer, and I sorry my grammer mistakes
Your Bindings won't work, because you dont use the right syntax:
ItemsSource="ComplexNameDays"
should be
ItemsSource="{Binding ComplexNameDays}"
The second binding is also wrong:
ItemsSource="ComplexNameDays.FacebookFriends"

Categories