Binding to a combo box in Silverlight - silverlight

I've got a combo box which has a collection of Country objects as it's source (it binds to an Countries property in the view model).
The Country combo is shown on part of a user form. The user form is populated from a user object. A user object holds a CountryID only (i.e a foreign key to Country). I currently populate my user form by binding the name text box to the name property in my user object etc. However, when I come to the Country combo binding, I'm really stuck.
My binding for the Combo is:
ItemsSource="{Binding Countries}" DisplayMemberPath="CountryDescription"
So for the currently loaded user, I need to take their Country ID and bind it somehow to the Country combo? How can I do this, as the country combo does not have a list of ints, but a list of Country objects. I though about using a converter but this seems like overkill as the combo has a country object in its source with the corresponding CountryID that I want.
So is there a way to get it to get the user CountryID property to bind to the Country combo and get the Country combo to should the friendly user name? I need two way binding, as the user needs to be able to select a different country, which should then update the corresponding countryID property in the user object.
Any help greatly appreciated! Cheers...
EDIT
Here's a cut down version of what I've got (I've left out all notifypropertychanged code for clarity)
class User
{
public int UserID
{
get;set;
}
public string Username
{
get;set;
}
public int CountryID
{
get;set;
}
}
class Country
{
public int CountryID
{
get;set;
}
public string CountryDescription
{
get;set;
}
}
My view model has a Countries property, that is simply a list of Country objects (binding shown above). The view has a text box for user name, and a combo box to show the country description. My view model has a "User" property for the view to bind to. The binding for the user name is :
<TextBox x:Name="NameBox" Text=" {Binding User.Username, Mode=TwoWay}"
DataContext="{Binding}" />
The problem I have is the binding for the selecteditem for the country combo. Let's say I have 2 country objects in the combo as follows:
CountryID = 1, CountryDescription = "France"
CountryID = 2, CountryDescription = "Spain"
A user is set up as:
UserID = 1, Username = "Bob", CountryID = 1.
The combo needs to show "France". But if the user changes France to Spain, the user's CountryID needs to change to 2.

private Country _SelectedCountry;
public Country SelectedCountry
{
get
{
return _SelectedCountry;
}
set
{
if(value != null && value != _SelectedCountry)
{
if(User.CountryID != value.CountryID)
User.CountryID = value.CountryID;
_SelectedCountry = value;
}
}
}
Don't Forget to place RaisePropertyChanged in all required places.
If you have any problems understanding this then let me know.

Basically you needed a ValueMemberPath which does exist on the combo box, presumably as they thought it was not needed.
Instead each item in the combox binds to a Country item, so what you really need is a value converter that transforms a Country object to an ID number/int (and back again if you need two way, otherwise the code below will do the job).
e.g. something like:
public class CountryConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Country country = (Country)value;
return country.CountryId;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
/// Not sure yet the best way to get the correct Country object instance, unless you can attach it to the convertor, pass it as a ConvertorParameter or just make it global. :)
}
}
The problem with two-way is that to convert back from a number to the correct instance of a Country object, you need access to the actual list of Country objects used by the control.
Anyway: You then simply bind the SelectedItem of the ComboBox to the User.CountryID property (also specifying the converter of course).
<TextBox x:Name="NameBox" SelectedItem={Binding User.CountryID, Convertor={StaticResource CountryConverter}} Text="{Binding User.Username, Mode=TwoWay}" DataContext="{Binding}" />
And declare the converter in the page resources like:
<UserControl.Resources>
<local:CountryConverter x:Key="CountryConverter" />
</UserControl.Resources>

Related

How do I bind ComboBox with WPF and get SelectedItem?

