0

I create a custom control "CustomAutoCompleteBox" (which inherit of AutoCompleteBox) with one dependency property "CurrentItem".

public static readonly DependencyProperty CurrentItemProperty =
        DependencyProperty.Register("CurrentItem", typeof(CityEntity), typeof(CustomAutoCompleteBox),
            new FrameworkPropertyMetadata(
                null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

    public CityEntity CurrentItem
    {
        get { return (CityEntity)GetValue(CurrentItemProperty); }
        set { SetValue(CurrentItemProperty, value); }
    }

This custom control have also a property "InternalCurrentItem".

public CityEntity InternalCurrentItem
    {
        get { return _internalCurrentCity; }

        set
        {
            if (_internalCurrentCity == value) return;

            _internalCurrentCity = value;
            OnPropertyChanged();

            CurrentItem = value;
        }
    }

The DataContext is define to himself in the constructor :

public VilleAutoCompleteBox()
    {
        DataContext = this;

        ...
    }

And the Style set ItemsSource and SelectedItem like this:

<Style TargetType="{x:Type infrastructure_controls:CustomAutoCompleteBox}" BasedOn="{StaticResource AutoCompleteBoxFormStyle}">
    <Setter Property="ItemsSource" Value="{Binding InternalItems, Mode=OneWay}" />
    <Setter Property="SelectedItem" Value="{Binding InternalCurrentItem, Mode=TwoWay}" />
    ...
</Style>

In summary, ItemsSource is bind to internal property "InternalItems" and SelectedItem is bind to internal property "InternalCurrentItem".

For use it, I declare this CustomAutoCompleteBox like this :

<infrastructure_usercontrols:CustomAutoCompleteBox Width="200" CurrentItem="{Binding DataContext.VmCurrentItem, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Mode=TwoWay}" />

I have bind the dependency property "CurrentItem" to the ViewModel's property "VmCurrentItem".

Everything works fine except for one thing.

When I type text in the control, the InternalCurrentItem property changes correctly. Same for the CurrentItem property in my ViewModel.

Concretely, InternalCurrentItem is correctly modified (Set). This property sets the CurrentItem dependency property, and this dependency property sets VmCurrentItem.

The opposite is not true. If I change directly the value of the VmCurrentItem property in the ViewModel, the CurrentItem property is not changed. I do not understand why.

Hathors
  • 693
  • 1
  • 7
  • 13
  • 1
    As a note, you should generally not set a control's DataContext to itself, because it prevents that the control inherits the DataContext of its parent control or window. You can easily find a proof of this rule when you look at the complexity of your CurrentItem binding. Better simply write the control's "internal" bindings with RelativeSource. See e.g. this answer: https://stackoverflow.com/a/28982771/1136211 – Clemens Jun 01 '17 at 08:27
  • I have updated my code following your recommendations. This is cleaner but does not fix the problem. – Hathors Jun 01 '17 at 09:39

2 Answers2

1

The first case causes the following chain of events:

  • SelectedItem is changed
  • InternalCurrentItem is updated by the framework due to the binding
  • You manually update CurrentItem in the InternalCurrentItem setter
  • VmCurrentItem is updated by the framework due to the binding

In the opposite direction this is what happens:

  • VmCurrentItem is changed
  • CurrentItem is updated by the framework due to the binding

...and that's it. There's no binding and no piece of code that would update InternalCurrentItem when CurrentItem changes. So what you need to do is to register a PropertyChangedCallback for your CurrentItemProperty which will update InternalCurrentItem:

public static readonly DependencyProperty CurrentItemProperty =
    DependencyProperty.Register(
        "CurrentItem",
        typeof(CityEntity),
        typeof(CustomAutoCompleteBox),
        new FrameworkPropertyMetadata
        {
            BindsTwoWayByDefault = true,
            PropertyChangedCallback = CurrentItemPropertyChanged
        });

private static void CurrentItemPropertyChanged(
     DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var control = (CustomAutoCompleteBox)d;
    control.InternalCurrentItem = (CityEntity)e.NewValue;
}
Grx70
  • 10,041
  • 1
  • 40
  • 55
  • Thank you for your very clear and precise answer. But I bring a little correction. In the opposite direction, CurrentItem is not updated :( That is the point that I do not understand. If CurrentItem was properly modified, I would have to manually change InternalCurrentItem as in your example. – Hathors Jun 01 '17 at 08:16
  • Yes, I've just noticed that. Does your view-model raise `PropertyChanged`? And are you getting any binding related messages in the output window? Also, if your using some kind of view-model injection make sure you're modifying the same view-model instance that your control is bound to. – Grx70 Jun 01 '17 at 08:17
  • Yes, the ViewModel raise PropertyChanged : public CityEntity VmCurrentItem { get { return _vmCurrentItem; } set { if (_vmCurrentItem == value) return; _vmCurrentItem = value; OnPropertyChanged(() => VmCurrentItem); } } – Hathors Jun 01 '17 at 08:20
  • Nothing in the output window. – Hathors Jun 01 '17 at 08:35
  • I have solved the problem by adding the PropertyChangedCallBack. – Hathors Jun 01 '17 at 10:02
0

You need to declare the property the same way as the first:

public static readonly DependencyProperty InternalCurrentItemProperty =
        DependencyProperty.Register("InternalCurrentItem", typeof(CityEntity), typeof(CustomAutoCompleteBox),
            new FrameworkPropertyMetadata(
                null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

public CityEntity InternalCurrentItem
{
    get{ return (CityEntity)GetValue(InternalCurrentItemProperty); }

    set
    {
        SetValue(InternalCurrentItemProperty, value);
    }
}
Romano Zumbé
  • 7,893
  • 4
  • 33
  • 55
  • Small additional precision : When I change directly the value of the VmCurrentItem property in the ViewModel, the CurrentItem property is not changed. Yet the opposite is true. When I modify CurrentItem, VmCurrentItem is changed. The problem does not come from InternalCurrentItem. – Hathors Jun 01 '17 at 08:05