2009-06-25 1 views
15

У меня есть вопрос, подобный следующему сообщение:Как связать DataGridColumn.Visibility?

Silverlight DataGridTextColumn Binding Visibility

мне нужно иметь столбец в Silverlight DataGrid быть Запись видна/разрушился на основе значения в пределах ViewModel. Для этого я пытаюсь привязать свойство видимости к 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.

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

+0

Любое разрешение на это? Я не уверен, что происходит (dp выглядит хорошо для меня ...), но если вы не можете добавить новую строку DependencyProperty и привязать к ней, похоже, что это проблема. –

ответ

7

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

Во-первых, вам необходимо наследовать от 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 для скрытия столбцов на основе х: Имя в виде 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"}; 
} 

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

+0

Спасибо ... Я еще не реализовал его, но похоже, что это может сделать трюк! –

+1

Прекрасно работает с Silverlight .. :) Большое спасибо, вы сделали мой день .. !! –

3

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

+0

Спасибо захари. Если вы описываете ситуацию, можете ли вы описать, почему работает привязка к свойству «Связывание»? –

+0

извините ... Мне жаль, что я не знаю, но это то, где заканчивается знание моего Silverlight. – zachary

+0

Это неправда. Silverlight позволяет привязываться к зависимым свойствам DependencyObjects. Проблема заключается только в том, что свойство Visibility столбцов не DependencyProperties. – John

0

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

1

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

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

2

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

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

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

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

6

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

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

}

+0

Наконец-то решение, которое действительно работает! – Oskar

+0

Мне пришлось прокомментировать вторую последнюю строку (n.MyVisibility = ...), чтобы сделать последующие операции переключения. – Oskar

+0

Привет, это не работает для моего дела. Неудовлетворительно. –

1

решение 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

Обратите внимание, что проблема не такая простая, как «Видимость», не являющаяся свойством зависимости. В 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 Hide DataGridColumn via a binding

Binding Visible property of a DataGridColumn in WPF DataGrid

1

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

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