Search code examples
c#wpfxamlscrollvieweruniformgrid

Using Scrollviewer on UniformGrid


I'm struggling a lot finding a solution to my problem. I can't understand why my view is breaking as soon as I wrap it inside a scrollviewer.

The layout idea is the following:

<ScrollViewer>
    <ItemsControl>
        <Scrollviewer>
           <ItemsControl>

My data structure is basically a collection of collections. I want to scroll horizontally the parent collection, and then scroll vertically the children collections.

Both itemscontrols has as itempanel a uniformgrid, the parent has a uniformgrid with set rows=1, so it will create only columns for each child. The child itemscontrol has as itempanel a uniformgrid with set columns=1, so it will create only rows for each element. This way, in case the different child collections has different amount of items, the items will stretch so match the max length. And that's the behavior I want. It's visually appealing IMO.

But I can't get it to work, if I put just the inner scrollviewer, everything works fine, expect for the behaviour that each child itemscontrol will shrink as more children get added. And I don't want this, I would like to have a minimum width, and if the parent itemscontrol get larger then the actual available space, then I can scroll horizontally.

If I wrap my outer itemscontrol inside a scollviewer, everything breaks, I can't see anything. I think it's a problem related to the uniforms grids, because if i set some max height and max width on some places, then I can see the content, but it's not what I'm looking for.

Here's my view:

