Привязка видимости WPF DataGridTemplateColumn под MVVM

У меня есть DataGrid, привязанный к ICollectionView в моей ViewModel. DataGrid находится внутри UserControl, который используется в нескольких различных сценариях данных, некоторые из которых требуют определенных столбцов DataGrid, а другие — нет.

Я просто хочу привязать свойство Visibility DataGridTemplateColumn к свойству Content внутренней метки, поэтому, если ни одна из строк не содержит значения, оно будет скрыто. У меня есть набор преобразователей String to Visibility, но я не могу понять, как найти свойство Content внутренней этикетки.

<DataGridTemplateColumn Header="Groups" Width="*" CanUserSort="True" SortMemberPath="Groups" Visibility="{Binding ElementName=lbl, Path=Content, Converter={StaticResource StringToVisibilityConverter}}">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <Label Name="lbl" Content="{Binding Path=Groups}" />
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

Какие-либо предложения?


person Aaron    schedule 17.03.2011    source источник
comment
Любой, кто ищет ответ, может обратиться к stackoverflow.com/questions/7955318/   -  person Milan Raval    schedule 11.02.2015


Ответы (5)


Я где-то читал о переполнении стека (не могу найти точную запись), что столбцам DataGridColumn не назначается контекст данных, потому что они не являются FrameworkElement. Чтобы обойти это, мне пришлось использовать код, похожий на этот:

    <DataGridTemplateColumn 
         Header="Groups" 
         Width="*" 
         CanUserSort="True" 
         SortMemberPath="Groups" 
         Visibility"{Binding RelativeSource={x:Static RelativeSource.Self}, 
                        Path=(FrameworkElement.DataContext).IsGroupsVisible, 
                        Converter={StaticResource booleanToVisiblityConverter}}">
         <DataGridTemplateColumn.CellTemplate>         
              <DataTemplate>             
                   <Label Name="lbl" Content="{Binding Path=Groups}" />         
              </DataTemplate>     
         </DataGridTemplateColumn.CellTemplate> 
    </DataGridTemplateColumn> 

Where 

<UserControl.Resources>
    <BooleanToVisibilityConverter x:Key="booleanToVisibilityConverter" /> 
</UserControl.Resources>
person Andy.Streeval    schedule 17.03.2011
comment
Привет, Энди, интересно. Теперь никаких ошибок привязки не возникает, но он все еще не работает. Преобразователь даже не вызывается... - person Aaron; 17.03.2011
comment
Является ли группа свойством ViewModel, к которому привязан DataGrid? Или это свойство отдельных элементов, к которым привязаны строки в DataGrid? Я предполагаю, что это и то, и другое, поскольку вы хотите отображать текст в отдельных ячейках, а видимость для столбца в сетке может быть привязана только к одному свойству. - person Andy.Streeval; 17.03.2011
comment
Это свойство отдельных элементов в ObservableCollection, которое имеет значение Source для CollectionViewSource и возвращается в DataGrid как ICollectionView. Я также попытался привязать его к праву свойства в ViewModel, называемому IsGroupsVisible, что менее элегантно, но и это не улавливается. - person Aaron; 17.03.2011
comment
Я изменил приведенный выше код, чтобы использовать свойство IsGroupsVisible. - person Andy.Streeval; 17.03.2011
comment
Это даже не вызов свойства ViewModel! Я начинаю думать, что это ошибка WPF. Когда я размещаю тот же код: Visibility={Binding RelativeSource={x:Static RelativeSource.Self}, Path=(FrameworkElement.DataContext).IsGroupVisible, Converter={StaticResource BoolToVisibilityConverter}} на любом другом элементе управления в моем представлении, он отлично работает . - person Aaron; 17.03.2011
comment
Да, вы можете загрузить такой инструмент, как Snoop, по адресу snoopwpf.codeplex.com. Если я не могу устранить ошибку привязки (которой у вас нет) в окне вывода. Обычно моим следующим шагом является установка точек останова в свойствах Getter, чтобы убедиться, что они попадают. После этого я возвращаюсь к утилите Snoop, которая позволяет вам видеть DataContext вашего UserControl/Controls. - person Andy.Streeval; 17.03.2011
comment
Спасибо за подсказку, Энди. Интересно, что Snoop говорит, что DataContext для этого столбца равен Groups, что и установлено для свойства Header. Поэтому я понятия не имею, как это получается, поскольку это DataContext, и, поскольку вы не можете напрямую определить DataContext для DataGridTemplateColumn, я в недоумении. - person Aaron; 17.03.2011
comment
как сказал Энди, DataGridColumns, которые вы определяете, не наследуют контекст данных. поэтому, если ваша сетка данных (‹-- DATAGRID!) имеет контекст данных (‹-- не itemssource:), который вы хотите, с вашим свойством с информацией для видимости, то {Binding RelativeSource={x:Static RelativeSource.Self},Path =(FrameworkElement.DataContext).YOURPROPERTYFORVISIBILITY, Converterstuff...} верно. для тестирования вы можете поместить эту привязку к своему свойству заголовка: Header={Binding...}, чтобы вы могли понять, куда идет ваша привязка. - person blindmeis; 18.03.2011
comment
Если конвертер не вызывается, вам необходимо включить DataGridContextHelper - person SliverNinja - MSFT; 17.01.2012

