如何在 MVVM 中的一个 StackPanel 中动态设置 Extenders 内的 3 个 DataGrids 的高度?
how to set heights of 3 DataGrids inside Extenders in one StackPanel in MVVM dynamically?
每个 DataGrid 的高度应取决于:
- 是否扩展了其他扩展器
- DataGrid 有多少行
- 如果扩展其他Extender,其他DataGrids有多少行。
- 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
}
}
每个 DataGrid 的高度应取决于:
- 是否扩展了其他扩展器
- DataGrid 有多少行
- 如果扩展其他Extender,其他DataGrids有多少行。
- 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
}
}