wpfxamlblend

ItemsControl - Grid child elements auto resize


I'm using Rachel Lim's GridHelper to get dynamic number of rows. What I wanted to achieve is to have each row displayed one below another (done), to be able to resize them (done - using GridSplitter) and to have content resized proportionally to the screen size.

Result:

enter image description here

What I would like to have: enter image description here

Xaml:

<Grid>
    <ItemsControl ItemsSource="{Binding RowSource}" >
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Grid local:GridHelper.RowCount="{Binding RowCount}" />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemContainerStyle>
            <Style TargetType="ContentPresenter">
                <Setter Property="Grid.Row" Value="{Binding RowNumber}"/>
            </Style>
        </ItemsControl.ItemContainerStyle>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                    </Grid.RowDefinitions>
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*" />
                            <ColumnDefinition Width="Auto" />
                        </Grid.ColumnDefinitions>
                        <DataGrid>
                            <DataGrid.Columns>
                                <DataGridTextColumn Header="Col 1" />
                                <DataGridTextColumn Header="Col 2" />
                                <DataGridTextColumn Header="Col 3" />
                            </DataGrid.Columns>
                        </DataGrid>
                        <Button Grid.Column="1" Content="Btn" />
                    </Grid>
                    <GridSplitter Height="5" VerticalAlignment="Bottom" HorizontalAlignment="Stretch" Grid.Row="0" ResizeDirection="Rows" ResizeBehavior="CurrentAndNext"/>
                </Grid>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</Grid>

ViewModel:

internal class MyViewModel
{
    public ObservableCollection<RowInfo> RowSource { get; set; }

    public int RowCount { get { return RowSource.Count; } }

    public MyViewModel()
    {
        RowSource = new ObservableCollection<RowInfo>()
        {
            new RowInfo() { RowNumber = 0 },
            new RowInfo() { RowNumber = 1 },
            new RowInfo() { RowNumber = 2 }
        };
    }
}

RowInfo:

public class RowInfo
{
    public int RowNumber { get; internal set; }
}

Solution

  • I think your approach is all wrong. You cannot use an ItemsControl, as the GridSplitter items need to be at the ItemsPanel level rather than in the DataTemplate - otherwise, it won't work.

    You are better off using a custom behavior on the Grid Itself - see example code below:

    public class GridAutoRowChildBehavior : Behavior<Grid>
    {
        public static readonly DependencyProperty ItemTemplateProperty =
            DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(GridAutoRowChildBehavior),
                new PropertyMetadata(null, OnGridPropertyChanged));
    
        public static readonly DependencyProperty ItemsSourceProperty =
            DependencyProperty.Register("ItemsSource", typeof(object), typeof(GridAutoRowChildBehavior),
                new PropertyMetadata(null, OnGridPropertyChanged));
    
        private static void OnGridPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((GridAutoRowChildBehavior) d).ResetGrid();
        }
    
        private void ResetGrid()
        {
            var source = ItemsSource as IEnumerable;
            if (source == null || ItemTemplate == null)
                return;
            AssociatedObject.Children.Clear();
            AssociatedObject.RowDefinitions.Clear();
            var count = 0;
            foreach (var item in source)
            {
                var content = new ContentPresenter
                {
                    ContentTemplate = ItemTemplate,
                    Content = item
                };
                var splitter = new GridSplitter
                {
                    Height = 5,
                    VerticalAlignment = VerticalAlignment.Bottom,
                    HorizontalAlignment = HorizontalAlignment.Stretch
                };
                AssociatedObject.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
                Grid.SetRow(content,count);
                Grid.SetRow(splitter,count);
                AssociatedObject.Children.Add(content);
                AssociatedObject.Children.Add(splitter);
                count++;
            }
    
        }
    
        public DataTemplate ItemTemplate
        {
            get { return (DataTemplate) GetValue(ItemTemplateProperty); }
            set { SetValue(ItemTemplateProperty, value); }
        }
    
    
        public object ItemsSource
        {
            get { return GetValue(ItemsSourceProperty); }
            set { SetValue(ItemsSourceProperty, value); }
        }
    }
    

    Then in your XAML you code it up like this:

    <Grid>
        <i:Interaction.Behaviors>
            <local:GridAutoRowChildBehavior ItemsSource="{Binding RowsSource}">
                <local:GridAutoRowChildBehavior.ItemTemplate>
                    <DataTemplate>
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*" />
                                <ColumnDefinition Width="Auto" />
                            </Grid.ColumnDefinitions>
                            <DataGrid>
                                <DataGrid.Columns>
                                    <DataGridTextColumn Header="Col 1" />
                                    <DataGridTextColumn Header="Col 2" />
                                    <DataGridTextColumn Header="Col 3" />
                                </DataGrid.Columns>
                            </DataGrid>
                            <Button Grid.Column="1" Content="Btn" />
                        </Grid>
                    </DataTemplate>
                </local:GridAutoRowChildBehavior.ItemTemplate>
            </local:GridAutoRowChildBehavior>
        </i:Interaction.Behaviors>
    </Grid>
    

    I have tested this and it works exactly as you need.

    The only additional thing you need to do is add the Nuget package Systems.Windows.Interactivity.WPF to your project