I am sorry I am asking question on stack over flow first time so might be some thing i done wrong.
I have started learning WPF MVVM and I'm stuck.
I have one parameter master table in a database, and it has the columns parameterid, parametername, parametertype. It is populated with master data, e.g., country master has parametertype "country", city has "city" parametertype, but parameterid will be unique.
Now I have a customer page, where in the customerviewmodel object I have countryId and CityId parameters to be saved inside the customer object.
How can I bind this parameterId directly with customer.CountryId from XAML? Or is that not possible?
Currently I have achieved it as follows: defined one CountrySelectedItem Property and bound it with combobox SelectedItem. When its property changes, when anyone changes its value then in viewmodel I set the Customer's CountryId in CountrySelectedItem set property.
How do I validate the Customer's countryID property? In the combo box, I have bound the Parameter Master's object so can't write data Annotation's Required attribute to the Parameter Master Entity.
Here are the complete Scenario
public class Customer
{
public int CustomerId { get; set; }
[Required]
public string CustomerName { get; set; }
[Required]
public int CountryId { get; set; }
}
<telerik:RadComboBox x:Name="cmCountryId" Grid.Row="3" Grid.Column="1" HorizontalAlignment="Left"
ItemsSource="{Binding LstCountry}"
HorizontalContentAlignment="Left" VerticalAlignment="Center"
DisplayMemberPath="ParameterName"
SelectedItem="{Binding SelectedCountry,Mode=TwoWay}" Height="25" Width="200" >
</telerik:RadComboBox>
/* To Fill DropDown Country DropDown From ParameterMaster*/
public List<ParameterEntity> LstCountry { get; set; }
LstContainerType = new List<ParameterEntity>(parameterService.GetParamterTypeDetail("COUNTRY").ToList());
/*
Parameter Master has ParameterId,ParameterName,ParameterType -- All Master Data Stored Inside It with Different ParameterType.
*/
/* When User Select CountrId then I Set It's ParameterID As CountryID In My CustomerEntity */
public ParameterEntity SelectedCountry
{
get
{
return _selectedCountry;
}
set
{
_selectedCountry = value;
RaisePropertyChanged("SelectedCountry");
SelectedCustomerEntity.CountryId = _selectedCountry != null ? _selectedCountry.ParameterId : 0;
}
}
So Here Is my question for above Scneraio.
1) To Bind Customer Object's CountryId Property From CountryDropDown is that any other option available then this one.
SelectedCustomerEntity.CountryId = _selectedCountry != null ? _selectedCountry.ParameterId : 0;
// please have a look SelectedCountry property.
Something like , I do not need to write Selected Property and i Can Directly Set ParameterId To CountryId Of CustomerEntity From XAML
Or this one is right scneario.
2) Another Question Is How To do Validation On CountryId ComboBox.
i mean As Mention in CustomerEntity has Required CountryId But In XAMl Design of SelectedItem="{Binding SelectedCountry,Mode=TwoWay}"
What should i write to display if user has not select any country from dropdown. Should I write a logic on Save Button manully ?
I'll start by making an assumption that your creating a form that will have a submit button on it that will save the selected data in some manor.
For the combobox, you can use the SelectedValuePath to select a field from the type you are binding as your selections.
<ComboBox SelectedValue="{Binding SelectedCountry}"
ItemsSource="{Binding Countries}"
SelectedValuePath="ParameterId" />
Then your SelectedCountry property in your view model will be set to the ParameterId.
For the second question of validation as well as how to bind to the Customer.CountryIdin your submit function on your model you would first check if SelectedCountry is null or empty then error out showing the validation error message. Otherwise set the Customer.CountryId = SelectedCountry and save the customer.

comboboxedit binding selected value to field with another type of objects in itemssource

I am using wpf and MVVM pattern. I need to bind my comboboxedit ItemsSource to collection of User class. It contains Employee field, which contains string FullName field. I need to bind selected FullName value to string Field of another object in my ViewModel (Document->UserFullName). How can I do this.
If I understand you correctly, you have a ComboBox bound to a List of User-instances. The User class has a property of type Employee and the Employee class has a property called FullName of type string. The viewmodel also has a property of type Document and the Document class has a property called UserFullName of type string. When you select a value (a user) in the ComboBox you want to set the value of FullName (User.Employee.FullName) to the UserFullName property of Document (Document.UserFullName).
Correct?
If that is the only thing you want to do, maybe the easisest solution would be to not bind the ComboBox to a collection of User-instances but to a collection of strings that is the FullName of those users (from Employee). That collection wouldn't be to hard to create just by iterating through your list of users. If you bind the ComboBox to a collection of strings then you should be able to just bind the SelectedValue of the ComboBox directly to the UserFullName of the Document (Document.UserFullName).
Another solution would be to have a property "SelectedUser" of type User in your viewmodel and bind the SelectedValue of the ComboBox to this. Whenever the value of this change you also set the value of Document.UserFullName, like this:
private User _selectedUser;
public User SelectedUser
{
get
{
_return _selectedUser;
}
set
{
if (value != _selectedUser)
{
_selectedUser = value;
Document.UserFullName = _selectedUser.Employee.FullName;
OnPropertyChanged("SelectedUser");
}
}
}

