First some background, which can be condensed into editing a property VatCode of VatCodeViewModel.
I have a StockItem with two particular properties in the ViewModel:
public class StockItemViewModel : ViewModelBase
{
private VatCodeViewModel _vatCode;
public VatCodeViewModel VatCode
{
get { return _vatCode; }
set
{
if (_vatCode != value)
{
_vatCode = value;
RaisePropertyChanged("VatCode");
}
}
}
}
The VatCode property accepts a VatCodeViewModel type.
To manage the editing experience, I have a ViewModel called EditStockItemViewModel. This has meta-data such as IsDirty, IsNew, etc., but has the Item property set to the item being edited – in this case an instance of StockItemViewModel. The Item property is in the base class (of TViewModel == StockItemViewModel) …
public class UnMappedEditableViewModelBase<TViewModel> : ViewModelBase
{
private TViewModel _item;
public TViewModel Item
{
get { return _item; }
set
{
if (_item != value)
{
_item = value;
RaisePropertyChanged("Item");
}
}
}
}
and the implementation class (EditStockItemViewModel, which has Item of StockItemViewModel) …
public class EditStockItemViewModel : UnMappedEditableViewModelBase<StockItemViewModel>
{
private ObservableCollection<VatCodeViewModel> _vatCodes=new ObservableCollection<VatCodeViewModel>();
public ObservableCollection<VatCodeViewModel> VatCodes
{
get { return _vatCodes; }
set
{
if (_vatCodes != value)
{
_vatCodes = value;
RaisePropertyChanged("VatCodes");
}
}
}
public EditStockItemViewModel()
:base()
{
if (IsInDesignMode)
{
}
else
{
RefreshVatCodesList(null); // refreshes VatCodes property
Save = new RelayCommand(() =>
{
// save functionality snipped
}, () =>
{
bool canExecute = Item.VatCode!=null; // this is ALWAYS null - binding failing
return canExecute;
});
}
}
}
Therefore the ViewModel.Item property is always the item being edited.
A fragment of my view …
<TextBlock Text="VAT Code:" Grid.Column="1" Grid.Row="3" Style="{StaticResource ComboHeaderTextBlock}" />
<telerik:RadComboBox Grid.Column="2" Grid.Row="3" Style="{StaticResource RadComboBox}" Width="300" HorizontalAlignment="Left"
ItemsSource="{Binding VatCodes}" SelectedValuePath="Item.VatCode"
ClearSelectionButtonVisibility="Collapsed"
CanAutocompleteSelectItems="True"
CanKeyboardNavigationSelectItems="True"
IsEditable="False"
OpenDropDownOnFocus="False"
IsFilteringEnabled="False"
EmptyText="Select ...">
<telerik:RadComboBox.SelectedValue>
<Binding Path="Item.VatCode" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay" RelativeSource="{RelativeSource FindAncestor,AncestorType={x:Type UserControl}}" >
<Binding.ValidationRules>
<DataErrorValidationRule />
</Binding.ValidationRules>
</Binding>
</telerik:RadComboBox.SelectedValue>
<telerik:RadComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Key}" Style="{StaticResource TextBlock}" />
<TextBlock Text="{Binding Name}" Style="{StaticResource DimTextBlock}" />
<TextBlock Text="{Binding ActiveRate.Rate}" Margin="5 5 0 5" />
<TextBlock Text="%" Margin="0 5 5 5" />
</StackPanel>
</DataTemplate>
</telerik:RadComboBox.ItemTemplate>
</telerik:RadComboBox>
So at the end of all this, I have:
VatCode that binds to [ViewModel].Item.VatCode and uses [ViewModel].VatCodes as source.
The list is populated and appears fine. I know that the ViewModel is binding correctly.
The problem is the VatCode is NOT binding to the Item.VatCode property. So when I get to the Save method, the Item.VatCode property is null (ie. not working).
I am getting the following binding error which appears to be related:
System.Windows.Data Error: 17 : Cannot get ‘Item’ value (type
‘String’) from ” (type ‘VatCodeViewModel’).
BindingExpression:Path=Item.VatCode; DataItem=’VatCodeViewModel’
(HashCode=27875274); target element is ‘RadComboBox’ (Name=”); target
property is ‘NoTarget’ (type ‘Object’)
TargetParameterCountException:’System.Reflection.TargetParameterCountException:
Parameter count mismatch.
Clearly the error indicates my binding expression Item.VatCode is suspect, but I’m not sure how to correct it.
Found it, I was misled by the example provided in the documentation. I should not have used the path
SelectedValuePath. So my new code is: