How do I bind ComboBox with WPF and get SelectedItem? - wpf

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.

Related

Avoid Two way binding between objects in C#

When i assign a value of a class object to other class object and when i change a property of 1st object it reflects in other object.
Example:
class A has property of type int Aid
SelectedA is a property of a Viewmodel Class which is binded to A WPF View.
SelectedA.Aid is in TwoWay binding with a combo box.
i have created another object objectA and assigned objectA=SelectedA.
Problem when i change combobox value value of objectA.Aid is also changed.
Thanks in advanced i need to avoid binding of objectA with SelectedA.
Vehicle dbvalue
private Vehicle _selectedA;
public Vehicle SelectedA
{
get { return _selectedA; }
set
{
_selectedA = value;
RaisePropertyChanged("SelectedA");
}
}
public partial class A
{
public int AID { get; set; }
public string AName { get; set; }
}
<ComboBox
DisplayMemberPath="AName"
ItemsSource="{Binding items}"
SelectedValue="{Binding SelectedA.AID}"
SelectedValuePath="AName "/>
In viewmodel Class
i have used
dbvalue = SelectedA;
When i change combobox value dbvalue.AID is also changed.
You can set the behaviour of your SelectedValue binding to only update when the source changes.
SelectedValue="{Binding SelectedA.AID, Mode=OneWay}"
These are the other possible values for the Binding Mode lifted from the MSDN site:
TwoWay updates the target property or the property whenever either the target property or the source property changes.
OneWay updates the target property only when the source property changes.
OneTime updates the target property only when the application starts or when the DataContext undergoes a change.
OneWayToSource updates the source property when the target property changes.
Default causes the default Mode value of target property to be used.
Original MSDN article
To prevent of this problem, you can set it each property of object as follows:
objectA.AId = SelectedA.AId
objectA.AName = SelectedA.AName
Good Luck

WPF: SelectedItem Binding Not Showing With DisplayMemberPath