Чтобы использовать RelativeSource.Self в качестве привязки RelativeSource для DataGridTemplateColumn, необходимо добавить DataGridContextHelper для вашего приложения. Это по-прежнему требуется для WPF 4 DataGrid.

<DataGridTemplateColumn 
         Header="Groups" 
         Width="*" 
         CanUserSort="True" 
         SortMemberPath="Groups" 
         Visibility"{Binding RelativeSource={x:Static RelativeSource.Self}, 
                        Path=(FrameworkElement.DataContext).IsGroupsVisible, 
                        Converter={StaticResource booleanToVisiblityConverter}}">
         <DataGridTemplateColumn.CellTemplate>         
              <DataTemplate>             
                   <Label Name="lbl" Content="{Binding Path=Groups}" />         
              </DataTemplate>     
         </DataGridTemplateColumn.CellTemplate> 
    </DataGridTemplateColumn> 
person SliverNinja - MSFT    schedule 16.01.2012

Это было бы лучше достигнуто через свойство Groups в ViewModel; так как в конечном итоге это то, что Label использует в любом случае.

<DataGridTemplateColumn Header="Groups" Width="*" CanUserSort="True" SortMemberPath="Groups" Visibility="{Binding Groups, Converter={StaticResource SomeConverter}}">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <Label Name="lbl" Content="{Binding Path=Groups}" />
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
person Aaron McIver    schedule 17.03.2011
comment
Привет, ребята, я уже пробовал это и получил эту ошибку во время выполнения: Ошибка System.Windows.Data: 2: не удается найти управляющий элемент FrameworkElement или FrameworkContentElement для целевого элемента. BindingExpression:Путь=Группы; Элемент данных = ноль; целевой элемент — DataGridTemplateColumn (HashCode=38907373); целевое свойство — «Видимость» (тип «Видимость») - person Aaron; 17.03.2011
comment
К сожалению, это действительно сработало, когда я использовал CollectionViewSource, определенный в XAML, задав для источника привязки видимости значение ColletionViewSource. Мне пришлось переместить CollectionViewSource в ViewModel, чтобы иметь дело с сортировкой/фильтрацией, и это нарушило эту привязку видимости. Любые другие идеи? - person Aaron; 17.03.2011
comment
CollectionViewSource — это XAML-прокси для CollectionView. В вашей ViewModel верните ICollectionView ala...CollectionViewSource.GetDefaultView (ваша коллекция) - person Aaron McIver; 17.03.2011
comment
Извините, моя ошибка, это то, что я возвращаю из своей ViewModel для ItemsSource моего DataGrid, и это выдает эту ошибку System.Windows.Data: 2 в привязке видимости. Я даже пытался привязать к выделенному свойству в ViewModel, и он все еще не может его найти. - person Aaron; 17.03.2011
comment
Это во время компиляции... или во время выполнения? Это поражает ваш преобразователь? - person Aaron McIver; 17.03.2011

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