<Grid>
    <ScrollViewer HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Disabled">

    <ItemsControl
        x:Name="Vertimags"
        MaxHeight="{Binding ElementName=ME, Path=ActualHeight}"
        ItemsSource="{Binding Vertimags}"
        ScrollViewer.CanContentScroll="True"
        ScrollViewer.HorizontalScrollBarVisibility="Visible">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Grid x:Name="CurrentVertimag">

                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="*"/>
                    </Grid.RowDefinitions>
                    <TextBlock
                        Padding="15 0 15 0"
                        HorizontalAlignment="Left"
                        Panel.ZIndex="1"
                        Background="{StaticResource WidgetBackgroundDark}"
                        FontSize="40"
                        Foreground="white"
                        Text="{Binding Name}">
                        <TextBlock.RenderTransform>
                            <TranslateTransform X="100" Y="0"/>
                        </TextBlock.RenderTransform>
                    </TextBlock>
                    <Line
                        Margin="10 0 10 0"
                        VerticalAlignment="Center"
                        Stroke="White"
                        StrokeThickness="2"
                        X1="0"
                        X2="{Binding ElementName=CurrentVertimag, Path=ActualWidth}"/>
                    <ScrollViewer Grid.Row="1"
                        Padding="0 0 0 10"
                        VerticalScrollBarVisibility="Hidden">
                        <ItemsControl x:Name="VertimagShelves" ItemsSource="{Binding Carriers}">
                            <ItemsControl.ItemTemplate>
                                <DataTemplate>
                                    <Button
                                        x:Name="ShelfButton"
                                        Background="{Binding Color}"
                                        Command="{Binding ElementName=ME, Path=DataContext.SendRequestToMoveShelfCommand}"
                                        CommandParameter="{Binding CarrierName}">
                                        <Button.Style>
                                            <Style TargetType="Button">
                                                <Setter Property="Template">
                                                    <Setter.Value>
                                                        <ControlTemplate TargetType="Button">
                                                            <Border x:Name="ShelfContainerBorder" Background="{TemplateBinding Background}">
                                                                <Border.Clip>
                                                                    <MultiBinding Converter="{StaticResource DimensionToRectGeometryConverter}">
                                                                        <Binding ElementName="ShelfContainerBorder" Path="ActualWidth"/>
                                                                        <Binding ElementName="ShelfContainerBorder" Path="ActualHeight"/>
                                                                        <Binding ElementName="ShelfContainerBorder" Path="CornerRadius"/>
                                                                    </MultiBinding>
                                                                </Border.Clip>
                                                                <Border.RenderTransformOrigin>
                                                                    <Point X="0.5" Y="0.5"/>
                                                                </Border.RenderTransformOrigin>
                                                                <Border.RenderTransform>
                                                                    <ScaleTransform ScaleX="1" ScaleY="1"/>
                                                                </Border.RenderTransform>
                                                                <Border.Style>
                                                                    <Style TargetType="Border">
                                                                        <Setter Property="Opacity" Value="1"/>
                                                                        <Setter Property="Margin" Value="10"/>
                                                                        <Setter Property="MinHeight" Value="70"/>
                                                                        <Setter Property="MinWidth" Value="400"/>
                                                                        <Setter Property="CornerRadius" Value="5"/>
                                                                    </Style>
                                                                </Border.Style>
                                                                <Viewbox MaxHeight="{Binding ElementName=ShelfContainerBorder, Path=ActualHeight, Converter={StaticResource RelativeHeightConverter}, ConverterParameter=80}">
                                                                    <ContentPresenter/>
                                                                </Viewbox>
                                                                <VisualStateManager.VisualStateGroups>
                                                                    <!--  Stati del bottone  -->
                                                                    <VisualStateGroup x:Name="CommonStates">

                                                                        <!--  Stato normale  -->
                                                                        <VisualState x:Name="Normal">
                                                                            <Storyboard>
                                                                                <DoubleAnimation
                                                                                    Storyboard.TargetName="ShelfContainerBorder"
                                                                                    Storyboard.TargetProperty="Opacity"
                                                                                    To="1"
                                                                                    Duration="0:0:0.2">
                                                                                    <DoubleAnimation.EasingFunction>
                                                                                        <QuadraticEase EasingMode="EaseInOut"/>
                                                                                    </DoubleAnimation.EasingFunction>
                                                                                </DoubleAnimation>
                                                                                <DoubleAnimation
                                                                                    Storyboard.TargetName="ShelfContainerBorder"
                                                                                    Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleX)"
                                                                                    To="1"
                                                                                    Duration="0:0:0.2">
                                                                                    <DoubleAnimation.EasingFunction>
                                                                                        <QuadraticEase EasingMode="EaseInOut"/>
                                                                                    </DoubleAnimation.EasingFunction>
                                                                                </DoubleAnimation>
                                                                                <DoubleAnimation
                                                                                    Storyboard.TargetName="ShelfContainerBorder"
                                                                                    Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleY)"
                                                                                    To="1"
                                                                                    Duration="0:0:0.2">
                                                                                    <DoubleAnimation.EasingFunction>
                                                                                        <QuadraticEase EasingMode="EaseInOut"/>
                                                                                    </DoubleAnimation.EasingFunction>
                                                                                </DoubleAnimation>
                                                                            </Storyboard>
                                                                        </VisualState>

                                                                        <!--  Stato Hover  -->
                                                                        <VisualState x:Name="MouseOver">
                                                                            <Storyboard>
                                                                                <DoubleAnimation
                                                                                    Storyboard.TargetName="ShelfContainerBorder"
                                                                                    Storyboard.TargetProperty="Opacity"
                                                                                    To="0.7"
                                                                                    Duration="0:0:0.2">
                                                                                    <DoubleAnimation.EasingFunction>
                                                                                        <QuadraticEase EasingMode="EaseInOut"/>
                                                                                    </DoubleAnimation.EasingFunction>
                                                                                </DoubleAnimation>
                                                                                <DoubleAnimation
                                                                                    Storyboard.TargetName="ShelfContainerBorder"
                                                                                    Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleX)"
                                                                                    To="1.03"
                                                                                    Duration="0:0:0.2">
                                                                                    <DoubleAnimation.EasingFunction>
                                                                                        <QuadraticEase EasingMode="EaseInOut"/>
                                                                                    </DoubleAnimation.EasingFunction>
                                                                                </DoubleAnimation>
                                                                                <DoubleAnimation
                                                                                    Storyboard.TargetName="ShelfContainerBorder"
                                                                                    Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleY)"
                                                                                    To="1.03"
                                                                                    Duration="0:0:0.2">
                                                                                    <DoubleAnimation.EasingFunction>
                                                                                        <QuadraticEase EasingMode="EaseInOut"/>
                                                                                    </DoubleAnimation.EasingFunction>
                                                                                </DoubleAnimation>
                                                                            </Storyboard>
                                                                        </VisualState>

                                                                        <!--  Stato Premuto  -->
                                                                        <VisualState x:Name="Pressed">
                                                                            <Storyboard>
                                                                                <DoubleAnimation
                                                                                    Storyboard.TargetName="ShelfContainerBorder"
                                                                                    Storyboard.TargetProperty="Opacity"
                                                                                    To="0.7"
                                                                                    Duration="0:0:0.07">
                                                                                    <DoubleAnimation.EasingFunction>
                                                                                        <QuadraticEase EasingMode="EaseInOut"/>
                                                                                    </DoubleAnimation.EasingFunction>
                                                                                </DoubleAnimation>
                                                                                <DoubleAnimation
                                                                                    Storyboard.TargetName="ShelfContainerBorder"
                                                                                    Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleX)"
                                                                                    To="1"
                                                                                    Duration="0:0:0.07">
                                                                                    <DoubleAnimation.EasingFunction>
                                                                                        <QuadraticEase EasingMode="EaseInOut"/>
                                                                                    </DoubleAnimation.EasingFunction>
                                                                                </DoubleAnimation>
                                                                                <DoubleAnimation
                                                                                    Storyboard.TargetName="ShelfContainerBorder"
                                                                                    Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleY)"
                                                                                    To="1"
                                                                                    Duration="0:0:0.07">
                                                                                    <DoubleAnimation.EasingFunction>
                                                                                        <QuadraticEase EasingMode="EaseInOut"/>
                                                                                    </DoubleAnimation.EasingFunction>
                                                                                </DoubleAnimation>
                                                                            </Storyboard>
                                                                        </VisualState>

                                                                    </VisualStateGroup>
                                                                </VisualStateManager.VisualStateGroups>
                                                            </Border>
                                                        </ControlTemplate>
                                                    </Setter.Value>
                                                </Setter>
                                            </Style>
                                        </Button.Style>
                                        <StackPanel>
                                            <TextBlock
                                                HorizontalAlignment="Center"
                                                Foreground="White"
                                                Style="{StaticResource tb_BadgeText}"
                                                Text="{Binding CarrierName}"/>
                                            <TextBlock Grid.Row="1"
                                                HorizontalAlignment="Center"
                                                VerticalAlignment="Center"
                                                Foreground="White"
                                                Style="{StaticResource tb_BadgeText}">
                                                <Run Text="{Binding CarrierCurrentOccupation}"/>
                                                <Run Text="/"/>
                                                <Run Text="{Binding CarrierMaxCapacity}"/>
                                            </TextBlock>
                                        </StackPanel>
                                    </Button>
                                </DataTemplate>
                            </ItemsControl.ItemTemplate>
                            <ItemsControl.ItemsPanel>
                                <ItemsPanelTemplate>
                                    <UniformGrid Columns="1"/>
                                </ItemsPanelTemplate>
                            </ItemsControl.ItemsPanel>
                        </ItemsControl>
                    </ScrollViewer>
                </Grid>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <UniformGrid Rows="1"/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    </ItemsControl>
    </ScrollViewer>
