32

I have the following object in App.xaml

<Application.Resources>
        <ResourceDictionary>
            <GridView x:Key="myGridView" x:Shared="false">
                             <GridViewColumn Header="Created" DisplayMemberBinding="{Binding Path=Created}"/>

... more code ...

And I use this grid view in multiple places. Example:

<ListView x:Name="detailList"   View="{StaticResource myGridView}" ...>

In one of the usages (such as detailList above), I'd like to hide the Created column, possibly using XAML?

Any ideas?

Dave Clemmer
  • 3,741
  • 12
  • 49
  • 72
bh213
  • 6,343
  • 9
  • 43
  • 52

11 Answers11

21

Actually, I find the easiest solution is via attached properties:

public class GridViewColumnVisibilityManager
{       
    static void UpdateListView(ListView lv)
    {
        GridView gridview = lv.View as GridView;
        if (gridview == null || gridview.Columns == null) return;
        List<GridViewColumn> toRemove = new List<GridViewColumn>();
        foreach (GridViewColumn gc in gridview.Columns)
        {
            if (GetIsVisible(gc) == false)
            {
                toRemove.Add(gc);
            }
        }
        foreach (GridViewColumn gc in toRemove)
        {
            gridview.Columns.Remove(gc);
        }
    }

    public static bool GetIsVisible(DependencyObject obj)
    {
        return (bool)obj.GetValue(IsVisibleProperty);
    }

    public static void SetIsVisible(DependencyObject obj, bool value)
    {
        obj.SetValue(IsVisibleProperty, value);
    }

    public static readonly DependencyProperty IsVisibleProperty =
        DependencyProperty.RegisterAttached("IsVisible", typeof(bool), typeof(GridViewColumnVisibilityManager), new UIPropertyMetadata(true));


    public static bool GetEnabled(DependencyObject obj)
    {
        return (bool)obj.GetValue(EnabledProperty);
    }

    public static void SetEnabled(DependencyObject obj, bool value)
    {
        obj.SetValue(EnabledProperty, value);
    }

    public static readonly DependencyProperty EnabledProperty =
        DependencyProperty.RegisterAttached("Enabled", typeof(bool), typeof(GridViewColumnVisibilityManager), new UIPropertyMetadata(false,
            new PropertyChangedCallback(OnEnabledChanged)));

        private static void OnEnabledChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        ListView view = obj as ListView;
        if (view != null)
        {
            bool enabled = (bool)e.NewValue;
            if (enabled)
            {
                view.Loaded += (sender, e2) =>
                {
                    UpdateListView((ListView)sender);
                };
                view.TargetUpdated += (sender, e2) =>
                {
                    UpdateListView((ListView)sender);
                };
                view.DataContextChanged += (sender, e2) =>
                {
                    UpdateListView((ListView)sender);
                };
            }
        }
    }
}

Then, it can be used as so:

<ListView foo:GridViewColumnVisibilityManager.Enabled="True">
...
<GridViewColumn Header="Status" foo:GridViewColumnVisibilityManager.IsVisible="{Binding ShowStatusColumn}">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate> ...
Ben McMillan
  • 578
  • 4
  • 12
  • 2
    my implementation expects the visibility of a column to be static for a load - if you expect this to change, however, you may need to fix UpdateListView() to do something else to hide and show the column (Width=0). – Ben McMillan Jun 21 '10 at 20:59
  • This is the best solution I've found so far, and I've managed to get it to work with dynamic changing of Visibile property. See my answer. It's particularly usefull if you want to hide by default and later show the column if necessary. – surfen Mar 09 '12 at 13:28
  • This solution does not work for me. When I debugged, I found out that method UpdateListView is called first and IsVisible property value is get afterwards. I guess this is reason in my case but I don't know why? Could anybody explain why it may happen? – Pegieo Jun 30 '14 at 09:42
13

Based on Ben McMillan's answer, but supports dynamic changing of visible property. I've simplified his solution further by removing the IsEnabled property.

public class GridViewColumnVisibilityManager
{
    static Dictionary<GridViewColumn, double> originalColumnWidths = new Dictionary<GridViewColumn, double>();

    public static bool GetIsVisible(DependencyObject obj)
    {
        return (bool)obj.GetValue(IsVisibleProperty);
    }

    public static void SetIsVisible(DependencyObject obj, bool value)
    {
        obj.SetValue(IsVisibleProperty, value);
    }

    public static readonly DependencyProperty IsVisibleProperty =
        DependencyProperty.RegisterAttached("IsVisible", typeof(bool), typeof(GridViewColumnVisibilityManager), new UIPropertyMetadata(true, OnIsVisibleChanged));

