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:26

I don't know how much this will help, but I've run into the lack of dependency property problem with data grid columns myself in my latest project. What I did to get around it, was to create an event in the grid column view model, then when the grid is being assembled in the client, use a closure to subscribe the grid column to the column view model. My particular problem was around width. It starts with the view model class for the grid column, which looks something like this pseudo-code:

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

Then there's the code that assembles the grid:

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

When the datagrid is resized in the application, the resize event is picked up and calls the resize method on the viewmodel the grid is bound to. This in turn calls the resize method of each grid column view model. The grid column view model then raises the ColumnResized event, which the data grid text column is subscribed to, and it's width is updated.

I realise this isn't directly solving your problem, but it was a way I could "bind" a view model to a data grid column when there are no dependency properties on it. The closure is a simple construct that nicely encapsulates the behaviour I wanted, and is quite understandable to someone coming along behind me. I think it's not too hard to imagine how it could be modified to cope with visibility changing. You could even wire the event handler up in the load event of the page/user control.

查看更多
Anthone
3楼-- · 2019-01-18 11:26

GreatTall1's solution is great, but it need to bit change to make it work.

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"));
}
查看更多
Fickle 薄情
4楼-- · 2019-01-18 11:32

Here's the solution I've come up with using a little hack.

First, you need to inherit from 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;
        }
    }
}

The DataGridEx class adds a new DP for hiding columns based on the x:Name of a DataGridColumn and its descendants.

To use in your 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>

You need to add these to your ViewModel or whatever data context you use.

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

To unhide, you only need to remove the corresponding name from the list or recreate it without the unhidden name.

查看更多
登录 后发表回答