</Grid>

If you have any idea on how I could handle this, that would be great.

Thanks in advance


Solution

  • I've found a solution. Instead of using unifromgrids as itemspanel, i use grids, and then dynamically add rows and columns from code behind. In addition to that, im also specifying the columns width, that way i was able to accomplish my needs.

    I did as such:

    private void VertimagsPanel_Loaded(object sender, RoutedEventArgs e)
    {
        //Popolo dinamicamente gli itemspanel, che sono grid, con le colonne e le righe
        if (sender is Grid vertimagsPanel)
        {
            for (int i = 0; i < Vertimags.Items.Count; i++)
            {
                vertimagsPanel.ColumnDefinitions.Add(new ColumnDefinition());
                UpdateColumnsWidth(vertimagsPanel);
    
                if (Vertimags.ItemContainerGenerator.ContainerFromIndex(i) is UIElement vertimagUIElement)
                {
                    Grid.SetColumn(vertimagUIElement, i);
    
    
                    var vertimagShelvesPanel = FindVisualChild<Grid>(vertimagUIElement, "VertimagShelvesPanel");
                    var vertimagShelves = FindVisualChild<ItemsControl>(vertimagUIElement, "VertimagShelves");
    
                    if (vertimagShelves is null || vertimagShelvesPanel is null) continue;
    
                    for (int j = 0; j < vertimagShelves.Items.Count; j++)
                    {
                        vertimagShelvesPanel.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
                        if (vertimagShelves.ItemContainerGenerator.ContainerFromIndex(j) is UIElement carrierUIElement)
                        {
                            Grid.SetRow(carrierUIElement, j);
                        }
                    }
                }
            }
        }
    }
    

    UpdateColumns is basically setting the column width based on how many elements are inside my collection so the math is actual visible available space divided by elements count. FindVisualChild is the classic helper method that allows you to find a specific child component in the visual tree