Если элемент не является частью визуального дерева, то RelativeSource никогда не будет работать.
В этом случае вам нужно попробовать другую технику, впервые предложенную Томасом Левеском.
У него есть решение в его блоге под [WPF] Как связать данные, когда DataContext не наследуется . И это работает абсолютно блестяще!
В маловероятном случае, если его блог не работает, приложение A содержит зеркальную копию его статьи .
Пожалуйста, не комментируйте здесь, пожалуйста комментируйте непосредственно в своем блоге .
Приложение A: Зеркало поста в блоге
Свойство DataContext в WPF чрезвычайно удобно, потому что оно автоматически наследуется всеми дочерними элементами элемента, которому вы его назначаете; поэтому вам не нужно устанавливать его заново для каждого элемента, который вы хотите связать. Однако в некоторых случаях DataContext недоступен: это происходит для элементов, которые не являются частью визуального или логического дерева. Тогда может быть очень сложно связать свойство с этими элементами…
Давайте проиллюстрируем это на простом примере: мы хотим отобразить список продуктов в DataGrid. В сетке мы хотим иметь возможность отображать или скрывать столбец Price, основываясь на значении свойства ShowPrice, предоставляемого ViewModel. Очевидный подход - привязать видимость столбца к свойству ShowPrice:
<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
Visibility="{Binding ShowPrice,
Converter={StaticResource visibilityConverter}}"/>
К сожалению, изменение значения ShowPrice не имеет никакого эффекта, и столбец всегда виден ... почему? Если мы посмотрим на окно вывода в Visual Studio, мы увидим следующую строку:
System.Windows.Data Ошибка: 2: не удается найти управляющий FrameworkElement или FrameworkContentElement для целевого элемента. BindingExpression: Path = ShowPrice; DataItem = NULL; целевой элемент - «DataGridTextColumn» (HashCode = 32685253); Целевым свойством является «Видимость» (тип «Видимость»)
Сообщение довольно загадочное, но смысл на самом деле довольно прост: WPF не знает, какой FrameworkElement использовать для получения DataContext, поскольку столбец не принадлежит визуальному или логическому дереву DataGrid.
Мы можем попытаться настроить привязку, чтобы получить желаемый результат, например, установив RelativeSource для самой DataGrid:
<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
Visibility="{Binding DataContext.ShowPrice,
Converter={StaticResource visibilityConverter},
RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}}"/>
Или мы можем добавить CheckBox, привязанный к ShowPrice, и попытаться связать видимость столбца со свойством IsChecked, указав имя элемента:
<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
Visibility="{Binding IsChecked,
Converter={StaticResource visibilityConverter},
ElementName=chkShowPrice}"/>
Но, похоже, ни один из этих обходных путей не работает, мы всегда получаем один и тот же результат ...
На данный момент кажется, что единственным жизнеспособным подходом было бы изменить видимость столбцов в code-behind, чего мы обычно предпочитаем избегать при использовании шаблона MVVM ... Но я не собираюсь так скоро сдаваться, по крайней мере, пока есть другие варианты ?
Решение нашей проблемы на самом деле довольно простое и использует преимущества класса Freezable. Основное назначение этого класса - определить объекты, которые имеют изменяемое и доступное только для чтения состояние, но в нашем случае интересной особенностью является то, что объекты Freezable могут наследовать DataContext, даже если они не находятся в визуальном или логическом дереве. Я не знаю точный механизм, который обеспечивает такое поведение, но мы собираемся воспользоваться этим, чтобы заставить нашу работу связывания ...
Идея состоит в том, чтобы создать класс (я назвал его BindingProxy по причинам, которые должны скоро стать очевидными), который наследует Freezable и объявляет свойство зависимостей Data:
public class BindingProxy : Freezable
{
#region Overrides of Freezable
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
#endregion
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
// Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
Затем мы можем объявить экземпляр этого класса в ресурсах DataGrid и связать свойство Data с текущим DataContext:
<DataGrid.Resources>
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
</DataGrid.Resources>
Последний шаг - указать этот объект BindingProxy (легко доступный со StaticResource) в качестве источника для привязки:
<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
Visibility="{Binding Data.ShowPrice,
Converter={StaticResource visibilityConverter},
Source={StaticResource proxy}}"/>
Обратите внимание, что к пути привязки был добавлен префикс «Данные», поскольку теперь этот путь относится к объекту BindingProxy.
Теперь привязка работает правильно, иСтолбец правильно отображается или скрывается на основе свойства ShowPrice.