Home > other >  WPF: Dynamically hiding a grid column doesn't always work correctly using Styles or IValueConve
WPF: Dynamically hiding a grid column doesn't always work correctly using Styles or IValueConve

Time:12-08

I have a Grid with three columns - left, right, and a grid splitter in between. I need to hide one of the panels. What I need is to set Width=0 for two Splitter columns, and for the Panel column. However, this approach works well only from code. When I use Example of the issue

The code is shown below. The main idea is to set Width and MaxWidth properties for grid colums to 0, and then back to * for the panel, and to Auto for Splitter.

1. The way it works (code behind):

private void TogglePanelVisibility(bool isVisible)
{
    if (isVisible)
    {
        // Restore saved parameters:
        _mainWindow.ColumnPanel.Width = new GridLength(_columnPanelWidth, GridUnitType.Star);
        _mainWindow.ColumnPanel.MaxWidth = double.PositiveInfinity;

        _mainWindow.ColumnSplitter.Width = new GridLength(_columnSplitterWidth, GridUnitType.Auto);
        _mainWindow.ColumnSplitter.MaxWidth = double.PositiveInfinity;
        return;
    }

    // Save parameters:
    _columnSplitterWidth = _mainWindow.ColumnSplitter.Width.Value;
    _columnPanelWidth = _mainWindow.ColumnPanel.Width.Value;

    // Hide panel:
    _mainWindow.ColumnPanel.Width = new GridLength(0);
    _mainWindow.ColumnPanel.MaxWidth = 0;
    _mainWindow.ColumnSplitter.Width = new GridLength(0);
    _mainWindow.ColumnSplitter.MaxWidth = 0;
}

2. The way it doesn't work (XAML Style)

 <Window.Resources>
     <Style x:Key="showColumnStar" TargetType="{x:Type ColumnDefinition}">
         <Style.Setters>
             <Setter Property="Width" Value="*" />
             <Setter Property="MaxWidth" Value="{x:Static system:Double.PositiveInfinity}" />
         </Style.Setters>
         <Style.Triggers>
             <DataTrigger Binding="{Binding IsPanelVisible}" Value="False">
                 <DataTrigger.Setters>
                     <Setter Property="Width" Value="0" />
                     <Setter Property="MaxWidth" Value="0" />
                 </DataTrigger.Setters>
             </DataTrigger>
         </Style.Triggers>
     </Style>

     <Style x:Key="showColumnAuto" TargetType="{x:Type ColumnDefinition}">
         <Style.Setters>
             <Setter Property="Width" Value="Auto" />
             <Setter Property="MaxWidth" Value="{x:Static system:Double.PositiveInfinity}" />
         </Style.Setters>
         <Style.Triggers>
             <DataTrigger Binding="{Binding IsPanelVisible}" Value="False">
                 <DataTrigger.Setters>
                     <Setter Property="Width" Value="0" />
                     <Setter Property="MaxWidth" Value="0" />
                 </DataTrigger.Setters>
             </DataTrigger>
         </Style.Triggers>
     </Style>
 </Window.Resources>

 <!-- ... -->
 
 <Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="Auto" Style="{StaticResource showColumnAuto}" />
        <ColumnDefinition Width="*" Style="{StaticResource showColumnStar}" />
    </Grid.ColumnDefinitions>
    <!-- ... -->
</Grid>

3. And the last scenario using value converters

XAML:

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="{Binding IsPanelVisible, Converter={StaticResource BoolToGridSizeConverter}, ConverterParameter='Auto'}" />
        <ColumnDefinition Width="{Binding IsPanelVisible, Converter={StaticResource BoolToGridSizeConverter}, ConverterParameter='*'}" />
    </Grid.ColumnDefinitions>
    <!-- ... -->
</Grid>

C#:

internal class BoolToGridRowColumnSizeConverter : IValueConverter
 {
     public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
     {
         var param = parameter as string;
         var unitType = GridUnitType.Star;

         if (param != null && string.Compare(param, "Auto", StringComparison.InvariantCultureIgnoreCase) == 0)
         {
             unitType = GridUnitType.Auto;
         }

         return ((bool)value == true) ? new GridLength(1, unitType) : new GridLength(0);
     }

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

There is one more thing I learning from debugging the case #3 - on an attempt to show or hide the panel after moving the splitter, the Converted is calling only once.

Could you please tell me what is happening, why the cases #2 and #3 do not work properly?

The full source code of the demo project are on GitHub.

Thank you in advance!

CodePudding user response:

Could you please tell me what is happening, why the cases #2 and #3 do not work properly?

Because the GridSplitter sets the local value of the Width property of the ColumnDefinition and local values always take precedence over style setter values as explained in the docs.

So you have to set the local value of the Width property like you do in the code-behind.

CodePudding user response:

I cloned your Code:

as for #2:

        <Grid Grid.Row="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="Auto" Style="{StaticResource showColumnAuto}" />
                <ColumnDefinition Width="Auto" Style="{StaticResource showColumnStar}" /> 
            </Grid.ColumnDefinitions>

setting the 3rd column to "Auto" did the trick

for #3: if you debug your solution and set a breakpoint in your converter, you will see, that after moving your Splitter in the 3rd block, the converter is only reached once, so for the column with the splitter. Moving the splitter destroys your Width-binding on the 3rd Column (as @mm8 pointed out in his answer).

so either you manage to redefine your Binding after the splitter moved (e.g. PreviewMouseLeftButtonUp event) or just let this approach go.

  • Related