MVVM Caliburn.micro comboboxes Country-City doesn't work properly - c#

I'm using MVVM with Caliburn.Micro and i have a problem.
So i have 2 comboboxes on view. First is to represent a list of countries and second of citites. I want the cities list to update whenever the country in the first list is changed, with corresponding list of cities. My problem is that cities list doesn't update.
Here is my code:
public class MyViewModel : PropertyChangedBase
{
Company company = new Company();
List<string> countries = new List<string> {"USA","Germany" };
public string Name
{
get { return company.name; }
set { company.name = value;
}
}
public List<string> Countries
{
get { return countries; }
set {
company.country = ToString();
NotifyOfPropertyChange(() => Countries);
NotifyOfPropertyChange(() => Cities);
}
}
public List<string> Cities
{
get {
switch (company.country)
{
case "USA": return new List<string> { "New York", "Los Angeles" };
case "Germany": return new List<string> { "Hamburg", "Berlin" };
default: return new List<string> { "DEFAULT", "DEFAULT" };
}
}
set { company.city = value.ToString();
NotifyOfPropertyChange(() => Cities);
}
}
}
Now the cities list remain with default members ( DEFAULT, DEFAULT). The view contains only 2 comboboxes with corresponding names:
<Grid>
<ComboBox x:Name="Countries" />
<ComboBox x:Name="Cities" />
</Grid>
Some suggestions ?
Sorry for my bad english.

Bind the SelectedItem property of the country ComboBox to a source property of the view model:
<ComboBox x:Name="Countries" ItemsSource="{Binding Countries}" SelectedItem="{Binding SelectedCountry}" />
<ComboBox x:Name="Cities" ItemsSource="{Binding Cities}" />
...and raise the PropertyChanged event for the Cities property in the setter of this one:
public class MyViewModel : PropertyChangedBase
{
Company company = new Company();
List<string> countries = new List<string> { "USA", "Germany" };
public string SelectedCountry
{
get { return company.country; }
set
{
company.country = value;
NotifyOfPropertyChange(() => SelectedCountry);
NotifyOfPropertyChange(() => Cities);
}
}
public List<string> Countries
{
get { return countries; }
set
{
countries = value;
NotifyOfPropertyChange(() => Countries);
NotifyOfPropertyChange(() => Cities);
}
}
public List<string> Cities
{
get
{
switch (company.country)
{
case "USA": return new List<string> { "New York", "Los Angeles" };
case "Germany": return new List<string> { "Hamburg", "Berlin" };
default: return new List<string> { "DEFAULT", "DEFAULT" };
}
}
}
}
Please refer to the following blog post for a complete example and more information about how to implement this kind of cascading ComboBoxes: https://blog.magnusmontin.net/2013/06/17/cascading-comboboxes-in-wpf-using-mvvm/

Related

Filtering of combobox in c# wpf

XAML:
<ComboBox x:Name="cmb" HorizontalAlignment="Left"
Margin="183,39,0,0"
VerticalAlignment="Top"
Width="120"
ItemsSource="{Binding FilteredNames, Mode=OneWay}"
IsTextSearchEnabled="True"
IsEditable="True"
TextSearch.Text="{Binding Filter, UpdateSourceTrigger=PropertyChanged}"/>
ViewModel:
public List<string> FilteredNames
{
get
{
return (names.FindAll(x => x.Contains(filter))).ToList<string>();
}
}
public string Filter
{
get
{
return this.filter;
}
set
{
this.filter = value;
NotifyPropertyChanged("FilteredNames");
}
}
public ViewModel()
{
this.names = new List<string>() { "Jerry", "Joey", "Roger", "Raymond", "Jessica", "Mario",
"Jonathan" };
this.filter = "";
}
This is what I have implemented. Please help me out how to get filtered dropdown in combobox.
Like when I input "j", I want to get all the items containing "j" in it.
You should bind string input to ComboBox's Text property:
Text="{Binding Filter, UpdateSourceTrigger=PropertyChanged}"
Also I would suggest using CollectionView for filtering like this:
public ICollectionView FilteredNames { get; set; }
IList<string> names = new List<string>() { "Jerry", "Joey", "Roger", "Raymond", "Jessica", "Mario", "Jonathan" };
public VM()
{
FilteredNames = CollectionViewSource.GetDefaultView(names);
FilteredNames.Filter = (obj) =>
{
if (!(obj is string str))
return false;
return str.Contains(filter);
};
}
string filter = "";
public string Filter
{
get
{
return this.filter;
}
set
{
this.filter = value;
FilteredNames?.Refresh();
}
}

