Мы все столкнулись с проблемой, когда нам нужно скрыть столбец DataGrid некоторым условием. Существует как минимум два подхода к решению этой проблемы. Для подходов требуется прокси-элемент. Я использую ложь этих подходов. Как видите, использование FreezableProxy
качестве прокси-элемента не требует ContentControl
, но требует указания дополнительного класса (FreezableProxy
). Использование FrameworkElement
качестве прокси не требует указания дополнительного класса (например, FreezableProxy
), но требует добавления ContentControl
для разметки.
XAML:
<Window.Resources>
<SampleRadioBoxCheckedConverter:FreezableProxy x:Key="FreezableProxy" Data="{Binding}"/>
<FrameworkElement x:Key="FrameworkElement" DataContext="{Binding}"/>
</Window.Resources>
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<DataGrid AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name"/>
<DataGridTextColumn Header="Type"
Visibility="{Binding Data.IsPartnerColumnVisible, Source={StaticResource FreezableProxy}}"/>
<DataGridTextColumn Header="Number"
Visibility="{Binding DataContext.IsPartnerColumnVisible, Source={StaticResource FrameworkElement}}"/>
</DataGrid.Columns>
</DataGrid>
<ContentControl Grid.Row="1" Content="{StaticResource FrameworkElement}" Visibility="Collapsed"></ContentControl>
</Grid>
Код-за:
public class FreezableProxy : Freezable
{
protected override Freezable CreateInstanceCore()
{
return new FreezableProxy();
}
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object),
typeof(FreezableProxy));
}
public partial class MainWindow : INotifyPropertyChanged
{
private Visibility _isPartnerColumnVisible = Visibility.Hidden;
public Visibility IsPartnerColumnVisible
{
get
{
return _isPartnerColumnVisible;
}
set
{
_isPartnerColumnVisible = value;
RaisePropertyChanged("IsPartnerColumnVisible");
}
}
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged(String prop)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
}
Взгляды в сторону выглядят очень похожими. Предложите использовать второй подход (используя FrameworkElement
). Затем у меня возникает вопрос - почему мне нужно указать дополнительный ContentControl
если Binding
действительно установлен в {StaticResource FrameworkElement}
? Какое волшебство делает ContentControl
? Когда мы используем FrezableProxy
нам не нужно указывать какие ContentControl-s
либо ContentControl-s
или что-то еще, и он отлично работает.
ОБНОВИТЬ
<FrameworkElement x:Key="FrameworkElement" DataContext="{Binding}"/>
/////<ContentControl Grid.Row="1" Content="{StaticResource FrameworkElement}" Visibility="Collapsed"></ContentControl>
Почему это не работает? ContentControl комментируется, но я явно задал свойство DataContext
FrameworkElement
.
Проблема - DataGridColumns не лежит в том же дереве Visual, что и в его родительском DataGrid. Следовательно, привязка не работает, потому что DataContext не наследуется, не может быть найден с использованием RelativeSource, потому что это зависит от визуального дерева.
Таким образом, все упомянутые подходы должны передавать DataContext в столбцы.
Теперь, чтобы разница между двумя подходами:
Подход FreezableProxy:
Фактическая ссылка источника для этого здесь. И здесь определяется магия за Freezable, поэтому цитирование из той же ссылки:
Основная цель класса Freezable - определить объекты, которые имеют изменяемое и доступное только для чтения состояние, но интересная особенность в нашем случае состоит в том, что объекты Freezable могут наследовать DataContext, даже если они не находятся в визуальном или логическом дереве.
Таким образом, вам не нужен какой-либо ContentControl или какой-либо прокси-контроль, потому что с помощью freezable на месте мы получаем унаследованный DataContext автоматически.
Подход FrameworkElement + ContentControl:
Свойство DataContext объявляется с флагом FrameworkMetadataOptions.Inherits
. Что это означает, что дочерний контроль наследует его автоматически от его родительского элемента, если явно не установлен для дочернего элемента управления.
Таким образом ContentControl автоматически наследует DataContext из Grid, а дочерний элемент ContentControl наследует его от ContentControl. Вот почему FrameworkElement наследует его от ContentControl. (Не нужно привязывать DataContext вручную). Это будет работать:
<FrameworkElement x:Key="FrameworkElement"/>
Другой подход - использовать x:Reference
как описано здесь в моем ответе.
Вы ошибаетесь. Использованный элемент не должен быть ContentControl
и может быть любым элементом FrameworkElement
. Вы также, похоже, смущены тем, как этот метод работает, поскольку вы не используете требуемую директиву x:Reference
в Binding
:
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<DataGrid AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name"/>
<DataGridTextColumn Header="Number"
Visibility="{Binding DataContext.IsPartnerColumnVisible,
Source={x:Reference FrameworkElement}}"/>
</DataGrid.Columns>
</DataGrid>
<FrameworkElement Name="someElement" Grid.Row="1" Visibility="Collapsed" />
</Grid>
Эта проблема вызвана тем, что DataGridTextColumn
не является частью основного визуального дерева. Мы можем использовать директиву x:Reference
здесь, потому что она не зависит от того, что исходный элемент находится в том же визуальном дереве, что и Binding
котором он используется. Короче говоря, мы просто используем этот элемент FrameworkElement
(который может быть любым элементом управления) здесь, так что мы можем получить доступ к DataContext
из основного визуального дерева через него.
Source={x:Reference someElement}}
:) Это выглядит хорошо (и работает), но эта строка всегда вызывает исключение «Поставщик услуг пропускает службу INameResolver» во время разработки :(
Freezable
вообще не имеет свойстваDataContext
. И, как я вижу, мы все равно не используем это свойство. Мы используем свойствоData
, добавленное вручную. Как я понимаю,DataContext
ofWindow
извлекается из свойстваData
и это хитрость. FrameworkElement + ContentControl - я понял, но все еще есть вопрос об этом подходе, см. Обновление, плз.DataContext of Window is extracted from Data property and that is the trick
- да, вот как это работает.