3

I have a window with a DataGrid that I want to hide certain columns based on the contents of the ObservableCollection that is the ItemSource for the DataGrid.

Based on this question: Conditional element in xaml depending on the binding content

I wrote a VisibilityConverter:

 public class StringLengthVisiblityConverter : IValueConverter
{
    public StringLengthVisiblityConverter() { }
    public Object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value == null || value.ToString().Length == 0)
        {
            return Visibility.Collapsed;
        }
        else
        {
            return Visibility.Visible;
        }
    }

    public Object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Here is the XAML:

        <DataGrid.Resources>
            <local:StringLengthVisiblityConverter x:Key="VisConverter"/>
        </DataGrid.Resources>
        <DataGrid.Columns>

            <DataGridTextColumn  Header="Switch Port" Binding="{Binding FCPort}"/>
            <DataGridTextColumn Width="*" Header="WWPN" Binding="{Binding Path=WWPN}" 
                         Visibility="{Binding Path=WWPN, Converter={StaticResource VisConverter}}"/>
            <DataGridTextColumn Header="FCID" Binding="{Binding Path=FCID}"
            Visibility="{Binding Path=FCID, Converter={StaticResource VisConverter}}"/>

        </DataGrid.Columns>
    </DataGrid>

I loaded the collection with instances of a class where the WWPN and FCID are both null. I expected those columns to be hidden in the datagrid, but they were still visible. I added a breakpoint to the VisbilityConverter and ran it through a debugger but it doesn't look like it's getting called.

Community
  • 1
  • 1
David Green
  • 1,210
  • 22
  • 38
  • 1
    Do you like to hide the full column, if all items are empty strings or null? Then you could bind to the Items (let the path empty) and create a MultiValueConverter iterating through your items. – WPFGermany Nov 16 '16 at 06:15
  • 1
    That's because `DataGridColumn` derives directly from `DependencyObject` and not `FrameworkElement` or `FrameworkContentElement`, thus the data context is not inherited. Check out the output window, you should be able to find a `System.Windows.Data: Error: 2` message there. – Grx70 Nov 16 '16 at 08:54
  • [Here's a good article](https://blogs.msdn.microsoft.com/jaimer/2008/11/22/forwarding-the-datagrids-datacontext-to-its-columns/) with explanation and a workaround for your exact problem. – Grx70 Nov 16 '16 at 09:24
  • @Grx70 - thanks. that answers the question. – David Green Nov 16 '16 at 12:23

1 Answers1

1

You need to check each value in the DataGridTextColumn and set Column.Visibility accordingly. To wrap all the logic I've derived from DataGrid.

public class MyDataGrid : DataGrid
{
    private void ValidateColumnVisibility()
    {
        if (Items.Count == 0 || Items[0] == CollectionView.NewItemPlaceholder) return;
        var itemClass = Items[0].GetType();
        foreach (var column in Columns)
        {
            if (column.GetType() == typeof(DataGridTextColumn))
            {
                // find the property that this column is bound to
                DataGridBoundColumn boundColumn = column as DataGridBoundColumn;
                Binding binding = boundColumn.Binding as Binding;
                string boundPropertyName = binding.Path.Path;
                var prop = itemClass.GetProperty(boundPropertyName);

                // Validating Column.Visibility when first value is null
                // when all values are null -> Visibility.Collapsed
                // bound value not a string -> Visibility.Visible
                // all bound strings empty  -> Visibility.Collapsed
                if (prop.GetValue(Items[0]) == null)
                {
                    column.Visibility = Visibility.Collapsed;
                    foreach (var item in Items)
                    {
                        if (item != CollectionView.NewItemPlaceholder)
                        {
                            if (prop.GetValue(item) != null)
                            {
                                if (prop.GetValue(item).GetType() != typeof(String))
                                    column.Visibility = Visibility.Visible;
                                else if (String.IsNullOrEmpty(prop.GetValue(item) as String) == false)
                                    column.Visibility = Visibility.Visible;
                            }
                        }
                    }
                }

                // First value not null and all bound strings empty -> Visibility.Collapsed
                else if (prop.GetValue(Items[0]).GetType() == typeof(String))
                {
                    column.Visibility = Visibility.Collapsed;
                    foreach (var item in Items)
                    {
                        if (item != CollectionView.NewItemPlaceholder)
                        {
                            if (String.IsNullOrEmpty(prop.GetValue(item) as String) == false)
                                column.Visibility = Visibility.Visible;
                        }
                    }
                }
            }
        }
    }

    protected override void OnItemsSourceChanged(
        IEnumerable oldValue, IEnumerable newValue)
    {
        base.OnItemsSourceChanged(oldValue, newValue);
        ValidateColumnVisibility();
    }

    protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
    {
        base.OnItemsChanged(e);
        ValidateColumnVisibility();
    }
}

Using MyDataGrid you no longer need to set Column.Visibility in XAML as it happens automagically.

Funk
  • 10,976
  • 1
  • 17
  • 33