    private static void OnIsVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        GridViewColumn gc = d as GridViewColumn;
        if (gc == null)
            return;

        if (GetIsVisible(gc) == false)
        {
            originalColumnWidths[gc] = gc.Width;
            gc.Width = 0;
        }
        else
        {
            if (gc.Width == 0)
                gc.Width = originalColumnWidths[gc];
        }
    }
}
Community
  • 1
  • 1
surfen
  • 4,644
  • 3
  • 34
  • 46
  • Will this not leak memory? You are storing the control itself in a static dictionary... – Rashack Mar 26 '14 at 12:43
  • 1
    I think you're right. One way to avoid this is to use ConditionalWeakTable instead of Dictionary: http://msdn.microsoft.com/en-us/library/dd287757%28v=vs.100%29.aspx. Another option is to create a second attached property to keep original width. – surfen Mar 29 '14 at 18:00
  • 1
    Setting the column width to 0 also has another side effect: The gripper of this column is hiding the gripper of the column to the left. So to resize the column to the left, the user has to resize this column first to reach the gripper of the left column. – pogosama Aug 12 '15 at 14:10
  • 1
    Does not work... The user can resize the column to view the contents. – SINGULARITY Jan 27 '16 at 15:39
  • I had the same problem with resize, If found a solution. See [on that topic](http://stackoverflow.com/a/39014317/6479770) – A.Pissicat Aug 18 '16 at 09:17
6

You best bet is probably to create a custom control by inheriting from the GridView class, adding the required columns, and exposing a meaningful property to show/hide a particular column. Your custom GridView class could look like this:

using System;
using System.Windows.Controls;

namespace MyProject.CustomControls
{
    public class CustomGridView : GridView
    {
        private GridViewColumn _fixedColumn;
        private GridViewColumn _optionalColumn;

        public CustomGridView()
        {
            this._fixedColumn = new GridViewColumn() { Header = "Fixed Column" };
            this._optionalColumn = new GridViewColumn() { Header = "Optional Column" };

            this.Columns.Add(_fixedColumn);
            this.Columns.Add(_optionalColumn);
        }

        public bool ShowOptionalColumn
        {
            get { return _optionalColumn.Width > 0; }
            set
            {
                // When 'False' hides the entire column
                // otherwise its width will be set to 'Auto'
                _optionalColumn.Width = (!value) ? 0 : Double.NaN;
            }
        }

    }
}

Then you can simply set that property from XAML like in this example:

<Window x:Class="WpfApplication1.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:cc="clr-namespace:MyProject.CustomControls"
        Title="Window1"
        Height="300"
        Width="300">
    <StackPanel>
        <ListView>
            <ListView.View>
                <cc:CustomGridView ShowOptionalColumn="False" />
            </ListView.View>
        </ListView>

        <ListView>
            <ListView.View>
                <cc:CustomGridView ShowOptionalColumn="True" />
            </ListView.View>
        </ListView>
    </StackPanel>
</Window>

Optionally, you could make the 'CustomGridView.ShowOptionalColumn' a DependencyProperty to be able to use it as a binding target.

Enrico Campidoglio
  • 56,676
  • 12
  • 126
  • 154
4

Taken from here

<ListView Grid.Column="1" Grid.Row="1"  Name="FicheList" >
            <ListView.Resources>
                <ResourceDictionary>
                    <Style x:Key="hiddenStyle" TargetType="GridViewColumnHeader">
                        <Setter Property="Visibility" Value="Collapsed"/>
                    </Style>
                </ResourceDictionary>
            </ListView.Resources>
            <ListView.View>
                <GridView>
                    <GridViewColumn DisplayMemberBinding="{Binding Code}" Header="Code" Width="0" HeaderContainerStyle="{StaticResource hiddenStyle}" />
                    <GridViewColumn DisplayMemberBinding="{Binding FicheTitle}" Header="Title" Width="100" />
                    <GridViewColumn DisplayMemberBinding="{Binding CategoryName}" Header="Category" Width="100" />
                    <GridViewColumn DisplayMemberBinding="{Binding UpdateDate}" Header="Update Date" Width="100" />

                </GridView>
            </ListView.View>
        </ListView>
Saber
  • 2,440
  • 1
  • 25
  • 40
2

I have a much simpler solution than using an Attached Behavior.

All you have to do is bind the Width Property of the GridViewColumn to a boolean on your ViewModel. Then create a simple Converter like BooleanToWidthConverter that takes a boolean and returns a double, zero if its false, x width if its true.

I hope this helps and makes your life easier.

XAML:

<GridViewColumn x:Name="MyHiddenGridViewColumn"
                Width={Binding Path=IsColumnVisibleProperty, Converter={StaticResource BooleanToWidthConverter}}">
   <!-- GridViewColumn.HeaderTemplate etc. goes here. -->
</GridViewColumn>

Converter:

public class BooleanToWidthConverter : IValueConverter
    {
        private const double Column_Width = 40.0;

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value != null && value != DependencyProperty.UnsetValue)
            {
                bool isVisible = (bool) value;

                return isVisible ? Column_Width : 0;
            }
            return Column_Width;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
Jim Kniest
  • 91
  • 5
1

This is my code , it works very well in my project. if you don't like to add some external code.

    /// <summary>
    /// show/hide datagrid column
    /// </summary>
    /// <param name="datagrid"></param>
    /// <param name="header"></param>
    private void ToggleDataGridColumnsVisible()
    {
        if (IsNeedToShowHideColumn())
        {
            foreach (GridViewColumn column in ((GridView)(this.ListView1.View)).Columns)
            {
                GridViewColumnHeader header = column.Header as GridViewColumnHeader;
                if (header != null)
                {
                    string headerstring = header.Tag.ToString();

                    if (!IsAllWaysShowingHeader(headerstring ) )
                    {
                        if (IsShowingHeader())
                        {

                        }
                        else
                        {
                            //hide it
                            header.Template = null;
                            column.CellTemplate = null;
                            column.Width = 0;
                        }
                    }
                }

            }

        }
    }
ariso
  • 1,433
  • 6
  • 21
  • 35
0

In a small utility I wrote, I have a list view where the user can hide/show some columns. There is no Visibility property on the columns, so I decided to set the hidden columns width to zero. Not ideal, as the user can still resize them and make them visible again.

Anyway, to do this, I used:

<GridViewColumn.Width>
    <MultiBinding Converter="{StaticResource WidthConverter}" Mode="TwoWay">
        <Binding Path="ThreadIdColumnWidth" Mode="TwoWay" />
        <Binding Path="IsThreadIdShown" />
    </MultiBinding>
</GridViewColumn.Width>

IsThreadIdShown is bound to a check box on the toolbar. And the multi-value converter is:

public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
    if (values.Length != 2) {
        return null;
    }

    object o0 = values[0];
    object o1 = values[1];

    if (! (o1 is bool)) {
        return o0;
    }
    bool toBeDisplayed = (bool) o1;
    if (! toBeDisplayed) {
        return 0.0;
    }

    if (! (o0 is double)) {
        return 0;
    }

    return (double) o0;
}

