Как связать DataGridColumn.Visibility? - PullRequest
15 голосов
/ 25 июня 2009

У меня есть проблема, похожая на следующий пост:

Видимость привязки столбцов Silverlight DataGridTextColumn

Мне нужно, чтобы столбец в DataGrid Silverlight был виден / свернут на основе значения в ViewModel. Для этого я пытаюсь привязать свойство Visibility к ViewModel. Однако вскоре я обнаружил, что свойство Visibility не является DependencyProperty, поэтому оно не может быть связано.

Чтобы решить эту проблему, я попытался создать подкласс для своего собственного DataGridTextColumn. С помощью этого нового класса я создал объект DependencyProperty, который в конечном итоге вносит изменения в свойство DataGridTextColumn.Visibility. Это хорошо работает, если я не привязываю данные. В тот момент, когда я связываю данные с моим новым свойством, оно перестает работать, за исключением AG_E_PARSER_BAD_PROPERTY_VALUE.

public class MyDataGridTextColumn : DataGridTextColumn
{
    #region public Visibility MyVisibility

    public static readonly DependencyProperty MyVisibilityProperty =
        DependencyProperty.Register("MyVisibility", typeof(Visibility), typeof(MyDataGridTextColumn), new PropertyMetadata(Visibility.Visible, OnMyVisibilityPropertyChanged));

    private static void OnMyVisibilityPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var @this = d as MyDataGridTextColumn;

        if (@this != null)
        {
            @this.OnMyVisibilityChanged((Visibility)e.OldValue, (Visibility)e.NewValue);
        }
    }

    private void OnMyVisibilityChanged(Visibility oldValue, Visibility newValue)
    {
        Visibility = newValue;
    }

    public Visibility MyVisibility
    {
        get { return (Visibility)GetValue(MyVisibilityProperty); }
        set { SetValue(MyVisibilityProperty, value); }
    }

    #endregion public Visibility MyVisibility
}

Вот небольшой фрагмент XAML.

<DataGrid ....>
    <DataGrid.Columns>
        <MyDataGridTextColumn Header="User Name"
                              Foreground="#FFFFFFFF"
                              Binding="{Binding User.UserName}"
                              MinWidth="150"
                              CanUserSort="True"
                              CanUserResize="False"
                              CanUserReorder="True"
                              MyVisibility="{Binding Converter={StaticResource BoolToVisibilityConverter}, Path=ShouldShowUser}"/>
        <DataGridTextColumn .../>
    </DataGrid.Columns>
</DataGrid>

Пара важных фактов.

  • Конвертер действительно определен выше в локальных ресурсах.
  • Конвертер правильный, он используется во многих других местах в решении.
  • Если я заменим синтаксис {Binding} для свойства MyVisibility на "Collapsed", столбец фактически исчезнет.
  • Если я создаю новое свойство DependencyProperty (то есть строку Foo) и привязываюсь к нему, я получаю исключение AG_E_PARSER_BAD_PROPERTY_VALUE.

У кого-нибудь есть идеи, почему это не работает?

Ответы [ 9 ]

7 голосов
/ 20 сентября 2010

Вот решение, которое я придумал, используя небольшой взлом.

Во-первых, вам нужно наследовать от DataGrid.

public class DataGridEx : DataGrid
{
    public IEnumerable<string> HiddenColumns
    {
        get { return (IEnumerable<string>)GetValue(HiddenColumnsProperty); }
        set { SetValue(HiddenColumnsProperty, value); }
    }

    public static readonly DependencyProperty HiddenColumnsProperty =
        DependencyProperty.Register ("HiddenColumns", 
                                     typeof (IEnumerable<string>), 
                                     typeof (DataGridEx),
                                     new PropertyMetadata (HiddenColumnsChanged));

    private static void HiddenColumnsChanged(object sender,
                                             DependencyPropertyChangedEventArgs args)
    {
        var dg = sender as DataGrid;
        if (dg==null || args.NewValue == args.OldValue)
            return;

        var hiddenColumns = (IEnumerable<string>)args.NewValue;
        foreach (var column in dg.Columns)
        {
            if (hiddenColumns.Contains ((string)column.GetValue (NameProperty)))
                column.Visibility = Visibility.Collapsed;
            else
                column.Visibility = Visibility.Visible;
        }
    }
}

