UpdateSource не работает в CoerceValueCallback - PullRequest
0 голосов
/ 27 сентября 2019

Моей общей целью является создание TwoWay Attached DependencyProperty (или OneWayToSource), который всегда сохраняет свой связанный источник обновленным до определенного значения.В моем сценарии реального мира это неконстантный объект, который зависит от объекта, к которому он прикреплен.

В моем примере используются следующие модели и представление:

public class ViewModel : ViewModelBase
{
  public ViewModel()
  {
    firstContainer = new Container();
    otherContainer = new Container();
    TestContainer = firstContainer;

    SwitchContainersCommand = new DelegateCommand(SwitchContainers);
  }

  private Container firstContainer;
  private Container otherContainer;

  public Container TestContainer 
  {
    get { return testContainer; }
    set { Set(ref testContainer, value); }
  }
  private Container testContainer;

  public ICommand SwitchContainersCommand { get; }
  private void SwitchContainers()
  {
    if (TestContainer == firstContainer)
      TestContainer = otherContainer;
    else
      TestContainer = firstContainer;
  }
}

public class Container : ViewModelBase
{
  public object TestTarget
  {
    get { return testTarget; }
    set { Set(ref testTarget, value); }
  }
  private object testTarget = new SampleValue();
}

public class SampleValue
{
  public override string ToString()
  {
    return "unprovided value";
  }
}

Представление:

<Window>
  <Window.DataContext>
    <local:ViewModel/>
  </Window.DataContext>
  <StackPanel local:ProvideToSource.Target="{Binding TestContainer.TestTarget, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
    <TextBlock><Run Text="ToString: "/><Run Text="{Binding TestContainer.TestTarget}"/></TextBlock>
    <Button Command="{Binding SwitchContainersCommand}" HorizontalAlignment="Left">
      <TextBlock Text="Switch"/>
    </Button>
  </StackPanel>
</Window>

В частности, у нас есть прикрепленное свойство local:ProvideToSource.Target, которое связывает с TestContainer.TestTarget в двух направлениях.

Теперь код для ProvideToSource не слишком сложен.На Coerce Value он пытается вместо этого установить для свойства желаемое значение.Это прекрасно работает при первом запуске.

public static class ProvideToSource
{
  private const string Target = nameof(Target);
  public static DependencyProperty TargetProperty = DependencyProperty.RegisterAttached(nameof(Target), typeof(object), typeof(ProvideToSource), new PropertyMetadata(null, null, CoerceValue));

  public static object GetTarget(DependencyObject dependent)
  {
    return dependent.GetValue(TargetProperty);
  }

  public static void SetTarget(DependencyObject dependent, object value)
  {
    dependent.SetValue(TargetProperty, value);
  }

  private static readonly object TestValue = new ProvidedObject();

  private static object CoerceValue(DependencyObject dependent, object value)
  {
    if (value != TestValue)
    {
      dependent.SetValue(TargetProperty, TestValue);
    }

    return TestValue;
  }
}

public class ProvidedObject
{
  public override string ToString()
  {
    return "provided me";
  }
}

В идеале, когда привязка устанавливается на новый источник (который не обновлен), он должен перейти и обновить источник, чтобы он былценность, которую мы желаем.Тем не менее, он делает это только при первоначальном создании.

Когда приложение запускается и отображается окно, в тексте говорится, что желаемое «предоставлено мне»;однако, когда кнопка нажата и TestContainers поменялся местами, привязка переоценивает свое значение Coerce (поскольку оно обнаруживает изменение), но когда вызывается SetValue, свойство backing не обновляется, и отображается текст "необеспеченное значение "

Есть ли причина для такого поведения, и как я могу обойти это?Я перепробовал много вещей;однако, я просто не могу получить исходное значение, которое будет успешно установлено из присоединенного свойства после инициализации привязки.Обратите внимание, что я пробовал это с PropertyChangedCallback и без него, и, похоже, я не могу получить это, чтобы помочь с этой проблемой.

Кроме того, явный вызов BindingOperations.GetBindingExpression(dependent, TargetProperty)?.UpdateSource(); в обратных вызовах CoerceValue или PropertyChanged также не выполняется.

1 Ответ

0 голосов
/ 28 сентября 2019

Я верю, что наконец нашел лучшее решение, которое я могу.Один из моих самых больших пробелов в знаниях был о системе Диспетчер;хотя я не думаю, что неправильный поток вызвал все проблемы, с которыми я столкнулся, я бы предпочел быть в безопасности, чем сожалеть.