make method generic for different viewmodel

I have a method which accepts one viewmodel. i have to replicate same method for different viewmodel. i tried something like functionname (t model) but it didnt work.I am new to generics.
Can some one help me
private void SetUpUserTypeDropDown(RegisterViewModel model)
{
var usertypes = GetuserTypes();
model.UserTypes = new List<SelectListItem> { };
usertypes.ForEach(t => model.UserTypes.Add(new SelectListItem() { Text = t.Text, Value = t.Value }));
}
private void SetUpUserforBackOfficeTypeDropDown(BackOfficeViewModel model)
{
var usertypes = GetuserTypes();
model.UserTypes = new List<SelectListItem> { };
usertypes.ForEach(t => model.UserTypes.Add(new SelectListItem() { Text = t.Text, Value = t.Value }));
}
private void SetUpProfileTypeDropDown(MyProfileViewModel model)
{
var usertypes = GetuserTypes();
model.UserTypes = new List<SelectListItem> { };
usertypes.ForEach(t => model.UserTypes.Add(new SelectListItem() { Text = t.Text, Value = t.Value }));
}
I am forced to copy paste same code with different method names. Can i get some help in this regard how to make a generic method which have input view model as generic input
Since all your *ViewModel-classes have a property named UserTypes of type List<SelectListItem> you can think about creating a base class for all *ViewModels:
public class ViewModelBase
{
public List<SelectListItem> UserTypes { get; set; }
// ... further code?
}
and inherit your view models from that class, for example:
public class RegisterViewModel : ViewModelBase
{
// implement specific behaviour
}
Then your method does not need to be generic, but can simply take a ViewModelBase as parameter:
private void SetUpUserType(ViewModelBase model)
{
var usertypes = GetuserTypes();
model.UserTypes = new List<SelectListItem> { };
usertypes.ForEach(t => model.UserTypes.Add(new SelectListItem() { Text = t.Text, Value = t.Value }));
}
Just for completeness, you can make this method generic:
private void SetUpUserType<T>(T model) where T : ViewModelBase
{
var usertypes = GetuserTypes();
model.UserTypes = new List<SelectListItem> { };
usertypes.ForEach(t => model.UserTypes.Add(new SelectListItem() { Text = t.Text, Value = t.Value }));
}
and use the constraint (where T : ViewModelBase) to make sure it's a type derived from ViewModelBase supporting that UserTypes property.

Wpf Depended combobox doesn't dispaly SelectedItem in .Net 4.5

