Я верю, что наконец нашел лучшее решение, которое я могу.Один из моих самых больших пробелов в знаниях был о системе Диспетчер;хотя я не думаю, что неправильный поток вызвал все проблемы, с которыми я столкнулся, я бы предпочел быть в безопасности, чем сожалеть.
В следующем коде раздела определяется свойство двустороннего связывания, которое всегда будет выдвигать егожелаемое значение обратно в привязанное свойство источника - если источник уведомляет о привязке обновления, привязка будет возвращаться назад и обновлять источник до того, каким должно быть желаемое значение - эффективно предоставляя надежный метод передачи произвольной информации обратно из представленияв 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
меньше шаблонов.