Класс DataGridEx добавляет новый DP для сокрытия столбцов на основе x: Name элемента DataGridColumn и его потомков.

Для использования в вашем XAML:

<my:DataGridEx x:Name="uiData"
               DataContext="{Binding SomeDataContextFromTheVM}"
               ItemsSource="{Binding Whatever}"
               HiddenColumns="{Binding HiddenColumns}">
    <sdk:DataGridTextColumn x:Name="uiDataCountOfItems">
                            Header="Count"
                            Binding={Binding CountOfItems}"
    </sdk:DataGridTextColumn>
</my:DataGridEx>

Вам необходимо добавить их в ViewModel или любой другой контекст данных, который вы используете.

private IEnumerable<string> _hiddenColumns;
public IEnumerable<string> HiddenColumns
{
    get { return _hiddenColumns; }
    private set
    {
        if (value == _hiddenColumns)
            return;

        _hiddenColumns = value;
        PropertyChanged (this, new PropertyChangedEventArgs("HiddenColumns"));
    }
}

public void SomeWhereInYourCode ()
{
    HiddenColumns = new List<string> {"uiDataCountOfItems"};
}

Чтобы отобразить, вам нужно только удалить соответствующее имя из списка или воссоздать его без скрытого имени.

6 голосов
/ 20 октября 2010

У меня есть другое решение этой проблемы, в котором используется подход, аналогичный свойству «Binding», которое вы найдете в DataGridTextColumn. Так как классы столбцов являются DependencyObjects, вы не можете напрямую привязать к ним данные, НО, если вы добавите ссылку на FrameworkElement, который реализует INotifyPropertyChanged, вы можете передать привязку данных в элемент, а затем использовать свойство зависимости, чтобы уведомить Столбец о том, что привязка данных изменилась.

Следует отметить, что наличие привязки к самому столбцу вместо Grid, вероятно, будет означать, что вы захотите использовать DataContextProxy , чтобы получить доступ к полю, к которому вы хотите привязать видимость. to (привязка столбца будет по умолчанию к области ItemSource).

using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace XYZ.Controls
{
public class ExtendedDataGridTextColumn : DataGridTextColumn
{
    private readonly Notifier _e;

    private Binding _visibilityBinding;
    public Binding VisibilityBinding
    {
        get { return _visibilityBinding; }
        set
        {
            _visibilityBinding = value;
            _e.SetBinding(Notifier.MyVisibilityProperty, _visibilityBinding);
        }
    }

    public ExtendedDataGridTextColumn()
    {
        _e = new Notifier();
        _e.PropertyChanged += ToggleVisibility;
    }

    private void ToggleVisibility(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == "Visibility")
            this.Visibility = _e.MyVisibility;
    }

    //Notifier class is just used to pass the property changed event back to the column container Dependency Object, leaving it as a private inner class for now
    private class Notifier : FrameworkElement, INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public Visibility MyVisibility
        {
            get { return (Visibility)GetValue(MyVisibilityProperty); }
            private set { SetValue(MyVisibilityProperty, value); }
        }

        public static readonly DependencyProperty MyVisibilityProperty = DependencyProperty.Register("MyVisibility", typeof(Visibility), typeof(Notifier), new PropertyMetadata(MyVisibilityChanged));

        private static void MyVisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var n = d as Notifier;
            if (n != null)
            {
                n.MyVisibility = (Visibility) e.NewValue;
                n.PropertyChanged(n, new PropertyChangedEventArgs("Visibility"));
            }
        }
    }
}

}

3 голосов
/ 27 июня 2009

Столбец сетки данных наследуется от DependencyObject вместо FrameworkElement. В WPF это не составило бы труда ... но в silverlight вы можете связывать только объекты FrameworkElement. Таким образом, вы получаете описательное сообщение об ошибке AG_E_PARSER_BAD_PROPERTY_VALUE при попытке.

2 голосов
/ 15 июля 2009

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

public delegate void ColumnResizedEvent(double width);

public class GridColumnViewModel : ViewModelBase
{
    public event ColumnResizedEvent ColumnResized;

    public void Resize(double newContainerWidth)
    {
        // some crazy custom sizing calculations -- don't ask...
        ResizeColumn(newWidth);
    }

    public void ResizeColumn(double width)
    {
        var handler = ColumnResized;
        if (handler != null)
            handler(width);
    }
}

