Pokud dynamicky měníte velikost buňky v Silverlight DataGrid kontrolu, narazíte na problém, že výška řádku data gridu se podle potřeby správně zvětšuje, ale už se zpátky nezmenšuje.
Vyvolání chyby
Pro vyvolání tohoto stavu použijeme následující příklad. DataGrid obsahuje jeden sloupec s wrap textem, který dynamicky podle šířky sloupce mění počet řádků ve kterých je text zobrazen, a jeden sloupec s tlačítkem pro změnu velikosti buňky.
<sdk:DataGrid x:Name="grdTest" Margin="30" AutoGenerateColumns="False" CanUserReorderColumns="False" CanUserResizeColumns="True" CanUserSortColumns="False" >
<sdk:DataGrid.Columns>
<sdk:DataGridTemplateColumn Header="Text">
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="TextBlock text" Margin="4" VerticalAlignment="Center" HorizontalAlignment="Left"/>
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
</sdk:DataGridTemplateColumn>
<sdk:DataGridTemplateColumn Header="Wrap text" Width="150">
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="Wrap TextBlock text" Margin="4" VerticalAlignment="Center" HorizontalAlignment="Left" TextWrapping="Wrap"/>
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
</sdk:DataGridTemplateColumn>
<sdk:DataGridTemplateColumn Header="Resize button">
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid VerticalAlignment="Center">
<StackPanel Margin="4" Orientation="Horizontal" VerticalAlignment="Center" Height="20">
<ToggleButton Margin="20,0,20,0" Padding="10,0,10,0" Content="Resize" Click="ResizeButton_Click" />
</StackPanel>
</Grid>
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
</sdk:DataGridTemplateColumn>
</sdk:DataGrid.Columns>
</sdk:DataGrid>
private void ResizeButton_Click(object sender, RoutedEventArgs e)
{
var button = (ToggleButton)sender;
var panel = (StackPanel)VisualTreeHelper.GetParent(button);
panel.Height = button.IsChecked.Value ? 50 : 20;
}
Výsledek po dvojím stisknutí tlačítka Resize tj. 3 řádek je stejně roztažený jako řádek 2:
Příčina
Ve třídě DataGridCellsPresenter je v metodě MeasureOverride vypočítávaná velikost buněk DataGridu, používá se zde pomocná vlastnost DesiredHeight. Problém spočívá v tom, že tato vlastnost DesiredHeight není pro nás případ, kdy má DataGrid RowHeight hodnotu Auto (if (double.IsNaN(this.OwningGrid.RowHeight))), nulována. Problém je také popsán zde (ale pro Silverlight Toolkit DataGrid), nicméně uvedená řešení jsou buď nefunkční nebo nevyhovující pro obecný případ.
Řešení
Řešení spočívá v úpravě DataGrid controlu jeho poděděním. V něm budeme řešit následující:
- Odchycení události SizeChanged na elementech (CellContent) jednotlivých buněk (odchytávat SizeChanged celého řádku nám nestačí).
- Vyvolání přepočtu velikosti řádku DataGridu pomoci metod InvalidateMeasure a Measure při nastavení dataGrid.RowHeight = 0 (způsobí v DataGridCellsPresenter shození vlastnosti DesiredHeight).
Kód třídy FixedDataGrid vypadá následovně:
/// <summary>
/// Fixed DataGrid control
/// </summary>
/// <remarks>
/// Fixing issue: DataGrid Row Auto-Resize only grows row height but won't shrink
/// </remarks>
public class FixedDataGrid : DataGrid
{
/// <summary>
/// Overrides OnLoadingRow
/// </summary>
protected override void OnLoadingRow(DataGridRowEventArgs e)
{
if (double.IsNaN(this.RowHeight))
{
e.Row.Loaded += Row_Loaded;
}
base.OnLoadingRow(e);
}
private void Row_Loaded(object sender, RoutedEventArgs e)
{
var row = (DataGridRow)sender;
row.Loaded -= Row_Loaded;
for (int col = 0; col < this.Columns.Count; col++)
{
var cellElement = this.Columns[col].GetCellContent(row);
if (cellElement != null)
{
cellElement.SizeChanged += CellElement_SizeChanged;
}
}
}
private static void CellElement_SizeChanged(object sender, SizeChangedEventArgs e)
{
//Fix issue: DataGrid Row Auto-Resize only grows row height but won't shrink
var dataGrid = GetParentOf<DataGrid>((FrameworkElement)sender);
if (dataGrid != null && double.IsNaN(dataGrid.RowHeight))
{
var row = DataGridRow.GetRowContainingElement((FrameworkElement)sender);
//Fore recalculating row height
try
{
dataGrid.RowHeight = 0;
row.InvalidateMeasure();
row.Measure(row.RenderSize);
}
finally
{
//Restore RowHeight
dataGrid.RowHeight = double.NaN;
}
}
}
private static T GetParentOf<T>(FrameworkElement element) where T : FrameworkElement
{
while (element != null)
{
T item = element as T;
if (item != null)
{
return item;
}
element = (FrameworkElement)VisualTreeHelper.GetParent(element);
}
return null;
}
}
Třída je ke stažení zde.