0

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 styles or value converters, it works only in some cases.

Everything works as expeted until I move the splitter, panels hide but leave white space (for the case #2/Styles), or doesn't hide one of the panels (for case #3/IValueConverter). Only the "code behind" way works correctly in all cases. The GIF image below illustrates the behavior.

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!

Mar
  • 941
  • 1
  • 10
  • 18
  • why don't you use just Visibility property of column? – Ugur Dec 07 '21 at 09:46
  • @Ugur, grid columns do not have Visibility. Here's the [article on CodeProject](https://www.codeproject.com/Articles/437237/WPF-Grid-Column-and-Row-Hiding) on how to add Visibility, but it still suggests the same - set Width to zero. – Mar Dec 07 '21 at 09:51
  • Sorry may bad. Instead of changing width of columns, you can rather hide gui element inside the grid. that means, you can set column with as "*" or "auto" then hide component inside. – Ugur Dec 07 '21 at 09:58
  • I have tried your suggestion, thank you! However, unfortunately, it works the same way - perfectly just after starting up, but if I move splitter, a white 'hole' remains on the screen (exactly as in case #2). – Mar Dec 07 '21 at 10:06

2 Answers2

1

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.

mm8
  • 163,881
  • 10
  • 57
  • 88
  • Thank you for your answer, it explains much. But I still have the question: why does the issue occur only if I move the Splitter right? If I move it left, the `GridSplitter` should set the local value of the `Width` as well and the panel shouldn't disappear, but it does. – Mar Dec 08 '21 at 11:49
1

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.

dba
  • 1,159
  • 1
  • 14
  • 22
  • Thank you, changing the 3rd column to `Auto` does the trick, thank you! But why? What is the difference here between `Star` and `Auto`? According to @mm8's answer, the 'GridSplitter` should set the internal `Width` of the `ColumnDefinition` anyway and, therefore, skip the `Width` set in `Style`. – Mar Dec 08 '21 at 11:53
  • for #3: I tried to redefine the Binding in `DragCompleted` (it rises after the `GridSplitter` was moved), and it works (with some complaints). However, the main idea of my question was to be able to show and hide panels mostly in XAML with a minimum of code - with the use of the boolean `IsPanelVisible` property in my ViewModel and optionally with a help of a `ValueConverter`. If to use code, the #1 approach probably would work better. – Mar Dec 08 '21 at 12:05