public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) {

    return new object[] { (double)value, Binding.DoNothing };
}
Timores
  • 14,439
  • 3
  • 46
  • 46
0

This works for me
Need to bind the Visibility on both the header and the content
In this case it is at the end so I don't worry about the Width
BUT the user does not get a UI hook to reset the width so if you set the width to zero it is gone

<GridViewColumn Width="60">
    <GridViewColumnHeader HorizontalContentAlignment="Stretch" HorizontalAlignment="Stretch"
                            Visibility="{Binding Source={x:Static Application.Current}, Path=MyGabeLib.CurUser.IsInRoleSysAdmin, Converter={StaticResource bvc}}">
        <TextBlock>WS<LineBreak/>Count</TextBlock>
    </GridViewColumnHeader>
    <GridViewColumn.CellTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Path=WordScoreCount, StringFormat={}{0:N0}}" HorizontalAlignment="Right"
                        Visibility="{Binding Source={x:Static Application.Current}, Path=MyGabeLib.CurUser.IsInRoleSysAdmin, Converter={StaticResource bvc}}"/>
        </DataTemplate>
    </GridViewColumn.CellTemplate>
</GridViewColumn>
paparazzo
  • 44,497
  • 23
  • 105
  • 176
0
<GridViewColumn Width="{Binding Tag, RelativeSource={RelativeSource AncestorType=ListView}, Converter={converters:BooleanToWidthConverter}, ConverterParameter=100}">
                            <GridViewColumn.HeaderContainerStyle>
                                <Style TargetType="{x:Type GridViewColumnHeader}" BasedOn="{StaticResource ColumnHeaderStyle}">
                                    <Setter Property="IsEnabled" Value="False"/>
                                    <Style.Triggers>
                                        <DataTrigger Binding="{Binding Tag, RelativeSource={RelativeSource AncestorType=ListView}}" Value="true">
                                            <Setter Property="IsEnabled" Value="True"/>
                                        </DataTrigger>
                                    </Style.Triggers>
                                </Style>
                            </GridViewColumn.HeaderContainerStyle>
                            <GridViewColumn.Header>
                                <StackPanel Tag="columnHeader" Orientation="Horizontal">
                                </StackPanel>
                            </GridViewColumn.Header>
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <ContentControl>
                                        <TextBlock Text="test" />
                                    </ContentControl>
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>

