WPF DataGridColumn Visibility

1

Мы все столкнулись с проблемой, когда нам нужно скрыть столбец 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.

Теги:
wpf
visibility
datagridcolumn

2 ответа

2
Лучший ответ

Проблема - 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 как описано здесь в моем ответе.

  • 0
    Хм, я вижу, что класс Freezable вообще не имеет свойства DataContext . И, как я вижу, мы все равно не используем это свойство. Мы используем свойство Data , добавленное вручную. Как я понимаю, DataContext of Window извлекается из свойства Data и это хитрость. FrameworkElement + ContentControl - я понял, но все еще есть вопрос об этом подходе, см. Обновление, плз.
  • 0
    DataContext of Window is extracted from Data property and that is the trick - да, вот как это работает.
Показать ещё 4 комментария
0

Вы ошибаетесь. Использованный элемент не должен быть 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 из основного визуального дерева через него.

  • 0
    Я думаю, что вы имеете в виду Source={x:Reference someElement}} :) Это выглядит хорошо (и работает), но эта строка всегда вызывает исключение «Поставщик услуг пропускает службу INameResolver» во время разработки :(

Ещё вопросы

Сообщество Overcoder
Наверх
Меню