如何在 MVVM 中的一个 StackPanel 中动态设置 Extenders 内的 3 个 DataGrids 的高度?

how to set heights of 3 DataGrids inside Extenders in one StackPanel in MVVM dynamically?

每个 DataGrid 的高度应取决于:

  1. 是否扩展了其他扩展器
  2. DataGrid 有多少行
  3. 如果扩展其他Extender,其他DataGrids有多少行。
  4. Extender 所在的 StackPanel 的高度

所有扩展器应该始终可见。

如果例如只打开最上面的扩展器,它的 DataGrid 有 100 行,DataGrid 的高度应该几乎是 StackPanel 的高度,但在底部,其他两个扩展器应该是可见的并且最上面的 DataGrid 可以滚动。

当打开 2 或 3 个扩展器时,如果 DataGrids 中有足够的数据,StackPanel 应该 "full"。

我尝试绑定高度属性(用于最下面的扩展器对象信息)

    <Expander Header="Object information" IsExpanded="{Binding ObjectInformationExpanded}">
        <DataGrid Name="ObjectInformationGrid" CanUserAddRows="False" CanUserResizeColumns="True" CanUserSortColumns="True" IsReadOnly="True" 
               ItemsSource="{Binding SelectedObjectAttributes}" AutoGenerateColumns="False" 
                  Height="{Binding ObjectInformationHeight, Mode=TwoWay}"
                  ScrollViewer.CanContentScroll="True"
                  ScrollViewer.VerticalScrollBarVisibility="Visible">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Field" Binding="{Binding Item1}" />
                <DataGridTextColumn Header="Value" Binding="{Binding Item2}" />
            </DataGrid.Columns>
        </DataGrid>
</Expander>

但是现在我的ViewModel有很多和presentation(View)相关的代码,仍然没有把StackPanel的高度计算进去。相反,我设置了硬编码的双精度值,这不是很好的解决方案。

下面是一些简单的代码,当扩展器为 opened/closed 且 DataGrid 绑定集合发生更改时,它会更新绑定高度属性(此处仅适用于最上面的 DataGrid):

   private void UpdateHeights()
   {
        if (SelectedObjectsExpanded == true)
        {
            if (_selectedObjects.Count == 0)
            {
                SelectedObjectsHeight = double.NaN;
            }
            else if (_selectedObjects.Count < 8)
            {
                SelectedObjectsHeight = 100;
            }
            else
            {
                if (ObjectInformationExpanded && SelectedSwitchesExpanded)
                    SelectedObjectsHeight = 100;
                else
                    SelectedObjectsHeight = 200;
            }
        }
        //...

谢谢!

这样的东西行得通吗?

基本思路是将所有 3 个 Expanders 放在一个包含 3 行的 Grid 中,并将每行的 RowDefinition.Height 绑定到 Expander.IsExpanded。如果扩展器折叠(只占用需要的 space),则使用 Converter 将值设置为 Auto,如果展开(占用所有剩余的 space,则设置为 * ]。如果多行设置为*高度,则在它们之间平均共享space)。

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="{Binding IsExpanded, ElementName=Expander1, Converter={x:Static MyBoolToGridSizeConverter}}" />
        <RowDefinition Height="{Binding IsExpanded, ElementName=Expander2, Converter={x:Static MyBoolToGridSizeConverter}}" />
        <RowDefinition Height="{Binding IsExpanded, ElementName=Expander3, Converter={x:Static MyBoolToGridSizeConverter}}" />
    </Grid.RowDefinitions>

    <Expander Grid.Row="0" x:Name="Expander1">
        <DataGrid />
    </Expander>
    <Expander Grid.Row="1" x:Name="Expander2">
        <DataGrid />
    </Expander>
    <Expander Grid.Row="2" x:Name="Expander3">
        <DataGrid />
    </Expander>
</Grid>
public class BoolToGridSizeConverter : IValueConverter
{
    public object Convert(object value, Type targetType, 
        object parameter, CultureInfo culture)
    {
        if (value is bool && (bool)value)
            return new GridLength(1, GridUnitType.Star);

        return GridLength.Auto;
    }

    public object ConvertBack(object value, Type targetType, 
        object parameter, CultureInfo culture)
    {
        // this is not needed
    }
}