I've got a ComboBox (inside a ListView) that used to be tied to a collection of strings. However, I'm switching to having it use a custom class instead.
Now the ComboBox is bound to an ObservableCollection of type Area. For display, I'm showing the Name property with DisplayMemberPath. This works great, but the box is no longer loading with the current value selected.
<ListView x:Name="TestListView" ItemsSource="{Binding TestViewList, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ListView.View>
<GridView>
<GridViewColumn Header="Area">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding DataContext.AreaList, ElementName=BVTWindow}" DisplayMemberPath="Name" SelectedItem="{Binding Path=Area, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
public ObservableCollection<ViewTest> TestViewList { get; private set; }
public ObservableCollection<Area> AreaList { get; private set; }
public class ViewTest : BindableBase
{
public string Description { get; set; }
public Area Area { get; set; }
}
public partial class Area
{
public long ID { get; set; }
public string Name { get; set; }
}
"Area" is the same class as the Collection, and is a property of the class the ListView's ItemsSource is bound to. Am I doing this the right way? Any help is much appreciated.
UPDATE:
I had a theory that perhaps using the "Name" property (which is a string) for display in the box made the SelectedItem attribute look for a string rather than something of type Area. I changed the class for TestViewList to use a string to keep track of Area, but that didn't change the program's behavior at all.
UPDATE 2:
Above, I've added the pertinent lines from the view model related to the ComboBox and ListView in question.
Update 3:
I've changed my Area property from inline to expanded, including the SetProperty that handles raising. It now works for new items added to the ItemSource at run-time, but is still a blank selection for on items loaded at program start.
public class ViewTest : BindableBase
{
public string Description { get; set; }
public Area Area
{
get
{
return this.area;
}
set
{
this.SetProperty(ref this.area, value);
}
}
}
Update 4:
It turns out Paul Gibson's answer was correct. The reason my existing entries weren't loading properly had to do with a logic error in the way I was loading items from the database, and not a problem with the xaml bindings. Everything works now (with respect to those ComboBoxes, at least).
Based on what you are saying I think my comment is the answer. This is the case for a single combobox. In your case you need one for every line of the grid, so you may have to programatically add the binding to members of a list by index.
In your view model if you also have (single case):
private Area _curSelArea;
public Area curSelArea
{
get { return _curSelArea; }
set
{
_curSelArea = value;
RaisePropertyChanged("curSelArea");
}
}
Then you can bind to the property with:
SelectedItem="{Binding DataContext.curSelArea . . . }"
The view model can set the initial value of curSelArea if it is known initially.
EDIT: After actually having to do this I found that someone extended the DataGridComboBoxColumn to facilitate better binding. Check out this link if you are trying to do this: http://joemorrison.org/blog/2009/02/17/excedrin-headache-35401281-using-combo-boxes-with-the-wpf-datagrid/

Binding to a combo box in 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>

Binding combobox in WPF datagrid with parameters

I have a DataGrid with a combobox inside a template column. Elsewhere on this screen, the user makes a 'customer' selection from a separate control altogether. In order to populate the comboboxes in my datagrid, I need to pass in that selected customer as a parameter, in addition to other information from each row in the grid.
Basically... the grid contains part information, and the combobox items are based on a combination of the following: selected customer, part number, and manufacturer. Each row's combobox can potentially have a different source list. Is there a way I can bind the ItemsSource for that combobox in XAML?
I may not understand correctly, but you could possibly have an object that contains all that information together, and bind that to the combo box.
ie
public class ContextualInfo
{
public Customer Customer { get; set; }
public int PartNumber { get; set; }
public Manufacturer Manufacturer { get; set; }
}
In reply to comment.
How about having the rows returned from the query to also be in the ContextualInfo mentioned above? You can then bind the itemsource to that. You could potentially run the query in the constructor for the ContextualInfo class.

TreeViewItem.ItemContainerGenerator.ContainerFromItem inconsistent results

I have a TreeView on my page. It's bound to a collection of clients containing contracts, like:
public class Client
{
public int ClientID { get; set; }
public string Name { get; set; }
public List<Contract> Contracts { get; set; }
}
public class Contract
{
public int ContractID { get; set; }
public int ClientID { get; set; }
public string Name { get; set; }
}
The XAML for my TreeView is as follows:
<sdk:TreeView x:Name="tvClientContract" ItemsSource="{Binding ClientContracts}">
<sdk:TreeView.ItemTemplate>
<sdk:HierarchicalDataTemplate ItemsSource="{Binding Path=Contracts}">
<TextBlock Text="{Binding Path=Name}" />
</sdk:HierarchicalDataTemplate>
</sdk:TreeView.ItemTemplate>
</sdk:TreeView>
Where ClientContracts is a List<Clients>. The binding works fine and I have a hierarchical grid.
The issue that I'm having is when opening the form with the TreeView on it I want to select the current Client, I currently use the following code:
TreeViewItem client = (TreeViewItem)tvClientContract.ItemContainerGenerator.ContainerFromItem(aClient);
or
TreeViewItem client = (TreeViewItem)tvClientContract.ItemContainerGenerator.ContainerFromIndex(tvClientContract.Items.IndexOf(aClient));
client.IsSelected = true;
but this returns inconsistent results, for example I open the form when client 'ABC' is selected and client will be null. I open it again when client 'ABC' is selected and it returns the correct TreeViewItem. Has anyone came across this before or know anything I can look at to help solve the issue?
I run the above code within the Loaded event of the TreeView.
I figured out what's happening here, the clue is in the MSDN documentation for the return value of ItemContainerGenerator.ContainerFromItem():
A UIElement that corresponds to the
given item. Returns null if the item
does not belong to the item
collection, or if a UIElement has not
been generated for it.
It looks like when null is returned, a UIElement hasn't been created for the item yet.
I got round this by using
tvClientContract.UpdateLayout();
to update the layout and ensure a UIElement exists before calling
ItemContainerGenerator.ContainerFromItem()
I think that there could be some condition where "UpdateLoayout will not work":
if the TreeView is in recycling mode and the item is not in the visible portion and/or also in a "add" operation where the TreeViewItem is created on another thread.
The solution is to use similar solution as I describe in:
WPF: Select TreeViewItem broken past the root level

Resources