How to bind DataGridColumn.Visibility?

2019-01-18 10:36发布

I have an issue similar to the following post:

Silverlight DataGridTextColumn Binding Visibility

I need to have a Column within a Silverlight DataGrid be visibile/collapsed based on a value within a ViewModel. To accomplish this I am attempting to Bind the Visibility property to a ViewModel. However I soon discovered that the Visibility property is not a DependencyProperty, therefore it cannot be bound.

To solve this, I attempted to subclass my own DataGridTextColumn. With this new class, I have created a DependencyProperty, which ultimately pushes the changes to the DataGridTextColumn.Visibility property. This works well, if I don't databind. The moment I databind to my new property, it fails, with a AG_E_PARSER_BAD_PROPERTY_VALUE exception.

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
}

Here is a small snippet of the 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>

A couple important facts.

  • The Converter is indeed defined above in the local resources.
  • The Converter is correct, it is used many other places in the solution.
  • If I replace the {Binding} syntax for the MyVisibility property with "Collapsed" the Column does in fact disappear.
  • If I create a new DependencyProperty (i.e. string Foo), and bind to it I receive the AG_E_PARSER_BAD_PROPERTY_VALUE exception too.

Does anybody have any ideas as to why this isn't working?

9条回答
男人必须洒脱
2楼-- · 2019-01-18 11:08

Note that the problem isn't just as simple as 'Visibility' not being a dependency property. In a DataGrid the columns aren't part of the visual 'tree' so you can't use AncestorType even in WPF (or Silverlight 5).

Here's a couple WPF related links (please comment if any of these work for Silverlight - sorry I don't have time to test now)

Has a really nice explanation of the problem and failures of certain solutions (and a clever solution): http://tomlev2.wordpress.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/

And a couple StackOverflow questions:

WPF Hide DataGridColumn via a binding

Binding Visible property of a DataGridColumn in WPF DataGrid

查看更多
\"骚年 ilove
3楼-- · 2019-01-18 11:08

From your MyDataGridTextColumn class, you could get the surrounding DataGrid. Then you get your ViewModel out of the DataContext of the DataGrid and add a handler to the PropertyChanged event of your ViewModel. In the handler you just check for the property name and its value and change the Visibility of the Column accordingly. Its not quite the best solution, but it should work ;)

查看更多
做自己的国王
4楼-- · 2019-01-18 11:12

The datagrid column inherits from DependencyObject instead of FrameworkElement. In WPF this would be no big deal... but in silverlight you can only bind to FrameworkElement objects. So you get the descriptive error message of AG_E_PARSER_BAD_PROPERTY_VALUE when you try.

查看更多
【Aperson】
5楼-- · 2019-01-18 11:15

I have another solution to this problem that uses an approach similar to the "Binding" property that you find on DataGridTextColumn. Since the column classes are DependencyObjects, you can't directly databind to them, BUT if you add a reference to a FrameworkElement that implements INotifyPropertyChanged you can pass a databinding through to the element, and then use a dependency property to notify the Column that the databinding has changed.

One thing to note is that having the binding on the Column itself instead of the Grid will probably mean that you will want to use a DataContextProxy to get access to the field that you want to bind the Visibility to (the column binding will default to the scope of the 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"));
            }
        }
    }
}

}

查看更多
祖国的老花朵
6楼-- · 2019-01-18 11:17

This works on a data grid template column:

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;
        }
    }
}
查看更多
倾城 Initia
7楼-- · 2019-01-18 11:25

Chris Mancini,

you do not create binding to "Binding" property of data grid column. Well, you write "{Binding User.UserName}", but it doesn't create binding, because (as zachary said) datagrid column doesn't inherit from FrameworkElement and hasn't SetBinding method. So expression "{Binding User.UserName}" simply creates Binding object and assign it to Binding property of column (this property is type of Binding). Then datagrid column while generates cells content (GenerateElement - protected method) uses this Binding object to set binding on generated elements (e.g. on Text property of generated TextBlock) which are FrameworkElements

查看更多
登录 后发表回答