Monday, July 29, 2024

RowDefinition with Height="*" not honored inside ScrollViewer, bug?

I came across a problem with an Infragistics XamDataGrid inside a grid inside a ScrollViewer. The problem occurs when I populate the data grid with more data than will fit. Instead of the data grid scrolling, the data grid expands vertically beyond the height of the row it is in. Take a look at this XAML and code.

<Window x:Class="GridHeight.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:GridHeight"
        xmlns:igDP="http://infragistics.com/DataPresenter"
        mc:Ignorable="d"
        DataContext="{Binding RelativeSource={RelativeSource Self}}"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <RoutedCommand x:Key="PopulateCommand"/>
        <local:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
    </Window.Resources>
    <Window.CommandBindings>
        <CommandBinding Command="{StaticResource PopulateCommand}" Executed="Populate_Executed"/>
    </Window.CommandBindings>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="auto"/>
        </Grid.RowDefinitions>
        <Border Grid.Row="0">
            <Expander Header="Expand" IsExpanded="{Binding IsExpanded}"/>
        </Border>
        <ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="auto"/>
                    <RowDefinition Height="*"/>
                    <RowDefinition Height="auto"/>
                </Grid.RowDefinitions>
                <Border Grid.Row="0" Visibility="{Binding IsExpanded, Converter={StaticResource BooleanToVisibilityConverter }}">
                    <TextBlock Text="Expanded" Padding="20"/>
                </Border>
                <igDP:XamDataGrid Grid.Row="1" DataSource="{Binding Items}" ScrollingMode="Immediate">
                    <igDP:XamDataGrid.FieldLayouts>
                        <igDP:FieldLayout>
                            <igDP:FieldLayout.Fields>
                                <igDP:TextField Label="Item" Name="Item"/>
                            </igDP:FieldLayout.Fields>
                        </igDP:FieldLayout>
                    </igDP:XamDataGrid.FieldLayouts>
                </igDP:XamDataGrid>
                <Button Grid.Row="2" Content="Populate" Command="{StaticResource PopulateCommand}" />
            </Grid>
        </ScrollViewer>
    </Grid>
</Window>

--------------------------------------------

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Configuration;
using System.Runtime.CompilerServices;
using System.Windows;

namespace GridHeight
{
    public partial class MainWindow : Window, INotifyPropertyChanged
    {

        private bool _IsExpanded = false;
        public bool IsExpanded
        {
            get => _IsExpanded;
            set => SetProperty(ref _IsExpanded, value);
        }
        public class cItem
        {
            public cItem(int i) => Item = $"Item {i}";
            public String Item { get; set; }
        }

        private List<cItem> _Items = new();

        public List<cItem>? Items
        {
            get => _Items;
            set => SetProperty(ref _Items, value);
        }

        private void Populate_Executed(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
        {
            Items = Enumerable.Range(1, 100).Select(i => new cItem(i)).ToList();
        }

        public event PropertyChangedEventHandler? PropertyChanged;
        public void SetProperty<T>(ref T storage, T value, [CallerMemberName] string name = "")
        {
            if (!Object.Equals(storage, value))
            {
                storage = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }
    }
}

If you run this application and click the [Populate] button you can see both the grid and the scroll viewer get a scrollbar. I would only expect the grid to have a scroll bar.


The problem is not in the datagrid as I had originally thought. The grid that contains the datagrid is changing its size because the datagrid asked for more room. If we constrain the size of the grid, we can prevent it from expanding. Change the inner grid's declaration to add a Height attribute...

<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
    <Grid Height="{Binding Path=ActualHeight, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=FrameworkElement}}">
        <Grid.RowDefinitions>

Now run the application again and click [Populate]. The DataGrid stays the correct size and the Scroll Viewer does not get a scroll bar. This is what we want to see.


You can toggle the Expand expander and resize the window to verify the data grid is behaving correctly.