class BooleanToWidthConverter :IValueConverter {

    public object Convert (object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is bool b)
        {
            return b ? parameter : 0;
        }
        return 0;
    }

    public object ConvertBack (object value, Type targetType, object parameter, CultureInfo culture)
    {
        return null;
    }
}

Unfortunately, there is no "IsVisible" Property in GridViewColumn But, I have a solution can solve this problem by simple and complete way:

  1. Setting width to 0. Many Dev just stop at this step, because it seems to have been hidden but not. We still make it extendible hence bringing it back on the UI by resize column, so we need to do more step 2.
  2. Disable resize GridViewColumn by set GridViewColumnHeader IsEnabled = false.

Code sample above:

Luke Le
  • 728
  • 1
  • 9
  • 24
0

A bit late, but I thought this might still be useful:

Based on the code (and comments) of A.Pissicat, Surfen and Ben McMillan, I've updated GridViewColumnVisibilityManager as follows:

    /// <summary>
    /// Used to hide the attached <see cref="GridViewColumn"/> and prevent the user
    /// from being able to access the column's resize "gripper" by
    /// temporarily setting both the column's width and style to
    /// a value of 0 and null (respectively) when IsVisible
    /// is set to false. The prior values will be restored
    /// once IsVisible is set to true.
    /// </summary>
    public static class GridViewColumnVisibilityManager
    {
        public static readonly DependencyProperty IsVisibleProperty =
            DependencyProperty.RegisterAttached(
                "IsVisible",
                typeof(bool),
                typeof(GridViewColumnVisibilityManager),
                new UIPropertyMetadata(true, OnIsVisibleChanged));

        private static readonly ConditionalWeakTable<GridViewColumn, ColumnValues> OriginalColumnValues = new ConditionalWeakTable<GridViewColumn, ColumnValues>();

        public static bool GetIsVisible(DependencyObject obj) => (bool)obj.GetValue(IsVisibleProperty);

        public static void SetIsVisible(DependencyObject obj, bool value) => obj.SetValue(IsVisibleProperty, value);

        private static void OnIsVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (!(d is GridViewColumn gridViewColumn))
            {
                return;
            }

            if (!GetIsVisible(gridViewColumn))
            {
                // Use GetOrCreateValue to ensure
                // we have a place to cache our values.
                // Any previous values will be overwritten.
                var columnValues = OriginalColumnValues.GetOrCreateValue(gridViewColumn);
                columnValues.Width = gridViewColumn.Width;
                columnValues.Style = gridViewColumn.HeaderContainerStyle;

                // Hide the column by
                // setting the Width to 0.
                gridViewColumn.Width = 0;

                // By setting HeaderContainerStyle to null,
                // this should remove the resize "gripper" so
                // the user won't be able to resize the column
                // and make it visible again.
                gridViewColumn.HeaderContainerStyle = null;
            }
            else if (gridViewColumn.Width == 0
                     && OriginalColumnValues.TryGetValue(gridViewColumn, out var columnValues))
            {
                // Revert back to the previously cached values.
                gridViewColumn.HeaderContainerStyle = columnValues.Style;
                gridViewColumn.Width = columnValues.Width;
            }
        }

        private class ColumnValues
        {
            public Style Style { get; set; }

            public double Width { get; set; }
        }
    }

If you wish to use a particular HeaderContainerStyle Style instead of null when hiding the column, then replace:

gridViewColumn.HeaderContainerStyle = columnValues.Style;

with

gridViewColumn.HeaderContainerStyle = Application.Current.TryFindResource(@"disabledColumn") as Style;

and change @"disabledColumn" to whatever name you want to use.

Grahamvs
  • 669
  • 6
  • 16
0

I'd suggest using a custom property (or hijacking an existing one) on the parent and then using a custom style on the gridviewcolumnheader to reference that ancestor property. Like this:

<Window.Resources>
    <Style TargetType="{x:Type GridViewColumnHeader}">
        <Setter Property="Visibility" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}, Path=Tag}"/>
    </Style>
    <GridView x:Key="myGridView" x:Shared="false">                             
        <GridViewColumn Header="Created" DisplayMemberBinding="{Binding Path=Created}"/>    
    </GridView>
</Window.Resources>
<Grid x:Name="LayoutRoot">
    <StackPanel>
        <ListView x:Name="detailList"   View="{StaticResource myGridView}"/>
        <ListView x:Name="detailListHide" Tag="{x:Static Member=Visibility.Hidden}" View="{StaticResource myGridView}"/>
    </StackPanel>
</Grid>
Ben Reierson
  • 1,099
  • 6
  • 8