I'm trying to embed some functionality into a reusable UserControl in my MVVM style app. The MVVM framework is mostly hand rolled at this point, as the WPF app started life being written as basically a WinForms app.
The UserControl I'm making is just a ComboBox
in the UI, with some data loading done by the ViewModel. All ViewModels in the application are loaded by a ViewModel locator, so I don't need to inject ViewModels into ViewModels, this works everywhere else in the application, so I don't think it's an issue here:
<UserControl DataContext="{Binding Common_PopulationSelector,Source={StaticResource ViewModelLocator}}">
<ComboBox ItemsSource="{Binding Population}" SelectedItem="{Binding SelectedPopulation}" DisplayMemberPath="PopulationName" IsEditable="False"></ComboBox>
</UserControl>
The relevent bits of the ViewModel:
public class PopulationSelectorViewModel : BaseViewModel
{
public override async void OnCreate()
{
//Called by the ViewModelLocator when this isntance is created
//Data loading done here, puts a list of Populations into the Populations collection
//SelectedPopulation gets set to a default value here
}
public ObservableCollection<Population> Populations { get; }
private Population _selectedPopulation;
public Population SelectedPopulation
{
get => _selectedPopulation;
set
{
OnPropertyChanged(ref _selectedPopulation, value);
InvokePropertyChanged(nameof(PopulationId));
}
}
public int PopulationId
{
get => _selectedPopulation?.PopulationID ?? 0;
set
{
SelectedPopulation= Populations.FirstOrDefault(r => r.PopulationID == value) ?? _selectedPopulation;
InvokePropertyChanged(nameof(PopulationId));
}
}
}
OnPropertyChanged
and InvokePropertyChanged
work elsewhere in the application, and just handle dealing with the plumbing around INotifyPropertyChanged
These bits work fine, the data gets loaded, SelectedPopulation changes, along with the PopulationId property.
Where the issue comes in is exposing PopulationId as a XAML property that consumers of the UserControl can bind to. This control gets consumed like this:
<views:PopulationSelector PopulationId="{Binding Path=DataContext.PopulationId, RelativeSource={RelativeSource AncestorType={x:Type local:SearchParameters}}, Mode=TwoWay}" />
SearchParameters is the UserControl containing the selector. To expose PopulationId, I have this in the XAML codebehind:
public partial class PopulationSelector : UserControl
{
public PopulationSelector()
{
InitializeComponent();
this.SetBinding(PopulationIdProperty, new Binding("PopulationId") { Mode = BindingMode.TwoWay });
}
public static readonly DependencyProperty PopulationIdProperty = DependencyProperty.Register(
"PopulationId", typeof(int), typeof(PopulationSelector), new PropertyMetadata(-1));
public int PopulationId
{
get
{
return (int)GetValue(PopulationIdProperty);
}
set
{
var oldValue = (int)GetValue(PopulationIdProperty);
if (oldValue != value) SetValue(PopulationIdProperty, value);
}
}
}
This is apparently where the break occurs. I'm setting the default value of the DependencyProperty here to -1, and that does get changed to 0, so binding from the UserControl consuming this appears to work. But the PopulationId WPF property never appears to update from the ViewModel.
So, in short (This is so long since I was trying to rubber duck while writing this up): I'm trying to bind to a WPF property on a user control that's bound to the ViewModel for that control. Binding to the user control itself appears to work, binding from the view model to the user control does not. How can I get WPF to do what I want?
Edit:
I've made a few changes to my code snippets that bring it up to date with my recent attempts at making this work
- Updated setter in codebehind for the usercontrol, to match the reference I was using, which is this: Twoway-bind view's DependencyProperty to viewmodel's property?
- Updated the binding between XAML property and ViewModel. When posting this, I simplified the code a bit (before I was binding to a property off SelectedPopulation), and the posted code wouldn't work. I've added a setter to PopulationId on the view model so two way binding will properly work
Playing with breakpoints a little this morning, here's what I've established:
- The setter for SelectedPopulation in the ViewModel is being called, so binding between the ComboBox and ViewModel is fine.
- The getter for PopulationId in the ViewModel is never called
- The getter or setter for PopulationId in the UserControl codebehind is never getting called
- The setter for PopulationId in the view consuming this user control is never being called
I also just tried this answer: https://stackoverflow.com/a/21851139/117651 where the binding between the ViewModel and XAML property is done in XAML. The getter in the ViewModel is called, and the PopulationId XAML property shows the expected value in the property explorer while running in the debugger. However, the binding between the consumer and the XAML property doesn't work, and the value in the ViewModel remains 0.
I also have PresentationTraceSources
hooked up to nlog, and there's no complaints in the logs with regards to binding expressions.