MVVM WPF - ComboBox two way binding inside ItemsControl - wpf

I am working on this problem for about a day now.
For some reason I am unable to TwoWay bind a value to a ComboBox if it is inside a ItemsControl. Outside works just fine.
I have an ObservableCollection of int? in my ViewModel:
private ObservableCollection<int?> _sorterExitsSettings = new ObservableCollection<int?>();
public ObservableCollection<int?> SorterExitsSettings
{
get { return _sorterExitsSettings; }
set
{
if (_sorterExitsSettings != value)
{
_sorterExitsSettings = value;
RaisePropertyChanged("SorterExitsSettings");
}
}
}
My XAML:
<ItemsControl ItemsSource="{Binding SorterExitsSettings}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=ItemsControl}, Path=DataContext.ScanRouter.Stores}"
SelectedValue="{Binding Path=., Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="name" SelectedValuePath="id" IsEditable="True" />
</DataTemplate>
</ItemsControl.ItemTemplate>
So the ComboBox is populated with a list of stores. It works fine so far.
The ObservableCollection SorterExitsSettings even has some values set which are shown in the displayed ComboBoxes. So setting the SelectedValue also works.
However when I change a selection, SorterExitsSettings wont change. While when I implement the ComboBoxes(100) without an ItemsControl it suddenly works fine.
<ComboBox ItemsSource="{Binding ScanRouter.Stores}" DisplayMemberPath="name" SelectedValuePath="id" IsEditable="True" SelectedValue="{Binding SorterExitsSettings[0], Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
Even better when I implement the ComboBoxes using the ItemsControl and also the example ComboBox shown above. When I change the single ComboBox's value it will change the value of the ComboBox inside the ItemsControl, but not the other way around.
Did somebody encounter this problem before?
My guess was that the ItemsControl doesn't like the fact that I am binding my selected value to an item in a list. However when I bind directly to a ViewModel property(Store) it also doesn't work.
I also tried using SelctedItem instead of SelectedValue and populate the ObservableCollection with Store objects instead of int?.

The problem is that you're binding your ComboBox's SelectedValue directly to the collection elements which are type int ?. This won't work, binding targets have to be properties. Try wrapping your int ? values in a class and expose the value as a property of that class with a getter and setter, i.e. something like this:
private ObservableCollection<Wrapper> _sorterExitsSettings = new ObservableCollection<Wrapper>();
... etc...
And:
public class Wrapper
{
public int? Value {get; set;}
}
And finally:
<ComboBox ... SelectedValue="{Binding Path=Value, Mode=TwoWay...
Post back here if you still have problems.

Related

WPF cascading ComboBoxes not binding when window loads

I'm using WPF and MVVM, and have a support ticket window that has cascading ComboBoxes as follows. The first is bound to an ObservableCollection<ProblemCode> on the view model. The ProblemCode objects have a self-referencing property to their child codes, down to a level of four codes. The XAML for the ComboBoxes looks like this (simplified, and only three shown for brevity)...
<ComboBox ItemsSource="{Binding ElementName=Root, Path=DataContext.ProblemCodes, Mode=TwoWay}"
Name="ProblemCodeLevel1"
DisplayMemberPath="Description"
SelectedValuePath="ID"
SelectedValue="{Binding ProblemCode1ID, Mode=TwoWay}" />
<ComboBox ItemsSource="{Binding ElementName=ProblemCodeLevel1, Path=SelectedItem.Children}"
Name="ProblemCodeLevel2"
DisplayMemberPath="Description"
SelectedValuePath="ID"
SelectedValue="{Binding ProblemCode2ID, Mode=TwoWay}" />
<ComboBox ItemsSource="{Binding ElementName=ProblemCodeLevel2, Path=SelectedItem.Children}"
Name="ProblemCodeLevel3"
DisplayMemberPath="Description"
SelectedValuePath="ID"
SelectedValue="{Binding ProblemCode3ID, Mode=TwoWay}" />
When I load a window for a new ticket, the first ComboBox is correctly populated. Selecting an item populates the second and so on. When I save the ticket, the data is correctly saved.
However, when I save the ticket and reopen the window, only the first ComboBox has the selected item set. The other ComboBoxes don't have anything set.
I guess that the first ComboBox is set as the data is available when the data binding takes place. At that stage, as the first ComboBox is data bound, the second one doesn't yet have any items, so doesn't get bound. Same for the third and so on.
Anyone any suggestions as to how to get the binding working? I probably could hack this by adding code to catch various events, but apart from breaking the MVVM pattern, it sounds like none of those situations that would end up convoluted and buggy.
Generally speaking you shouldn't bind directly to elements, you should be binding to properties in your view model. That way you know the property notification is being done properly and you can add breakpoints etc to confirm the bindings are all working as well. In this particular case you need to add something like SelectedItem="{Binding Level1Item}" to your first ComboBox and then add a property for it in your view model:
public ProblemCode _Level1Item;
public ProblemCode Level1Item
{
get { return this._Level1Item; }
set
{
if (this._Level1Item != value)
{
this._Level1Item = value;
RaisePropertyChanged(() => this.Level1Item);
}
}
}
Then your second ComboBox binds to this property instead of Element.SelectedItem.Children:
<ComboBox ItemsSource="{Binding Level1Item.Children}"
...etc...
Repeat for the second and third ComboBoxes and you'll have the functionality you're after.

WPF bound combobox using converter

I have a ComboBox that is bound to an EnumerableRowCollection<T> :
ComboFamilyStatus.ItemsSource = EnumerableRowCollection<TaxDataSet.SourcesOfValuesRow> coll;
My xaml lookes like this:
<ComboBox Name="ComboFamilyStatus" DisplayMemberPath="Description"
Text="{Binding FamilyStatus, Converter={StaticResource FamilyStatusStringConverter}}">
I'm using the DisplayMemberPath to show the description of the row. The SourcesOfValuesRow has a value and a description and in the combo I want to see the description text. The Text is bound to the database where the FamilyStatus is saved as an int value this is why I added a converter.
My question is if the converter could convert from the int value to the string using the itemsource from the combobox? I don't see that the converter knows anything about the combo. In the meantime I wrote the converter to take again the EnumerableRowCollection<TaxDataSet.SourcesOfValuesRow> from the database and find there the matched description - this can't be the simplest way to do this!
Any suggestions??
In this case, you're better off using a DataTemplate, instead of a Converter.
You already have a data class. Just use a DataTemplate that inserts a Textblock bound to the int value, then apply your converter there.
<ComboBox>
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:TaxDataSet.SourcesOfValuesRow}">
<TextBlock Text="{Binding FamilyStatus, Converter={StaticResource FamilyStatusStringConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox>
Change your SourcesOfValuesRow FamilyStatusProperty to an enum. Deriving from int lets you cast it directly.
enum FamilyStatusValues : int
{
[Description("Married")]
Married,
[Description("Divorced")]
Divorced,
[Description("Living Together")]
LivingTogether
}
Then in your converter use this code
ConvertTo(object value, ...)
{
FieldInfo field = value.GetType().GetField(value.ToString());
object[] attribs = field.GetCustomAttributes(typeof(DescriptionAttribute), true));
if(attribs.Length > 0)
{
return ((DescriptionAttribute)attribs[0]).Description;
}
return string.Empty;
}
There is no need to use any converter. It worked for me using this:
<ComboBox Name="FamilyStatus" Grid.Row="7" Grid.Column="1" ItemsSource="{Binding Source={StaticResource comboProvider}}"
SelectedValuePath="Value" DisplayMemberPath="Description" SelectedValue="{Binding FamilyStatus}">
Where DisplayMemberPath is the string from the TaxDataSet.SourcesOfValuesRow and SelectedValuePath is the int value. The SelectedValue is the value from the contact table (instead of writing in the combo Text="{Binding FamilyStatus, Converter={StaticResource FamilyStatusStringConverter}}).

Binding TwoWay to SelectedItem: "Wrong way" synchronization on initialization

I am trying to bind a property of my DataContext to the SelectedItem on a ComboBox like this:
<ComboBox x:Name="ElementSelector"
ItemsSource="{Binding Source={StaticResource Elements}}"
DisplayMemberPath="ElementName"
SelectedItem="{Binding ValueElement, Mode=TwoWay}">
where the Elements resource is a CollectionViewSource (don't know, whether this matters).
When everything is initialized, the property ValueElement of the DataContext is set to the first item in the CollectionViewSource. What I want, is to initialize it the other way around: I would like to set SelectedItem of the ComboBox to the value of the property or null if no matching item is contained.
How can this be done?
EDIT - Additional information:
The ComboBox is part of a DataTemplate:
<DataTemplate x:Key="ReferenceTemplate"
DataType="viewModels:ElementMetaReferenceViewModel">
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<ResourceDictionary>
<views:ElementsForReferenceViewSource x:Key="Elements"
Source="{Binding DataContext.CurrentProject.Elements, ElementName=Root}"
ReferenceToFilterFor="{Binding}"/>
</ResourceDictionary>
</StackPanel.Resources>
<TextBlock Text="{Binding PropertyName}"/>
<ComboBox x:Name="ElementSelector"
ItemsSource="{Binding Source={StaticResource Elements}}"
DisplayMemberPath="ElementName"
SelectedItem=""{Binding ValueElement, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
The ElementsForReferenceViewSource simply derives from CollectionViewSource and implements an additional DependencyProperty which is used for filtering.
The DataContext of the items in the CollectionViewSource look like this:
public class ElementMetaReferenceViewModel : ViewModelBase<ElementMetaReference, ElementMetaReferenceContext>
{
...
private ElementMetaViewModel _valueElement;
public ElementMetaViewModel ValueElement
{
get { return _valueElement; }
set
{
if (value == null) return;
_valueElement = value;
Model.TargetElement = value.Model;
}
}
...
}
For people encountering the same issue
The above code works as expected. The solution was getting the stuff behind the scenes right. Make sure, that the instance of the ViewModel which is the value of the property you want to bind to is definitely contained in the CollectionViewSource.
In my case the issue was deserializing an object tree incorrectly, so objects were instantiated twice. Then for each object a distinct ViewModel was initialized and then obviously the value of the property was not contained in the list.
Remark
To check whether this is an issue in your case, you can try the following:
Override the ToString() methods of the ViewModels displayed in the ComboBox like this:
public override string ToString()
{
return "VM"+ Model.GetHashCode().ToString();
}
Then you can easily compare the items in the source collection with the value on your property. Not the most professional way, but it did the job for me.

WPF DataGrid TwoWay Binding

I have property UserSet which contains from ObservableCollection<GridRow>.
GridRow - my class, which contains 4 properties like this:
public int Id
{
get { return id; }
set
{
id = value;
RaisePropertyChanged("Id");
}
}
I populate UserSet, then Grid binds to it. When I change id field works setter Id. It sets right values.
But, after all changes, when I click other button my UserSet has not modified values. So I can't get updated Grid.
This is my XAML:
<DataGrid ItemsSource="{Binding UsersSet, Mode=TwoWay}" AutoGenerateColumns="True">
</DataGrid>
Please help.
You could try and set the UpdateSourceTrigger:
<DataGrid ItemsSource="{Binding UsersSet,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
AutoGenerateColumns="True">
</DataGrid>
Without knowing the rest of your code it is pretty hard to guess.

Bbinding combobox within dataform to view model property outside dataform's context

I have two properties in my view model:
//Relationship has property ReasonForEndingId
private Relationship editRelationship;
public Relationship EditRelationship
{
get
{
return editRelationship;
}
set
{
if (editRelationship != value)
{
editRelationship = value;
RaisePropertyChanged(EditRelationshipChangedEventArgs);
}
}
}
//ReasonForLeaving has properties Reason & Id
private IList<ReasonForLeaving> reasonsComboList { get; set; }
public IList<ReasonForLeaving> ReasonsComboList
{
get
{
return reasonsComboList;
}
private set
{
if (reasonsComboList != value)
{
reasonsComboList = value;
RaisePropertyChanged(ReasonsComboListChangedEventArgs);
}
}
}
In my xaml I have the following: (specifically note the binding on the dataform and combobox)
<toolkit:DataForm x:Name="EditForm" CurrentItem="{Binding EditRelationship, Mode=TwoWay}">
<toolkit:DataForm.EditTemplate>
<DataTemplate>
<StackPanel>
<toolkit:DataField>
<ComboBox x:Name="EndReasonCombo" ItemsSource="{Binding ReasonsComboList}" DisplayMemberPath="Reason" SelectedValuePath="Id" SelectedValue="{Binding ReasonForEndingId, Mode=TwoWay}"/>
</toolkit:DataField>
So, I'm trying to bind to a list that exists in my viewmodel (the datacontext for the page). However, the DataForm's datacontext is EditRelationship. ReasonsComboList does not exist within EditRelationship.
How can I bind the combobox so that it will display the list of items available in ReasonsComboList?
Thanks for your help!
Here's what I did (tested and works):
Within a DataForm this won't work (because its a DataTemplate) :
<ComboBox MinWidth="150" DisplayMemberPath="Name" Name="cbCompanies"
SelectedItem="{Binding TODOCompany,Mode=TwoWay}"
ItemsSource="{Binding ElementName=control, Path=ParentModel.Companies}" />
But you can do this instead:
<ComboBox MinWidth="150" DisplayMemberPath="Name" Name="cbCompanies"
SelectedItem="{Binding TODOCompany,Mode=TwoWay}"
Loaded="cbCompanies_Loaded"/>
Code behind:
private void cbCompanies_Loaded(object sender, RoutedEventArgs e)
{
// set combobox items source from wherever you want
(sender as ComboBox).ItemsSource = ParentModel.Companies;
}
if you set your datacontext using locator in this way
DataContext="{Binding FormName, Source={StaticResource Locator}}"
<ComboBox ItemsSource="{Binding FormName.ReasonsComboList, Source={StaticResource Locator}, Mode=OneWay}"
DisplayMemberPath="Reason" SelectedValuePath="Id"
SelectedValue="{Binding ReasonForEndingId, Mode=TwoWay}"/>
tested and working
I haven't tested this with your exact scenario, but you should be able to reference the DataContext of some parent element when binding the ItemsSource of the ComboBox. Basically using Silverlight's element-to-element binding to actually bind to some property on the parent container's DataContext instead of the current element's DataContext.
For example, if your main ViewModel was the DataContext of the LayoutRoot element you should be able to do something like this:
<ComboBox x:Name="EndReasonCombo" ItemsSource="{Binding DataContext.ReasonsComboList, ElementName=LayoutRoot}" DisplayMemberPath="Reason" SelectedValuePath="Id" SelectedValue="{Binding ReasonForEndingId, Mode=TwoWay}"/>
Creating a Silverlight DataContext Proxy to Simplify Data Binding in Nested Controls
Disclaimer: This may not actually work for the DataForm, but is suitable for the same problem when using a DataGrid. but I'm putting it here as an answer because it was an interesting read and helped me understand some things when I experienced the same problem.

Resources