Databinding with kind of join over two lists

I am familiar with the basics of databinding in wpf. However I now have a problem which I wonder how to solve.
Imagine following use case:
I have a global ObservableCollection called "AItems" of Type A.
I have some Objects of Type B and each has a ObservableCollection "BItems" of type A.
The BItems Collections can contain Objects of the global AItems Collection.
I want to visualize this by a ListView.
Each line should contain an A-Object and a checkbox.
I want the ListView to show all elements of the AItems-Collection. Items which are assigned to the B-Object should be marked with a checked checkbox. All other checkboxes should be unchecked.
My questions are now:
How should I set the datacontext?
How can I make that checking a checkbox inserts its item to the BItems-Collection and unchecking removes it?
I hope anyone can understand my problem.
Thanks for replies.
I'm not clear on the latter part of your question. Partly it's because your naming convention is confusing; I'd expect a collection named BItems to contain objects of type B, not A.
So I'm going to change your nomenclature a bit so that I don't get confused. Instead of A, I'll call the first class User, and instead of B, I'll call the second class Group. A Group contains a collection of User objects, named Users. The global collections look like this:
List<User> Users;
List<Group> Groups;
It's easy to determine if a given User u is in any group:
return Groups.Where(g => g.Users.Contains(u)).Any();
Easy, but computationally expensive if you have many groups and they contain many users. We'll get back to that in a second.
Right away, I see that one of your questions has got a problem:
How can I make that checking a checkbox inserts its item to the BItems-Collection and unchecking removes it?
What should happen if I check an unchecked user? Which group (or groups, since more than one group can contain a user) should it be added to?
Since you say that you want checked items to be "assigned to the B-Object", I'm going to assume that the UI is only looking at one group at a time - we'll call it the SelectedGroup. This is good, because g.Users.Contains(u) is much less expensive than the query I showed above.
If this is so, what you need to do is wrap your User in a class that exposes an IsChecked property. I'd call this class UserViewModel, since that's what it is. The class needs three properties (at a minimum):
public User User { get; set; }
public Group SelectedGroup { get; set; }
public bool IsChecked
{
get { return SelectedGroup.Users.Contains(this.User); }
set
{
if (value != IsChecked)
{
if (IsChecked)
{
SelectedGroup.Users.Remove(this.User);
}
else
{
SelectedGroup.Users.Add(this.User);
}
}
}
}
Your ListView is bound to an ObservableCollection<UserViewModel> named, say, UserViewModels. Whenever SelectedGroup is set, you need to rebuild this collection:
UserViewModels = new ObservableCollection<UserViewModel>(
Users.Select(u => new UserViewModel { User=u, SelectedGroup=SelectedGroup }));
You could avoid rebuilding the collection by implementing INotifyPropertyChanged in the UserViewModel class, and having it raise PropertyChanged for the IsChecked property whenever SelectedGroup changes.
Also, it would probably be responsible to include null-reference checking in the IsChecked property, so that the program doesn't throw an exception if SelectedGroup or SelectedGroup.Users is null.
You can bind a list box to Aitems and using a converter to set the isChecked property
<ListBox ItemsSource="{Binding AItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding}" IsChecked="{Binding Mode=OneTime, Converter={StaticResource BItemCheckConverter}}"></CheckBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
public class BItemCheckConverter : IValueConverter
{
public List<Aitems> BItems { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (BItems.Contains((value as Aitems)) return true;
else return false
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
I don't know how's your object model, so take the above code as is.
HTH

Difference between SelectedItem, SelectedValue and SelectedValuePath

What is the difference betweeen the following:
SelectedItem
SelectedValue
SelectedValuePath
All these dependency properties are defined in Selector class. I often confuse SelectedItem with SelectedValue , and SelectedValue with SelectedValuePath.
I would like to know the difference between them, and also when do we use them, especially SelectedValue and SelectedValuePath. Please explain their use with some simple examples.
Their names can be a bit confusing :). Here's a summary:
The SelectedItem property returns the entire object that your list is bound to. So say you've bound a list to a collection of Category objects (with each Category object having Name and ID properties). eg. ObservableCollection<Category>. The SelectedItem property will return you the currently selected Category object. For binding purposes however, this is not always what you want, as this only enables you to bind an entire Category object to the property that the list is bound to, not the value of a single property on that Category object (such as its ID property).
Therefore we have the SelectedValuePath property and the SelectedValue property as an alternative means of binding (you use them in conjunction with one another). Let's say you have a Product object, that your view is bound to (with properties for things like ProductName, Weight, etc). Let's also say you have a CategoryID property on that Product object, and you want the user to be able to select a category for the product from a list of categories. You need the ID property of the Category object to be assigned to the CategoryID property on the Product object. This is where the SelectedValuePath and the SelectedValue properties come in. You specify that the ID property on the Category object should be assigned to the property on the Product object that the list is bound to using SelectedValuePath='ID', and then bind the SelectedValue property to the property on the DataContext (ie. the Product).
The example below demonstrates this. We have a ComboBox bound to a list of Categories (via ItemsSource). We're binding the CategoryID property on the Product as the selected value (using the SelectedValue property). We're relating this to the Category's ID property via the SelectedValuePath property. And we're saying only display the Name property in the ComboBox, with the DisplayMemberPath property).
<ComboBox ItemsSource="{Binding Categories}"
SelectedValue="{Binding CategoryID, Mode=TwoWay}"
SelectedValuePath="ID"
DisplayMemberPath="Name" />
public class Category
{
public int ID { get; set; }
public string Name { get; set; }
}
public class Product
{
public int CategoryID { get; set; }
}
It's a little confusing initially, but hopefully this makes it a bit clearer... :)
Chris
To answer a little more conceptually:
SelectedValuePath defines which property (by its name) of the objects bound to the ListBox's ItemsSource will be used as the item's SelectedValue.
For example, if your ListBox is bound to a collection of Person objects, each of which has Name, Age, and Gender properties, SelectedValuePath=Name will cause the value of the selected Person's Name property to be returned in SelectedValue.
Note that if you override the ListBox's ControlTemplate (or apply a Style) that specifies what property should display, SelectedValuePath cannot be used.
SelectedItem, meanwhile, returns the entire Person object currently selected.
(Here's a further example from MSDN, using TreeView)
Update: As #Joe pointed out, the DisplayMemberPath property is unrelated to the Selected* properties. Its proper description follows:
Note that these values are distinct from DisplayMemberPath (which is defined on ItemsControl, not Selector), but that property has similar behavior to SelectedValuePath: in the absence of a style/template, it identifies which property of the object bound to item should be used as its string representation.
SelectedItem and SelectedValue are an object.
and SelectedValuePath is a string.
for example using the ListBox:
Below listbox1.SelectedValue becomes a string value.
string value = listbox1.SelectedValue;
if you say give me listbox1.SelectedItem it will give you the entire object.
ListItem item = listbox1.SelectedItem;
string value = item.value;
inspired by this question I have written a blog along with the code snippet here. Below are some of the excerpts from the blog
SelectedItem – Selected Item helps to bind the actual value from the DataSource which will be displayed. This is of type object and we can bind any type derived from object type with this property. Since we will be using the MVVM binding for our combo boxes in that case this is the property which we can use to notify VM that item has been selected.
SelectedValue and SelectedValuePath – These are the two most confusing and misinterpreted properties for combobox. But these properties come to rescue when we want to bind our combobox with the value from already created object. Please check my last scenario in the following list to get a brief idea about the properties.
Every control that uses Collections to store data have SelectedValue, SelectedItem property. Examples of these controls are ListBox, Dropdown, RadioButtonList, CheckBoxList.
To be more specific if you literally want to retrieve Text of Selected Item then you can write:
ListBox1.SelectedItem.Text;
Your ListBox1 can also return Text using SelectedValue property if value has set to that before. But above is more effective way to get text.
Now, the value is something that is not visible to user but it is used mostly to store in database. We don't insert Text of ListBox1, however we can insert it also, but we used to insert value of selected item. To get value we can use
ListBox1.SelectedValue
Source

2 (or more) ComboBoxes dependent on each other

EDIT: The issue underneath is fixed, GO TO EDIT2 in this post.
I have an Organisation entity and a Region entity. An object of type Organisation can have one or more Region objects connected to it, thus I have a foreign key in my Region entity to the Organisation Entity. The Organisation and Region objects are pulled from my database using WCF RIA and entity framework. I want to put the Organisation objects in one ComboBox and the Region objects in another ComboBox, and when selecting an organsation having the ComboBox for Region objects automatically only showing regions that are connected to the selected organisation. Should be pretty basic, but the way I've designed it right now it doesnt work at all.
So, any hint to how I can achive this? A simple simple codeexample is much appreciated!
(I'm using SL4,WCF RIA MVVM)
EDIT2 EDIT2 EDIT2 EDIT2 EDIT2 EDIT2 EDIT2 EDIT2 EDIT2 EDIT2 EDIT2 EDIT2:
Using Venomo's ElemntBinding answer this is working great for me now when I want to add a new object to my collection, and I'm just pulling the avaible countries and connected regions and then type a site in a textbox...So I get my combination of Organisation, region and site in my database :)
Now, I've got a new problem when I want to EDIT a site in my collection. In EDIT mode, I want the two dropdowns to be preselected and disabled (BusinessRule is that I can edit the sitename, not which organisation og region it's connected to). So by setting the SelectedIndex property on Organisation combobox I get my organisation selected, but when doing the same on the Regions combobox it fails with an Object Reference error.
You can achieve this with some clever ElementBindings.
Basic example:
Let's say we have a simple class like this:
public class Country
{
public string Name { get; set; }
public IEnumerable<string> Regions { get; set; }
}
Then, we'll have two ComboBoxes: one for choosing a country and another for choosing a region in that country. The second one should update itself when the value of the first one changes.
Okay, first we have to tell Silverlight how to display a Country. For complex scenarios we could use a DataTemplate for that, but for now, we will only need the DisplayMemberPath property of the ComboBox class.
<ComboBox x:Name="cbCountries" DisplayMemberPath="Name"/>
So, we create a simple collection of these objects in the code behind:
var countries = new List<Country>()
{
new Country
{
Name = "USA",
Regions = new List<string>
{
"Texas", "New York", "Florida", ...
},
},
new Country
{
Name = "UK",
Regions = new List<string>
{
"Scotland", "Wales", "England", ...
},
},
...
};
I know that those are not all of the regions in the example countries, but this is a Silverlight example and not a geographical lesson.
Now, we have to set the ItemsSource of the ComboBox to this collection.
cbCountries.ItemsSource = countries;
Both of these can be in the constructor in the code-behind.
Okay, now back to XAML!
We'll need another ComboBox and a way for telling it to get its items from the other collection dynamically.
Binding its ItemsSource to the other ComboBox's selected item is just the most obvious way to achieve that.
<ComboBox x:Name="cbRegion" ItemsSource="{Binding ElementName=cbCountries, Path=SelectedItem.Regions}"/>
This should do the trick quite simply.
If you use MVVM:
You can bind to the ItemsSource of the first ComboBox from the ViewModel. The rest remains the same.
To tell what the selected values are to the ViewModel, use Two-way bindings on the SelectedItem property of both ComboBoxes and bind that to whatever property you have for it in the ViewModel.
If your collection can change dynamically:
If the list of the countries (or whatever it is that you want to use this for) can change during runtime, it is best if you implement INotifyPropertyChanged for the Country class and for the regions, use ObservableCollection<>.
If it doesn't need to change during runtime, no need to bother with these.
View Model:
public ObservableCollection<Organisation> Organisations { get; set; }
private Organisation selectedOrganisation;
public Organisation SelectedOrganisation
{
get { return this.selectedOrganisation; }
set
{
if (this.selectedOrganisation != value)
{
this.selectedOrganisation = value;
this.NotifyPropertyChanged("SelectedOrganisation");
this.UpdateRegions();
}
}
private IEnumerable<Region> regions;
public IEnumerable<Region> Regions
{
get { return this.regions; }
set
{
if (this.regions != value)
{
this.regions = value;
this.NotifyPropertyChanged("Regions");
}
}
}
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
private void UpdateRegions()
{
// If regions are not pre-populated you might need to perform a new call to
// the service that retrieves organisations in order to retrieve the associated Regions entities
// for the SelectedOrganisation organisation
this.Regions = this.SelectedOrganisation.Regions;
}
In your View:
<ComboBox x:Name="Organisations" ItemsSource="{Binding Organisations}" SelectedItem="{Binding SelectedOrganisation, Mode=TwoWay}" />
<ComboBox x:Name="Regions" ItemsSource="{Binding Regions}" />

Resources