0
votes

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.

1
it is not clear what goes on in UserControl - its sample code is incompleteASh
@ASh thx, added the code behind too.bas
What is triggering the PropertyChangedCallback?Sotiris Koukios-Panopoulos
The instantiation of the control in the MainWindow : SomeText="My changed text"bas

1 Answers

0
votes

You are using the wrong binding mode for your use case.

When you specify OneWayToSource, you are allowing the data to flow only from your textbox to the property in your ViewModel, as the source is the MyText property.

Try removing the Mode=OneWayToSource, or use TwoWay if you want the text to be updated both from View and ViewModel. (IIRC TwoWay is the default mode for TextBox control).

Also, is your ViewModel implementing the INotifyPropertyChanged Interface to support the bindings?

A small summary that explains the different modes is in this SO answer