简体   繁体   中英

How to bind the visibility of a Datagrid Column to a Checkbox in WPF

I have several columns in a DataGrid that I would like to be visible or hidden based on CheckBox for that column. I searched quite a bit beforehand and found a number of different answers but in each case they either didn't work or were targeting different circumstances.

If I understand correctly ElementName won't work because the Visual Tree isn't available. However I have tried setting the Source and RelativeSource without any luck. In its current state it simple returns the Error: Unresolved reference 'filterDuration'. The error stops the program from running.

Updated with Suggestions below and more information but still unresolved

Here is the Converter: (it never even steps into this)

[ValueConversion(typeof(bool), typeof(Visibility))]
public class FilterVisibilityConverter : IValueConverter
{ 
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        bool isSelected = System.Convert.ToBoolean(value);

        return isSelected ? Visibility.Visible : Visibility.Hidden;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        var v = (Visibility)value; 
        return v == Visibility.Hidden ? false : true;
    }
}

Here is the CheckBox:

<DataTemplate x:Key="Settings">
        <DockPanel>                 
            <GroupBox Header="Filter Columns" DockPanel.Dock="Right">
                <ag:AutoGrid Columns="150, 150, 150" RowCount="7" RowHeight="20"> 
                    <CheckBox Content="Id" />
                    <CheckBox Content="Desc" />
                    <CheckBox x:Name="filterDuration"  Content="Duration" />                          
                </ag:AutoGrid>
            </GroupBox>
        </DockPanel>
    </DataTemplate>

Here is the DataGrid: (Extra Columns and attributes removed for brevity)

<DockPanel Grid.Row="0" Grid.Column="0">
        <Expander  DockPanel.Dock="Top" Header="{x:Static p:Resources.shSettings}"  ContentTemplate="{StaticResource Settings}" IsExpanded="True" />
        <Border DockPanel.Dock="Top" Background="#FF595959">
            <TextBlock Text="{x:Static p:Resources.shViolations}" Style="{StaticResource SectionHeaderText}" /> 
        </Border>
        <DataGrid IsReadOnly="True" ItemsSource="{Binding items}" SelectedItem="{Binding Path=selecteditem}" AutoGenerateColumns="False"    >
            <DataGrid.Columns>
                <DataGridTextColumn Header="Duration" Binding="{Binding Duration}" Visibility="{Binding Source={x:Reference filterDuration}, Path=IsChecked, Converter={StaticResource FilterVisibility}}" />
                <DataGridTextColumn Header="CTG Id" Binding="{Binding Id}" />
            </DataGrid.Columns>
        </DataGrid>
    </DockPanel>

Your example works for me, you shouldn't let yourself get confused by the error in the designer, at runtime it works - x:Reference bindings seem to confuse the designer quite a bit.

Besides that, you have some minor errors in your value converter:

  • ConvertBack returning 'value' is wrong, it should convert a Visbility to a Boolean. You should implement this correctly because you made a two-way binding. (Though I'm not sure why you are doing that, I'd say a one-way binding is enough. The visibility of a column is not something which can changed by the user after all, so no need to propagate its changes back to the checkbox, right?)
  • You should declare what types your value converter handles in an attribute on the class: [ValueConversion(typeof(bool), typeof(Visibility))]

UPDATE

With your updated question you'll need a way to link the different contexts. If you can't do it by adding a property to your ViewModel you can still create a resource object. The other answer by Rohit Vats abuses a CheckBox as such a resource object. If you wanted a "cleaner" way just define a new DependencyObject subclass with a DependencyProperty for the checked state, then put an instance of that class into a static resource. This resource can then be used to have the controls communicate with each other.

Controls inside DataTemplate doesn't have same name scope and hence can't binded from controls outside it's name scope either via ElementName or x:Reference.

As a workaround you can achieve that by following way:

  1. Declare a dummy checkBox under resources section.
  2. Bind IsChecked of filterDuration with this dummy checkBox.
  3. Bind Visibility of DataGridTextColumn with dummy checkBox IsChecked property.

Code:

<DockPanel .....>
   <DockPanel.Resources>
      <CheckBox x:Key="DummyCheckBox"/>
   </DockPanel.Resources>
   .........
      <DataGridTextColumn Header="Duration" Binding="{Binding Duration}"
          Visibility="{Binding Source={StaticResource DummyCheckBox}, 
                  Path=IsChecked, Converter={StaticResource FilterVisibility}}"/>
   ......... 
</DockPanel>

and in DataTemplate :

<CheckBox x:Name="filterDuration" Content="Duration"
         IsChecked="{Binding IsChecked, Source={StaticResource DummyCheckBox}}"/>

Note - You can also declare the resource under window resources section in case it's not accessible from Dockpanel resources section in dataTemplate.


Obviously ideal solution would be to:

  1. Have bool property in ViewModel.
  2. Bind filterDuration checkBox to that bound property.
  3. Bind Visibility of dataGrid column to that bound bool property.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM