WPF:动态隐藏网格列并不总是使用 Styles 或 IValueConverter 正常工作

Posted

技术标签:

【中文标题】WPF:动态隐藏网格列并不总是使用 Styles 或 IValueConverter 正常工作【英文标题】:WPF: Dynamically hiding a grid column doesn't always work correctly using Styles or IValueConverter 【发布时间】:2022-01-12 09:08:14 【问题描述】:

我有一个包含三列的网格 - 左、右和中间的网格拆分器。我需要隐藏其中一个面板。我需要为两个 Splitter 列和 Panel 列设置 Width=0。但是,这种方法仅适用于代码。当我使用styles 或value converters 时,它只在某些情况下有效。

在我移动拆分器、面板隐藏但留下空白(对于案例#2/样式)或不隐藏其中一个面板(对于案例#3/IValueConverter)之前,一切都按预期工作。只有“代码背后”的方式在所有情况下都能正常工作。下面的 GIF 图片说明了这种行为。

代码如下所示。主要思想是将网格列的 Width 和 MaxWidth 属性设置为 0,然后将面板设置为 *,将拆分器设置为 Auto。

1.它的工作方式(代码隐藏):

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。它不起作用的方式(XAML 样式)

 <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。最后一个使用值转换器的场景

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();
     
 

我从调试案例 #3 中学到了另外一件事 - 在移动拆分器后尝试显示或隐藏面板时,Converted 只调用一次。

您能否告诉我发生了什么,为什么案例 #2 和 #3 不能正常工作?

演示项目的完整源代码在GitHub。

提前谢谢你!

【问题讨论】:

为什么不只使用列的 Visibility 属性? @Ugur,网格列没有可见性。这是关于如何添加可见性的article on CodeProject,但它仍然建议相同 - 将宽度设置为零。 抱歉可能不好。您可以将 gui 元素隐藏在网格内,而不是更改列的宽度。这意味着,您可以将列设置为“*”或“auto”,然后将组件隐藏在里面。 你的建议我试过了,谢谢!然而,不幸的是,它的工作方式相同 - 刚启动后就完美了,但如果我移动分离器,屏幕上会留下一个白色的“洞”(与情况 #2 完全相同)。 【参考方案1】:

我克隆了你的代码:

至于#2:

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

将第三列设置为“自动”就可以了

对于#3: 如果您调试解决方案并在转换器中设置断点,您会看到,在将拆分器移动到第三个块中之后,转换器仅到达一次,因此对于带有拆分器的列。移动拆分器会破坏第三列上的宽度绑定(正如@mm8 在他的回答中指出的那样)。

因此,您要么在拆分器移动后重新定义绑定(例如 PreviewMouseLeftButtonUp 事件),要么放弃这种方法。

【讨论】:

谢谢,将第 3 列更改为 Auto 可以解决问题,谢谢!但为什么? StarAuto 之间有什么区别?根据@mm8 的回答,'GridSplitter` 应该设置ColumnDefinition 的内部Width,因此,跳过Style 中设置的Width for #3:我尝试重新定义DragCompleted 中的绑定(在GridSplitter 被移动后它会上升),并且它有效(有一些抱怨)。但是,我的问题的主要思想是能够以最少的代码在 XAML 中显示和隐藏面板——在我的 ViewModel 中使用布尔值IsPanelVisible 属性,并可以选择在ValueConverter 的帮助下。如果使用代码,#1 方法可能会更好。【参考方案2】:

您能否告诉我发生了什么,为什么案例 #2 和 #3 不能正常工作?

因为GridSplitter 设置ColumnDefinitionWidth 属性的本地值,并且本地值始终优先于样式设置器值,如docs 中所述。

因此,您必须像在代码隐藏中那样设置 Width 属性的本地值。

【讨论】:

感谢您的回答,它解释了很多。但是我仍然有一个问题:为什么只有当我将拆分器向右移动时才会出现问题?如果我将它向左移动,GridSplitter 也应该设置Width 的本地值,并且面板不应该消失,但确实会消失。

以上是关于WPF:动态隐藏网格列并不总是使用 Styles 或 IValueConverter 正常工作的主要内容,如果未能解决你的问题,请参考以下文章

在网格中自动排列 wpf 控件

在函数中通过引用向 data.table 添加新列并不总是有效

在 WPF 中隐藏网格行

在 WPF 中隐藏网格行

动态加载资源字典文件到wpf应用程序会出错

如何通过 C# WPF 在 Excel 工作表中隐藏网格线