В следующем коде раздела определяется свойство двустороннего связывания, которое всегда будет выдвигать егожелаемое значение обратно в привязанное свойство источника - если источник уведомляет о привязке обновления, привязка будет возвращаться назад и обновлять источник до того, каким должно быть желаемое значение - эффективно предоставляя надежный метод передачи произвольной информации обратно из представленияв ViewModel.

При первом запуске CoerceValueCallback инициализирует желаемое значение свойства и сохраняет его в частном свойстве зависимости Cache, а также принудительно обновляет привязку до этого значения.При всех последующих запусках PropertyChangedCallback проверит, изменяется ли свойство на что-либо, что не является желаемым значением, и принудительно вернет привязку к желаемому значению (загруженному из свойства зависимостей Cache).

public static class ProvideToSource
{
  private const string Target = nameof(Target);
  public static DependencyProperty TargetProperty = DependencyProperty.RegisterAttached(nameof(Target), typeof(object), typeof(ProvideToSource), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, ValueChanged, CoerceValue, true, UpdateSourceTrigger.Explicit));

  public static object GetTarget(DependencyObject dependent)
  {
    return dependent.GetValue(TargetProperty);
  }

  public static void SetTarget(DependencyObject dependent, object value)
  {
    dependent.SetValue(TargetProperty, value);
  }

  private const string Cache = nameof(Cache);
  private static DependencyProperty CacheProperty = DependencyProperty.RegisterAttached(nameof(Cache), typeof(object), typeof(ProvideToSource));

  private static object CoerceValue(DependencyObject dependent, object value)
  {
    if (dependent.GetValue(CacheProperty) == null)
    {
      value = GetOrCreateCachedValue(dependent);
      dependent.Dispatcher.Invoke(() => UpdateValueAndBindings(dependent, value));
    }

    return value;
  }

  private static void ValueChanged(DependencyObject dependent, DependencyPropertyChangedEventArgs args)
  {
    var cachedValue = GetOrCreateCachedValue(dependent);
    if (args.NewValue != cachedValue)
    {
      dependent.Dispatcher.Invoke(() => UpdateValueAndBindings(dependent, cachedValue));
    }
  }

  private static void UpdateValueAndBindings(DependencyObject dependent, object value)
  {
    var bindingExpression = BindingOperations.GetBindingExpression(dependent, TargetProperty);
    if (bindingExpression != null && bindingExpression.Status != BindingStatus.Detached)
    {
      bindingExpression?.UpdateTarget(); //This call seems out of place but is required
      SetTarget(dependent, value);
      bindingExpression?.UpdateSource();
    }
    else
    {
      SetTarget(dependent, value);
    }
  }

  private static object GetOrCreateCachedValue(DependencyObject dependent)
  {
    var item = dependent.GetValue(CacheProperty);
    if (item == null)
    {
      item = new ProvidedObject();  // Generate item here
      dependent.SetValue(CacheProperty, item);
    }
    return item;
  }
}

Используя этот код вместо определения TargetProperty в вопросе, вы убедитесь, что текущий ViewModel.TestContainer.TestTarget всегда синхронизирован с экземпляром ProvidedObject.

Первоначальныйset выполняется в CoerceValueCallback, чтобы гарантировать, что начальное значение установлено, поскольку PropertyChangedCallback может не быть установлено при начальном запуске через свойство зависимостей, если оно связано с нулевым значением.

Последующие наборывыполняются в PropertyChangedCallback, чтобы гарантировать, что свойство зависимости уже считает себя полностью обновленным, прежде чем пытаться немедленно изменить его обратно, вместо того, чтобы пытаться принудительно вернуть его обратно в середине приведения.Кроме того, поскольку UpdateTarget() требуется вызвать до того, как мы сможем UpdateSource() (из-за некоторого необычного поведения, когда исходный объект-источник заменяется другим объектом-источником), мы полагаемся на PropertyChangedCallback, отфильтровывающего эффекты этого вызова, поскольку он не будет думать, что привязка еще меняет значение, и не вызывает бесконечной рекурсии - вызов UpdateTarget() из CoerceValueCallback вызывает бесконечную рекурсию.

Код можно изменить для любого отдельноготип свойства зависимости, но у object меньше шаблонов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...