15

我有一个类似于以下帖子的问题:

Silverlight DataGridTextColumn 绑定可见性

我需要根据 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>

几个重要的事实。

  • 转换器确实在上面的本地资源中定义。
  • 转换器是正确的,它在解决方案中的许多其他地方使用。
  • 如果我将 MyVisibility 属性的 {Binding} 语法替换为“Collapsed”,则 Column 实际上会消失。
  • 如果我创建一个新的 DependencyProperty(即字符串 Foo)并绑定到它,我也会收到 AG_E_PARSER_BAD_PROPERTY_VALUE 异常。

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

4

9 回答 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类添加了一个新的DP,用于基于DataGridColumn及其后代的x:Name隐藏列。

要在 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"};
}

要取消隐藏,您只需从列表中删除相应的名称或在没有未隐藏名称的情况下重新创建它。

于 2010-09-20T17:50:15.060 回答
6

对于这个问题,我有另一个解决方案,它使用类似于您在 DataGridTextColumn 上找到的“绑定”属性的方法。由于列类是 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"));
            }
        }
    }
}

}

于 2010-10-20T01:05:42.820 回答
3

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

于 2009-06-26T20:11:36.260 回答
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); };
        }
    }
}

当数据网格在应用程序中调整大小时,resize 事件被拾取并在网格绑定到的视图模型上调用 resize 方法。这反过来调用每个网格列视图模型的调整大小方法。网格列视图模型然后引发ColumnResized事件,数据网格文本列被订阅,并且它的宽度被更新。

我意识到这并不能直接解决您的问题,但这是我可以在没有依赖属性时将视图模型“绑定”到数据网格列的一种方式。闭包是一个简单的构造,它很好地封装了我想要的行为,并且对于我身后的人来说是完全可以理解的。我认为不难想象如何对其进行修改以应对可见度的变化。您甚至可以在页面/用户控件的加载事件中连接事件处理程序。

于 2009-07-15T07:57:58.263 回答
1

克里斯·曼奇尼

您不创建与数据网格列的“绑定”属性的绑定。好吧,您编写“{Binding User.UserName}”,但它不会创建绑定,因为(正如 zachary 所说)datagrid 列没有从 FrameworkElement 继承并且没有 SetBinding 方法。因此表达式“{Binding User.UserName}”只是创建了 Binding 对象并将其分配给列的 Binding 属性(该属性是 Binding 的类型)。然后 datagrid 列在生成单元格内容时(GenerateElement - protected 方法)使用此 Binding 对象设置对作为 FrameworkElements 的生成元素(例如,在生成的 TextBlock 的 Text 属性上)的绑定

于 2009-07-09T10:04:31.670 回答
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"));
}
于 2011-01-13T05:39:48.780 回答
1

请注意,问题不仅仅是“可见性”不是依赖属性那么简单。在 DataGrid 中,列不是可视“树”的一部分,因此即使在 WPF(或 Silverlight 5)中也不能使用 AncestorType。

这是几个与 WPF 相关的链接(如果其中任何一个适用于 Silverlight,请发表评论 - 抱歉,我现在没有时间测试)

对某些解决方案的问题和失败有一个非常好的解释(以及一个聪明的解决方案): http ://tomlev2.wordpress.com/2011/03/21/wpf-how-to-bind-to-data-when-该-datacontext-is-not-inherited/

还有几个 StackOverflow 问题:

WPF 通过绑定隐藏 DataGridColumn

在 WPF DataGrid 中绑定 DataGridColumn 的 Visible 属性

于 2012-02-23T01:53:00.590 回答
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;
        }
    }
}
于 2012-03-21T20:00:03.800 回答
0

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

于 2009-06-27T15:50:31.163 回答