I have main combobox (Categories) and depended combobox (Subcategories). I want it to display SelectedItems when window opens. All works fine in .Net 4.0, but it doesn't work in .Net 4.5. I have two computeres with these .Net versions.
In .net 4.5. only main combobox displays SelectedItem, depended doesn't. How can I fix it?
I made test project to all of you who're interested, just copy and paste. I have no idea how I can make it smaller, sry. But it is simple, clear code example 100% generates the problem.
XAML:
<Window x:Class="GridTest.TestWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Converter="clr-namespace:GridTest"
Title="TestWindow"
Height="300"
Width="300">
<Window.Resources>
<Converter:CategoryConverter x:Key="CategoryConverter"/>
</Window.Resources>
<Grid>
<DataGrid Name="_dataGrid"
CanUserAddRows="False"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ComboBox Grid.Column="0"
Name="_categories"
ItemsSource="{Binding Categories}"
DisplayMemberPath="Name"
SelectedItem="{Binding SelectedCategory, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
</ComboBox>
<ComboBox Grid.Column="1"
SelectedItem="{Binding SelectedSubcategory, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Name">
<ComboBox.ItemsSource>
<MultiBinding Converter="{StaticResource CategoryConverter}">
<Binding Path="Subcategories"/>
<Binding Path="SelectedItem"
ElementName="_categories"/>
</MultiBinding>
</ComboBox.ItemsSource>
</ComboBox>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Code:
public class CategoryConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values[0] == null) return null;
var subcategories = values[0] as List<Subcategory>;
if (subcategories == null) return null;
var category = values[1] as Category;
if (category == null) return subcategories;
return subcategories.Where(g => g.CategoryId == category.Id);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public enum CategoryKinds
{
Car = 0,
Fruit = 1,
}
public class Category
{
public Int32 Id { get; set; }
public String Name { get; set; }
public override Boolean Equals(object obj)
{
var c = obj as Category;
if (c == null) return false;
return Id == c.Id;
}
}
public class Subcategory
{
public Int32 Id { get; set; }
public String Name { get; set; }
public Int32 CategoryId { get; set; }
public override Boolean Equals(object obj)
{
var sc = obj as Subcategory;
if (sc == null) return false;
return Id == sc.Id;
}
}
public class DataGridItem
{
public List<Category> Categories { get; set; }
public Category SelectedCategory { get; set; }
public List<Subcategory> Subcategories { get; set; }
public Subcategory SelectedSubcategory { get; set; }
public DataGridItem()
{
Categories = new List<Category>
{
new Category
{
Id = (Int32)CategoryKinds.Car, Name = "Car"
},
new Category
{
Id = (Int32)CategoryKinds.Fruit, Name = "Fruit"
}
};
Subcategories = new List<Subcategory>
{
new Subcategory
{
Id = 1,
Name = "Volvo",
CategoryId = (Int32) CategoryKinds.Car
},
new Subcategory
{
Id = 2,
Name = "Nissan",
CategoryId = (Int32) CategoryKinds.Car
},
new Subcategory
{
Id = 3,
Name = "Banana",
CategoryId = (Int32)CategoryKinds.Fruit
},
new Subcategory
{
Id = 4,
Name = "Lemon",
CategoryId = (Int32)CategoryKinds.Fruit
},
};
}
}
/// <summary>
/// Interaction logic for TestWindow.xaml
/// </summary>
public partial class TestWindow : Window
{
public List<DataGridItem> GridItems { get; set; }
public TestWindow()
{
InitializeComponent();
DataContext = this;
GridItems = new List<DataGridItem>
{
new DataGridItem
{
SelectedCategory = new Category
{
Id = (Int32)CategoryKinds.Car, Name = "Car"
},
SelectedSubcategory = new Subcategory
{
Id = 2,
Name = "Nissan",
CategoryId = (Int32) CategoryKinds.Car
}
},
new DataGridItem
{
SelectedCategory = new Category
{
Id = (Int32)CategoryKinds.Fruit, Name = "Fruit"
},
SelectedSubcategory = new Subcategory
{
Id = 4,
Name = "Lemon",
CategoryId = (Int32) CategoryKinds.Car
}
}
};
_dataGrid.ItemsSource = GridItems;
}
}
UPDATE
With approach suggested by Ilan and charly_b code will work fine.
GridItems = new List<DataGridItem>
{
new DataGridItem(),
new DataGridItem()
};
GridItems[1].SelectedCategory = GridItems[1].Categories[0];
GridItems[1].SelectedSubcategory = GridItems[1].Subcategories[1];
GridItems[0].SelectedCategory = GridItems[0].Categories[1];
GridItems[0].SelectedSubcategory = GridItems[0].Subcategories[3];
This code will result to:
Fruit - Lemon
Car - Nissan
But I have solution that will work even if you set SelectedItem that don't belong to ItemsSource of Combobox. You can override GetHashCode method like this:
public override int GetHashCode()
{
return Name.GetHashCode();
}
Obviously, in .Net 4.5 some of WPF methods operating with searching SelectedItem in Combobox's ItemsSource have different implementation from .Net 4.0 and now they use GetHashCode method :)
Try the next changes, the best practice is to use the source collection items in order to define the selected item. Firstly it is an architectural error to use a new item to define the selection (in both 4.5 and 4 dot.net versions). And second I advice you to use the mvvm approach (including INotifyPropertyChange implementation) to develop wpf related applications, and then all selection logic have to be moved to ViewModel and separated from the code behind (xaml.cs files).
public MainWindow()
{
InitializeComponent();
DataContext = this;
var f = new DataGridItem();
var firstselectedCategory = f.Categories.FirstOrDefault();
if (firstselectedCategory != null)
{
f.SelectedCategory = firstselectedCategory;
f.SelectedSubcategory =
f.Subcategories.FirstOrDefault(subcategory => subcategory.CategoryId == firstselectedCategory.Id);
}
else
{
f.SelectedCategory = null;
f.SelectedSubcategory = null;
}
var s = new DataGridItem();
var secondSelectedCategory = s.Categories.FirstOrDefault(category => !Equals(category, f.SelectedCategory));
if (secondSelectedCategory != null)
{
s.SelectedCategory = secondSelectedCategory;
s.SelectedSubcategory =
s.Subcategories.FirstOrDefault(subcategory => subcategory.CategoryId == secondSelectedCategory.Id);
}
else
{
s.SelectedCategory = null;
s.SelectedSubcategory = null;
}
GridItems = new List<DataGridItem>
{
f,s,
};
#region
//GridItems = new List<DataGridItem>
//{
// new DataGridItem
// {
// SelectedCategory = new Category
// {
// Id = (Int32) CategoryKinds.Car,
// Name = "Car"
// },
// SelectedSubcategory = new Subcategory
// {
// Id = 2,
// Name = "Nissan",
// CategoryId = (Int32) CategoryKinds.Car
// }
// },
// new DataGridItem
// {
// SelectedCategory = new Category
// {
// Id = (Int32) CategoryKinds.Fruit,
// Name = "Fruit"
// },
// SelectedSubcategory = new Subcategory
// {
// Id = 4,
// Name = "Lemon",
// CategoryId = (Int32) CategoryKinds.Fruit
// }
// }
//};
#endregion
_dataGrid.ItemsSource = GridItems;
}
The xaml code was not changed.
How it looks like:
.
I'll be glad to help if will have problems with the code.
Regards.
The Combobox SelectedItem object must be contained inside the Combobox's ItemsSource List.
In order to make your Programm work you can replace the SelectedSubCategory Property with the following code: (I would not use it like this in the production code, but it demonstrates how it works)
private Subcategory SelectedSubcategoryM;
public Subcategory SelectedSubcategory
{
get
{
return this.SelectedSubcategoryM;
}
set
{
this.SelectedSubcategoryM = (from aTest in this.Subcategories
where aTest.Id == value.Id
select aTest).Single();
}
}

