C# WPF MVVM ComboBox SelectedItem - c#

I know that it's been here a million times but I don't know what's wrong in my code. I tried everything but the ComboBox is not binding SelectedItem correctly.
Here is my complete sandbox solution. You can also find it on GitHub (https://github.com/LukasNespor/ComboBoxBinding).
BindableBase.cs
public class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public virtual void RaisePropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
ContactModel.cs
public class ContactModel : BindableBase
{
private int _Id;
public int Id
{
get { return _Id; }
set
{
_Id = value;
RaisePropertyChanged(nameof(Id));
}
}
private string _Name;
public string Name
{
get { return _Name; }
set
{
_Name = value;
RaisePropertyChanged(nameof(Name));
}
}
private string _Phone;
public string Phone
{
get { return _Phone; }
set
{
_Phone = value;
RaisePropertyChanged(nameof(Phone));
}
}
public override bool Equals(object obj)
{
if (obj != null || !(obj is ContactModel))
return false;
return ((ContactModel)obj).Id == this.Id;
}
public override string ToString()
{
return $"{Name} {Phone}";
}
}
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
...>
<Grid>
<ComboBox Width="200" Height="23"
SelectedItem="{Binding ViewModel.SelectedContact}" ItemsSource="{Binding ViewModel.Contacts}" />
</Grid>
MainWindows.xaml.cs
public partial class MainWindow : Window
{
public MainViewModel ViewModel { get; set; }
public MainWindow()
{
InitializeComponent();
DataContext = this;
ViewModel = new MainViewModel();
ViewModel.SelectedContact = new ContactModel
{
Id = 2,
Name = "Some Guy",
Phone = "+123456789"
};
}
}
MainViewModel.cs
public class MainViewModel : BindableBase
{
public List<ContactModel> Contacts
{
get
{
return new List<ContactModel>
{
new ContactModel() {Id = 1, Name = "John Doe", Phone = "+166666333" },
new ContactModel() {Id = 2, Name = "Some Guy", Phone = "+123456789" }
};
}
}
private ContactModel _SelectedContact;
public ContactModel SelectedContact
{
get { return _SelectedContact; }
set
{
_SelectedContact = value;
RaisePropertyChanged(nameof(SelectedContact));
}
}
}

You need do Sync the list with the current selected item:
1.Xaml:
<ComboBox ItemsSource="{Binding Contacts}" SelectedItem="{Binding SelectedContact}" IsSynchronizedWithCurrentItem="True" />
2.ViewModel:
public ObservableCollection<ContactModel> Contacts { get; set; }
public MainViewModel()
{
_model = new Model {Name = "Prop Name" };
Contacts = new ObservableCollection<ContactModel>
{
new ContactModel {Id = 1, Name = "John Doe", Phone = "+166666333"},
new ContactModel {Id = 2, Name = "Some Guy", Phone = "+123456789"}
};
SelectedContact = Contacts[0];
}
private ContactModel _SelectedContact;
public ContactModel SelectedContact
{
get { return _SelectedContact; }
set
{
_SelectedContact = value;
OnPropertyChanged(nameof(SelectedContact));
}
}

Related

CollectinView Grouping doesnt show all Items in the last Group

im working on an Mobile App for Android in Xamarin Forms, and have one problem. I have a ObservableCollection that i fill with the following Model.
private int _category_ID;
public int Category_ID
{
get
{
return _category_ID;
}
set
{
_category_ID = value;
OnPropertyChanged("_category_ID");
}
}
private string _category_Name;
public string Category_Name
{
get
{
return _category_Name;
}
set
{
_category_Name = value;
OnPropertyChanged("_category_Name");
}
}
private string _category_Description;
public string Category_Description
{
get
{
return _category_Description;
}
set
{
_category_Description = value;
OnPropertyChanged("_category_Description");
}
}
public CategoryModel(string name, List<ProductModel> products) : base(products)
{
Category_Name = name;
}
That works fine and all Categorys and Items shows right when i Debugging in the ObservableCollection. Example: Categroy 1 = 2 Items Category 2 = 3 Items Category 3 = 4 Items.
That works.
But my problem is, that when i use a CollectionView like this
<CollectionView ItemsSource="{Binding ObservCollectionCategory}" IsGrouped="True">
<CollectionView.ItemTemplate>
<DataTemplate>
<ScrollView>
<Grid Padding="10">
<StackLayout BackgroundColor="Blue">
<Label Text="{Binding Product_Name}"/>
<Label Text="{Binding Product_Description}"/>
</StackLayout>
</Grid>
</ScrollView>
</DataTemplate>
</CollectionView.ItemTemplate>
<CollectionView.GroupHeaderTemplate>
<DataTemplate >
<Label Text="{Binding Category_Name}"/>
</DataTemplate>
</CollectionView.GroupHeaderTemplate>
</CollectionView>
The View shows me only the first item of the last categroy and the other ignores. All other Categorys shows right.
When i create a blank Categroy at the end of the ObservableCollection then all Items, from the last category, shows in the View. But i have an empty Group then at the end.
I have try to show me the count in the header, and the count the right one (4).
You could try the code below.
Model:
public class ProductModel : INotifyPropertyChanged
{
private string _product_Name;
public string Product_Name
{
get
{
return _product_Name;
}
set
{
_product_Name = value;
OnPropertyChanged("Product_Name");
}
}
private string _product_Description;
public string Product_Description
{
get
{
return _product_Description;
}
set
{
_product_Description = value;
OnPropertyChanged("Product_Description");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class CategoryModel : List<ProductModel>, INotifyPropertyChanged
{
private string _category_Name;
public event PropertyChangedEventHandler PropertyChanged;
public string Category_Name
{
get
{
return _category_Name;
}
set
{
_category_Name = value;
OnPropertyChanged("Category_Name");
}
}
public CategoryModel(string name, List<ProductModel> diary) : base(diary)
{
Category_Name = name;
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Code behind:
public ObservableCollection<CategoryModel> ObservCollectionCategory { get; set; } = new ObservableCollection<CategoryModel>();
public Page18()
{
InitializeComponent();
ObservCollectionCategory.Add(new CategoryModel("Categroy 1", new List<ProductModel>
{
new ProductModel(){ Product_Name="item1", Product_Description="Description1"},
new ProductModel(){ Product_Name="item2", Product_Description="Description2"},
}));
ObservCollectionCategory.Add(new CategoryModel("Categroy 2", new List<ProductModel>
{
new ProductModel(){ Product_Name="item1", Product_Description="Description1"},
new ProductModel(){ Product_Name="item2", Product_Description="Description2"},
new ProductModel(){ Product_Name="item3", Product_Description="Description3"},
}));
ObservCollectionCategory.Add(new CategoryModel("Categroy 3", new List<ProductModel>
{
new ProductModel(){ Product_Name="item1", Product_Description="Description1"},
new ProductModel(){ Product_Name="item2", Product_Description="Description2"},
new ProductModel(){ Product_Name="item3", Product_Description="Description3"},
new ProductModel(){ Product_Name="item4", Product_Description="Description4"},
}));
this.BindingContext = this;
}
Output:

How to bind my Id value from XAML UI to ViewModel

I'm new to Xamarin Forms, I am trying to get/Pass the Id value from XAML UI to my ViewModel
My XAML:
TODO
My VM:
private int id;
public int Id
{
get
{
return id;
}
set
{
if (id != value)
{
id = value;
OnPropertyChanged("Id");
}
}
}
public string result { get; set; }
public ICommand SubmitResultsCommand
{
get
{
return new Command(async () =>
{
IsLoading = true;
Result _result = new Result();
var response
= await _services.SubmitResultsAsync(result, id, Settings.AccessToken);
IsLoading = false;
});
}
}
in ViewModel
Define the binding property which you need to bind to view in xaml
public class MyViewModel: INotifyPropertyChanged
{
// it is necessary if you want to change the value of Id in runtime
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string id;
public string Id
{
get
{
return id;
}
set
{
if (id != value)
{
id = value;
NotifyPropertyChanged("Id");
}
}
}
// other properties
}
In ContentPage
Set the BindingContext
public MainPage()
{
InitializeComponent();
BindingContext = new MyViewModel();
}

How to add methods in an ObservableCollection?

I have an ObservableCollection that generates a button with the data of a Model in which I pass an image, a color and a link. The problem is that I want to put a method in it and when I press the button, call the method that I put into the ObservableCollection.
[Edited]
I am making a FloatingButton like this:
This floating button is made with a custom control. Each small button has a background color, an image and a link which are in Items.cs. In the viewmodel, I create an ObservableCollections with the Items.cs data that every time I add a list, a new button is added. What I want to do is to be able to add in addition to an image, a link and a color, a method that depending on the button you press, does what I want.
If I press the first small button that will be the first index in the list, that when I press it, it does one thing, and if I press the fourth button it will call another method that I want.
Example:
ItemList.Add(new Items { Website = "https://google.es/", Image = "web.png", ColorButton = "#B52D50", Method = "DoSomething" });
ItemList.Add(new Items { Website = "https://facebook.com/", Image = "facebook.png", ColorButton = "#B52D50", Method = "OpenFacebookApp" });
public void DoSomething()
{
//Do Something
}
public void OpenFacebookApp()
{
//Open Facebook App
}
This is my code:
ViewModel.cs:
public class ViewModel : INotifyPropertyChanged
{
//==============================================================
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
//==============================================================
string imageprimarybutton;
public string FirstImage
{
get => imageprimarybutton; set
{
imageprimarybutton = value;
OnPropertyChanged();
}
}
//=============
string firstButtonColor;
public string FirstButtonColor
{
get => firstButtonColor; set
{
firstButtonColor = value;
OnPropertyChanged();
}
}
//=============
private bool isVisible;
public bool IsVisible
{
get => isVisible;
set
{
isVisible = value;
OnPropertyChanged();
}
}
//=============
public ICommand LaunchWeb { get; private set; }
public ICommand OpenFloating { get; private set; }
public ObservableCollection<Items> ItemList { get; set; }
//=============
public ViewModel()
{
IsVisible = false;
FirstImage = "dots.png";
FirstButtonColor = "#B52D50";
OpenFloating = new Command(openFloatingButton);
LaunchWeb = new Command(async (url) =>
{
string AppLink = (string)url;
await Launcher.TryOpenAsync(AppLink);
});
ItemList = new ObservableCollection<Items>();
ItemList.Add(new Items { Website = "https://facebook.com/", Image = "facebook.png", ColorButton = "#B52D50" /*What I want: Method=OpenApp*/});
ItemList.Add(new Items { Website = "https://twitter.com/", Image = "twitter.png", ColorButton = "#B52D50" });
ItemList.Add(new Items { Website = "https://www.instagram.com/", Image = "insta.png", ColorButton = "#B52D50" });
ItemList.Add(new Items { Website = "https://google.com/", Image = "web.png", ColorButton = "#B52D50" });
}
/* And here the method I call in ItemList
public void OpenApp() {
Do something
}
*/
bool firstStart = true;
bool nextClick = true;
public void openFloatingButton()
{
if (firstStart)
{
FirstImage = "cross.png";
FirstButtonColor = "#6F1B31";
IsVisible = true;
firstStart = false;
}
else
{
if (nextClick)
{
FirstImage = "dots.png";
FirstButtonColor = "#B52D50";
IsVisible = false;
nextClick = false;
}
else
{
FirstImage = "cross.png";
FirstButtonColor = "#6F1B31";
IsVisible = true;
nextClick = true;
}
}
}
}
Items.cs:
public class Items : INotifyPropertyChanged
{
string url, image, color;
public string Website
{
get { return url; }
set {
url = value;
OnPropertyChanged("url");
}
}
public string Image
{
get {
return image;
}
set{
image = value;
OnPropertyChanged("image");
}
}
public string ColorButton{
get {
return color;
}
set{
color = value;
OnPropertyChanged("color");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I wrote a simple example for you and hope you can understand how the binding works.
First I add a Name property in the Items model which is use for distinguish different button:
public class Items : INotifyPropertyChanged
{
string url { get; set; }
string image { get; set; }
string color { get; set; }
string name { get; set; }
public string Website
{
get { return url; }
set
{
url = value;
OnPropertyChanged("Website");
}
}
public string Image
{
get
{
return image;
}
set
{
image = value;
OnPropertyChanged("Image");
}
}
public string ColorButton
{
get
{
return color;
}
set
{
color = value;
OnPropertyChanged("ColorButton");
}
}
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged("Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Then in the View Model, there is a MethodCommand which you can bind to the buttons:
public class viewModel {
public ObservableCollection<Items> ItemList { get; set; }
public ICommand MethodCommand { get; set; }
public viewModel()
{
ItemList = new ObservableCollection<Items>();
ItemList.Add(new Items { Website = "https://facebook.com/", Image = "facebook.png", ColorButton = "#B52D50", Name = "facebook"/*What I want: Method=OpenApp*/});
ItemList.Add(new Items { Website = "https://twitter.com/", Image = "twitter.png", ColorButton = "#B52D50", Name = "twitter" });
ItemList.Add(new Items { Website = "https://www.instagram.com/", Image = "insta.png", ColorButton = "#B52D50", Name = "insta" });
ItemList.Add(new Items { Website = "https://google.com/", Image = "web.png", ColorButton = "#B52D50", Name = "web" });
MethodCommand = new Command(test);
}
private void test(object obj)
{
string itemName= obj as string;
Console.WriteLine(itemName);
if (itemName == "facebook")
{
//perform your method with facebook
}
else if (itemName == "twitter")
{
//perform your method with twitter
}
else if (itemName == "insta")
{
//...
}
else
{
//...
}
}
}
Here is the code in Xaml, bind the MethodCommand to the Command and bind Name to the CommandParameter:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Name="myPage"
x:Class="App416.MainPage">
<CollectionView ItemsSource="{Binding ItemList}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10" >
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button Grid.Column="1"
Text="{Binding Name}"
Command="{Binding Source={x:Reference myPage}, Path=BindingContext.MethodCommand}"
CommandParameter="{Binding Name}"
/>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</ContentPage>
And at last, set the bindingContext in MainPage:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
this.BindingContext = new viewModel();
}
}
Please feel free to ask me any question if you have:).

Binding a RibbonComboBox to filtered ObservableCollection causing errors

Using C# I'm trying to filter a RibbonComboBox list using RibbonRadioButtons but I can't solve an error I keep receiving.
The list of countries is in an ObservableCollection which I'm filtering using ListCollectionView. With help from another user (C# WPF Filter ComboBox based on RadioButtons) I now have it partially working but if I click on a radio button the list in the ComboBox is updated but nothing is shown in the ComboBox; I expected the first item in the list to be displayed. If I select a country then click another continent from a RadioButton I receive the error shown below on the line 'public bool Africa {... CountryView.Refresh()}' or whichever button I clicked on.
[Code updated 25 Sep to reflect comments.]
RibbonComboBox errors
Object reference not set to an instance of an object.
In VS Output window:
Error: 40 : BindingExpression path error: 'DisplayName' property not found on 'object'
When I changed the RibbonComboBox to a ComboBox in the XAML as shown below it does seem to work correctly though it generates another error. However I would prefer to use the RibbonComboBox but not sure how to resolve the problem. Any help you can give to get it working would be appreciated.
XAML
<Grid>
<DockPanel>
<r:Ribbon DockPanel.Dock="Top" x:Name="Ribbon">
<r:RibbonGroup Header="Continent" Width="260">
<!--<ComboBox x:Name="CountryList" Width="100" ItemsSource="{Binding CountryView}" SelectedItem="{Binding SelectedCountry}" DisplayMemberPath="DisplayName"/>-->
<r:RibbonComboBox x:Name="CountryList" Height="Auto" SelectionBoxWidth="230" VerticalAlignment="Center">
<r:RibbonGallery x:Name="cbSelectedCountry" SelectedValue="{Binding SelectedCountry, Mode=TwoWay}" SelectedValuePath="DisplayName" >
<r:RibbonGalleryCategory x:Name="cbCountryList" ItemsSource="{Binding CountryView}" DisplayMemberPath="DisplayName" />
</r:RibbonGallery>
</r:RibbonComboBox>
<WrapPanel>
<r:RibbonRadioButton x:Name="All" Label="All" GroupName="ContinentGroup" Height="Auto" Width="Auto" HorizontalAlignment="Left" IsChecked="{Binding Path=All}">
</r:RibbonRadioButton>
<r:RibbonRadioButton x:Name="Africa" Label="Africa" GroupName="ContinentGroup" Height="Auto" Width="Auto" HorizontalAlignment="Left" IsChecked="{Binding Path=Africa}">
</r:RibbonRadioButton>
<r:RibbonRadioButton x:Name="America" Label="America" GroupName="ContinentGroup" Height="Auto" Width="Auto" HorizontalAlignment="Left" IsChecked="{Binding Path=America}">
</r:RibbonRadioButton>
</WrapPanel>
</r:RibbonGroup>
</r:Ribbon>
</DockPanel>
</Grid>
C# Code behind (DataContext):
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows.Data;
public class MySettings : INotifyPropertyChanged
{
private readonly ObservableCollection<Country> countries;
private ContinentViewModel selectedContinent;
private static string selectedCountry;
private int selectedRadioGroup;
private ObservableCollection<ContinentViewModel> continents;
private ListCollectionView countryView;
public event PropertyChangedEventHandler PropertyChanged;
private bool _All;
private bool _Africa;
private bool _America;
public bool All { get => _All; set { _All = value; CountryView.Refresh(); SelectedCountry = countries[0].ToString(); } }
public bool Africa { get => _Africa; set { _Africa = value; CountryView.Refresh(); SelectedCountry = countries[0].ToString(); } }
public bool America { get => _America; set { _America = value; CountryView.Refresh(); SelectedCountry = countries[0].ToString(); } }
public MySettings()
{
countries = new ObservableCollection<Country>(
new[]
{
new Country() { Continent = Continent.Africa, DisplayName = "Algeria" },
new Country() { Continent = Continent.Africa, DisplayName = "Egypt" },
new Country() { Continent = Continent.Africa, DisplayName = "Chad" },
new Country() { Continent = Continent.Africa, DisplayName = "Ghana" },
new Country() { Continent = Continent.America, DisplayName = "Canada" },
new Country() { Continent = Continent.America, DisplayName = "Greenland" },
new Country() { Continent = Continent.America, DisplayName = "Haiti" }
});
CountryView = (ListCollectionView)CollectionViewSource.GetDefaultView(countries);
CountryView.Filter += CountryFilter;
Continents = new ObservableCollection<ContinentViewModel>(Enum.GetValues(typeof(Continent)).Cast<Continent>().Select(c => new ContinentViewModel { Model = c }));
}
private bool CountryFilter(object obj)
{
var country = obj as Country;
if (country == null) return false;
if (All) return true;
if (Africa) return country.Continent == Continent.Africa;
if (America) return country.Continent == Continent.America;
return true;
}
public ObservableCollection<ContinentViewModel> Continents
{
get { return continents; }
set
{
continents = value;
}
}
public ListCollectionView CountryView
{
get { return countryView; }
set
{
countryView = value;
}
}
public class Country
{
public string DisplayName { get; set; }
public Continent Continent { get; set; }
}
public enum Continent
{
All,
Africa,
America
}
public class ContinentViewModel
{
public Continent Model { get; set; }
public string DisplayName
{
get
{
return Enum.GetName(typeof(Continent), Model);
}
}
}
public ContinentViewModel SelectedContinent
{
get { return selectedContinent; }
set
{
selectedContinent = value;
OnContinentChanged();
this.OnPropertyChanged("SelectedContinent");
}
}
private void OnContinentChanged()
{
CountryView.Refresh();
}
public int SelectedRadioGroup
{
get { return selectedRadioGroup; }
set
{
selectedRadioGroup = value;
}
}
public string SelectedCountry
{
get { return selectedCountry; }
set
{
if (selectedCountry == value) return;
selectedCountry = value;
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I have modified your class to solve your issues. The changes in code is:
Instead of CountryView = new ListCollectionView(countries) do CountryView = (ListCollectionView)CollectionViewSource.GetDefaultView(countries)
On every refresh of your list i.e on checking of the CheckBox set the SelectedItem of your ComboBox i.e SelectedCountry in your case as following :
SelectedCountry = _All ? countries.FirstOrDefault().DisplayName : SelectedCountry;
SelectedCountry = _Africa ? countries.Where(_ => _.Continent == Continent.Africa).FirstOrDefault().DisplayName : SelectedCountry;
SelectedCountry = _America ? countries.Where(_ => _.Continent == Continent.America).FirstOrDefault().DisplayName : SelectedCountry;
Call OnPropertyChanged for all the properties.
private readonly ObservableCollection<Country> countries;
private ContinentViewModel selectedContinent;
private static string selectedCountry;
private int selectedRadioGroup;
private ObservableCollection<ContinentViewModel> continents;
private ListCollectionView countryView;
public event PropertyChangedEventHandler PropertyChanged;
private bool _All;
private bool _Africa;
private bool _America;
public bool All
{
get
{
return _All;
}
set
{
_All = value;
CountryView.Refresh();
SelectedCountry = _All ? countries.FirstOrDefault().DisplayName : SelectedCountry;
OnPropertyChanged("All");
}
}
public bool Africa
{
get
{
return _Africa;
}
set
{
_Africa = value;
CountryView.Refresh();
SelectedCountry = _Africa ? countries.Where(_ => _.Continent == Continent.Africa).FirstOrDefault().DisplayName : SelectedCountry;
OnPropertyChanged("Africa");
}
}
public bool America
{
get
{
return _America;
}
set
{
_America = value;
CountryView.Refresh();
SelectedCountry = _America ? countries.Where(_ => _.Continent == Continent.America).FirstOrDefault().DisplayName : SelectedCountry;
OnPropertyChanged("America");
}
}
public MySettings()
{
countries = new ObservableCollection<Country>(
new[]
{
new Country() { Continent = Continent.Africa, DisplayName = "Algeria" },
new Country() { Continent = Continent.Africa, DisplayName = "Egypt" },
new Country() { Continent = Continent.Africa, DisplayName = "Chad" },
new Country() { Continent = Continent.Africa, DisplayName = "Ghana" },
new Country() { Continent = Continent.America, DisplayName = "Canada" },
new Country() { Continent = Continent.America, DisplayName = "Greenland" },
new Country() { Continent = Continent.America, DisplayName = "Haiti" }
});
CountryView = (ListCollectionView)CollectionViewSource.GetDefaultView(countries);
CountryView.Filter += CountryFilter;
Continents = new ObservableCollection<ContinentViewModel>(Enum.GetValues(typeof(Continent)).Cast<Continent>().Select(c => new ContinentViewModel { Model = c }));
}
private bool CountryFilter(object obj)
{
var country = obj as Country;
if (country == null) return false;
if (All && !Africa && !America) return true;
else if (!All && Africa && !America) return country.Continent == Continent.Africa;
else if (!All && !Africa && America) return country.Continent == Continent.America;
return true;
}
public ObservableCollection<ContinentViewModel> Continents
{
get { return continents; }
set
{
continents = value;
OnPropertyChanged("Continents");
}
}
public ListCollectionView CountryView
{
get { return countryView; }
set
{
countryView = value;
OnPropertyChanged("CountryView");
}
}
public class Country
{
public string DisplayName { get; set; }
public Continent Continent { get; set; }
}
public enum Continent
{
All,
Africa,
America
}
public class ContinentViewModel
{
public Continent Model { get; set; }
public string DisplayName
{
get
{
return Enum.GetName(typeof(Continent), Model);
}
}
}
public ContinentViewModel SelectedContinent
{
get { return selectedContinent; }
set
{
selectedContinent = value;
OnContinentChanged();
this.OnPropertyChanged("SelectedContinent");
}
}
private void OnContinentChanged()
{
CountryView.Refresh();
}
public int SelectedRadioGroup
{
get { return selectedRadioGroup; }
set
{
selectedRadioGroup = value;
OnPropertyChanged("SelectedRadioGroup");
}
}
public string SelectedCountry
{
get { return selectedCountry; }
set
{
if (selectedCountry == value) return;
selectedCountry = value;
OnPropertyChanged("SelectedCountry");
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

WPF MVVM Populate combobox OnPropertyChanged of another combobox

I want to populate my combobox2 after combobox1 selection changed event.
Here's some part of my XAML:
<ComboBox Name="cmbWorkcode"
ItemsSource="{Binding Workcodes}"
DisplayMemberPath="WorkcodeName"
SelectedValuePath="WorkcodeID"
SelectedValue="{Binding Path=WorkcodeId, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<ComboBox Name="cmbProcess"
ItemsSource="{Binding Processes}"
DisplayMemberPath="ProcessName" SelectedValuePath="ProcessId"
SelectedValue="{Binding Path=ProcessId, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
Some part of my ViewModel:
class MainWindowViewModel : ObservableObject
{
private ObservableCollection<Workcode> _workcodes = new ObservableCollection<Workcode>();
public ObservableCollection<Workcode> Workcodes
{
get { return _workcodes; }
set
{
_workcodes = value;
OnPropertyChanged("Workcodes");
}
}
private int _workcodeId;
public int WorkcodeId
{
get { return _workcodeId; }
set
{
_workcodeId = value;
OnPropertyChanged("WorkcodeId");
}
}
private ObservableCollection<Process> _processes = new ObservableCollection<Process>();
public ObservableCollection<Process> Processes
{
get { return _processes; }
set
{
_processes = value;
OnPropertyChanged("Processes");
}
}
private int _processId;
public int ProcessId
{
get { return _processId; }
set
{
_processId = value;
OnPropertyChanged("ProcessId");
}
}
public MainWindowViewModel()
{
PopulateWorkcode();
}
private void PopulateWorkcode()
{
using (var db = new DBAccess())
{
db.ConnectionString = ConfigurationManager.ConnectionStrings["connString"].ConnectionString;
db.Query = #"SELECT workcodeId, workcode FROM workcode";
DataTable data = db.GetData();
if (data != null)
{
foreach (DataRow row in data.Rows)
{
int workcodeId = Convert.ToInt32(row["workcodeId"].ToString());
string workcodeName = row["workcode"].ToString();
_workcodes.Add(new Workcode(workcodeId, workcodeName));
}
}
}
}
private void PopulateProcess()
{
using (var db = new DBAccess())
{
db.ConnectionString = ConfigurationManager.ConnectionStrings["connString"].ConnectionString;
db.Query = #"SELECT ProcessId, ProcessName FROM `process` WHERE WorkcodeId = #workcodeId";
DataTable data = db.GetData(new[] {new MySqlParameter("#workcodeId", _workcodeId.ToString())});
if (data != null)
{
foreach (DataRow row in data.Rows)
{
int id = Convert.ToInt32(row["ProcessId"].ToString());
string name = row["ProcessName"].ToString();
_processes.Add(new Process(id, name));
}
}
}
}
}
My problem is I don't know where do I trigger my PopulateProcess() method so that my combobox2 will be populated base on the selection of combobox1. Thanks for all the time and help! :)
--EDIT--
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Workcode
{
public int WorkcodeId { get; set; }
public string WorkcodeName { get; set; }
public Workcode(int id, string name)
{
WorkcodeId = id;
WorkcodeName = name;
}
}
initially the second combobox is empty and on select of the first combobox changed just pupulate the process
private int _workcodeId;
public int WorkcodeId
{
get { return _workcodeId; }
set
{
_workcodeId = value;
OnPropertyChanged("WorkcodeId");
if(WorkcodeID>0) PopulateProcess();
}
}
I can understand you want to have the next combobox to fill with data based on the previous value. Since i don't have classes of your type, i will give a simple example,
class ItemListViewModel<T> : INotifyPropertyChanged where T : class
{
private T _item;
private ObservableCollection<T> _items;
public ItemListViewModel()
{
_items = new ObservableCollection<T>();
_item = null;
}
public void SetItems(IEnumerable<T> items)
{
Items = new ObservableCollection<T>(items);
SelectedItem = null;
}
public ObservableCollection<T> Items
{
get { return _items; }
private set
{
_items = value;
RaisePropertyChanged("Items");
}
}
public T SelectedItem
{
get { return _item; }
set
{
_item = value;
RaisePropertyChanged("SelectedItem");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then have the main viewmodel that will be bound to the DataContext of the view. Have the Load methods do what you want
class MyViewModel : INotifyPropertyChanged
{
public MyViewModel()
{
First = new ItemListViewModel<string>();
Second = new ItemListViewModel<string>();
Third = new ItemListViewModel<string>();
First.PropertyChanged += (s, e) => Update(e.PropertyName, First, Second, LoadSecond);
Second.PropertyChanged += (s, e) => Update(e.PropertyName, Second, Third, LoadThird);
LoadFirst();
}
public ItemListViewModel<string> First { get; set; }
public ItemListViewModel<string> Second { get; set; }
public ItemListViewModel<string> Third { get; set; }
private void LoadFirst()
{
First.SetItems(new List<string> { "One", "Two", "Three" });
}
private void LoadSecond()
{
Second.SetItems(new List<string> { "First", "Second", "Third" });
}
private void LoadThird()
{
Third.SetItems(new List<string> { "Firsty", "Secondly", "Thirdly" });
}
private void Update<T0, T1>(string propertyName, ItemListViewModel<T0> parent, ItemListViewModel<T1> child, Action loadAction)
where T0 : class
where T1 : class
{
if (propertyName == "SelectedItem")
{
if (parent.SelectedItem == null)
{
child.SetItems(Enumerable.Empty<T1>());
}
else
{
loadAction();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
In XAML,
<ComboBox ItemsSource="{Binding First.Items}" SelectedItem="{Binding First.SelectedItem}" />
<ComboBox ItemsSource="{Binding Second.Items}" SelectedItem="{Binding Second.SelectedItem}" />
<ComboBox ItemsSource="{Binding Third.Items}" SelectedItem="{Binding Third.SelectedItem}" />
The issue is here
<ComboBox Name="cmbWorkcode"
ItemsSource="{Binding Workcodes}"
DisplayMemberPath="WorkcodeName"
SelectedValuePath="WorkcodeId"
SelectedValue="{Binding Path=WorkcodeId, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
It should be WorkcodeId instead of WorkcodeID. rest you can try as Nishanth replied
public int WorkcodeId
{
get { return _workcodeId; }
set
{
if(_workcodeId !=value)
{
_workcodeId = value;
OnPropertyChanged("WorkcodeId");
PopulateProcess();
}
}
}

Categories