Тогда есть код, который собирает сетку:

public class CustomGrid
{
    public CustomGrid(GridViewModel viewModel)
    {
        // some stuff that parses control metadata out of the view model.
        // viewModel.Columns is a collection of GridColumnViewModels from above.
        foreach(var column in viewModel.Columns)
        {
            var gridCol = new DataGridTextColumn( ... );
            column.ColumnResized  += delegate(double width) { gridCol.Width = new DataGridLength(width); };
        }
    }
}

Когда размер сетки данных изменяется в приложении, происходит событие resize и вызывает метод resize для модели представления, с которой связана сетка. Это, в свою очередь, вызывает метод изменения размера каждой модели представления столбца сетки. Затем модель представления столбца сетки вызывает событие ColumnResized, на которое подписан текстовый столбец сетки данных, и его ширина обновляется.

Я понимаю, что это не решает вашу проблему напрямую, но это был способ, которым я мог бы "привязать" модель представления к столбцу таблицы данных, когда на нем нет свойств зависимости. Закрытие - это простая конструкция, которая прекрасно описывает желаемое поведение и вполне понятна для тех, кто идет за мной. Я думаю, не сложно представить, как его можно изменить, чтобы справиться с изменением видимости. Вы даже можете подключить обработчик событий к событию загрузки страницы / пользовательского элемента управления.

1 голос
/ 22 марта 2012

Это работает для столбца шаблона сетки данных:

public class ExtendedDataGridColumn : DataGridTemplateColumn
{
    public static readonly DependencyProperty VisibilityProperty = DependencyProperty.Register("Visibility", typeof(Visibility), typeof(DataGridTemplateColumn), new PropertyMetadata(Visibility.Visible, VisibilityChanged));
    public new Visibility Visibility
    {
        get { return (Visibility)GetValue(VisibilityProperty); }
        set { SetValue(VisibilityProperty, value); }
    }
    private static void VisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if ((DataGridTemplateColumn)d != null)
        {
            ((DataGridTemplateColumn)d).Visibility = (Visibility)e.NewValue;
        }
    }
}
1 голос
/ 23 февраля 2012

Обратите внимание, что проблема не просто в том, что 'Visibility' не является свойством зависимости. В DataGrid столбцы не являются частью визуального «дерева», поэтому вы не можете использовать AncestorType даже в WPF (или Silverlight 5).

Вот пара ссылок, связанных с WPF (пожалуйста, прокомментируйте, если какая-то из этих работ для Silverlight - извините, у меня нет времени сейчас тестировать)

Имеет действительно хорошее объяснение проблемы и сбоев некоторых решений (и умное решение): http://tomlev2.wordpress.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/

И пара вопросов StackOverflow:

WPF Скрыть DataGridColumn через привязку

Свойство Visible Visible элемента DataGridColumn в WPF DataGrid

1 голос
/ 13 января 2011

Решение GreatTall1 великолепно, но для его работы нужно немного измениться.

var n = d as Notifier;
if (n != null)
{
     //Assign value in the callback will break the binding.
     //n.MyVisibility = (Visibility)e.NewValue;
     n.PropertyChanged(n, new PropertyChangedEventArgs("Visibility"));
}
1 голос
/ 09 июля 2009

Крис Манчини,

вы не создаете привязку к свойству «Связывание» столбца сетки данных. Ну, вы пишете "{Binding User.UserName}", но он не создает привязку, потому что (как сказал Захарий) столбец сетки данных не наследуется от FrameworkElement и не имеет метода SetBinding. Поэтому выражение "{Binding User.UserName}" просто создает объект Binding и присваивает его свойству Binding столбца (это свойство типа Binding). Затем столбец таблицы данных при создании содержимого ячеек (метод GenerateElement - защищенный) использует этот объект Binding для установки привязки к сгенерированным элементам (например, к свойству Text сгенерированного TextBlock), которые являются FrameworkElements

0 голосов
/ 27 июня 2009

Из вашего класса MyDataGridTextColumn вы можете получить окружающий DataGrid. Затем вы извлекаете вашу ViewModel из DataContext из DataGrid и добавляете обработчик в событие PropertyChanged вашей ViewModel. В обработчике вы просто проверяете имя свойства и его значение и соответственно изменяете видимость столбца. Это не совсем лучшее решение, но оно должно работать;)

...