如何绑定DataGridColumn的可见性?

15

我遇到了与以下帖子类似的问题:

Silverlight DataGridTextColumn Binding Visibility

我希望根据 ViewModel 中的某个值来控制 Silverlight DataGrid 中的一列是否可见。为了实现这一点,我尝试将 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>

几个重要的事实。

  • 转换器确实在本地资源中定义。
  • 转换器是正确的,在解决方案的许多其他地方都被使用。
  • 如果我用 "Collapsed" 替换 MyVisibility 属性的 {Binding} 语法,该列确实会消失。
  • 如果我创建一个新的 DependencyProperty(例如 string Foo),并绑定它,我也会收到 AG_E_PARSER_BAD_PROPERTY_VALUE 异常。

有人对为什么这不起作用有任何想法吗?


有关这个问题有任何解决方案吗?我不确定发生了什么(dp 看起来没问题...),但如果您无法添加新的字符串 DependencyProperty 并绑定到它,那似乎就是问题所在。 - Erik Mork
9个回答

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 类增加了一个新的依赖属性,可以根据 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"};
}

要取消隐藏,您只需要从列表中删除相应的名称或重新创建它而不包括取消隐藏的名称。

谢谢...我还没有实现它,但它看起来可能会解决问题! - Chris Mancini
1
非常适用于Silverlight.. :) 非常感谢,你让我的一天变得完美..!! - Naresh Ravlani

6
我有另一种解决这个问题的方法,它使用了与DataGridTextColumn上的“Binding”属性类似的方法。由于列类是DependencyObjects,您无法直接对其进行数据绑定,但是如果您添加一个实现INotifyPropertyChanged的FrameworkElement引用,就可以将数据绑定传递到元素中,然后使用依赖属性来通知Column数据绑定已更改。
需要注意的一点是,将绑定放在Column本身而不是Grid上可能意味着您需要使用DataContextProxy来访问要将Visibility绑定到的字段(列绑定将默认为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"));
            }
        }
    }
}

}


我不得不注释掉倒数第二行(n.MyVisibility = ...),以使后续的切换工作正常。 - Oskar

3

datagrid列继承自DependencyObject而不是FrameworkElement。在WPF中,这没什么大不了的...但在Silverlight中,你只能绑定到FrameworkElement对象。因此,当你尝试时,会得到AG_E_PARSER_BAD_PROPERTY_VALUE的描述性错误消息。


谢谢 Zachary。如果您所描述的情况属实,能否说明一下为什么“Binding”属性的绑定有效? - Chris Mancini
抱歉...我希望我知道,但那就是我的Silverlight知识的尽头。 - Xander
这并不是真的。Silverlight 允许绑定到 DependencyObjects 的依赖属性。问题只是列的 Visibility 属性不是 DependencyProperties。 - John

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

当应用程序中调整数据网格大小时,会捕获调整大小事件并调用网格绑定的视图模型上的调整大小方法。然后,视图模型会调用每个网格列视图模型的调整大小方法。网格列视图模型然后引发“ColumnResized”事件,该事件由数据网格文本列订阅,并更新其宽度。
我意识到这并没有直接解决你的问题,但这是我可以在没有依赖属性的情况下将视图模型“绑定”到数据网格列的一种方式。闭包是一个简单的构造,很好地封装了我想要的行为,并且对于跟随我的人来说也很容易理解。我认为很容易想象如何修改它以处理可见性更改。您甚至可以在页面/用户控件的加载事件中连接事件处理程序。

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

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

Chris Mancini,

你没有为数据网格列创建“Binding”属性的绑定。虽然你写了“{Binding User.UserName}”,但它并没有创建绑定,因为(正如Zachary所说)数据网格列不继承自FrameworkElement,并且没有SetBinding方法。 因此,“{Binding User.UserName}”表达式仅创建Binding对象并将其分配给列的Binding属性(此属性是Binding类型)。 然后,当数据网格列生成单元格内容(GenerateElement - 受保护的方法)时,使用此Binding对象在生成的元素上设置绑定(例如,在生成的TextBlock的Text属性上),这些元素是FrameworkElements。


1
请注意,问题并不仅仅是“Visibility”不是依赖属性这么简单。在DataGrid中,列不是视觉“树”的一部分,因此即使在WPF(或Silverlight 5)中也不能使用AncestorType。
以下是几个与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

0
从您的MyDataGridTextColumn类中,您可以获取周围的DataGrid。 然后,您可以从DataGrid的DataContext中获取ViewModel,并向其添加PropertyChanged事件处理程序。在处理程序中,您只需检查属性名称及其值,并相应地更改列的可见性。 这不是最好的解决方案,但它应该可以工作;)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接