Вопрос

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

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

Мне нужно, чтобы столбец в сетке данных 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 на "Свернутый", столбец фактически исчезнет.
  • Если я создам новое свойство DependencyProperty (т.е.string Foo), и привязавшись к нему, я также получаю исключение AG_E_PARSER_BAD_PROPERTY_VALUE.

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

Это было полезно?

Решение

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

Во-первых, вам нужно наследовать от 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;
        }
    }
}

В Таблица данных класс добавляет новый DP для скрытия столбцов на основе x:Имя из a DataGridColumn Столбец 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"};
}

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

Другие советы

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

Следует отметить одну вещь: наличие привязки к самому столбцу вместо сетки, вероятно, будет означать, что вы захотите использовать DataContextProxy - Файл DataContextProxy чтобы получить доступ к полю, к которому вы хотите привязать видимость (привязка столбца по умолчанию будет соответствовать области 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"));
            }
        }
    }
}

}

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

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

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 для viewmodel, к которому привязана сетка.Это, в свою очередь, вызывает метод изменения размера каждой модели представления столбцов сетки.Затем модель представления столбцов сетки поднимает ColumnResized событие, на которое подписан текстовый столбец таблицы данных, и его ширина обновляется.

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

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

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

Решение 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"));
}

Обратите внимание, что проблема не так проста, поскольку "Видимость" не является свойством зависимости.В 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 с помощью привязки

Привязка видимого свойства DataGridColumn в WPF DataGrid

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

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;
        }
    }
}

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top