I ran into an issue which I posted about before here. I am still struggling with this issue, so I attempted to break it down in a smaller code setup.
The problem:
I have a binding of dependency property to view model, which does not update the viewmodel with it's changed value @construction time.
The binding seems correct, because changing the value in XAML after the application started (relying on xaml hot reload) does update the view model with changes.
I can reproduce the problem with the following setup:
MainWindow:
<Grid>
<local:UserControl1
SomeText="My changed text"
DataContext="{Binding UserControlViewModel}"/>
</Grid>
MainViewModel:
public class MainViewModel
{
public UserControlViewModel UserControlViewModel { get; set; }
public MainViewModel()
{
UserControlViewModel = new UserControlViewModel();
}
}
UserControl:
<UserControl.Resources>
<Style TargetType="local:UserControl1">
<Setter Property="SomeText" Value="{Binding MyText, Mode=OneWayToSource}"></Setter>
</Style>
</UserControl.Resources>
<Grid>
<TextBlock Text="{Binding MyText}"></TextBlock>
</Grid>
UserControl code behind:
public static readonly DependencyProperty SomeTextProperty = DependencyProperty.Register(
nameof(SomeText),
typeof(string),
typeof(UserControl1),
new PropertyMetadata("default text", PropertyChangedCallback));
public string SomeText { get; set; }
private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// first and only update: 'default text' => 'My changed text'
}
public UserControl1()
{
InitializeComponent();
}
UserControl view model:
public class UserControlViewModel
{
// Setter is called with 'default text'
// AFTER the property changed callback is triggered with updated text
public string MyText { get; set; }
}
When I run the app, the text 'default text' is displayed, while I expected 'My changed text'.
When I then change the SomeText property in XAML, then again I see the changed callback fire, and consequently I see the view model setter get updated. This time with the changed value. Thus, the binding seems to work fine, but during startup it fails to update the view model with the (already known) changed value.
Can anybody explain what is causing this issue? Is there a way around this problem?
Update
I just found out, that when I change the XAML (using hot reload) the update sequence is:
- first the viewmodel's setter is set
- then the OnPropertyChanged callback fires.
- the result is that the changed value is displayed on the UI
That's the opposite of what happens at construction time. Then the order is :
- The OnPropertyChanged callback fires
- The view model's setter is set.
- The result is that the default value is displayed on the UI (as described in the original issue)
This is actually really weird. Because when the Property Changed callback fires (during start up) I can cast the DependencyObject
back to my UserControl and check its data context. The datacontext is null at the time.
My previous experiment with hot reload proves that eventually the binding works perfect.
- Thus step 1 is to set the text 'my changed text' to the dependency property.
- Step 2 is to connect the view model as datacontext to MyUserControl
- Step 3 is that the bindings are evaulated and in this case, the binding (onewaytosource) gets it's initial sync, but with the old value.
To me, this looks like a bug in WPF.