person Community    schedule 17.03.2011

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

Мое приложение использует DataGrid с AutoGenerateColumns=False и использует DataGridTemplateColumn, поэтому DataContext был установлен до добавления столбцов в сетку.

Вот класс прикрепленного свойства:

public sealed class DataGridColumnDataContextForwardBehavior
{
    private DataGrid dataGrid = null;

    public DataGridColumnDataContextForwardBehavior(DataGrid dataGrid)
    {
        this.dataGrid = dataGrid;

        dataGrid.Columns.CollectionChanged += DataGridColumns_CollectionChanged;
    }

    private void DataGridColumns_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        var IsDataContextForwardingEnabled = GetIsDataContextForwardingEnabled(dataGrid);

        if (IsDataContextForwardingEnabled && dataGrid.DataContext != null)
        {
            if (e.Action == NotifyCollectionChangedAction.Add)
            {
                foreach (DataGridColumn column in e.NewItems)
                {
                    column.SetValue(FrameworkElement.DataContextProperty, dataGrid.DataContext);
                }
            }
        }
    }

    static DataGridColumnDataContextForwardBehavior()
    {
        FrameworkElement.DataContextProperty.AddOwner(typeof(DataGridColumn));
        FrameworkElement.DataContextProperty.OverrideMetadata(typeof(DataGrid), 
            new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits, new PropertyChangedCallback(OnDataContextChanged)));
    }

    public static readonly DependencyProperty IsDataContextForwardingEnabledProperty = 
        DependencyProperty.RegisterAttached("IsDataContextForwardingEnabled", typeof(bool), typeof(DataGridColumnDataContextForwardBehavior), 
            new FrameworkPropertyMetadata(false, OnIsDataContextForwardingEnabledChanged));

    public static void OnDataContextChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        DataGrid dataGrid = obj as DataGrid;
        if (dataGrid == null) return;

        var IsDataContextForwardingEnabled = GetIsDataContextForwardingEnabled(dataGrid);
        if (IsDataContextForwardingEnabled)
        {
            foreach (DataGridColumn col in dataGrid.Columns)
            {
                col.SetValue(FrameworkElement.DataContextProperty, e.NewValue);
            }
        }
    }

    static void OnIsDataContextForwardingEnabledChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        var dataGrid = obj as DataGrid;
        if (dataGrid == null) return;

        new DataGridColumnDataContextForwardBehavior(dataGrid);

        if (!(e.NewValue is bool)) return;

        if ((bool)e.NewValue && dataGrid.DataContext != null)
            OnDataContextChanged(obj, new DependencyPropertyChangedEventArgs(FrameworkElement.DataContextProperty, dataGrid.DataContext, dataGrid.DataContext));
    }

    public static bool GetIsDataContextForwardingEnabled(DependencyObject dataGrid)
    {
        return (bool)dataGrid.GetValue(IsDataContextForwardingEnabledProperty);
    }

    public static void SetIsDataContextForwardingEnabled(DependencyObject dataGrid, bool value)
    {
        dataGrid.SetValue(IsDataContextForwardingEnabledProperty, value);
    }
}

Еще одна неочевидная вещь — как правильно использовать привязку для DataGridTemplateColumn:

<DataGrid bhv:DataGridColumnDataContextForwardBehavior.IsDataContextForwardingEnabled="True">
<DataGrid.Columns>
<DataGridTemplateColumn Visibility="{Binding Path=DataContext.Mode, RelativeSource={RelativeSource Self}, Converter={StaticResource SharedFilesModeToVisibilityConverter}, ConverterParameter={x:Static vmsf:SharedFilesMode.SharedOut}}"/>

Важно использовать Path=DataContext.MyProp и RelativeSource Self.

Единственное, что мне не нравится в текущей реализации - для обработки события DataGrid.Columns.CollectionChanged я создаю экземпляр своего класса и не сохраняю ссылку на него. Так что теоретически GC может убить его со временем, не уверен, как правильно с этим справиться в данный момент, подумает об этом и обновит свой пост позже. Приветствуются любые идеи и критика.

person Arkady Marchenko    schedule 09.11.2016