LINQ to remove objects from a list of objects based on another list of objects

public class Car
{
private string _manufacturer = string.empty;
private string _color = string.empty;
private string _modelLine= string.empty;
public string Manufacturer
{
get { return _manufacturer; }
set { _manufacturer= value; }
}
public string Color
{
get { return _color; }
set { _color= value; }
}
public string ModelLine
{
get { return _modelLine; }
set { _modelLine= value; }
}
}
I have a List allCars, and I want to remove from the list all items that are in a second list List selectedCars. How can I accomplish this with LINQ?
I tried something similiar to:
List<Car> listResults = allCars.Except(selectedCars).ToList();
However it is not removing any items from the allCars list.
LINQ stands for Language INtegrated Query. It is for querying. Removing items from a list isn't querying. Getting all of the items that you want to remove is a query, or getting all of the items that shouldn't be removed is another query (that's the one that you have).
To remove items from the list you shouldn't use LINQ, you should use the appropriate List tools that it provides for mutating itself, such as RemoveAll:
var itemsToRemove = new HashSet<Car>(selectedCars); //use a set that can be efficiently searched
allCars.RemoveAll(car => itemsToRemove.Contains(car));
Sample Solution Code
var item = new Car { Color = "red", Manufacturer = "bmw", ModelLine = "3" };
var car = new Car { Color = "red", Manufacturer = "toyo", ModelLine = "2" };
var item1 = new Car { Color = "red", Manufacturer = "marc", ModelLine = "23" };
var carsa = new List<Car>
{
item1,
item,
car
};
var carsb = new List<Car>
{
item,
car
};
carsa.RemoveAll(carsb.Contains);

How to bind a List to a ComboBox?

I want to connect a BindingSource to a list of class objects and then objects value to a ComboBox.
Can anyone suggest how to do it?
public class Country
{
public string Name { get; set; }
public IList<City> Cities { get; set; }
public Country()
{
Cities = new List<City>();
}
}
is my class and I want to bind its name field to a BindingSource which could be then associated with a ComboBox
As you are referring to a combobox, I'm assuming you don't want to use 2-way databinding (if so, look at using a BindingList)
public class Country
{
public string Name { get; set; }
public IList<City> Cities { get; set; }
public Country(string _name)
{
Cities = new List<City>();
Name = _name;
}
}
List<Country> countries = new List<Country> { new Country("UK"),
new Country("Australia"),
new Country("France") };
var bindingSource1 = new BindingSource();
bindingSource1.DataSource = countries;
comboBox1.DataSource = bindingSource1.DataSource;
comboBox1.DisplayMember = "Name";
comboBox1.ValueMember = "Name";
To find the country selected in the bound combobox, you would do something like: Country country = (Country)comboBox1.SelectedItem;.
If you want the ComboBox to dynamically update you'll need to make sure that the data structure that you have set as the DataSource implements IBindingList; one such structure is BindingList<T>.
Tip: make sure that you are binding the DisplayMember to a Property on the class and not a public field. If you class uses public string Name { get; set; } it will work but if it uses public string Name; it will not be able to access the value and instead will display the object type for each line in the combo box.
For a backgrounder, there are 2 ways to use a ComboBox/ListBox
1) Add Country Objects to the Items property and retrieve a Country as Selecteditem. To use this you should override the ToString of Country.
2) Use DataBinding, set the DataSource to a IList (List<>) and use DisplayMember, ValueMember and SelectedValue
For 2) you will need a list of countries first
// not tested, schematic:
List<Country> countries = ...;
...; // fill
comboBox1.DataSource = countries;
comboBox1.DisplayMember="Name";
comboBox1.ValueMember="Cities";
And then in the SelectionChanged,
if (comboBox1.Selecteditem != null)
{
comboBox2.DataSource=comboBox1.SelectedValue;
}
public MainWindow(){
List<person> personList = new List<person>();
personList.Add(new person { name = "rob", age = 32 } );
personList.Add(new person { name = "annie", age = 24 } );
personList.Add(new person { name = "paul", age = 19 } );
comboBox1.DataSource = personList;
comboBox1.DisplayMember = "name";
comboBox1.SelectionChanged += new SelectionChangedEventHandler(comboBox1_SelectionChanged);
}
void comboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
person selectedPerson = comboBox1.SelectedItem as person;
messageBox.Show(selectedPerson.name, "caption goes here");
}
boom.
Try something like this:
yourControl.DataSource = countryInstance.Cities;
And if you are using WebForms you will need to add this line:
yourControl.DataBind();
If you are using a ToolStripComboBox there is no DataSource exposed (.NET 4.0):
List<string> someList = new List<string>();
someList.Add("value");
someList.Add("value");
someList.Add("value");
toolStripComboBox1.Items.AddRange(someList.ToArray());
public class Country
{
public string Name { get; set; }
public IList<City> Cities { get; set; }
public Country()
{
Cities = new List<City>();
}
}
public class City
{
public string Name { get; set; }
}
List<Country> Countries = new List<Country>
{
new Country
{
Name = "Germany",
Cities =
{
new City {Name = "Berlin"},
new City {Name = "Hamburg"}
}
},
new Country
{
Name = "England",
Cities =
{
new City {Name = "London"},
new City {Name = "Birmingham"}
}
}
};
bindingSource1.DataSource = Countries;
member_CountryComboBox.DataSource = bindingSource1.DataSource;
member_CountryComboBox.DisplayMember = "Name";
member_CountryComboBox.ValueMember = "Name";
This is the code I am using now.
As a small addition to this, I tried to incorporate something similar to this code, and was frustrated that adding/removing from the list was not reflected in the ComboBox. This is because the Add/Remove does not trigger the OnPropertyChange.
If you want to Add/Remove and have them reflected in the ComboBox, you will need to change List<> to ObservableCollection
List<Country> Countries
Should be replaced with
private ObservableCollection<Country> countries;
public ObservableCollection<Country> Countries
{
get { return countries; }
set
{
countries= value;
OnPropertyChanged("Countries");
}
}
Where OnPropertyChanged and ObservableCollection comes from
using System.Runtime.CompilerServices;
using System.Collections.ObjectModel;
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
All of this is more eloquently expressed in a previous explanation here

Categories